
import { AST } from './ast';


/**
 * Using Douglas Crockford paper on Vaughan Pratt "Top Down Operator Precedence"
 * For more info about this look here
 * https://crockford.com/javascript/tdop/tdop.html (added to docs folder so I dont loose it)
 * Not everything is used
 *
 * This function adds symbols to our AST
 *
 */
export function addSymbols(astInstance: AST) {

    const ast = astInstance;



    /**
     * Symbol id and an optional binding power that defaults to 0 and returns a symbol object for that id.
     * If the symbol already exists in the symbol_table, the function returns that symbol object.
     * Otherwise, it makes a new symbol object that inherits from the symbolTemplate, stores it in the symbol container, and returns it.
     * A symbol object initially contains an id, a value, a left binding power, and the stuff it inherits from the symbolTemplate.
     *
     */
    ast.symbol('(literal)').nud = function () {
        return this;
    };

    ast.symbol('(variable)').nud = function () {
        return this;
    };

    ast.symbol('(function)').nud = function () {
        return this;
    };



    ast.symbol('(end)');
    ast.symbol('(name)');
    ast.symbol(':');
    ast.symbol(';');
    ast.symbol(')');
    ast.symbol(']');
    ast.symbol('}');
    ast.symbol(',');



    /**
     * The infix function takes an id, a binding power, and an optional led function.
     * If a led function is not provided, the infix function supplies a default led that is useful in most cases.
     *
     */
    ast.infix('+', 50);
    ast.infix('-', 50);
    ast.infix('*', 60);
    ast.infix('%', 60);
    ast.infix('/', 60);
    ast.infix('===', 40);
    ast.infix('!==', 40);
    ast.infix('==', 40);
    ast.infix('!=', 40);
    ast.infix('<', 40);
    ast.infix('<=', 40);
    ast.infix('>', 40);
    ast.infix('^', 70);
    ast.infix('>=', 40);


    ast.infix('?', 20, function (left: any) {
        this.first = left;
        this.second = ast.expression(0);
        ast.advance(':');
        this.third = ast.expression(0);
        this.arity = 'ternary';

        return this;
    });


    ast.infix('.', 80, function (left: any) {
        this.first = left;
        if (ast.currentToken.arity !== 'variable') {
            ast.currentToken.error('Expected a property name.');
        }
        ast.currentToken.arity = 'variable';
        this.second = ast.currentToken;
        this.arity = 'binary';
        this.isObject = true;
        ast.advance();

        return this;
    });



    ast.infix('[', 80, function (left: any) {
        this.first = left;
        this.second = ast.expression(0);
        this.arity = 'binary';
        this.isObject = true;
        this.contextReset = true;
        ast.advance(']');

        return this;
    });



    ast.infix('(', 80, function (left: any) {
        if (!left) {
            return ast.expression(0);
        }
        const a: any[] = [];
        this.arity = 'binary';
        this.isFunction = true;
        this.first = left;
        this.second = a;
        if (ast.currentToken.id !== ')') {
            while (true) {
                a.push(ast.expression(0));
                if (ast.currentToken.id !== ',') {
                    break;
                }
                ast.advance(',');
            }
        }
        ast.advance(')');

        return this;
    });



    /**
     * Prefix operators are right associative.
     * A prefix does not have a left binding power because it does not bind to the left.
     * Prefix operators can also sometimes be reserved words.
     *
     */
    ast.prefix('-');
    ast.prefix('!');
    // ast.prefix('typeof'); // TODO: I dont have anything for this in my tokenizer
    ast.prefix('(', function () {
        const e = ast.expression(0);

        // TODO: check this one... will this even happend ?
        if (ast.currentToken.value === '|') {
            ast.valueConverter = ast.expression(0);
        }
        if (ast.currentToken.value === '&') {
            ast.behavior = ast.expression(0);
        } else {
            ast.advance(')');
        }

        return e;
    });

    ast.prefix('{', function () {

        const a = [];
        if (ast.currentToken.value !== '}') {
            while (true) {
                const n = ast.currentToken;
                ast.advance();
                ast.advance(':');
                const v = ast.expression(0);
                v.key = n.value;
                a.push(v);
                if (ast.currentToken.value !== ',') {
                    break;
                }
                ast.advance(',');
            }
        }
        ast.advance('}');
        this.first = a;
        this.arity = 'unary';

        return this;
    });

    /*   ast.stmt('{', function () {
          const x = ast.statements();

          ast.advance('}');

          return x;


      }); */


    ast.assignment('=');
    ast.assignment('+=');
    ast.assignment('-=');
    ast.assignment('*=');
    ast.assignment('/=');

    /**
     * valueConverter
     *
     */
    ast.prefix('|', function () {
        this.arity = 'valueConverter';
        const value = ast.expression(0);
        this.value = value.value;
        const a: any = [];
        this.args = a;
        while (true) {
            if (ast.currentToken.id !== ':') {
                break;
            }
            ast.advance(',');
            a.push(ast.expression(0));
        }

        // use last statement as argument
        this.args.unshift(ast.statementsArray.pop());

        ast.currentStatement = this;

        return this;
    });



    /**
     * behavior
     *
     */
    ast.prefix('&', function () {
        this.arity = 'behavior';
        const value = ast.expression(0);
        this.value = value.value;
        const a: any = [];
        this.args = a;
        while (true) {
            if (ast.currentToken.id !== ':') {
                break;
            }
            ast.advance(',');
            a.push(ast.expression(0));
        }

        // do I want to add last argument ?
        // this.args.push(ast.statementsArray);

        return this;
    });



    /**
     * expression
     *
     */
    ast.prefix('${', function () {
        this.e = ast.expression(0);
        ast.advance('}');

        return this.e;
    });



    /**
     * expression, alternative to ${
     *
     */
    ast.prefix('@{', function () {
        const e = ast.expression(0);
        ast.advance('}');

        return e;
    });



    /**
     * Those infix operators are left associative.
     * We can also make right associative operators, such as short-circuiting logical operators, by reducing the right binding power.
     *
     */
    ast.infixr('&&', 30);
    ast.infixr('||', 30);



    /**
     * expressions/part before expression is split with ';' operator
     *
     */
    ast.symbol(';').nud = function (): null {

        return null;
    };
}
