package ru.upos;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;

import static ru.upos.AsciiCharacters.*;

/**
 * Утилиты для работы с фреймами по протоколу UPOS MSB
 */
public class UposMsbFrame {

    private final static byte newProtocol = sharp.getValue();

    private byte id;
    private byte[] body;

    public byte[] getBody() {
        return body;
    }

    public byte getId() {
        return id;
    }

    // TODO: use proper table for optimize algorithm
    private byte[] calcCrc(byte[] data) {
        int crc = 0xffff;
        int polinomial = 0x1021;
        ByteArrayOutputStream os = new ByteArrayOutputStream();

        for (byte b : data) {
            for (int i = 0; i < 8; ++i) {
                boolean bit = ((b >> (7 - i) & 1) == 1);
                boolean c15 = ((crc >> 15 & 1) == 1);
                crc <<= 1;
                if (c15 ^ bit) crc ^= polinomial;
            }
        }
        crc &= 0xffff;
        os.write((crc & 0xff));
        os.write(((crc & 0xff00) >> 8));
        return os.toByteArray();
    }

    public UposMsbFrame() {
    }

    public UposMsbFrame(byte id) {
        this.id = id;
    }

    public byte[] createFrame(byte[] data) throws IOException {
        ByteArrayOutputStream blockBase64 = new ByteArrayOutputStream();
        ByteArrayOutputStream frame = new ByteArrayOutputStream();

        // create frame body
        blockBase64.write(id);
        blockBase64.write(data.length);
        blockBase64.write(data);
        blockBase64.write(calcCrc(blockBase64.toByteArray()));
        // create frame
        frame.write(stx.getValue());
        frame.write(newProtocol);
        frame.write(Base64.getEncoder().encode(blockBase64.toByteArray()));
        frame.write(etx.getValue());
        return frame.toByteArray();
    }

    private boolean parseUnwrapFrame(byte[] data) {
        ByteArrayOutputStream block = new ByteArrayOutputStream();
        int length;
        byte[] crc;

        id = data[0];
        length = data[1] & 0xFF;
        if (data.length - 4 != length) {
            return false;
        }
        block.write(data, 0, length + 2);
        body = new byte[length];
        System.arraycopy(data, 2, body, 0, length);
        crc = calcCrc(block.toByteArray());
        return (crc[0] == data[data.length - 2] && crc[1] == data[data.length - 1]);
    }

    public boolean parseFrame(byte[] data) {
        ByteArrayOutputStream frame = new ByteArrayOutputStream();

        if (data[0] != stx.getValue() || data[1] != newProtocol || data[data.length - 1] != etx.getValue()) {
            return false;
        }
        frame.write(data, 2, data.length - 3);
        return parseUnwrapFrame(Base64.getDecoder().decode(frame.toByteArray()));
    }

    public boolean isFinalFrame() {
        return ((id & 0x80) != 0x80);
    }
}
