'use strict';

const assert = require('assert');
const Node = require('./tree-node');

const HAS_DATA_FLAG = 1 << 0;
const DATA_DUPLET_FLAG = 1 << 1;
const HAS_BRANCHES_FLAG = 1 << 2;

const MAX_UINT16 = (1 << 16) - 1;
const MAX_BRANCH_MARKER_VALUE = HAS_DATA_FLAG | DATA_DUPLET_FLAG | HAS_BRANCHES_FLAG;

class ArrayMappedRadixTree {
    constructor(radixTree) {
        this.arr = [];

        this._convertNode(radixTree);

        for (const n of this.arr) {
            assert(n <= MAX_UINT16, `${n} overflows uint16`);
        }

        return this.arr;
    }

    _convertEdge(edge) {
        for (const cp of edge.filter) {
            assert(cp > MAX_BRANCH_MARKER_VALUE, 'filter code point shadows node marker');
            this.arr.push(cp);
        }

        this._convertNode(edge.node);
    }

    _writeNodeMarker(data, branches) {
        let marker = 0;

        if (data) {
            marker |= HAS_DATA_FLAG;

            if (data.length === 2) {
                marker |= DATA_DUPLET_FLAG;
            }
        }

        if (branches) {
            marker |= HAS_BRANCHES_FLAG;
        }

        this.arr.push(marker);
    }

    _writeBranches(branches) {
        const kvPairs = Object.keys(branches)
            .map(Number)
            .map(key => ({ key, branch: branches[key] }));

        const count = kvPairs.length;

        this.arr.push(count);

        const transitionTableIdx = this.arr.length;

        // NOTE: allocate space for transition table
        this.arr.length += count * 2;

        kvPairs
            .sort((pair1, pair2) => pair1.key - pair2.key)
            .forEach((pair, idx) => {
                const keyIdx = transitionTableIdx + idx;
                const branchIdx = keyIdx + count;

                this.arr[keyIdx] = pair.key;
                this.arr[branchIdx] = this.arr.length;

                if (pair.branch instanceof Node) {
                    this._convertNode(pair.branch);
                } else {
                    this._convertEdge(pair.branch);
                }
            });
    }

    _convertNode(node) {
        const data = node.data;
        const branches = node.branches;

        this._writeNodeMarker(data, branches);

        if (data) {
            data.forEach(cp => this.arr.push(cp));
        }

        if (branches) {
            this._writeBranches(branches);
        }
    }
}

module.exports = ArrayMappedRadixTree;