// Regular EXception

// type Char =
//      'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h'
//     | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p'
//     | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x'
//     | 'y' | 'z' | '?';

import type { Token } from './lexer.js';

type QM = Symbol;
type Star = Symbol;
type $ = Symbol;
type $$ = Symbol;
type Ex = QM | Star | $ | $$;
// type ValidTypes = Char | Ex;
type ValidTypes = Ex | Token;

type Equality = (x:ValidTypes,y:ValidTypes)=>boolean;

let QM:Ex;
let Star:Ex;
let $:Ex;
let $$:Ex;

QM = Symbol();
Star = Symbol();
$ = Symbol();
$$ = Symbol();

export { QM, Star, $, $$ };
export type { ValidTypes };

let wildcard:(x:ValidTypes)=>boolean;

wildcard = (x:ValidTypes) => (
    (x == QM)
    || (x == Star)
    || (x == $)
    || (x == $$)
);

interface Irex<a> {
    constructor:Function;
    match:(xs:a[])=>boolean;
    match_:(xs:a[],cap_:a[])=>boolean;
    es:a[];
    caps:a[][];
    capidx:number|null;
    capturing:boolean;
    equality:Equality;
}

export class REX<a extends ValidTypes>
 implements Irex<a> {
    es:a[];
    caps:a[][];
    capidx:number|null;
    capturing:boolean;
    equality:Equality;

    constructor(input:ValidTypes[], eq:Equality) {
        // add sanity checks
        this.es = input as a[];
        this.caps = [];
        this.capidx = null;
        this.capturing = false;
        this.equality = eq;
    }

    match(xs:a[]): boolean {
        return this.match_(xs, []);
    }

    match_(xs:a[],cap_:a[]): boolean {
        let x:a|null;
        let e:a|null;
        let cap:a[];
    
        cap = cap_;
        // console.log('-- xs=', xs);
        // console.log('-- cap=', cap);

        x = xs.shift()
            || null;
        e = this.es.shift()
            || null;

        if (!x)
            if (!this.es.length)
                return true;
            else
                return false;
        else if (!e)
            return false;

        if ((this.capidx !== null) && (this.capturing)) {
            // console.log('  * adding cap ', x, 'e=', e, 'x=', x);
            cap = cap.concat([x]);
        }

        if (!wildcard(e)) {
            if (this.equality(e,x))
                return this.match_(xs, cap);
            else
                return false;
        }
        else {
            switch (e) {
                case QM:
                    // console.log('[?]');
                    return this.match_(xs, cap);

                    break;
                
                case Star:
                    // console.log('[*]');
                    if ((this.es[0] == x))
                        void 0;
                    else if ((wildcard(this.es[0])
                        && (this.equality(this.es[1],x)))) {
                            if (this.es[0] == $$) {
                                cap.pop();
                                if (this.capidx === null)
                                    throw "Syntax error";
                                this.caps[this.capidx] = cap;
                                this.capturing = false;
                                this.es.shift();

                                return this.match_(
                                    xs, []
                                );
                            }

                            this.es.shift() &&
                                this.match_(xs, cap);
                        }
                    else
                        this.es = [e].concat(this.es);
                    return this.match_(xs, cap);

                    break;
                
                case $:
                    // console.log('[$]');
                    if (this.capidx === null)
                        this.capidx = 0;
                    else
                        this.capidx++;

                    this.capturing = true;

                    return this.match_(
                        [x].concat(xs), []
                    );

                    break;

                case $$:
                    console.log('[$$]');
                    if (this.capidx === null)
                        throw "Syntax error";

                    cap.pop();
                    this.caps[this.capidx] = cap;
                    this.capturing = false;
                    // console.log('  cap=', cap);

                    return this.match_(
                        [x].concat(xs), []
                    );

                    break;
            }
        }

        return false;
    }
}