/*
 * Custom Multi Select Form Control created by @mmeyer - Martin Meyer
 */

import { Component, OnChanges, Input, HostListener, HostBinding, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR  } from '@angular/forms';
import { SelectOption } from './select-option.model';
import { log } from 'util';

@Component({
  selector: 'app-multi-select',
  templateUrl: './multi-select.component.html',
  styleUrls: ['./multi-select.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => MultiSelectComponent),
      multi: true
    }
  ]
})
export class MultiSelectComponent implements OnChanges {

  @Input() options: SelectOption[] = [];
  @Input() _value: any[] = [];
  @Input() multiselect = true;
  @Input() placeholder = '';

  // control properties
  showDropdown = false;
  selectText: String;
  dropdownIcon: String;
  showPlaceholder: Boolean;

  // enable standard fous events
  @HostBinding('tabindex') tabindex: number = -1;

  // Host listeners
  @HostListener('focusout', ['$event.target'])
  onFocusout(target) {
    this.showDropdown = false;
    this.updateDropdownIcon();
  }

  get value() {
    return this._value;
  }

  set value(value) {
    this._value = value;
    this.propagateChange(this._value);

    // update the control propertyes
    this.updateOptions();
    this.updateShowPlaceholder();
    this.updateSelectText();
  }

  constructor() { }

  ngOnChanges() {
    this.showDropdown = false;

    // set all property controls
    this.updateOptions();
    this.updateShowPlaceholder();
    this.updateSelectText();
    this.updateDropdownIcon();
  }

  onClickSelect() {
    this.toggleDropdown();
  }

  private updateShowPlaceholder() {
    this.showPlaceholder = this.value.length === 0;
  }

  // Return text to be displayed on the "select box", if we have any value show it separated
  // by ', ' otherwise return the placeholder
  private updateSelectText() {
    if (this.options) {
      if (this.showPlaceholder) {
        this.selectText = this.placeholder;
      } else {
        this.selectText = this.value.map(val => {
          const innovator: SelectOption = this.findOptionByValue(val);
          if (innovator) {
            return innovator.text;
          }
        }).join(', ');
      }
    }
  }

  // hande selectin of an option
  private optionSelected(opt: SelectOption) {
    if (this.multiselect) {
      this.value = this.toggleValue(this.value, opt.value);
    } else {
      // if the value is the same as the stored value remove it otherwise set it
      // as the value
      this.value = this.value[0] === opt.value ? [] : [opt.value];
      this.toggleDropdown();
    }
  }

  // ControlValueAccessor interface implementation
  writeValue(value: any) {
    if (value !== undefined) {
      if (value instanceof Array) {
        this.value = value;
      } else if (value !== null) {
        this.value = [value];
      }
    }
  }

  propagateChange = (_: any) => { };

  registerOnChange(fn) {
    this.propagateChange = fn;
  }

  registerOnTouched() { }

  // End of ControlValueAccessor interface implementation

  // control when to show or hide the dropdown
  private toggleDropdown() {
    this.showDropdown = !this.showDropdown;
    this.updateDropdownIcon();
  }

  private findOptionByValue(value): SelectOption {
    return this.options.find(opt => opt.value === value);
  }

  // Add or remove a value from an array
  private toggleValue(arr: any[], value): any[] {
    const valIndex = arr.indexOf(value);
    if (valIndex === -1) {
      // add the value
      return arr.concat(value);
    } else {
      // remove the value
      return arr.filter((val, i) => i !== valIndex);
    }
  }

  // Update the options array to know if any of the values is selected
  private updateOptions() {
    if (this.options) {
      this.options = this.options.map(opt => {
        return {
          ...opt,
          selected: this.value.find(val => val === opt.value) !== undefined
        };
      });
    }
  }

  private updateDropdownIcon() {
    this.dropdownIcon = this.showDropdown ? 'arrow_drop_up' : 'arrow_drop_down';
  }

}
