"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Session = exports.Input = exports.Rune = void 0;
// The basic idea: find the most frequent digraph and replace it with a non-terminal
// Reference: Larsson, N. J.; Moffat, A. Offline Dictionary-Based Compression.
//     In Proc. Data Compression Conference ’99(DCC’99). IEEE Computer Society, 1999, p. 296.
class Rune {
    constructor(ir, barrier = false) {
        this.ir = ir;
        this.barrier = barrier;
        // A double-linked list node
        this.prev = this;
        this.next = this;
        // Another double-linked cycle to find out all the digraphs with same pattern
        this.digraph = null;
        this.prevSameDigraph = null;
        this.nextSameDigraph = null;
    }
}
exports.Rune = Rune;
class Input {
    constructor(nop) {
        this.sentinel = new Rune(nop);
        this.sentinel.barrier = true;
    }
    makeLink(a, b) {
        a.next = b;
        b.prev = a;
    }
    append(rune) {
        const last = this.sentinel.prev;
        last.next = rune;
        this.sentinel.prev = rune;
        rune.prev = last;
        rune.next = this.sentinel;
    }
    remove(rune) {
        const prev = rune.prev;
        const next = rune.next;
        this.makeLink(prev, next);
        this.makeLink(rune, rune);
    }
}
exports.Input = Input;
class DigraphSentinel {
    constructor() {
        this.next = this;
        this.prev = this;
    }
}
class Digraph extends DigraphSentinel {
    constructor(key, firstOccurrence) {
        super();
        this.key = key;
        this.firstOccurrence = firstOccurrence;
        this.count = 0; // count == 0 means sentinel
        this.next = this;
        this.prev = this;
    }
    static getKey(kp, a, b) {
        return `${kp.getIrKey(a.ir)}/${kp.getIrKey(b.ir)}`;
    }
    removeFromBucket() {
        const prev = this.prev;
        const next = this.next;
        this.prev.next = next;
        this.next.prev = prev;
        this.next = this.prev = this;
    }
    insertToBucket(head) {
        const bucketLast = head.prev;
        head.prev = this;
        bucketLast.next = this;
        this.prev = bucketLast;
        this.next = head;
    }
}
class Session {
    constructor(keyProvider) {
        this.keyProvider = keyProvider;
        this.buckets = [];
        this.digraphMap = new Map();
    }
    append(input, char) {
        this.appendRuneToInput(input, new Rune(char, this.keyProvider.isBarrier(char)));
    }
    appendString(input, str) {
        for (const char of str) {
            this.appendRuneToInput(input, new Rune(char, this.keyProvider.isBarrier(char)));
        }
    }
    appendRuneToInput(input, rune) {
        input.append(rune);
        this.addDigraph(rune.prev, rune);
    }
    addDigraph(rFirst, rSecond) {
        if (rSecond.barrier || rFirst.barrier)
            return null;
        const digKey = Digraph.getKey(this.keyProvider, rSecond.prev, rSecond);
        const dig = this.digraphMap.get(digKey);
        if (!dig || !dig.count) {
            // Create a new digraph link
            const dig = new Digraph(digKey, rFirst);
            dig.count = 1;
            rFirst.prevSameDigraph = rFirst;
            rFirst.nextSameDigraph = rFirst;
            rFirst.digraph = dig;
            this.digraphMap.set(digKey, dig);
            return dig;
        }
        else {
            if (rFirst.prev.digraph === dig)
                return null; // Don't overlap
            const digLastOcc = dig.firstOccurrence.prevSameDigraph;
            rFirst.digraph = dig;
            dig.count++;
            digLastOcc.nextSameDigraph = rFirst;
            dig.firstOccurrence.prevSameDigraph = rFirst;
            rFirst.prevSameDigraph = digLastOcc;
            rFirst.nextSameDigraph = dig.firstOccurrence;
            return null;
        }
    }
    removeDigraphLink(rune) {
        const dig = rune.digraph;
        if (rune.prevSameDigraph) {
            rune.prevSameDigraph.nextSameDigraph = rune.nextSameDigraph;
            if (dig.firstOccurrence === rune)
                dig.firstOccurrence = rune.prevSameDigraph;
        }
        if (rune.nextSameDigraph) {
            rune.nextSameDigraph.prevSameDigraph = rune.prevSameDigraph;
            if (dig.firstOccurrence === rune)
                dig.firstOccurrence = rune.nextSameDigraph;
        }
        rune.digraph = rune.prevSameDigraph = rune.nextSameDigraph = null;
    }
    updateDigBucket(dig) {
        if (!dig)
            return;
        dig.removeFromBucket();
        if (!dig.count) {
            this.digraphMap.delete(dig.key);
            return;
        }
        const existing = this.buckets[dig.count];
        if (existing) {
            dig.insertToBucket(existing);
        }
        else {
            const sen = new DigraphSentinel();
            dig.insertToBucket(sen);
            this.buckets[dig.count] = sen;
        }
    }
    collectBuckets() {
        for (const dig of this.digraphMap.values()) {
            this.updateDigBucket(dig);
        }
    }
    getEntry(count) {
        const sen = this.buckets[count];
        if (!sen)
            return null;
        const item = sen.next;
        if (item instanceof Digraph)
            return item;
        else
            return null;
    }
    stringOf(input) {
        let s = "";
        let node = input.sentinel.next;
        while (node !== input.sentinel) {
            s += this.keyProvider.getIrKey(node.ir);
            node = node.next;
        }
        return s;
    }
    doSubstitute(dig, nonTerminal) {
        if (!dig.count || !dig.firstOccurrence)
            return;
        const affectedDigraphs = new Set();
        dig.removeFromBucket();
        dig.count = 0;
        let occ = dig.firstOccurrence;
        do {
            const nextDigOcc = occ.nextSameDigraph;
            const prev = occ.prev;
            const next = occ.next;
            const nextNext = occ.next.next;
            if (prev.digraph) {
                prev.digraph.count--;
                affectedDigraphs.add(prev.digraph);
                this.removeDigraphLink(prev);
            }
            if (next.digraph) {
                next.digraph.count--;
                affectedDigraphs.add(next.digraph);
                this.removeDigraphLink(next);
            }
            // Insert the non-terminal
            const runeNT = new Rune(nonTerminal);
            prev.next = runeNT;
            nextNext.prev = runeNT;
            runeNT.prev = prev;
            runeNT.next = nextNext;
            // Build up adjacent digraphs
            affectedDigraphs.add(this.addDigraph(prev, runeNT));
            affectedDigraphs.add(this.addDigraph(runeNT, nextNext));
            occ = nextDigOcc;
        } while (occ && occ !== dig.firstOccurrence);
        for (const d of affectedDigraphs) {
            this.updateDigBucket(d);
        }
    }
    *doCompress(ntSrc, rb) {
        this.collectBuckets();
        let count = this.buckets.length - 1;
        while (count > 1) {
            const dig = this.getEntry(count);
            if (!dig) {
                count--;
                continue;
            }
            const irFirst = dig.firstOccurrence.ir;
            const irSecond = dig.firstOccurrence.next.ir;
            const nonTerminal = ntSrc.createNonTerminal(irFirst, irSecond);
            this.doSubstitute(dig, nonTerminal);
            if (rb)
                yield rb.createNonTerminalRule(nonTerminal, [irFirst, irSecond]);
        }
    }
    inputToRule(input, rb) {
        const parts = [];
        let sen = input.sentinel.next;
        while (sen && sen !== input.sentinel) {
            parts.push(sen.ir);
            sen = sen.next;
        }
        return rb.createInputRule(parts);
    }
}
exports.Session = Session;
//# sourceMappingURL=pairing.js.map