import { AjaxOptions } from "../services/http/AjaxOptions";
import { AtomControl } from "../web/controls/AtomControl";
import { AtomUI, ChildEnumerator } from "../web/core/AtomUI";
import { AtomBinder } from "./AtomBinder";
import { IAtomElement, IDisposable, INameValuePairs, INativeComponent } from "./types";

export abstract class BaseElementBridge<T extends IAtomElement> {

    public createBusyIndicator: () => IDisposable;

    public ajax: (
        url: string,
        options: AjaxOptions,
        success: (r) => void,
        failed: (r) => void,
        progress: (p) => void) => void;

    public setTemplate: (element: any, name: string, templateFactory: () => any) => void;

    public setImport: (element: any, name: string, templateFactory: () => any) => void;
    public reset: () => void;

    public abstract create(type: string): T;

    public abstract attachControl(element: T, control: AtomControl): void;

    public abstract addEventHandler(
        element: T,
        name: string,
        handler: EventListenerOrEventListenerObject,
        capture?: boolean): IDisposable;

    public abstract atomParent(element: T, climbUp?: boolean): AtomControl;

    public abstract elementParent(element: T): T;

    public abstract templateParent(element: T): AtomControl;

    public abstract visitDescendents(element: T, action: (e: T, ac: AtomControl) => boolean): void;

    public abstract dispose(element: T): void;

    public abstract appendChild(parent: T, child: T): void;

    public abstract getValue(element: HTMLElement, name: string): any;

    public abstract setValue(element: T, name: string, value: any): void;

    public abstract watchProperty(element: T, name: string, events: string[], f: (v: any) => void): IDisposable;

    public abstract loadContent(element: T, text: string): void;

    public abstract findChild(element: T, name: string): T;

    public abstract close(element: T, success: () => void, error: (e) => void): void;

    public refreshInherited(target: { element: T }, name: string, fieldName?: string): void {
        AtomBinder.refreshValue(target, name);
        if (!fieldName) {
            fieldName = "m" + name[0].toUpperCase() + name.substr(1);
        }
        this.visitDescendents(target.element, (e, ac) => {
            if (ac) {
                if (ac[fieldName] === undefined) {
                    this.refreshInherited(ac as any, name, fieldName);
                }
                return false;
            }
            return true;
        });

    }

}

export class AtomElementBridge extends BaseElementBridge<HTMLElement> {

    public addEventHandler(
        element: HTMLElement,
        name: string,
        handler: EventListenerOrEventListenerObject,
        capture?: boolean): IDisposable {
            element.addEventListener(name, handler, capture);
            return {
                dispose: () => {
                    element.removeEventListener(name, handler, capture);
                }
            };
        }

        public atomParent(element: HTMLElement, climbUp: boolean = true): AtomControl {
            const eAny: INameValuePairs = element as INameValuePairs;
            if (eAny.atomControl) {
                return eAny.atomControl;
            }
            if (!climbUp) {
                return null;
            }
            if (!element.parentNode) {
                return null;
            }
            return this.atomParent(this.elementParent(element));
    }

    public elementParent(element: HTMLElement): HTMLElement {
        const eAny = element as any;
        const lp = eAny._logicalParent;
        if (lp) {
            return lp;
        }
        return element.parentElement;
    }

    public templateParent(element: HTMLElement): AtomControl {
        if (!element) {
            return null;
        }
        const eAny = element as any;
        if (eAny._templateParent) {
            return this.atomParent(element);
        }
        const parent = this.elementParent(element);
        if (!parent) {
            return null;
        }
        return this.templateParent(parent);
    }

    public visitDescendents(element: HTMLElement, action: (e: HTMLElement, ac: AtomControl) => boolean): void  {

        const en = new ChildEnumerator(element);
        while (en.next()) {
            const iterator = en.current;
            const eAny = iterator as any;
            const ac = eAny ? eAny.atomControl : undefined;

            if (!action(iterator, ac)) {
                continue;
            }
            this.visitDescendents(iterator, action);
        }
    }

    public dispose(element: HTMLElement): void {
        const eAny = element as any;
        eAny.atomControl = undefined;
        delete eAny.atomControl;
    }

    public appendChild(parent: HTMLElement, child: HTMLElement): void {
        parent.appendChild(child);
    }

    public setValue(element: HTMLElement, name: string, value: any): void {
        element[name] = value;
    }

    public getValue(element: HTMLElement, name: string): any {
        return element[name];
    }

    public watchProperty(element: HTMLElement, name: string, events: string[], f: (v: any) => void): IDisposable {

        if (events.indexOf("change") === -1) {
            events.push("change");
        }

        const l = (e) => {
            const e1 = element as HTMLInputElement;
            const v = e1.type === "checkbox" ? e1.checked : e1.value;
            f(v);
        };
        for (const iterator of events) {
            element.addEventListener(iterator, l , false);
        }

        return {
            dispose: () => {
                for (const iterator of events) {
                    element.removeEventListener(iterator, l, false);
                }
            }
        };
    }

    public attachControl(element: HTMLElement, control: AtomControl): void {
        (element as any).atomControl = control;
    }

    public create(type: string): HTMLElement {
        return document.createElement(type);
    }

    public loadContent(element: HTMLElement, text: string): void {
        throw new Error("Not supported");
    }

    public findChild(element: HTMLElement, name: string): HTMLElement {
        throw new Error("Not supported");
    }

    public close(element: HTMLElement, success: () => void, error: (e) => void): void {
        throw new Error("Not supported");
    }
}

export class AtomBridge {

    public static instance: BaseElementBridge<IAtomElement> = new AtomElementBridge();

    public static create(name: string): IAtomElement {
        return this.instance.create(name);
    }

}
