import { Directive, Host, HostListener, HostBinding, forwardRef, EventEmitter, ElementRef, Input, Output, OnInit, OnDestroy, OnChanges, Renderer2, Provider, ContentChild, ContentChildren, QueryList } from '@angular/core';
import { FormControl, ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { MDCIconToggle, MDCIconToggleFoundation } from '@material/icon-toggle/index';
import { MDCMenu, MDCMenuFoundation, AnchorMargin, Corner, CornerBit } from '@material/menu/index';

enum TOGGLE_STATUS {
  OPEN = 0,
  CLOSE = 1
}

@Directive({
  selector: '[MDCDropdown]',
  exportAs: 'MDCDropdown'
})
export class MDCDropdownDirective {
  protected status: TOGGLE_STATUS = TOGGLE_STATUS.CLOSE;
  protected status$: Subject<TOGGLE_STATUS> = new Subject<TOGGLE_STATUS>();
  protected label$: Subject<any> = new Subject<any>();

  constructor(protected elementRef: ElementRef) {
  }

  open(): void {
    if (this.status !== TOGGLE_STATUS.OPEN) {
      this.status$.next(TOGGLE_STATUS.OPEN);
    }
    this.status = TOGGLE_STATUS.OPEN;
  }

  close(): void {
    if (this.status !== TOGGLE_STATUS.CLOSE) {
      this.status$.next(TOGGLE_STATUS.CLOSE);
    }
    this.status = TOGGLE_STATUS.CLOSE;
  }

  statusChange(): Observable<TOGGLE_STATUS> {
    return this.status$.asObservable();
  }

  setLabel(label: any): void {
    this.label$.next(label);
  }

  labelChange(): Observable<any> {
    return this.label$.asObservable();
  }
}

@Directive({
  selector: '[MDCDropdownToggleLabel]',
  exportAs: 'MDCDropdownToggleLabel'
})
export class MDCDropdownToggleLabelDirective implements OnInit, OnDestroy {
  element: HTMLElement;
  protected ngUnsubscribe: Subject<void> = new Subject<void>();

  constructor(@Host() public dropdown: MDCDropdownDirective,
              protected elementRef: ElementRef) {
  }

  ngOnInit(): void {
    this.element = this.elementRef.nativeElement;

    this.dropdown.labelChange()
      .takeUntil(this.ngUnsubscribe)
      .subscribe((label: any): void => {
        this.element.innerText = label;
      });
  }

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

@Directive({
  selector: '[MDCDropdownToggle]',
  exportAs: 'MDCDropdownToggle'
})
export class MDCDropdownToggleDirective implements OnInit, OnDestroy {
  element: HTMLElement;
  protected toggle: MDCIconToggle;
  protected ngUnsubscribe: Subject<void> = new Subject<void>();

  constructor(@Host() public dropdown: MDCDropdownDirective,
              protected elementRef: ElementRef) {
  }

  ngOnInit(): void {
    this.element = this.elementRef.nativeElement;
    this.toggle = MDCIconToggle.attachTo(this.element);

    this.dropdown.statusChange()
      .takeUntil(this.ngUnsubscribe)
      .subscribe((status: TOGGLE_STATUS): void => {
        if (status === TOGGLE_STATUS.OPEN) {
          this.toggle.on = true;
        } else {
          this.toggle.on = false;
        }
      });
  }

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

    this.toggle.getDefaultFoundation().destroy();
    this.toggle.destroy();
  }

  @HostListener('MDCIconToggleChange', ['$event'])
  protected change(event: CustomEvent): void {
    const detail: { isOn: boolean } = Object.assign({}, {isOn: false}, event.detail);
    if (detail.isOn) {
      this.dropdown.open();
    } else {
      this.dropdown.close();
    }
  }
}

@Directive({
  selector: '[MDCDropdownMenuItem]',
  exportAs: 'MDCDropdownMenuItem'
})
export class MDCDropdownMenuItemDirective implements OnInit {
  @Input() value: any;
  @Input() label: any;
  element: HTMLElement;

  constructor(@Host() public dropdown: MDCDropdownDirective,
              protected elementRef: ElementRef) {
  }

  ngOnInit(): void {
    this.element = this.elementRef.nativeElement;
  }
}

@Directive({
  selector: '[MDCDropdownMenu]',
  exportAs: 'MDCDropdownMenu',
  providers: [
    {provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MDCDropdownMenuDirective), multi: true}
  ]
})
export class MDCDropdownMenuDirective implements ControlValueAccessor, OnInit, OnDestroy {
  element: HTMLElement;
  @Input() yPosition: string = 'BOTTOM';
  @Input() xPosition: string = 'START';
  @Input() quickOpen: boolean = false;
  @Output() change: EventEmitter<any> = new EventEmitter<any>(true);

  protected menu: MDCMenu;
  protected selectedIndex: number | null | undefined = -1;
  protected ngUnsubscribe: Subject<void> = new Subject<void>();
  @ContentChildren(MDCDropdownMenuItemDirective) protected items: QueryList<MDCDropdownMenuItemDirective> = new QueryList<MDCDropdownMenuItemDirective>();

  private value: any;
  private onChange: any = Function.prototype;
  private onTouched: any = Function.prototype;

  constructor(@Host() public dropdown: MDCDropdownDirective,
              protected elementRef: ElementRef) {
  }

  ngOnInit(): void {
    this.element = this.elementRef.nativeElement;
    this.menu = MDCMenu.attachTo(this.element);
    this.menu.setAnchorCorner(this.prepareCorner(this.xPosition, this.yPosition));
    this.menu.quickOpen = this.quickOpen;

    this.dropdown.statusChange()
      .takeUntil(this.ngUnsubscribe)
      .subscribe((status: TOGGLE_STATUS): void => {
        if (status === TOGGLE_STATUS.OPEN) {
          this.menu.show(this.selectedIndex);
        } else {
          this.menu.hide();
        }
      });
  }

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

    this.menu.getDefaultFoundation().destroy();
    this.menu.destroy();
  }

  writeValue(value: any): void {
    this.value = value;

    if (this.items.length) {
      const item: MDCDropdownMenuItemDirective = this.items.first;
      const label: any = item.label || item.element.innerHTML;
      this.selectedIndex = 0;

      this.menu.selectedItemIndex = this.selectedIndex;
      this.dropdown.setLabel(label);
    }
  }

  registerOnChange(fn: (value: any) => void): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: () => {}): void {
    this.onTouched = fn;
  }

  protected prepareCorner(x: string, y: string): number {
    y = y.toUpperCase();
    x = x.toUpperCase();

    switch (y) {
      case 'TOP':
      case 'BOTTOM':
        break;
      default:
        y = 'BOTTOM';
        break;
    }

    switch (x) {
      case 'LEFT':
      case 'RIGHT':
      case 'START':
      case 'END':
        break;
      default:
        x = 'START';
        break;
    }

    const position: string = ([y, x]).join('_');
    const corner: number = MDCMenuFoundation.Corner[position];

    return corner;
  }

  @HostListener('MDCMenuSelected', ['$event'])
  protected selected(event: CustomEvent): void {
    const detail: { item: HTMLElement, index: number } = Object.assign({}, {item: undefined, index: null}, event.detail);
    if (this.items.length) {
      const item: MDCDropdownMenuItemDirective = this.items.find((item: MDCDropdownMenuItemDirective, index: number): boolean => {
        return index === detail.index;
      });

      if (item !== undefined && item.element === detail.item) {
        const value: any = item.value;
        const label: any = item.label || item.element.innerHTML;
        if (value !== undefined) {
          // console.log('Dropdown selected [index, value, label]', [detail.index, value, label]);

          this.selectedIndex = detail.index;
          this.change.emit(detail.index);

          this.value = value;
          this.onChange(value);

          this.menu.selectedItemIndex = this.selectedIndex;
          this.dropdown.setLabel(label);
        }
      }
    }

    this.dropdown.close();
  }

  @HostListener('MDCMenuCancel', ['$event'])
  protected cancel(event: CustomEvent): void {
    this.dropdown.close();
  }
}
