
import { PropertyObserverHandler } from './propertyObserverHandler';


/**
 * this handles changes on property by using defineProperty, other can then subscibe this to get calls when chnages occur
 *
 */
export class ClassPropertyObserver {
    private _class: Object | any;
    private key: string;
    private value: any;
    private callers: PropertyObserverHandler[];
    private c1: PropertyObserverHandler;
    private c2: PropertyObserverHandler;
    private c3: PropertyObserverHandler;
    private c4: PropertyObserverHandler;
    private c5: PropertyObserverHandler;
    private setterBind: any;
    private getterBind: any;


    constructor(_class: Object, key: string) {
        this._class = _class;
        this.key = key;
        this.setterBind = this.setter.bind(this);
        this.getterBind = this.getter.bind(this);

        if (typeof this._class === 'object') {
            this.value = this._class[this.key];
            this.observe();
        }
    }



    /**
     * unsubscribe
     *
     */
    public unsubscribe(caller: PropertyObserverHandler) {
        if (this.c1 === caller) {
            this.c1 = null;
        }
        if (this.c2 === caller) {
            this.c2 = null;
        }
        if (this.c3 === caller) {
            this.c3 = null;
        }
        if (this.c4 === caller) {
            this.c4 = null;
        }
        if (this.c5 === caller) {
            this.c5 = null;
        }

        if (this.callers && this.callers.indexOf(caller) !== -1) {
            this.callers.splice(this.callers.indexOf(caller), 1);
        }


    }



    /**
     * subscribe
     *
     */
    public subscribe(caller: PropertyObserverHandler) {
        if (this.c1 !== caller && this.c2 !== caller && this.c3 !== caller && this.c4 !== caller && this.c5 !== caller) {
            if (!this.c1) {
                this.c1 = caller;
            } else {
                if (!this.c2) {
                    this.c2 = caller;
                } else {
                    if (!this.c3) {
                        this.c3 = caller;
                    } else {
                        if (!this.c4) {
                            this.c4 = caller;
                        } else {
                            if (!this.c5) {
                                this.c5 = caller;
                            } else {
                                if (!this.callers) {
                                    this.callers = [];
                                }
                                if (this.callers.indexOf(caller) === -1) {
                                    this.callers.push(caller);
                                }

                            }
                        }
                    }
                }
            }
        }
    }



    /**
     * helper for #13 to update something that can be observed like array length
     *
     */
    public forceUpdate() {
        if (this.c1) {
            const cc1 = this.c1;
            this.c1 = undefined;
            cc1.update();
        }

        if (this.c2) {
            const cc2 = this.c2;
            this.c2 = undefined;
            cc2.update();
        }

        if (this.c3) {
            const cc3 = this.c3;
            this.c3 = undefined;
            cc3.update();
        }

        if (this.c4) {
            const cc4 = this.c4;
            this.c4 = undefined;
            cc4.update();
        }

        if (this.c5) {
            const cc5 = this.c5;
            this.c5 = undefined;
            cc5.update();
        }

        if (this.callers && this.callers.length > 0) {
            const calls = this.callers.slice();
            this.callers = [];
            for (let i = 0; i < calls.length; i++) {
                calls[i].update();
            }
        }
    }



    /**
     * observer
     *
     */
    private observe() {

        // check if property is on prototype have getter/setter, if se we skip it.
        const descriptor = Reflect.getOwnPropertyDescriptor(this._class.__proto__, this.key);
        let oldDescriptor = null;
        if (descriptor) {
            oldDescriptor = descriptor.get || descriptor.set;
            if (oldDescriptor) {
                console.warn('skipping dirty observer - no-dirty  //todo remove this message');
            }
        }

        if (oldDescriptor === null) {
            if (!Reflect.defineProperty(this._class, this.key, {
                configurable: true,
                enumerable: true,
                set: this.setterBind,
                get: this.getterBind
            })) {
                console.warn('This key could not be observed:' + this.key);
            }
        }



    }



    /**
     * setter
     *
     */
    private setter(newValue: any) {
        if (newValue !== this.value) {
            if (typeof newValue === 'object') {
                if (newValue && newValue.__observer) {
                    newValue.__observer = null;
                }
                if (newValue && newValue.__observerArray) {
                    newValue.__observerArray = null;
                }
            }

            this.value = newValue;

            if (this.c1) {
                const cc1 = this.c1;
                this.c1 = undefined;
                cc1.update();
            }

            if (this.c2) {
                const cc2 = this.c2;
                this.c2 = undefined;
                cc2.update();
            }

            if (this.c3) {
                const cc3 = this.c3;
                this.c3 = undefined;
                cc3.update();
            }

            if (this.c4) {
                const cc4 = this.c4;
                this.c4 = undefined;
                cc4.update();
            }

            if (this.c5) {
                const cc5 = this.c5;
                this.c5 = undefined;
                cc5.update();
            }

            if (this.callers && this.callers.length > 0) {
                const calls = this.callers.slice();
                this.callers = [];
                for (let i = 0; i < calls.length; i++) {
                    calls[i].update();
                }
            }
        }


    }



    /**
     * getter
     *
     */
    private getter() {
        return this.value;
    }


}
