package com.alterdekim.hearthhack.component;

import com.alterdekim.hearthhack.component.processor.*;
import com.alterdekim.hearthhack.config.ObjectConfig;
import com.alterdekim.hearthhack.service.UserService;
import com.alterdekim.hearthhack.util.BattleNetPacket;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import javax.net.ssl.SSLSocket;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;

@Slf4j
@RequiredArgsConstructor
public class TcpConnection extends Thread {

    private final SSLSocket fromClient;
    private OutputStream outToClient;

    @Getter
    private int token;

    @Getter
    private final Map<Integer, Processor> processors;

    @Getter
    private final ObjectConfig dbfConfig;

    @Getter
    private final UserService userService;

    @Getter
    @Setter
    private Long userId;

    public void stopListeningAndDisconnect() {
        log.warn("Tried to stopListening");
        try {
            this.fromClient.close();
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }

    public void send(BattleNetPacket bp) throws Exception {
        //log.info("TcpConnection.send: service={}, method={}, body={}", bp.getHeader().getServiceId(), bp.getHeader().getMethodId(), Util.bytesToHex(bp.getBody()));
        this.outToClient.write(bp.Encode());
        this.outToClient.flush();
    }

    public void sendRaw( byte[] b ) throws Exception {
        this.outToClient.write(b);
        this.outToClient.flush();
    }

    private void processPacket( BattleNetPacket packet ) {
        try {
            Processor is = this.processors.get(Integer.parseUnsignedInt(Integer.toUnsignedString(packet.getHeader().getServiceId())));
            if( is == null ) {
                log.error("Can't process weird ProcessorId: " + Integer.parseUnsignedInt(Integer.toUnsignedString(packet.getHeader().getServiceId())));
                return;
            }
            //log.info("Incoming: {} / head: {}; body: {} ", is.getClass().getSimpleName(), packet.getHeader(), Util.bytesToHex(packet.getBody()));
            is.process(packet, this);
        } catch ( Exception e ) {
            log.error(e.getMessage());
        }
    }

    @Override
    public void run() {
        try {
            InputStream is = fromClient.getInputStream();
            outToClient = fromClient.getOutputStream();
            while (true) {
                try {
                    int count;
                    byte[] buffer = new byte[4096]; // or 4096, or more
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    while ((count = is.read(buffer)) > 0) {
                        baos.write(buffer, 0, count);
                        byte[] r = baos.toByteArray();
                        BattleNetPacket bp = new BattleNetPacket();
                        int offset = 0;
                        while( offset < r.length ) {
                            offset += bp.Decode(r, offset, r.length);
                            processPacket(bp);
                        }
                        baos.reset();
                    }
                    baos.close();
                } catch (Exception e) {
                    log.error(e.getMessage());
                    break;
                }
            }
        } catch ( Exception e ) {
            log.error(e.getMessage());
        }
    }

    public int nextToken() {
        return this.token++;
    }
}
