package ru.upos;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import static ru.upos.UposUtils.*;

/**
 * Сообщения UPOS для обмена данными с терминалом
 */
public class UposMessage {

    private UposCommand command;
    private long messageId;
    private byte[] messageBody;

    public UposMessage(UposCommand command, long messageId) {
        this.command = command;
        this.messageId = messageId;
    }

    public UposCommand getCommand() {
        return command;
    }

    public long getMessageId() {
        return messageId;
    }

    public byte[] getMessageBody() {
        return messageBody;
    }

    public void setMessageBody(byte[] messageBody) {
        this.messageBody = messageBody;
    }

    private byte[] getCmdGetReady() throws IOException {
        // TODO: получение данных из параметров операции или по какому-либо алгоритму
        byte[] scrId = new byte[]{0x00, 0x00, 0x00, 0x00};
        // TODO: получение данных из параметров операции или по какому-либо алгоритму
        byte dlgNum = 0;
        ByteArrayOutputStream body = new ByteArrayOutputStream();

        body.write(scrId);
        body.write(dlgNum);
        return body.toByteArray();
    }

    private byte[] getDataAdditional(Parameters parameters) throws IOException {
        byte[] dataEmpty = {0x00};
        byte[] dataAmountAdditionalTag = {0x07, (byte)0xDF, 0x50, 0x04};
        ByteArrayOutputStream dataAdditional = new ByteArrayOutputStream();

        if (parameters.getAmountAdditional() > 0) {
            dataAdditional.write(dataAmountAdditionalTag);
            dataAdditional.write(convertLongMsb(parameters.getAmountAdditional()));
        } else {
            dataAdditional.write(dataEmpty);
        }
        return dataAdditional.toByteArray();
    }

    private byte[] getCmdTransaction(Parameters parameters) throws IOException {
        byte[] track2 = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
        byte[] rrn = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
        byte[] flags = convertLongMsb(parameters.getFlags());
        byte[] dataAdditional;
        ByteArrayOutputStream body = new ByteArrayOutputStream();

        // amount (offset: 0, size: 4)
        body.write(convertLongMsb(parameters.getAmount()));
        // card type (offset: 4, size: 1) // TODO: get from parameters
        body.write(0x00);
        // department number (offset: 5, size: 1) // TODO: get from parameters
        body.write(0x00);
        // operation type (offset: 6, size: 1)
        body.write(Byte.parseByte(parameters.getOperation().getOperationId()));
        // track2 (offset: 7, size: 40) // TODO: get from parameters
        body.write(track2);
        // request id (offset: 47, size: 4)
        body.write(convertLongMsb(System.currentTimeMillis() | 0x80000000));
        // rrn (offset: 51, size: 12) // TODO: get from parameters
        body.write(rrn);
        // flags (offset: 63, size: 4)
        body.write(flags);
        // additional parameters (offset: 67, size: var)
        dataAdditional = getDataAdditional(parameters);
        body.write(dataAdditional);
        return body.toByteArray();
    }

    private void createMessageBody(Parameters parameters) throws IOException {
        switch (command) {
        case GetReady:
            messageBody = getCmdGetReady();
            break;
        case Transaction:
            messageBody = getCmdTransaction(parameters);
            break;
        default:
            break;
        }
    }

    public byte[] createMessage(Parameters parameters) throws IOException {
        ByteArrayOutputStream message = new ByteArrayOutputStream();

        if (messageBody == null) {
            createMessageBody(parameters);
        }
        // command (offset: 0, size: 1)
        message.write(command.getCommandId());
        // message body length (offset: 1, size: 2)
        message.write(messageBody.length & 0xFF);
        message.write((messageBody.length >> 8) & 0xFF);
        // message id (offset: 3, size: 4)
        message.write(convertLongMsb(messageId));
        // message body (offset: 7, size: var)
        message.write(messageBody);
        return message.toByteArray();
    }

    private void parseCmdMastercallLan(DeviceInstructionProcess instruction) {
        int length = ((messageBody[4] << 8) & 0xFF00) | (messageBody[3] & 0xFF);
        byte[] buf;

        switch (instruction.getInstruction()) {
        case Open:
            if (length == 12) {
                instruction.setIpAddress(String.format("%d.%d.%d.%d", messageBody[7] & 0xFF, messageBody[8] & 0xFF, messageBody[9] & 0xFF, messageBody[10] & 0xFF));
                int port = ((messageBody[12] << 8) & 0xFF00) | (messageBody[11] & 0xFF);
                instruction.setIpPort(port);
                buf = new byte[4];
                System.arraycopy(messageBody, 13, buf, 0, 4);
                instruction.setTimeout(convertMsbLong(buf));
            }
            break;
        case Read:
            if (length == 6) {
                instruction.setMaxDataLen(((messageBody[6] << 8) & 0xFF00) | (messageBody[5] & 0xFF));
                buf = new byte[4];
                System.arraycopy(messageBody, 7, buf, 0, 4);
                instruction.setTimeout(convertMsbLong(buf));
            }
            break;
        case Write:
            buf = new byte[length];
            System.arraycopy(messageBody, 5, buf, 0, length);
            instruction.setData(buf);
            break;
        }
    }

    private void parseCmdMastercallPrinter(DeviceInstructionProcess instruction) {
        int length = ((messageBody[4] << 8) & 0xFF00) | (messageBody[3] & 0xFF);
        byte[] buf;

        switch (instruction.getInstruction()) {
        case Open:
            if (length == 3) {
                instruction.setCopyReceipt(false);
                if (messageBody[5] == 1) {
                    instruction.setCopyReceipt(true);
                }
            }
            break;
        case Write:
            buf = new byte[length - 1];
            System.arraycopy(messageBody, 6, buf, 0, length - 1);
            instruction.setData(buf);
            break;
        }
    }

    private void parseCmdMastercallWaitTimer(DeviceInstructionProcess instruction) {
        int length = ((messageBody[4] << 8) & 0xFF00) | (messageBody[3] & 0xFF);
        byte[] buf;

        switch (instruction.getInstruction()) {
        case Open:
            // TODO: emulate use timer value
            break;
        }
    }

    private void parseCmdMastercall(DeviceInstructionProcess instruction) throws Exception {
        instruction.setInstruction(DeviceInstruction.getInstruction(messageBody[0]));
        DeviceCode deviceCode = DeviceCode.getDeviceCode(messageBody[1]);
        instruction.setDeviceCode(deviceCode);
        switch (deviceCode) {
        case Lan:
            parseCmdMastercallLan(instruction);
            break;
        case Printer:
            parseCmdMastercallPrinter(instruction);
            break;
        case WaitTimer:
            parseCmdMastercallWaitTimer(instruction);
            break;
        }
    }

    private void parseCmdRunScreen() {
        // TODO: !!!
    }

    public void parseMessageBody(DeviceInstructionProcess instruction) throws Exception {
        switch (command) {
        case Response:
            break;
        case MasterCall:
            parseCmdMastercall(instruction);
            break;
        case RunScreen:
            parseCmdRunScreen();
            break;
        }
    }

    public boolean parseMessage(byte[] wrapMessage) throws Exception {
        command = UposCommand.getCommand(wrapMessage[0]);
        int length = ((wrapMessage[2] << 8) & 0xFF00) | (wrapMessage[1] & 0xFF);
        if (length != wrapMessage.length - 7) {
            return false;
        }
        byte[] convertBuf = new byte[4];
        System.arraycopy(wrapMessage, 3, convertBuf, 0, 4);
        messageId = convertMsbLong(convertBuf);
        messageBody = new byte[length];
        System.arraycopy(wrapMessage, 7, messageBody, 0, length);
        return true;
    }
}
