import { subscribeClassProperty } from './property/subscribeClassProperty';
import { PropertyObserverHandler } from './property/propertyObserverHandler';
import { BindingEngine } from './bindingEngine';
import {
    IAttribute,
    CONSTANTS,
    IElement,
    IListener
} from '../interface/exported';
import { createBindingContext } from './createBindingContext';



/**
  * callbacks changes in class if chnage method is added
  *
  */
const subscribeChangeBaseClass = class implements IListener {
    private key: string;
    private _class: IElement | IAttribute;
    private meta: any;
    public caller: PropertyObserverHandler;

    constructor(_class: IElement | IAttribute, key: string, meta: any) {
        this._class = _class;
        this.key = key;
        this.meta = meta;

    }



    /**
     * called on changes
     *
     */
    public call(newValue: any, oldValue: any) {
        if (oldValue !== newValue) {
            const key = this.key;
            const _class = this._class;
            const META = this.meta;

            if (_class.$bindingContext && key in _class.$bindingContext.$context) { // I should call this before?
                if (_class.$bindingContext.$context[key] !== newValue) {
                    _class.$bindingContext.$context[key] = newValue;
                }
            }

            if (_class[`${key}Changed`]) {
                _class[`${key}Changed`](newValue, oldValue);
            }

            if (_class[`attributesChanged`]) { // usefull so we dont need a handler for each
                _class[`attributesChanged`](key, newValue, oldValue);
            }

            if (META[key].options.changeHandler) {
                _class[META[key].options.changeHandler](key, newValue, oldValue);
            }

        }
    }
};



/**
  * creates a observer if bindable meta data is added
  *
  */
export function subscribeClassMetaBinding(_class: IElement | IAttribute) {
    const META = (_class as any).__proto__[CONSTANTS.META_BINDABLE];
    if (META) {
        const keys = Object.keys(META);
        for (const key of keys) {

            if (!(_class as any).__metaBinding) {
                (_class as any).__metaBinding = {};
            }
            if (!(_class as any).__metaBinding[key]) {
                (_class as any).__metaBinding[key] = {};
                (_class as any).__metaBinding[key].key = key;
                (_class as any).__metaBinding[key].options = {};
            }
            const CLASSMETA = (_class as any).__metaBinding[key];

            const subscribeInternal = new subscribeChangeBaseClass(_class, key, META);
            subscribeClassProperty(createBindingContext(_class), key, subscribeInternal);

            // save it for later so we can unsubscribe
            CLASSMETA.options.subscribeInternal = subscribeInternal;

            if (META[key].observableOnly === true) {
                // nothing atm
            } else {

                if ((_class as IElement).$attributes && !(_class as IAttribute).$attribute) {

                    // custom element
                    // we now check for if attribute have bind, else we just give the value

                    if (_class.$element && _class.$bindingContext) {
                        // need to make sure there is parent and element

                        const el = _class.$element as Element;
                        let att = `${META[key].options.attribute}.bind`;
                        let attrValue = el.getAttribute(att);
                        if (attrValue) {

                            const subscribeExternal: IListener = Object.create({
                                call: (newValue: any, oldValue: any) => {
                                    if (oldValue !== newValue) {
                                        _class[key] = newValue;
                                    }
                                }
                            });

                            subscribeClassProperty(_class.$bindingContext, attrValue, subscribeExternal);

                            // save it for later so we can unsubscribe
                            CLASSMETA.options.subscribeExternal = subscribeExternal;

                        } else {

                            // just set value, no bind
                            att = `${META[key].options.attribute}`;
                            attrValue = el.getAttribute(att);
                            if (attrValue) {
                                if (attrValue.indexOf('${') !== -1 || attrValue.indexOf('@{') !== -1) {
                                    const val = BindingEngine.tokenizeParseAndTraverseAST(attrValue, _class.$bindingContext);
                                    if (val) {
                                        _class[key] = val;
                                    }
                                } else {
                                    _class[key] = attrValue;
                                }
                            }
                        }
                    }

                } else {

                    // custom attribute
                    // - > need to extract values, they can be many in 1

                    if (_class.$element && _class.$bindingContext) {

                        // many or 1 check, if just value we only want that
                        if (keys.length === 1 && key === 'value') {

                            // only 1 key and its value, then we only use this
                            const el = _class.$element as Element;
                            const att = (_class as IAttribute).$attribute.name;
                            const haveBind = att.indexOf('.bind');
                            let attrValue = el.getAttribute(att);

                            if (haveBind !== -1) {
                                // binding

                                const subscribeExternal: IListener = Object.create({
                                    call: (newValue: any, oldValue: any) => {
                                        if (oldValue !== newValue) {
                                            _class[key] = newValue;
                                        }
                                    }
                                });

                                subscribeClassProperty(_class.$bindingContext, attrValue, subscribeExternal);

                                // save it for later so we can unsubscribe
                                CLASSMETA.options.subscribeExternal = subscribeExternal;
                            } else {

                                // no bind, just get value
                                attrValue = el.getAttribute(att);
                                if (attrValue) {
                                    if (attrValue.indexOf('${') !== -1 || attrValue.indexOf('@{') !== -1) {
                                        const val = BindingEngine.tokenizeParseAndTraverseAST(attrValue, _class.$bindingContext);
                                        if (val) {
                                            _class[key] = val;
                                        }
                                    } else {
                                        _class[key] = attrValue;
                                    }
                                }
                            }

                        } else {


                            // not just value and 1 @bindable

                            const el = _class.$element as Element;
                            const attributeName = (_class as IAttribute).$attribute.name;
                            let att = `${META[key].options.attribute}.bind`;
                            const attrValues: any[] = el.getAttribute(attributeName) ? el.getAttribute(attributeName).split(';') : null;

                            // do we have many values inside?
                            if (attrValues) {

                                // yes, lets get them and check for key/att
                                // atm checking for .bind
                                let attrValue: string;
                                attrValues.forEach((value: any) => {
                                    const test = value.split(':');
                                    if (test && test[0].trim() === att) {
                                        attrValue = test[1];
                                    }
                                });


                                if (attrValue) {

                                    const subscribeExternal: IListener = Object.create({
                                        call: (newValue: any, oldValue: any) => {
                                            if (oldValue !== newValue) {
                                                _class[key] = newValue;
                                            }
                                        }
                                    });

                                    subscribeClassProperty(_class.$bindingContext, attrValue, subscribeExternal);

                                    // save it for later so we can unsubscribe
                                    CLASSMETA.options.subscribeExternal = subscribeExternal;

                                } else {

                                    // no .bind
                                    // just set value if it is there
                                    att = `${META[key].options.attribute}`;
                                    let attrValue: string;
                                    attrValues.forEach((value: any) => {
                                        const test = value.split(':');
                                        if (test && test[0].trim() === att) {
                                            attrValue = test[1];
                                        }
                                    });

                                    if (attrValue) {
                                        if (attrValue.indexOf('${') !== -1 || attrValue.indexOf('@{') !== -1) {
                                            const val = BindingEngine.tokenizeParseAndTraverseAST(attrValue, _class.$bindingContext);
                                            if (val) {
                                                _class[key] = val;
                                            }
                                        } else {
                                            _class[key] = attrValue;
                                        }
                                    }
                                }
                            }
                        }
                    }

                }
            }

        }
    }
}
