/* tslint:disable:no-duplicate-imports max-line-length */
import {
  Component,
  ContentChild,
  ElementRef,
  EventEmitter,
  HostBinding,
  Input,
  OnInit,
  Output,
  TemplateRef,
  ViewEncapsulation,
} from '@angular/core';

import * as moment from 'moment';
import { Moment } from 'moment';
// import 'moment/locale/zh-cn';
import { NzLocaleService } from '../locale/index';
import { toBoolean } from '../util/convert';

export interface MonthInterface {
  index: number;
  name: string;
  year: number;
  isCurrentMonth: boolean;
  isSelectedMonth: boolean;
  disabled: boolean;
}

export type QuartersType = MonthInterface[];

export interface DayInterface {
  number: number;
  isLastMonth: boolean;
  isNextMonth: boolean;
  isCurrentDay: boolean;
  isSelectedDay: boolean;
  isInRange?: boolean;
  title: string;
  date: Moment;
  disabled: boolean;
  firstDisabled: boolean;
  lastDisabled: boolean;
}

export interface WeekInterface {
  days: DayInterface[];
}

export enum RangePart { Start = 0, End = 1 }

@Component({
  selector     : 'nz-calendar',
  encapsulation: ViewEncapsulation.None,
  template     : `
    <div
      [class.ant-fullcalendar-fullscreen]="nzFullScreen"
      [class.ant-patch-full-height]="nzDatePicker">
      <div class="ant-fullcalendar-header" *ngIf="nzShowHeader">
        <nz-select
          class="ant-fullcalendar-year-select" style="width: 75px;"
          [ngModel]="_showYear"
          (ngModelChange)="_showYear = $event;_buildCalendar()">
          <nz-option
            *ngFor="let year of _listOfYearName"
            [nzLabel]="year"
            [nzValue]="year">
          </nz-option>
        </nz-select>
        <nz-select
          class="ant-fullcalendar-month-select"
          style="width: 70px;"
          *ngIf="nzMode == 'month'"
          [ngModel]="_showMonth"
          (ngModelChange)="_showMonth = $event;_buildCalendar()">
          <nz-option
            *ngFor="let _month of _listOfMonthName;let i = index;"
            [nzLabel]="_month"
            [nzValue]="i">
          </nz-option>
        </nz-select>
        <nz-radio-group [(ngModel)]="nzMode">
          <label nz-radio-button [nzValue]="'month'">
            <span>{{ _monthUnit }}</span>
          </label><label nz-radio-button [nzValue]="'year'">
          <span>{{ _yearUnit }}</span>
        </label>
        </nz-radio-group>
      </div>
      <div
        [class.ant-fullcalendar-fullscreen]="nzFullScreen"
        [class.ant-fullcalendar]="!nzDatePicker"
        [class.ant-fullcalendar-full]="!nzDatePicker"
        [class.ant-patch-full-height]="nzDatePicker">
        <div
          [class.ant-fullcalendar-calendar-body]="!nzDatePicker"
          [class.ant-calendar-body]="!nzDatePicker"
          [class.ant-patch-full-height]="nzDatePicker">
          <table
            [class.ant-fullcalendar-table]="!nzDatePicker"
            [class.ant-calendar-table]="nzDatePicker"
            [class.ant-patch-full-height]="nzDatePicker"
            *ngIf="nzMode == 'month'">
            <thead>
            <tr>
              <th
                *ngFor="let _min of _listOfWeekName"
                [class.ant-fullcalendar-column-header]="!nzDatePicker"
                [class.ant-calendar-column-header]="nzDatePicker">
                <span class="ant-fullcalendar-column-header-inner">{{ _min }}</span>
              </th>
            </tr>
            </thead>
            <tbody
              [class.ant-fullcalendartbody]="!nzDatePicker"
              [class.ant-calendartbody]="nzDatePicker">
            <tr *ngFor="let week of _weeksCalendar">
              <ng-template [ngIf]="!nzDatePicker">
                <td
                  [attr.title]="day.title"
                  *ngFor="let day of week.days"
                  [class.ant-fullcalendar-cell]="!nzDatePicker"
                  [class.ant-calendar-cell]="nzDatePicker"
                  [class.ant-fullcalendar-last-month-cell]="day.isLastMonth"
                  [class.ant-fullcalendar-next-month-btn-day]="day.isNextMonth"
                  [class.ant-fullcalendar-selected-day]="day.isSelectedDay"
                  [class.ant-fullcalendar-today]="day.isCurrentDay">
                  <div class="ant-fullcalendar-date">
                    <div class="ant-fullcalendar-value" (click)="_clickDay($event,day)">{{ day.number }}</div>
                    <div class="ant-fullcalendar-content">
                      <ng-template
                        *ngIf="dateCell"
                        [ngTemplateOutlet]="dateCell"
                        [ngTemplateOutletContext]="{ $implicit: day}">
                      </ng-template>
                    </div>
                  </div>
                </td>
              </ng-template>
              <ng-template [ngIf]="nzDatePicker">
                <td
                  [attr.title]="day.title"
                  *ngFor="let day of week.days"
                  class="ant-calendar-cell"
                  [class.ant-calendar-disabled-cell-first-of-row]="day.firstDisabled"
                  [class.ant-calendar-disabled-cell-last-of-row]="day.lastDisabled"
                  [class.ant-calendar-disabled-cell]="day.disabled"
                  [class.ant-calendar-last-month-cell]="day.isLastMonth"
                  [class.ant-calendar-next-month-btn-day]="day.isNextMonth"
                  [class.ant-calendar-selected-day]="day.isSelectedDay"
                  [class.ant-calendar-in-range-cell]="day.isInRange && !day.isSelectedDay"
                  [class.ant-calendar-today]="day.isCurrentDay"
                  (mouseenter)="_onDayHover($event, day)">
                  <div class="ant-calendar-date"
                       (click)="_clickDay($event,day)">
                    {{ day.number }}
                  </div>
                </td>
              </ng-template>
            </tr>
            </tbody>
          </table>
          <table
            [class.ant-fullcalendar-month-panel-table]="!nzDatePicker"
            [class.ant-calendar-month-panel-table]="nzDatePicker"
            *ngIf="nzMode == 'year'">
            <tbody class="ant-fullcalendar-month-panel-tbody">
            <tr *ngFor="let quarter of _quartersCalendar">
              <ng-template [ngIf]="!nzDatePicker">
                <td
                  *ngFor="let month of quarter"
                  [attr.title]="month.name"
                  class="ant-fullcalendar-month-panel-cell"
                  [class.ant-fullcalendar-month-panel-selected-cell]="month.isSelectedMonth"
                  [class.ant-fullcalendar-month-panel-current-cell]="month.isCurrentMonth">
                  <div class="ant-fullcalendar-month">
                    <div class="ant-fullcalendar-value" (click)="_clickMonth($event,month)">{{ month.name }}</div>
                    <div class="ant-fullcalendar-content">
                      <ng-template
                        *ngIf="monthCell"
                        [ngTemplateOutlet]="monthCell"
                        [ngTemplateOutletContext]="{ $implicit: month}">
                      </ng-template>
                    </div>
                  </div>
                </td>
              </ng-template>
              <ng-template [ngIf]="nzDatePicker">
                <td
                  *ngFor="let month of quarter"
                  [attr.title]="month.name"
                  class="ant-calendar-month-panel-cell"
                  [class.ant-calendar-month-panel-selected-cell]="month.isSelectedMonth"
                  [class.ant-calendar-month-panel-cell-disabled]="month.disabled"
                  [class.ant-calendar-month-panel-current-cell]="month.isCurrentMonth">
                  <div class="ant-calendar-month-panel-month"(click)="_clickMonth($event,month)">
                    {{ month.name }}
                  </div>
                </td>
              </ng-template>
            </tr>
            </tbody>
          </table>
        </div>
      </div>
    </div>`,
  styleUrls    : [
    './style/index.less',
    './style/patch.less'
  ]
})
export class NzCalendarComponent implements OnInit {
  private _clearTime = true;
  private _datePicker = false;
  private _fullScreen = true;
  private _showHeader = true;
  private _isRange = false;

  _el: HTMLElement;
  _weeksCalendar: WeekInterface[] = [];
  _quartersCalendar: QuartersType[] = [];
  _listOfWeekName: string[] = [];
  _listOfMonthName: string[] = [];
  _listOfYearName: number[] = [];
  _disabledDate: (value: Date) => boolean;
  _yearUnit = '年';
  _monthUnit = '月';
  _showMonth = moment(new Date()).month();
  _showYear = moment(new Date()).year();
  _value: Date = new Date();
  _rangeValue: Date[] = [null, null];
  _hoveringSelectValue: Date;
  _locale = this._localeService.getLocale().locale;
  @ContentChild('dateCell') dateCell: TemplateRef<void>;
  @ContentChild('monthCell') monthCell: TemplateRef<void>;

  @Output() nzClickDay: EventEmitter<DayInterface> = new EventEmitter();
  @Output() nzClickMonth: EventEmitter<MonthInterface> = new EventEmitter();
  @Output() nzHoverDay: EventEmitter<DayInterface> = new EventEmitter();
  @Input() nzClearTime = true;
  @Input() nzMode = 'month';

  @Input()
  set nzFullScreen(value: boolean) {
    this._fullScreen = toBoolean(value);
  }

  get nzFullScreen(): boolean {
    return this._fullScreen;
  }

  @Input()
  set nzShowHeader(value: boolean) {
    this._showHeader = toBoolean(value);
  }

  get nzShowHeader(): boolean {
    return this._showHeader;
  }

  @Input()
  set nzIsRange(value: boolean) {
    this._isRange = toBoolean(value);
  }

  get nzIsRange(): boolean {
    return this._isRange;
  }

  @Input()
  set nzDisabledDate(value: (value: Date) => boolean) {
    this._disabledDate = value;
    this._buildCalendar();
  }

  get nzDisabledDate(): (value: Date) => boolean {
    return this._disabledDate;
  }

  @Input()
  @HostBinding('class.ant-patch-full-height')
  set nzDatePicker(value: boolean) {
    this._datePicker = toBoolean(value);
  }

  get nzDatePicker(): boolean {
    return this._datePicker;
  }

  @Input()
  set nzValue(value: Date) {
    if (this._value === value) {
      return;
    }
    this._value = value || new Date();
    this._showMonth = moment(this._value).month();
    this._showYear = moment(this._value).year();
    this._buildCalendar();
  }

  get nzValue(): Date {
    return this._value || new Date();
  }

  @Input()
  get nzRangeValue(): Date[] {
    return this._rangeValue;
  }

  set nzRangeValue(value: Date[]) {
    this._rangeValue = value;
    this._buildCalendar();
  }

  @Input()
  get nzHoveringSelectValue(): Date {
    return this._hoveringSelectValue;
  }

  set nzHoveringSelectValue(value: Date) {
    if (this._hoveringSelectValue === value) {
      return;
    }
    this._hoveringSelectValue = value;
    this._buildCalendar();
  }

  @Input()
  set nzShowYear(value: number) {
    this._showYear = value;
    this._buildCalendar();
  }

  get nzShowYear(): number {
    return this._showYear;
  }

  @Input()
  set nzShowMonth(value: number) {
    this._showMonth = value;
    this._buildCalendar();
  }

  get nzShowMonth(): number {
    return this._showMonth;
  }

  @Input()
  set nzLocale(value: string) {
    this._locale = value;
    moment.locale(this._locale);
  }

  get nzLocale(): string {
    return this._locale;
  }

  _removeTime(date: Moment): Moment {
    if (this.nzClearTime) {
      return date.hour(0).minute(0).second(0).millisecond(0);
    } else {
      return date;
    }
  }

  _clickDay($event: MouseEvent, day: DayInterface): void {
    $event.preventDefault();
    $event.stopPropagation();
    if (day.disabled) {
      return;
    }
    this.nzClickDay.emit(day);
  }

  _clickMonth($event: MouseEvent, month: MonthInterface): void {
    $event.preventDefault();
    $event.stopPropagation();
    if (month.disabled) {
      return;
    }
    this.nzClickMonth.emit(month);
  }

  _onDayHover($event: MouseEvent, day: DayInterface): void {
    $event.preventDefault();
    $event.stopPropagation();
    if (day.disabled || day.date.isSame(this._hoveringSelectValue)) {
      return;
    }
    this.nzHoverDay.emit(day);
  }

  _isSelectedDay(date: Moment, month: Moment): boolean {
    if (this.nzIsRange) {
      return (date.isSame(this._rangeValue[RangePart.Start], 'day')
        || date.isSame(this._rangeValue[RangePart.End], 'day')
        || date.isSame(this._hoveringSelectValue, 'day'))
        && date.month() === month.month();
    } else {
      return date.isSame(this.nzValue, 'day');
    }
  }

  _isInRange(date: Moment, month: Moment): boolean {
    let ghostDate: Date;
    if (this.nzIsRange && date.month() === month.month()) {
      if (this._rangeValue.every(e => moment(e).isValid())) {
        return date.isBetween.apply(date, this._rangeValue);
      }
      ghostDate = this._rangeValue.find(e => moment(e).isValid());
      if (ghostDate && this._hoveringSelectValue) {
        const start = moment.min(moment(ghostDate), moment(this._hoveringSelectValue)).toDate();
        const end = moment.max(moment(ghostDate), moment(this._hoveringSelectValue)).toDate();
        return date.isBetween(start, end);
      }
      return false;
    } else {
      return false;
    }
  }

  _buildMonth(d: Moment): WeekInterface[] {
    const weeks: WeekInterface[] = [];
    const _rawDate = this._removeTime(d);
    const start = _rawDate.clone().date(1).day(0);
    const month = _rawDate.clone();
    let done = false;
    const date = start.clone();
    let monthIndex = date.month();
    let count = 0;
    while (!done) {
      weeks.push({ days: this._buildWeek(date.clone(), month) });
      date.add(1, 'w');
      done = count++ > 4;
      monthIndex = date.month();
    }
    return weeks;
  }

  _buildWeek(firstDate: Moment, month: Moment): DayInterface[] {
    let date = firstDate;
    const days: DayInterface[] = [];
    for (let i = 0; i < 7; i++) {
      days.push({
        number       : date.date(),
        isLastMonth  : date.month() < month.month(),
        isNextMonth  : date.month() > month.month(),
        isCurrentDay : date.isSame(new Date(), 'day'),
        isSelectedDay: this._isSelectedDay(date, month),
        isInRange    : this._isInRange(date, month),
        title        : date.format('YYYY-MM-DD'),
        date,
        disabled     : this.nzDisabledDate && this.nzDisabledDate(date.toDate()),
        firstDisabled: this.nzDisabledDate && this.nzDisabledDate(date.toDate()) && (date.day() === 0 || (date.day() !== 0 && this.nzDisabledDate && !this.nzDisabledDate(date.clone().subtract(1, 'day').toDate()))),
        lastDisabled : this.nzDisabledDate && this.nzDisabledDate(date.toDate()) && (date.day() === 6 || (date.day() !== 6 && this.nzDisabledDate && !this.nzDisabledDate(date.clone().add(1, 'day').toDate())))
      });
      date = date.clone();
      date.add(1, 'd');
    }
    return days;
  }

  _buildYears(date: Moment): MonthInterface[][] {
    const quarters: MonthInterface[][] = [];
    let months: MonthInterface[] = [];
    for (let i = 0; i < 12; i++) {
      months.push({
        index          : i,
        name           : this._listOfMonthName[ i ],
        year           : date.year(),
        isCurrentMonth : moment(new Date()).month() === i && date.isSame(new Date(), 'year'),
        isSelectedMonth: this._showMonth === i,
        disabled       : this.nzDisabledDate && this.nzDisabledDate(date.month(i).toDate())
      });
      if ((i + 1) % 3 === 0) {
        quarters.push(months);
        months = [];
      }
    }
    return quarters;
  }

  _buildCalendar(): void {
    moment.locale(this._locale);
    /** TODO replace with real i18n*/
    if (this._locale !== 'zh-cn') {
      try {
        this._yearUnit = moment.duration(12, 'month').humanize().split(' ')[ 1 ][ 0 ].toUpperCase() + moment.duration(12, 'month').humanize().split(' ')[ 1 ].slice(1, moment.duration(12, 'month').humanize().split(' ')[ 1 ].length);
        this._monthUnit = moment.duration(4, 'week').humanize().split(' ')[ 1 ][ 0 ].toUpperCase() + moment.duration(4, 'week').humanize().split(' ')[ 1 ].slice(1, moment.duration(4, 'week').humanize().split(' ')[ 1 ].length);
      } catch (e) { }
    }

    if (this._locale === 'ru-RU') {
      try {
        const monthText = moment.duration(12, 'month').humanize().split(' ')[ 0 ];
        const weekText =  moment.duration(4, 'week').humanize().split(' ')[ 0 ];

        this._yearUnit = monthText.charAt(0).toUpperCase() + monthText.slice(1);
        this._monthUnit = weekText.charAt(0).toUpperCase() + weekText.slice(1);
      } catch (e) { }
    }

    this._listOfYearName = this._generateYears(this._showYear);
    this._listOfWeekName = moment.weekdaysMin();
    this._listOfMonthName = moment.monthsShort();
    const date = moment(this.nzValue).year(this._showYear).month(this._showMonth);
    this._weeksCalendar = this._buildMonth(date);
    this._quartersCalendar = this._buildYears(date);
  }

  _generateYears(year: number): number[] {
    const listOfYears: number[] = [];
    for (const i of Array.from(Array(20).keys())) {
      listOfYears.push(i - 10 + year);
    }
    return listOfYears;
  }

  constructor(private _elementRef: ElementRef, private _localeService: NzLocaleService) {
    this._el = this._elementRef.nativeElement;
  }

  ngOnInit(): void {
    this._buildCalendar();
  }
}
