package com.munjaon.client.server.service;

import com.munjaon.client.model.MunjaonMsg;
import com.munjaon.client.server.packet.*;
import com.munjaon.client.service.DatabaseTypeWorker;
import org.json.simple.JSONObject;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class ReportClientService extends Service {
    private SocketChannel socketChannel;
    private long lastPacketSendTime = 0;
    private String address;
    private int port;
    private String id;
    private String pwd;
    private DatabaseTypeWorker worker;

    ByteBuffer headBuffer = ByteBuffer.allocateDirect(Header.HEADER_LENGTH);
    ByteBuffer linkBodyBuffer = ByteBuffer.allocateDirect(LinkCheck.LINK_CHECK_BODY_LENGTH);
    ByteBuffer reportBodyBuffer = ByteBuffer.allocateDirect(Report.REPORT_BODY_LENGTH);
    ByteBuffer reportBuffer = ByteBuffer.allocateDirect(Header.HEADER_LENGTH + Report.REPORT_BODY_LENGTH);


    public ReportClientService(String serviceName) {
        super(serviceName);
    }

    @Override
    public void checkReady() {
        worker = DatabaseTypeWorker.find(System.getProperty("DBMS"));
        if (worker == null) {
            saveSystemLog("[checkReady][DBMS : " + System.getProperty("DBMS") + " is not ready]");
            return;
        }

        this.IS_READY_YN = worker.checkTableExists();
        if (this.IS_READY_YN) {
            saveSystemLog("[checkReady][DBMS : " + System.getProperty("DBMS") + " is ready]");
        } else {
            saveSystemLog("[checkReady][DBMS : " + System.getProperty("DBMS") + " is not ready]");
        }
    }

    @Override
    public void initResources() {
        this.address = getProp("HOST");
        this.port = Integer.parseInt(getProp("PORT"));
        this.id = getProp("ID");
        this.pwd = getProp("PASSWORD");
        saveSystemLog("Try Connect to [ADDRESS : " + this.address + "] [PORT : " + this.port + "] [ID : " + this.id + "]");
        try {
            socketChannel = SocketChannel.open(new InetSocketAddress(this.address, this.port));
            socketChannel.configureBlocking(false);
        } catch (IOException e) {
            saveSystemLog("Connect Fail to [ADDRESS : " + this.address + "] [PORT : " + this.port + "] [ID : " + this.id + "]");
            saveSystemLog("ERROR [" + e.getMessage() + "]");
            saveSystemLog("ERROR DETAIL");
            saveSystemLog(e.toString());
            throw new RuntimeException(e);
        }
    }

    @Override
    public void releaseResources() {
        if (socketChannel != null) {
            try {
                socketChannel.close();
            } catch (IOException e) {
                saveSystemLog("ERROR [" + e.getMessage() + "]");
                saveSystemLog("ERROR DETAIL");
                saveSystemLog(e.toString());
                throw new RuntimeException(e);
            }
        }
    }

    @Override
    public void doService() {
        bind();
        while (isRun()) {
            try {
                messageService();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    private void bind() {
        ByteBuffer sendBuffer = Bind.makeBindBuffer(id, pwd);
        ByteBuffer recvBuffer = ByteBuffer.allocate(Header.HEADER_LENGTH + Bind.BIND_ACK_BODY_LENGTH);
        try {
            saveSystemLog("sendBuffer " + sendBuffer.position() + ":" + sendBuffer.limit());
            saveSystemLog("Bind Try Connect to " + this.address + ":" + this.port);
            socketChannel.write(sendBuffer);
            saveSystemLog("Bind Read to " + this.address + ":" + this.port);
            while (true) {
                int recvCount = socketChannel.read(recvBuffer);
                if (recvCount == -1) {
                    throw new RuntimeException("BIND ERROR");
                } else if (recvCount > 0) {
                    String resultCode = Bind.getBindAckResultCode(recvBuffer);
                    saveSystemLog("resultCode " + resultCode);
                    if ("00".equals(resultCode)) {
                        lastPacketSendTime = System.currentTimeMillis();
                        saveSystemLog("Bind OK");
                        break;
                    } else {
                        saveSystemLog("Bind FAIL");
                        throw new RuntimeException("Bind FAIL");
                    }
                }
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void messageService() {

        try {
            while (isRun()) {
                headBuffer.clear();
                int recvCount = socketChannel.read(headBuffer);
//                saveSystemLog("recvCount : " + recvCount);
                if (recvCount == -1) {
                    throw new RuntimeException("REPORT ERROR : Connection closed");
                } else if (recvCount > 0) {
//                    Packet.printBuffer(headBuffer);
                    String command = Header.getCommand(headBuffer);
                    if (command == null) {
                        continue;
                    }
                    saveSystemLog("command : " + command);
                    switch (Integer.parseInt(command)) {
                        case 5 :
                            reportBodyBuffer.clear();
                            socketChannel.read(reportBodyBuffer);
                            reportService(headBuffer, reportBodyBuffer);
                            break;
                        case 7 :
                            linkBodyBuffer.clear();
                            socketChannel.read(linkBodyBuffer);
                            linkCheckService(headBuffer, linkBodyBuffer);
                            break;
                        default: throw new RuntimeException("REPORT ERROR");
                    }
                } else {
                    Thread.sleep(10L);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    private void reportService(ByteBuffer headBuffer, ByteBuffer reportBodyBuffer) {
        try {
            saveSystemLog("Report Received");
            reportBuffer.clear();
            Packet.mergeBuffers(reportBuffer, headBuffer, reportBodyBuffer);

            MunjaonMsg data = Report.getMsgData(reportBuffer);
            if (data != null) {
                saveSystemLog("Report : " + data.toString());
                worker.updateToReport(data);
                socketChannel.write(Report.makeReportAckBuffer());
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    private void linkCheckService(ByteBuffer headBuffer, ByteBuffer linkBodyBuffer) {
        try {
            saveSystemLog("LinkCheck Received");
            socketChannel.write(LinkCheck.makeLinkCheckAckBuffer());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public JSONObject monitorService() {
        return null;
    }
}
