import { ComponentFactory, ComponentFactoryResolver, ComponentRef, Directive,
    Injector, Input, OnChanges, SimpleChanges, Type, ViewContainerRef } from '@angular/core';
import { NgForm } from '@angular/forms';
import { HypermediaField, LabelingService } from 'first-npm-package-nicule/core';

import { BaseInputComponent } from '../classes';
import { InputComponentResolver } from '../services';
import { HypermediaForm } from '../components';

@Directive({ selector: '[hmInputOf]' })
export class InputDirective implements OnChanges {
    private componentFactory: ComponentFactory<BaseInputComponent>;
    private inputSettings: any = {};
    private componentRef: ComponentRef<BaseInputComponent>;
    private isCreated = false;
    private field: HypermediaField;

    @Input()
    set hmInputOfSettings(settings: any) {
        if (settings) {
            this.inputSettings = settings;

            if (this.componentRef && this.componentRef.changeDetectorRef) {
                this.componentRef.instance.settings = settings;
            }
        }
    }

    @Input()
    set hmInputOf(field: HypermediaField) {
        if (field) {
            this.field = field;
        }
    }

    constructor(private viewContainer: ViewContainerRef,
                private componentFactoryResolver: ComponentFactoryResolver,
                private labelingService: LabelingService,
                private injector: Injector,
                private hmForm: HypermediaForm,
                private inputComponentResolver: InputComponentResolver
    ) { }

    ngOnChanges(changes: SimpleChanges): void {
        if ('hmInputOf' in changes) {
            this.onFieldChange(changes.hmInputOf.previousValue, changes.hmInputOf.currentValue);
        }
    }

    onFieldChange(oldField, newField): void {
        let componentType: Type<any>;
        let reRender = false;

        if (!this.field && !this.isCreated) {
            return;
        }

        if ((!this.isCreated && !this.componentFactory) ||
            oldField.name !== newField.name ||
            oldField.type !== newField.type) {

            componentType = this.inputComponentResolver.resolve(newField);
            const newFactory = this.componentFactoryResolver.resolveComponentFactory(componentType);

            reRender = newFactory !== this.componentFactory;

            this.componentFactory = newFactory;
        }

        if (this.isCreated && reRender) {
            this.delete();
        }

        if (newField && !this.isCreated) {
            this.create();
        } else if (newField && this.isCreated) {
            this.update();
        } else {
            this.delete();
        }

        if (newField && !this.isCreated) {
            this.create();
        } else if (newField && this.isCreated) {
            this.update();
        } else {
            this.delete();
        }
    }

    delete(): void {
        this.viewContainer.clear();
        this.componentRef.destroy();
        delete this.componentRef;
        this.isCreated = false;
    }

    update(): void {
        this.componentRef.instance.field = this.field;
        this.componentRef.instance.labelingService = this.labelingService;
        this.componentRef.instance.settings = { ...this.componentRef.instance.settings, ...this.inputSettings };
        this.componentRef.changeDetectorRef.detectChanges();
        this.componentRef.instance['onSettingsChanged']();
    }

    create(): void {
        let injector = this.injector;
        const parentInjector = this.injector;

        if (this.hmForm && this.hmForm.ngForm) {
            injector = {
                // Force injector to resolve form as the settings form
                get: (token, notFoundValue): any => {
                    if (token === NgForm) {
                        return this.hmForm.ngForm;
                    }

                    return parentInjector.get(token, notFoundValue);
                }
            };
        }

        this.componentRef = this.viewContainer.createComponent(this.componentFactory, 0, injector);
        this.isCreated = true;
        this.update();
    }
}
