import DataNode, {ChangeEvent, ServerSideErrorEvent, ValidEvent} from "./DataNode";
import EventHandler from "../Utility/EventHandler";

export default class InnerNode implements DataNode<object>{
    public onChange: EventHandler<ChangeEvent<any>> = new EventHandler();
    public onValidChange: EventHandler<ValidEvent> = new EventHandler();
    public onServerSideErrorsChange: EventHandler<ServerSideErrorEvent> = new EventHandler();

    public parent: null|InnerNode = null;
    public children: DataNode<any>[] = [];
    public index: string|number|null = null;
    protected containsExtraData: boolean = false;
    protected dataStorage: any = {};
    protected _serverSideErrors: any[] = [];

    protected _isValid: boolean = true;
    protected _isVisible: boolean = true;

    public validate(force:boolean = false): void{
        let isValid: boolean = true;
        for (const childrenKey in this.children) {
            if (force) this.children[childrenKey].validate(true);
            isValid = isValid && this.children[childrenKey].isValid;
        }
        this.isValid = isValid;
    }

    public addChildren(...children:DataNode<any>[]): void{
        children.forEach(child => {
            if (this.children.includes(child)) return;
            this.children.push(child);
            child.parent = this;
            child.onChange.add((event) => this.runChange(event));
            child.onValidChange.add(() => this.validate());
        });
    }

    public runChange(event: ChangeEvent<any>|null = null){
        this.validate();
        this.onChange.emit(event ?? new ChangeEvent(this));
    }

    public removeChild(child: DataNode<any>): void{
        for (const childrenKey in this.children) {
            if (this.children[childrenKey] === child) {
                delete this.children[childrenKey];
                child.parent = null;
                child.onChange.remove(() => this.runChange());
                child.onValidChange.remove(() => this.validate());
            }
        }
    }

    set isValid(value: boolean){
        if( this._isValid === value) return;
        this._isValid = value;
        this.onValidChange.emit(new ValidEvent(this._isValid, this._isVisible));
    }

    set isVisible(value: boolean){
        if( this._isVisible === value) return;
        this._isVisible = value;
        this.onValidChange.emit(new ValidEvent(this._isValid, this._isVisible));
    }

    get isValid(): boolean{
        return !this._isVisible || this._isValid;
    }

    get isChanged(): boolean{
        if (this.containsExtraData) return true;

        for (const childrenKey in this.children) {
            if (this.children[childrenKey].isChanged) return true;
        }

        return false;
    }

    set value(value: any){
        if (typeof value !== "object" || value == null) return;

        let indexObject: any = {};

        for (const childrenKey in this.children) {
            if (this.children[childrenKey].index === null) {
                this.children[childrenKey].value = value;
                continue;
            }

            if (value[this.children[childrenKey].index] === undefined) continue;
            this.children[childrenKey].value = value[this.children[childrenKey].index];
        }

        for (const childrenKey in this.children) {
            if (this.children[childrenKey].index === null) Object.assign(indexObject, this.children[childrenKey].valueOrDefault);
            else indexObject[this.children[childrenKey].index] = this.children[childrenKey].valueOrDefault;
        }

        if (this.index != null || this.parent == null){
            this.dataStorage = {};
            this.containsExtraData = false;
            for (const extraDataKey in value) {
                if (indexObject[extraDataKey] !== undefined) continue;
                this.containsExtraData = true;
                this.dataStorage[extraDataKey] = value[extraDataKey];
                this.dataStorage[extraDataKey] = value[extraDataKey];
            }
        }

        this.runChange();
    }

    set serverSideErrors(errors: any){
        if (typeof errors !== "object" || errors == null) {
            this._serverSideErrors = [];
            this.onServerSideErrorsChange.emit(new ServerSideErrorEvent([]));

            for (const childrenKey in this.children) {
                this.children[childrenKey].serverSideErrors = null;
            }
            return;
        }

        const ownErrors: any[] = [];

        for (const childrenKey in this.children) {
            if (this.children[childrenKey].index === null) {
                this.children[childrenKey].serverSideErrors = errors;
                this.children[childrenKey].serverSideErrors.forEach(error => ownErrors.push(error));
                continue;
            }

            this.children[childrenKey].serverSideErrors = errors[this.children[childrenKey].index];
            if (!(this.children[childrenKey].serverSideErrors.length === 0)) {
                ownErrors.push(errors[this.children[childrenKey].index]);
            }
        }

        this._serverSideErrors = ownErrors;
        this.onServerSideErrorsChange.emit(new ServerSideErrorEvent(ownErrors));
    }

    get value(): object|undefined{
        if (!this.isChanged){
            return undefined;
        }

        let returnObject: {[index:string]:any} = {};

        for (const childrenKey in this.children) {
            if (this.children[childrenKey].value === undefined) continue;
            if (this.children[childrenKey].index === null) Object.assign(returnObject, this.children[childrenKey].value);
            else returnObject[this.children[childrenKey].index] = this.children[childrenKey].value;
        }

        for (const extraDataKey in this.dataStorage) {
            if (returnObject[extraDataKey] === undefined && this.dataStorage[extraDataKey] !== undefined)
                returnObject[extraDataKey] = this.dataStorage[extraDataKey];
        }

        return returnObject;
    }

    get valueOrDefault(): object{
        let returnObject:{[index:string]:any} = {};

        for (const childrenKey in this.children) {
            if (this.children[childrenKey].index === null) Object.assign(returnObject, this.children[childrenKey].valueOrDefault);
            else returnObject[this.children[childrenKey].index] = this.children[childrenKey].valueOrDefault;
        }

        for (const extraDataKey in this.dataStorage) {
            if (returnObject[extraDataKey] === undefined && this.dataStorage[extraDataKey] !== undefined)
                returnObject[extraDataKey] = this.dataStorage[extraDataKey];
        }

        return returnObject;
    }

    get serverSideErrors(): any{
        return this._serverSideErrors;
    }
}