import { Component, Input, forwardRef, OnInit, Optional, Host, SkipSelf, Injector, EventEmitter, Output, ElementRef, ViewChild, OnDestroy } from '@angular/core';
import {
  ControlValueAccessor, DefaultValueAccessor,
  FormGroupDirective, ControlContainer, AbstractControlDirective,
  FormGroup, FormArray, FormControl, NgModel, NgControl, AbstractControl,
  NG_VALUE_ACCESSOR, Validators
} from '@angular/forms';
import { ValidatorFn } from '@angular/forms/src/directives/validators';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
import { IReference, KnokService } from '../../index';

function hasOwnProperty(context: any, thisArg: any, ...argArray: any[]): any {
  return Object.prototype.hasOwnProperty.call(context, thisArg, ...argArray);
}

@Component({
  selector: 'input-reference',
  exportAs: 'inputReference',
  templateUrl: './input-reference.component.pug',
  styleUrls: ['./input-reference.component.scss']
})
export class InputReferenceComponent implements OnInit, OnDestroy {
  @Input() formArrayName: string = null;
  @Input() externalControl: boolean = false;
  @Input() validators: ValidatorFn[] | null = null;

  _availableReferences: { [key: number]: IReference } = {};
  private ngUnsubscribe$: Subject<void> = new Subject<void>();

  get control(): AbstractControl | null {
    if (this.controlContainer) {
      if (this.isFormGroup(this.controlContainer.control)) {
        let group: FormGroup = this.controlContainer.control as FormGroup;
        if (this.formArrayName && group.contains(this.formArrayName)) {
          return group.get(this.formArrayName);
        }
      }
    }
    return null;
  }

  set control(control: AbstractControl | null) {
    if (this.controlContainer) {
      if (this.isFormGroup(this.controlContainer.control)) {
        let group: FormGroup = this.controlContainer.control as FormGroup;
        if (this.formArrayName && this.isFormArray(control)) {
          group.setControl(this.formArrayName, control);
        }
      }
    }
  }

  get length(): number {
    let n: number = 0;
    if (this.isFormArray(this.control)) {
      let control: FormArray = this.control as FormArray;
      n = control.length;
    }
    return n;
  }

  get availableReferences(): IReference[] {
    return Object.keys(this._availableReferences).slice().map(key => (this._availableReferences[+key]));
  }

  constructor(@Optional() private context: Injector,
              @Optional() @Host() @SkipSelf() private controlContainer: ControlContainer,
              private knokService: KnokService) {
  }

  ngOnInit(): void {
    this._availableReferences = [];

    if (this.isFormArray(this.control)) {
      let control: FormArray = this.control as FormArray;
      let values: any = control.value;

      if (Array.isArray(values) && values.length) {
        values.forEach((value: any, index: number): void => {
          let referenceControl: FormControl = new FormControl(value.reference || '', Validators.compose(this.validators));
          let referencesGroup: FormGroup = new FormGroup({reference: referenceControl});
          control.setControl(index, referencesGroup);

          let observables: Observable<any>[] = [referenceControl.statusChanges, referenceControl.valueChanges];
          Observable.combineLatest(...observables)
            .takeUntil(this.ngUnsubscribe$)
            .debounceTime(500)
            .distinctUntilChanged()
            .subscribe((data: any): void => {
              if (data[0] === 'VALID') {
                this.uploadReference(index, data[1]);
              }
              // console.warn(index, data[0], data[1]);
            });
          referenceControl.updateValueAndValidity({emitEvent: true});
        }, this);
      } else {
        let index: number = 0;
        let referenceControl: FormControl = new FormControl('', Validators.compose(this.validators));
        let referencesGroup: FormGroup = new FormGroup({reference: referenceControl});
        control.push(referencesGroup);

        let observables: Observable<any>[] = [referenceControl.statusChanges, referenceControl.valueChanges];
        Observable.combineLatest(...observables)
          .takeUntil(this.ngUnsubscribe$)
          .debounceTime(500)
          .distinctUntilChanged()
          .subscribe((data: any): void => {
            if (data[0] === 'VALID') {
              this.uploadReference(index, data[1]);
            }
            // console.warn(index, data[0], data[1]);
          });
        referenceControl.updateValueAndValidity({emitEvent: true});
      }

      // console.warn(this.control);
    }
  }

  ngOnDestroy(): void {
    this.ngUnsubscribe$.next();
    this.ngUnsubscribe$.complete();
  }

  isControlExists(control: AbstractControl): boolean {
    return control instanceof AbstractControl;
  }

  isControlDirtyOrTouched(control: AbstractControl): boolean {
    return (this.isControlExists(control) && (control.dirty || control.touched));
  }

  isFormGroup(control: AbstractControl): boolean {
    return control instanceof FormGroup;
  }

  isFormArray(control: AbstractControl): boolean {
    return control instanceof FormArray;
  }

  addReference(): void {
    if (this.isFormArray(this.control)) {
      let control: FormArray = this.control as FormArray;
      let referenceControl: FormControl = new FormControl('', Validators.compose(this.validators));
      let referencesGroup: FormGroup = new FormGroup({reference: referenceControl});
      control.push(referencesGroup);
    }
  }

  removeReference(index: number): void {
    if (this.isFormArray(this.control)) {
      const control: FormArray = this.control as FormArray;
      control.removeAt(index);
    }
  }

  uploadReference(index: number, url: string): void {
    this.knokService.getKnokFAReference({url: url})
      .take(1)
      .subscribe((reference: IReference): void => {
        this._availableReferences[index] = reference;
        console.warn(reference);
      }, () => (this._availableReferences[index] = undefined));
    // console.warn(index, url);
  }

  openUrlInNewTab(url: string): void {
    const win: Window | null = window.open(url, '_blank');
    if (win) {
      win.focus();
    }
  }
}
