import {ECalendarValue} from '../common/types/calendar-value-enum';
import {SingleCalendarValue} from '../common/types/single-calendar-value';
import {ECalendarMode} from '../common/types/calendar-mode-enum';
import {
  Component,
  EventEmitter,
  forwardRef,
  HostBinding,
  Input,
  OnChanges,
  OnInit,
  Output,
  SimpleChanges, ViewEncapsulation
} from '@angular/core';
import {TimeSelectService, TimeUnit} from './time-select.service';
import * as moment from 'moment';
import {Moment} from 'moment';
import {ITimeSelectConfig} from './time-select-config.model';
import {
  ControlValueAccessor,
  FormControl,
  NG_VALIDATORS,
  NG_VALUE_ACCESSOR,
  ValidationErrors,
  Validator
} from '@angular/forms';
import {CalendarValue} from '../common/types/calendar-value';
import {UtilsService} from '../common/services/utils/utils.service';
import {IDate} from '../common/models/date.model';

@Component({
  selector: 'dp-time-select',
  template: '<ul class="dp-time-select-controls">   <li class="dp-time-select-control dp-time-select-control-hours">     <button type="button"             [disabled]="!shouldShowIncrease(\'hour\')"             class="dp-time-select-control-up"             (click)="increase(\'hour\')">     </button>     <span class="dp-time-select-display-hours">{{hours}}</span>     <button type="button"             [disabled]="!shouldShowDecrease(\'hour\')"             class="dp-time-select-control-down"             (click)="decrease(\'hour\')"></button>   </li>   <li class="dp-time-select-control dp-time-select-separator">{{componentConfig.timeSeparator}}</li>   <li class="dp-time-select-control dp-time-select-control-minutes">     <button type="button"             [disabled]="!shouldShowIncrease(\'minute\')" class="dp-time-select-control-up"             (click)="increase(\'minute\')"></button>     <span class="dp-time-select-display-minutes">{{minutes}}</span>     <button type="button"             [disabled]="!shouldShowDecrease(\'minute\')" class="dp-time-select-control-down"             (click)="decrease(\'minute\')"></button>   </li>   <ng-container *ngIf="componentConfig.showSeconds">     <li class="dp-time-select-control dp-time-select-separator">{{componentConfig.timeSeparator}}</li>     <li class="dp-time-select-control dp-time-select-control-seconds">       <button type="button"               [disabled]="!shouldShowIncrease(\'second\')"               class="dp-time-select-control-up"               (click)="increase(\'second\')"></button>       <span class="dp-time-select-display-seconds">{{seconds}}</span>       <button type="button"               [disabled]="!shouldShowDecrease(\'second\')" class="dp-time-select-control-down"               (click)="decrease(\'second\')"></button>     </li>   </ng-container>   <li class="dp-time-select-control dp-time-select-control-meridiem" *ngIf="!componentConfig.showTwentyFourHours">     <button type="button"             [disabled]="!shouldShowToggleMeridiem()"             class="dp-time-select-control-up"             (click)="toggleMeridiem()"></button>     <span class="dp-time-select-display-meridiem">{{meridiem}}</span>     <button type="button"             [disabled]="!shouldShowToggleMeridiem()"             class="dp-time-select-control-down"             (click)="toggleMeridiem()"></button>   </li> </ul> ',
  styles: ['dp-time-select {  display: inline-block;}dp-time-select .dp-time-select-controls {  margin: 0;  padding: 0;  text-align: center;  line-height: normal;  background: #FFFFFF;}dp-time-select .dp-time-select-control {  display: inline-block;  width: 35px;  margin: 0 auto;  vertical-align: middle;  font-size: inherit;  letter-spacing: 1px;}dp-time-select .dp-time-select-control-up,dp-time-select .dp-time-select-control-down {  position: relative;  display: block;  width: 24px;  height: 24px;  margin: 3px auto;  cursor: pointer;}dp-time-select .dp-time-select-control-up::before,dp-time-select .dp-time-select-control-down::before {  position: relative;  content: \'\';  display: inline-block;  height: 8px;  width: 8px;  vertical-align: baseline;  border-style: solid;  border-width: 2px 2px 0 0;  transform: rotate(0deg);}dp-time-select .dp-time-select-control-up::before {  transform: rotate(-45deg);  top: 4px;}dp-time-select .dp-time-select-control-down::before {  transform: rotate(135deg);}dp-time-select .dp-time-select-separator {  width: 5px;}dp-time-select.dp-material .dp-time-select-control-up,dp-time-select.dp-material .dp-time-select-control-down {  box-sizing: border-box;  background: transparent;  border: none;  outline: none;  border-radius: 50%;}dp-time-select.dp-material .dp-time-select-control-up::before,dp-time-select.dp-material .dp-time-select-control-down::before {  left: 0;}dp-time-select.dp-material .dp-time-select-control-up:hover,dp-time-select.dp-material .dp-time-select-control-down:hover {  background: #E0E0E0;}'],
  encapsulation: ViewEncapsulation.None,
  providers: [
    TimeSelectService,
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => TimeSelectComponent),
      multi: true
    },
    {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => TimeSelectComponent),
      multi: true
    }
  ]
})
export class TimeSelectComponent implements OnInit, OnChanges, ControlValueAccessor, Validator {

  @Input() config: ITimeSelectConfig;
  @Input() displayDate: SingleCalendarValue;
  @Input() minDate: Moment | string;
  @Input() maxDate: Moment | string;
  @Input() minTime: Moment | string;
  @Input() maxTime: Moment | string;
  @HostBinding('class') @Input() theme: string;
  @Output() onChange: EventEmitter<IDate> = new EventEmitter();

  CalendarType = ECalendarMode;
  isInited: boolean = false;
  componentConfig: ITimeSelectConfig;
  _selected: Moment;
  inputValue: CalendarValue;
  inputValueType: ECalendarValue;
  validateFn: (inputVal: CalendarValue) => { [key: string]: any };

  hours: string;
  minutes: string;
  seconds: string;
  meridiem: string;

  set selected(selected: Moment) {
    this._selected = selected;
    this.calculateTimeParts(this.selected);
    this.onChangeCallback(this.processOnChangeCallback(selected));
  }

  get selected(): Moment {
    return this._selected;
  }

  api = {
    triggerChange: this.emitChange.bind(this)
  };

  constructor(public timeSelectService: TimeSelectService,
              public utilsService: UtilsService) {
  }

  ngOnInit() {
    this.isInited = true;
    this.init();
    this.initValidators();
  }

  init() {
    this.componentConfig = this.timeSelectService.getConfig(this.config);
    this.selected = this.selected || moment();
    this.inputValueType = this.utilsService.getInputType(this.inputValue, false);
  }

  ngOnChanges(changes: SimpleChanges) {
    if (this.isInited) {
      const {minDate, maxDate, minTime, maxTime} = changes;
      this.init();

      if (minDate || maxDate || minTime || maxTime) {
        this.initValidators();
      }
    }
  }

  writeValue(value: CalendarValue): void {
    this.inputValue = value;

    if (value) {
      const momentValue = this.utilsService
        .convertToMomentArray(value, this.timeSelectService.getTimeFormat(this.componentConfig), false)[0];
      if (momentValue.isValid()) {
        this.selected = momentValue;
        this.inputValueType = this.utilsService
          .getInputType(this.inputValue, false);
      }
    }
  }

  registerOnChange(fn: any): void {
    this.onChangeCallback = fn;
  }

  onChangeCallback(_: any) {
  };

  registerOnTouched(fn: any): void {
  }

  validate(formControl: FormControl): ValidationErrors | any {
    if (this.minDate || this.maxDate || this.minTime || this.maxTime) {
      return this.validateFn(formControl.value);
    } else {
      return () => null;
    }
  }

  processOnChangeCallback(value: Moment): CalendarValue {
    return this.utilsService.convertFromMomentArray(
      this.timeSelectService.getTimeFormat(this.componentConfig),
      [value],
      this.inputValueType
    );
  }

  initValidators() {
    this.validateFn = this.utilsService.createValidator(
      {
        minDate: this.minDate,
        maxDate: this.maxDate,
        minTime: this.minTime,
        maxTime: this.maxTime
      }, undefined, 'day');

    this.onChangeCallback(this.processOnChangeCallback(this.selected));
  }

  decrease(unit: TimeUnit) {
    this.selected = this.timeSelectService.decrease(this.componentConfig, this.selected, unit);
    this.emitChange();
  }

  increase(unit: TimeUnit) {
    this.selected = this.timeSelectService.increase(this.componentConfig, this.selected, unit);
    this.emitChange();
  }

  toggleMeridiem() {
    this.selected = this.timeSelectService.toggleMeridiem(this.selected);
    this.emitChange();
  }

  emitChange() {
    this.onChange.emit({date: this.selected, selected: false});
  }

  calculateTimeParts(time: Moment) {
    this.hours = this.timeSelectService.getHours(this.componentConfig, time);
    this.minutes = this.timeSelectService.getMinutes(this.componentConfig, time);
    this.seconds = this.timeSelectService.getSeconds(this.componentConfig, time);
    this.meridiem = this.timeSelectService.getMeridiem(this.componentConfig, time);
  }

  shouldShowDecrease(unit: TimeUnit): boolean {
    return this.timeSelectService.shouldShowDecrease(this.componentConfig, this.selected, unit);
  }

  shouldShowIncrease(unit: TimeUnit): boolean {
    return this.timeSelectService.shouldShowIncrease(this.componentConfig, this.selected, unit);
  }

  shouldShowToggleMeridiem(): boolean {
    return this.timeSelectService.shouldShowToggleMeridiem(this.componentConfig, this.selected);
  }
}
