import { tokenize } from './ast/tokenize';
import { generateAST } from './ast/generateAst';
import { ClassPropertyObserverCreator } from './property/classPropertyObserverCreator';
import { PropertyObserverHandler } from './property/propertyObserverHandler';
import { Cache } from '../utils/exported';
import { CONSTANTS, IBindingContext } from '../interface/exported';
import { getCorrectContext } from './contextOfObject';



/**
 * creates binding expression by getting tokens from the text
 *
 */
export function createBindingExpression(expression: string, _class: IBindingContext, setterClass: PropertyObserverHandler) {

    let attributesValues: any[] = [];
    let ast;

    if (expression) {

        if (!setterClass.attributesValues) {



            let tokens: any;
            let tokenValues: any = [];
            let tokenObject: any = [];
            if (Cache.expressionMap.has(expression)) {
                const cacheX = Cache.expressionMap.get(expression);
                ast = (<any>cacheX).ast;
                tokens = (<any>cacheX).tokens;
                tokenValues = (<any>cacheX).tokenValues;
                tokenObject = (<any>cacheX).tokenObject;


            } else {
                tokens = tokenize(expression);
                ast = generateAST(tokens);
                let curVal: any = null;
                let curObject: any = [];

                for (let next = 0; next < tokens.length; next++) {
                    const tok = tokens[next];
                    if (tok.type === 'variable') {
                        if (!curVal) {
                            curVal = tok.value;
                            curObject.push(tok.value);
                        } else {
                            curVal = curVal + tok.value;
                            curObject.push(tok.value);
                        }
                    } else {
                        if (tok.type === 'operator' && tok.value === '.') {
                            if (curVal) {
                                curVal = curVal + '.';
                            }
                        } else {
                            if (tok.type === 'operator' // support binding to "somevalue.array[0].name"
                                && tok.value === '['
                                && tokens[next + 2]
                                && tokens[next + 2].type === 'operator'
                                && tokens[next + 2].value === ']') {
                                if (curVal) {
                                    curVal = curVal + '[' + tokens[next + 1].value + ']';
                                }
                                next = next + 2;
                            } else {
                                if (curVal) {
                                    tokenValues.push(curVal);
                                    curVal = null;
                                    tokenObject.push(curObject);
                                    curObject = [];
                                }
                            }
                        }
                    }
                }
                if (curVal) {
                    tokenValues.push(curVal);
                    tokenObject.push(curObject);
                    curObject = [];
                    curVal = null;
                }
                Cache.expressionMap.set(expression, {
                    ast: ast,
                    tokens: tokens,
                    tokenValues: tokenValues,
                    tokenObject: tokenObject
                });
            }


            // for each token get the attribute on variables
            tokenValues.forEach((tok: any) => {
                const newctx = getCorrectContext(tok, _class);


                if (typeof tok === 'string'
                    && tok[0] !== '$'
                    && newctx
                    && newctx.__bindingContext
                    && newctx.$context.__proto__
                    && newctx.$context.__proto__[CONSTANTS.META_COMPUTEDFROM]
                    && newctx.$context.__proto__[CONSTANTS.META_COMPUTEDFROM][tok]) {
                    const computedFrom = newctx.$context.__proto__[CONSTANTS.META_COMPUTEDFROM];
                    computedFrom[tok].attributes.forEach((val: any) => {
                        if (attributesValues.indexOf(val) === -1) {
                            attributesValues.push(val);
                        }

                    });
                } else {
                    if (attributesValues.indexOf(tok) === -1) {
                        attributesValues.push(tok);
                    }
                }
            });

            // set ast to setter
            setterClass.setAst(ast);
        } else {
            attributesValues = setterClass.attributesValues;
        }

        // for each attribute found
        for (let i = 0; i < attributesValues.length; i++) {
            const newctx = getCorrectContext(attributesValues[i], _class);
            ClassPropertyObserverCreator.create(newctx || _class, attributesValues[i], setterClass);
        }

        // initiate setter class
        setterClass.init();
        setterClass.attributesValues = attributesValues;
    }

}



/**
 * remove binding expression
 *
 */
export function removeBindingExpression(text: string, _class: IBindingContext, handlerClass: PropertyObserverHandler) {
    if (text && handlerClass.attributesValues) {
        const attributesValues = handlerClass.attributesValues;
        for (let i = 0; i < attributesValues.length; i++) {
            const newctx = getCorrectContext(attributesValues[i], _class);
            ClassPropertyObserverCreator.remove(newctx || _class, attributesValues[i], handlerClass);
        }
    }
}
