import { traverseAST, getBehavior } from '../ast/traverseAst';
import { createBindingExpression, removeBindingExpression } from '../createBindingExpression';
import { IListener, IBindingContext } from '../../interface/exported';
import { ContainerBehavior } from '../../container/exported';



/**
 * Register/creates a property observer with expression
 * It be called on changes to value of expression
 *
 */
export class PropertyObserverHandler {
    private expression: string;
    private ast: any;
    private context: IBindingContext;
    private observing: boolean;
    private listener: IListener;
    private value: any = undefined;
    private isNew = true;
    public curBehavior: any;
    public attributesValues: string[];


    constructor(expression: string, listener: IListener) {
        this.expression = expression;
        this.listener = listener;
    }



    /**
     * binds observer handler to passed in context
     *
     */
    public bind(context: IBindingContext) {
        this.observing = true;
        this.context = context;
        createBindingExpression(this.expression, this.context, this);
    }


    /**
     * gets new AST "createNewBindingExpression"
     *
     */
    public setAst(ast: Object) {

        this.ast = ast;
        if (!this.curBehavior) {
            this.connectBehavior();
        }

    }



    /**
     * Sets init value
     *
     */
    public init() {
        if (this.isNew) {
            this.isNew = false;
            const oldValue = this.value;
            const newValue = traverseAST(this.ast, this.context);
            this.value = newValue;
            this.listenerCall(newValue, oldValue);
            this.isNew = false;
        }
    }



    /**
     * connectBehavior
     * only used for signal
     *
     */
    public connectBehavior() {
        const behaviors = getBehavior(this.ast);
        if (behaviors) {
            behaviors.forEach((behavior: { name: string, args: any[] }) => {
                if (behavior.name === 'signal') {
                    const x = ContainerBehavior.findBehavior(behavior.name);
                    if (x) {
                        this.curBehavior = new x(this, behavior.args);
                    }
                }
            });
        }
    }



    /**
     * Gets called by observer, it then calls the listener
     *
     */
    public update() {

        const newValue = traverseAST(this.ast, this.context);
        const oldValue = this.value;
        this.value = newValue;
        this.listenerCall(newValue, oldValue);
        this.bind(this.context);

    }

    public listenerCall(newValue: any, oldValue: any) {
        if (this.listener) {
            this.listener.call(newValue, oldValue);
        }
    }


    /**
     * Unbinds and clears all internals
     *
     */
    public unbind() {

        if (this.observing) {
            removeBindingExpression(this.expression, this.context, this);
        }

        // remove this from caller
        this.listener.caller = null;

        // remove rest of internals
        this.observing = false;
        this.context = null;
        this.listener = null;
        this.value = null;
    }

}
