import { customAttribute } from '../decorator/exported';
import { IAttribute, IBindingContext } from '../interface/exported';
import { BindingEngine } from '../binding/exported';
import { IListener } from '../interface/exported';
import { Cache, connectBehavior } from '../utils/exported';


// helper class for property changes
export class PropertyChangeHandler implements IListener {
    private firstRun = true;
    public name = 'valueAttribute';
    public caller: any;
    public value: any;

    constructor(
        public node: HTMLInputElement
    ) { }

    public call(newValue: any, oldValue: any) {
        if (oldValue !== newValue || this.firstRun) {
            this.value = newValue;
            switch (true) {


                /**
                 *  normal text input logic
                 *
                 */
                case this.node.type === 'textarea':
                case (this.node.type !== 'checkbox'
                    && this.node.type !== 'radio'
                    && this.node.type !== 'select-multiple'
                    && this.node.type !== 'select-one'):
                    this.node.value = newValue || '';
                    break;



                /**
                 *  checkbox logic
                 *
                 */
                case this.node.type === 'checkbox':

                    const modelBind = this.node.hasAttribute('model.bind') ? this.node.getAttributeNode('model.bind') : null;
                    if (modelBind && (modelBind as any).getContent) {
                        const x = (modelBind as any).getContent();
                        if (x) {
                            if (Array.isArray(newValue)) {
                                if (newValue.indexOf(x.value) !== -1) {
                                    this.node.checked = true;
                                } else {
                                    this.node.checked = false;
                                }
                            } else {
                                if (newValue === x.value) {
                                    this.node.checked = true;
                                } else {
                                    this.node.checked = false;
                                }
                            }
                        }
                    } else {
                        if (Array.isArray(newValue)) {
                            console.warn('error: value is array, this should not happen');
                        } else {
                            this.node.checked = newValue || false;
                        }

                    }
                    break;



                /**
                 *  radio group logic
                 *
                 */
                case this.node.type === 'radio':
                    if (this.node) {
                        const modelBind = this.node.hasAttribute('model.bind') ? this.node.getAttributeNode('model.bind') : null;
                        if (modelBind && (modelBind as any).getContent) {
                            const x = (modelBind as any).getContent();
                            if (x) {
                                if (newValue === x.value) {
                                    this.node.checked = true;
                                }
                            }
                        }
                    }
                    break;



                /**
                 *  select with option group *single select
                 *
                 */
                case this.node.type === 'select-one':
                    const optionsSingle = (this.node as any).options;
                    let index = null;
                    for (let i = 0; i < optionsSingle.length; i++) {
                        const modelBind = optionsSingle[i].hasAttribute('model.bind') ? optionsSingle[i].getAttributeNode('model.bind') : null;
                        if (modelBind && (modelBind as any).getContent) {
                            const x = (modelBind as any).getContent();
                            if (x) {
                                if (Array.isArray(newValue)) {
                                    if (newValue[0] === x.value) {
                                        index = i;
                                    }
                                } else {
                                    if (newValue === x.value) {
                                        index = i;
                                    }
                                }
                            }
                        }
                    }
                    (this.node as any).selectedIndex = index;
                    break;


                /**
                 *  select with option group *multi select
                 *
                 */
                case this.node.type === 'select-multiple':
                    const optionsMany = (this.node as any).options;
                    for (let i = 0; i < optionsMany.length; i++) {
                        const modelBind = optionsMany[i].hasAttribute('model.bind') ? optionsMany[i].getAttributeNode('model.bind') : null;
                        if (modelBind && (modelBind as any).getContent) {
                            const x = (modelBind as any).getContent();
                            if (x) {
                                if (Array.isArray(newValue)) {
                                    if (newValue.indexOf(x.value) !== -1) {
                                        optionsMany[i].selected = true;
                                    } else {
                                        optionsMany[i].selected = false;
                                    }
                                    if (newValue.length === 0 && x.value === null) {
                                        optionsMany[i].selected = true;
                                    }
                                } else {
                                    if (newValue === x.value) {
                                        optionsMany[i].selected = true;
                                    } else {
                                        optionsMany[i].selected = false;
                                    }
                                }
                            }
                        }
                    }
                    break;
            }
            this.firstRun = false;
        }
    }
}


@customAttribute('value.bind')
@customAttribute('checked.bind')
export class ValueAttribute implements IAttribute {

    // build in properties added by our template engine
    public $element: HTMLInputElement;
    public $attribute: Attr;
    public $bindingContext: IBindingContext;
    public name: string;
    public attributeName: string;
    public trigger: string[];
    public valueConverters: string[] = [];
    public eventHandlerBinded: any;


    // private vars
    private expressionValue: string;
    private propertyChangeHandler: PropertyChangeHandler;



    constructor() {

        // our listener
        this.eventHandlerBinded = this.eventHandler.bind(this);
        // nothing atm
    }

    public attached() {
        if (this.$element.type === 'select-multiple') {
            // model is not ready
            this.propertyChangeHandler.call(this.propertyChangeHandler.value, 'somethign % its not');
        }
    }

    /**
     * created
     *
     */
    public created() {

        this.expressionValue = (this.$attribute as any).value;
        this.attributeName = this.expressionValue.split('|')[0].trim().split('&')[0].trim();
        this.trigger = null; // ok, connectBehavior set this
        this.name = (this.$attribute as any).name;
        this.propertyChangeHandler = new PropertyChangeHandler(this.$element);


        // check type is normal input
        switch (true) {


            /**
             *  normal text input logic
             *
             */
            case (this.$element.tagName === 'INPUT'
                && this.$element.type !== 'checkbox'
                && this.$element.type !== 'radio'
                && this.$element.type !== 'select-multiple'
                && this.$element.type !== 'select-one') || this.$element.tagName === 'TEXTAREA':
                BindingEngine.subscribeClassProperty(this.$bindingContext, this.expressionValue, this.propertyChangeHandler);
                connectBehavior(this.expressionValue, this);
                if (!this.trigger) {
                    (this.$element as any).addEventListener('input', (this.eventHandlerBinded as EventListenerOrEventListenerObject), false);
                } else {
                    this.trigger.forEach((trigger) => {
                        (this.$element as any).addEventListener(trigger, (this.eventHandlerBinded as EventListenerOrEventListenerObject), false);
                    });
                }
                break;



            /**
             *  checkbox logic
             *
             */
            case this.$element.tagName === 'INPUT' && this.$element.type === 'checkbox':
                BindingEngine.subscribeClassProperty(this.$bindingContext, this.expressionValue, this.propertyChangeHandler);
                connectBehavior(this.expressionValue, this);
                if (!this.trigger) {
                    (this.$element as any).addEventListener('click', (this.eventHandlerBinded as EventListenerOrEventListenerObject), false);
                } else {
                    this.trigger.forEach((trigger) => {
                        (this.$element as any).addEventListener(trigger, (this.eventHandlerBinded as EventListenerOrEventListenerObject), false);
                    });
                }

                break;



            /**
             *  radio group logic
             *
             */
            case this.$element.tagName === 'INPUT' && this.$element.type === 'radio':
                BindingEngine.subscribeClassProperty(this.$bindingContext, this.expressionValue, this.propertyChangeHandler);
                connectBehavior(this.expressionValue, this);
                if (!this.trigger) {
                    (this.$element as any).addEventListener('click', (this.eventHandlerBinded as EventListenerOrEventListenerObject), false);
                } else {
                    this.trigger.forEach((trigger) => {
                        (this.$element as any).addEventListener(trigger, (this.eventHandlerBinded as EventListenerOrEventListenerObject), false);
                    });
                }
                break;



            /**
             *  select with option group
             *
             */
            case this.$element.tagName === 'SELECT':
                BindingEngine.subscribeClassProperty(this.$bindingContext, this.expressionValue, this.propertyChangeHandler);
                connectBehavior(this.expressionValue, this);
                if (!this.trigger) {
                    (this.$element as any).addEventListener('change', (this.eventHandlerBinded as EventListenerOrEventListenerObject), false);
                } else {
                    this.trigger.forEach((trigger) => {
                        (this.$element as any).addEventListener(trigger, (this.eventHandlerBinded as EventListenerOrEventListenerObject), false);
                    });
                }
                break;
            default:
            // nothing

        }

    }


    // being able to use my ast would have been best.
    public runValueConverter(value: any) {
        const x = Cache.expressionMap.get(this.expressionValue);

        return BindingEngine.valueConvert((<any>x).ast, this.$bindingContext, value);
    }



    /**
     * detached
     *
     */
    public detached() {



        switch (true) {



            /**
             *  normal text input logic
             *
             */
            case (this.$element.tagName === 'INPUT'
                && this.$element.type !== 'checkbox'
                && this.$element.type !== 'radio'
                && this.$element.type !== 'select-multiple'
                && this.$element.type !== 'select-one') || this.$element.tagName === 'TEXTAREA':
                if (!this.trigger) {
                    this.$element.removeEventListener('input', this.eventHandlerBinded);
                } else {
                    this.trigger.forEach((trigger) => {
                        this.$element.removeEventListener(trigger, this.eventHandlerBinded);
                    });
                }
                break;



            /**
             *  checkbox logic
             *
             */
            case this.$element.tagName === 'INPUT' && this.$element.type === 'checkbox':
                if (!this.trigger) {
                    this.$element.removeEventListener('click', this.eventHandlerBinded);
                } else {
                    this.trigger.forEach((trigger) => {
                        this.$element.removeEventListener(trigger, this.eventHandlerBinded);
                    });
                }
                break;


            /**
             *  radio group logic
             *
             */
            case this.$element.tagName === 'INPUT' && this.$element.type === 'radio':
                if (!this.trigger) {
                    this.$element.removeEventListener('click', this.eventHandlerBinded);
                } else {
                    this.trigger.forEach((trigger) => {
                        this.$element.removeEventListener(trigger, this.eventHandlerBinded);
                    });
                }
                break;



            /**
             *  select with option group
             *
             */
            case this.$element.tagName === 'SELECT':
                if (!this.trigger) {
                    this.$element.removeEventListener('change', this.eventHandlerBinded);
                } else {
                    this.trigger.forEach((trigger) => {
                        this.$element.removeEventListener(trigger, this.eventHandlerBinded);
                    });
                }
                break;

            default:
                // just in case
                (this.$attribute as any).$bindingContext = null;
                break;
        }

        if (this.propertyChangeHandler.caller) {
            BindingEngine.unSubscribeClassProperty(this.$bindingContext, this.propertyChangeHandler);
        }



    }


    /**
     * event handler
     *
     */
    public eventHandler() {


        switch (true) {

            /**
             *  normal text input logic
             *
             */
            case (this.$element.tagName === 'INPUT'
                && this.$element.type !== 'checkbox'
                && this.$element.type !== 'radio'
                && this.$element.type !== 'select-multiple'
                && this.$element.type !== 'select-one') || this.$element.tagName === 'TEXTAREA':
                let value: any = (this.$element as HTMLInputElement).value;
                if (this.$element.type === 'number') {
                    value = (this.$element as HTMLInputElement).valueAsNumber;
                }

                if (this.$element.type === 'date') {
                    value = (this.$element as HTMLInputElement).valueAsDate;
                }

                BindingEngine.setValue(this.$bindingContext, this.attributeName, this.runValueConverter(value));
                break;



            /**
             *  checkbox logic
             *
             */
            case this.$element.tagName === 'INPUT' && this.$element.type === 'checkbox':
                const modelBindCheckbox = (this.$element as any).hasAttribute('model.bind') ? (this.$element as any).getAttributeNode('model.bind') : null;

                if (modelBindCheckbox) {

                    // if we have model bind then it is a group
                    const value = modelBindCheckbox.getContent().value;
                    let propertyType = BindingEngine.evaluateExpression(this.$attribute.nodeValue, this.$bindingContext);
                    const isArray = Array.isArray(propertyType);
                    const selected = this.$element.checked;
                    if (isArray) {

                        // if binded variable is array
                        if (selected) {

                            const ii = propertyType.indexOf(value);
                            if (ii === -1) {
                                propertyType = [...propertyType, this.runValueConverter(value)];
                                BindingEngine.setValue(this.$bindingContext, this.attributeName, propertyType);
                            }

                        } else {

                            const ii = propertyType.indexOf(value);
                            if (ii !== -1) {
                                propertyType.splice(ii, 1);
                                if (!propertyType.__array_observer__class) {
                                    propertyType = [...propertyType];
                                }
                                BindingEngine.setValue(this.$bindingContext, this.attributeName, propertyType);
                            } else {
                                this.$element.checked = false;
                            }
                        }

                    } else {

                        // no array, just set value
                        BindingEngine.setValue(this.$bindingContext, this.attributeName, this.runValueConverter(value) || false);
                    }
                } else {

                    // not group, just set value
                    BindingEngine.setValue(this.$bindingContext, this.attributeName, (this.$element as HTMLInputElement).checked || false);
                }
                break;



            /**
             *  radio group logic
             *
             */
            case this.$element.tagName === 'INPUT' && this.$element.type === 'radio':
                const modelBindRadio = (this.$element as any).hasAttribute('model.bind') ? (this.$element as any).getAttributeNode('model.bind') : null;
                if (modelBindRadio) {
                    const value = BindingEngine.evaluateExpression(modelBindRadio.nodeValue, modelBindRadio.getContent().$bindingContext);
                    BindingEngine.setValue(this.$bindingContext, this.attributeName, this.runValueConverter(value));
                } else {
                    BindingEngine.setValue(this.$bindingContext, this.attributeName, this.runValueConverter((this.$element as HTMLInputElement).checked));
                }
                break;



            /**
             *  select with option group
             *
             */
            case this.$element.tagName === 'SELECT':
                const propertyType = BindingEngine.evaluateExpression(this.$attribute.nodeValue, this.$bindingContext);
                const isArray = Array.isArray(propertyType);
                const length = (this.$element as any).selectedOptions.length;
                let finalValue = null;
                if (length > 1) {
                    const values = [];
                    for (let i = 0; i < length; i++) {
                        const x = (this.$element as any).selectedOptions[i];
                        const modelBind = x.hasAttribute('model.bind') ? x.getAttributeNode('model.bind') : null;
                        const value = BindingEngine.evaluateExpression(modelBind.nodeValue, modelBind.getContent().$bindingContext);
                        values.push(this.runValueConverter(value));
                    }
                    finalValue = values;
                } else {
                    if (length === 0) {
                        finalValue = null;
                    } else {
                        const x = (this.$element as any).selectedOptions[0];
                        const modelBind = x.hasAttribute('model.bind') ? x.getAttributeNode('model.bind') : null;
                        const value = BindingEngine.evaluateExpression(modelBind.nodeValue, modelBind.getContent().$bindingContext);
                        finalValue = value;
                    }
                }


                if (isArray) {
                    BindingEngine.setValue(this.$bindingContext, this.attributeName,
                        Array.isArray(finalValue) ? finalValue : finalValue === null ? [] : [finalValue]);
                } else {
                    BindingEngine.setValue(this.$bindingContext, this.attributeName,
                        Array.isArray(finalValue) ? finalValue.map(a => a) : finalValue);
                }
                break;


        }

    }

}
