import {Type, Subsystem, DataStart, SOF, PositionCmd0, PositionCmd1} from './constants';

class Frame {
    public readonly type: Type;
    public readonly subsystem: Subsystem;
    public readonly commandID: number;
    public readonly data: Buffer;

    public readonly length: number;
    public readonly fcs: number;

    public constructor(
        type: Type, subsystem: Subsystem, commandID: number, data: Buffer,
        length: number = null, fcs: number = null,
    ) {
        this.type = type;
        this.subsystem = subsystem;
        this.commandID = commandID;
        this.data = data;
        this.length = length;
        this.fcs = fcs;
    }

    public toBuffer(): Buffer {
        const length = this.data.length;
        const cmd0 = ((this.type << 5) & 0xE0) | (this.subsystem & 0x1F);

        let payload = Buffer.from([SOF, length, cmd0, this.commandID]);
        payload = Buffer.concat([payload, this.data]);
        const fcs = Frame.calculateChecksum(payload.slice(1, payload.length));

        return Buffer.concat([payload, Buffer.from([fcs])]);
    }

    public static fromBuffer(length: number, fcsPosition: number, buffer: Buffer): Frame {
        const subsystem: Subsystem = buffer.readUInt8(PositionCmd0) & 0x1F;
        const type: Type = (buffer.readUInt8(PositionCmd0) & 0xE0) >> 5;
        const commandID = buffer.readUInt8(PositionCmd1);
        const data = buffer.slice(DataStart, fcsPosition);
        const fcs = buffer.readUInt8(fcsPosition);

        // Validate the checksum to see if we fully received the message
        const checksum = this.calculateChecksum(buffer.slice(1, fcsPosition));

        if (checksum === fcs) {
            return new Frame(type, subsystem, commandID, data, length, fcs);
        } else {
            throw new Error("Invalid checksum");
        }
    }

    private static calculateChecksum(values: Buffer): number {
        let checksum = 0;

        for (const value of values) {
            checksum ^= value;
        }

        return checksum;
    }

    public toString(): string {
        return `${this.length} - ${this.type} - ${this.subsystem} - ${this.commandID} - ` +
            `[${[...this.data]}] - ${this.fcs}`;
    }
}

export default Frame;
