import StringBuilder from '../../util/StringBuilder';
import HighLevelEncoder from './HighLevelEncoder';
import { C40_ENCODATION, LATCH_TO_C40, ASCII_ENCODATION, C40_UNLATCH, } from './constants';
export class C40Encoder {
    getEncodingMode() {
        return C40_ENCODATION;
    }
    encodeMaximal(context) {
        const buffer = new StringBuilder();
        let lastCharSize = 0;
        let backtrackStartPosition = context.pos;
        let backtrackBufferLength = 0;
        while (context.hasMoreCharacters()) {
            const c = context.getCurrentChar();
            context.pos++;
            lastCharSize = this.encodeChar(c, buffer);
            if (buffer.length() % 3 === 0) {
                backtrackStartPosition = context.pos;
                backtrackBufferLength = buffer.length();
            }
        }
        if (backtrackBufferLength !== buffer.length()) {
            const unwritten = Math.floor((buffer.length() / 3) * 2);
            const curCodewordCount = Math.floor(context.getCodewordCount() + unwritten + 1); // +1 for the latch to C40
            context.updateSymbolInfo(curCodewordCount);
            const available = context.getSymbolInfo().getDataCapacity() - curCodewordCount;
            const rest = Math.floor(buffer.length() % 3);
            if ((rest === 2 && available !== 2) ||
                (rest === 1 && (lastCharSize > 3 || available !== 1))) {
                // buffer.setLength(backtrackBufferLength);
                context.pos = backtrackStartPosition;
            }
        }
        if (buffer.length() > 0) {
            context.writeCodeword(LATCH_TO_C40);
        }
        this.handleEOD(context, buffer);
    }
    encode(context) {
        // step C
        const buffer = new StringBuilder();
        while (context.hasMoreCharacters()) {
            const c = context.getCurrentChar();
            context.pos++;
            let lastCharSize = this.encodeChar(c, buffer);
            const unwritten = Math.floor(buffer.length() / 3) * 2;
            const curCodewordCount = context.getCodewordCount() + unwritten;
            context.updateSymbolInfo(curCodewordCount);
            const available = context.getSymbolInfo().getDataCapacity() - curCodewordCount;
            if (!context.hasMoreCharacters()) {
                // Avoid having a single C40 value in the last triplet
                const removed = new StringBuilder();
                if (buffer.length() % 3 === 2 && available !== 2) {
                    lastCharSize = this.backtrackOneCharacter(context, buffer, removed, lastCharSize);
                }
                while (buffer.length() % 3 === 1 &&
                    (lastCharSize > 3 || available !== 1)) {
                    lastCharSize = this.backtrackOneCharacter(context, buffer, removed, lastCharSize);
                }
                break;
            }
            const count = buffer.length();
            if (count % 3 === 0) {
                const newMode = HighLevelEncoder.lookAheadTest(context.getMessage(), context.pos, this.getEncodingMode());
                if (newMode !== this.getEncodingMode()) {
                    // Return to ASCII encodation, which will actually handle latch to new mode
                    context.signalEncoderChange(ASCII_ENCODATION);
                    break;
                }
            }
        }
        this.handleEOD(context, buffer);
    }
    backtrackOneCharacter(context, buffer, removed, lastCharSize) {
        const count = buffer.length();
        const test = buffer.toString().substring(0, count - lastCharSize);
        buffer.setLengthToZero();
        buffer.append(test);
        // buffer.delete(count - lastCharSize, count);
        /*for (let i = count - lastCharSize; i < count; i++) {
          buffer.deleteCharAt(i);
        }*/
        context.pos--;
        const c = context.getCurrentChar();
        lastCharSize = this.encodeChar(c, removed);
        context.resetSymbolInfo(); // Deal with possible reduction in symbol size
        return lastCharSize;
    }
    writeNextTriplet(context, buffer) {
        context.writeCodewords(this.encodeToCodewords(buffer.toString()));
        const test = buffer.toString().substring(3);
        buffer.setLengthToZero();
        buffer.append(test);
        // buffer.delete(0, 3);
        /*for (let i = 0; i < 3; i++) {
          buffer.deleteCharAt(i);
        }*/
    }
    /**
     * Handle "end of data" situations
     *
     * @param context the encoder context
     * @param buffer  the buffer with the remaining encoded characters
     */
    handleEOD(context, buffer) {
        const unwritten = Math.floor((buffer.length() / 3) * 2);
        const rest = buffer.length() % 3;
        const curCodewordCount = context.getCodewordCount() + unwritten;
        context.updateSymbolInfo(curCodewordCount);
        const available = context.getSymbolInfo().getDataCapacity() - curCodewordCount;
        if (rest === 2) {
            buffer.append('\0'); // Shift 1
            while (buffer.length() >= 3) {
                this.writeNextTriplet(context, buffer);
            }
            if (context.hasMoreCharacters()) {
                context.writeCodeword(C40_UNLATCH);
            }
        }
        else if (available === 1 && rest === 1) {
            while (buffer.length() >= 3) {
                this.writeNextTriplet(context, buffer);
            }
            if (context.hasMoreCharacters()) {
                context.writeCodeword(C40_UNLATCH);
            }
            // else no unlatch
            context.pos--;
        }
        else if (rest === 0) {
            while (buffer.length() >= 3) {
                this.writeNextTriplet(context, buffer);
            }
            if (available > 0 || context.hasMoreCharacters()) {
                context.writeCodeword(C40_UNLATCH);
            }
        }
        else {
            throw new Error('Unexpected case. Please report!');
        }
        context.signalEncoderChange(ASCII_ENCODATION);
    }
    encodeChar(c, sb) {
        if (c === ' '.charCodeAt(0)) {
            sb.append(0o3);
            return 1;
        }
        if (c >= '0'.charCodeAt(0) && c <= '9'.charCodeAt(0)) {
            sb.append(c - 48 + 4);
            return 1;
        }
        if (c >= 'A'.charCodeAt(0) && c <= 'Z'.charCodeAt(0)) {
            sb.append(c - 65 + 14);
            return 1;
        }
        if (c < ' '.charCodeAt(0)) {
            sb.append(0o0); // Shift 1 Set
            sb.append(c);
            return 2;
        }
        if (c <= '/'.charCodeAt(0)) {
            sb.append(0o1); // Shift 2 Set
            sb.append(c - 33);
            return 2;
        }
        if (c <= '@'.charCodeAt(0)) {
            sb.append(0o1); // Shift 2 Set
            sb.append(c - 58 + 15);
            return 2;
        }
        if (c <= '_'.charCodeAt(0)) {
            sb.append(0o1); // Shift 2 Set
            sb.append(c - 91 + 22);
            return 2;
        }
        if (c <= 127) {
            sb.append(0o2); // Shift 3 Set
            sb.append(c - 96);
            return 2;
        }
        sb.append(`${0o1}\u001e`); // Shift 2, Upper Shift
        let len = 2;
        len += this.encodeChar(c - 128, sb);
        return len;
    }
    encodeToCodewords(sb) {
        const v = 1600 * sb.charCodeAt(0) + 40 * sb.charCodeAt(1) + sb.charCodeAt(2) + 1;
        const cw1 = v / 256;
        const cw2 = v % 256;
        const result = new StringBuilder();
        result.append(cw1);
        result.append(cw2);
        return result.toString();
    }
}
