package io.hosuaby.restful.websocket.handlers;

import io.hosuaby.restful.domain.Teapot;
import io.hosuaby.restful.domain.TeapotMessage;
import io.hosuaby.restful.services.TeapotCrudService;
import io.hosuaby.restful.services.TeapotCommandService;
import io.hosuaby.restful.services.exceptions.teapots.TeapotAlreadyExistsException;
import io.hosuaby.restful.services.exceptions.teapots.TeapotNotExistsException;

import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.Random;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * Handler of incomming websocket connections from teapots.
 */
public class TeapotHandler extends TextWebSocketHandler {

    /** End-of-transmission character */
    private static final char EOT = 0x004;

    /** Cancel character */
    private static final char CAN = 0x024;

    /** Teapot CRUD service */
    @Autowired
    private TeapotCrudService crud;

    /** Teapot command service */
    @Autowired
    private TeapotCommandService commandService;

    /** Jackson object mapper */
    @Autowired
    private ObjectMapper jacksonMapper;

    /** Randomizer */
    @Autowired
    private Random randomizer;

    /**
     * Assign IP address to teapot and registers it into command service.
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session)
            throws IOException {
        String teapotId = (String) session.getAttributes().get("teapotId");

        /* Registers the teapot first */
        commandService.register(teapotId, session);

        /* Find the teapot by id */
        // TODO: move this check into handshake handler
        try {
            Teapot teapot = crud.find(teapotId);

            /* Assign IP adress to the teapot */
            Inet4Address ip = (Inet4Address) InetAddress.getByAddress(new byte[] {
                    (byte) 192, (byte) 168, (byte) 13, (byte) randomizer.nextInt(255)
            });

            teapot.setIp(ip);
            crud.update(teapotId, teapot);
        } catch (TeapotNotExistsException | TeapotAlreadyExistsException e) {

            /* Close websocket session */
            session.close(CloseStatus.NORMAL.withReason(e.getMessage()));

            e.printStackTrace();
        }
    }

    /**
     * Relays the message from the teapot to the client.
     */
    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message)
            throws JsonParseException, JsonMappingException, IOException {
        // TODO: replace Jackson by custom protocole
        TeapotMessage msg = jacksonMapper.readValue(
                message.getPayload(), TeapotMessage.class);

        String clientId = msg.getClientId();
        String payload  = msg.getPayload();

        char lastCharacter = payload.charAt(payload.length() - 1);

        /* If last character is EOT */
        boolean eot = lastCharacter == EOT;

        /* If first chararcter is CAN */
        boolean can = lastCharacter == CAN;

        /* Remove last character from payload if it is a control character */
        if (eot || can) {
            payload = payload.substring(0, payload.length() - 1);
        }

        /* Canceled by teapot */
        if (can) {
            commandService.submitError(clientId, payload);
            return;
        }

        commandService.submitResponse(msg.getClientId(), payload, eot);
    }

    /**
     * Unregisters disconnected teapot.
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
        String teapotId = (String) session.getAttributes().get("teapotId");

        /* Unregister teapot first */
        commandService.unregister(session);

        /* Set teapot's IP address to null */
        try {
            Teapot teapot = crud.find(teapotId);
            teapot.setIp(null);
            crud.update(teapotId, teapot);
        } catch (TeapotNotExistsException | TeapotAlreadyExistsException e) {
            e.printStackTrace();
        }
    }

}