import { Injectable, EventEmitter } from '@angular/core';

import moment from 'moment';
import 'moment-timezone';
import {
  TranslateService,
  LangChangeEvent,
  DefaultLangChangeEvent
} from '@ngx-translate/core';
import { Subscription } from 'rxjs';

export type DateUnit =
  | 'year'
  | 'years'
  | 'y'
  | 'month'
  | 'months'
  | 'M'
  | 'week'
  | 'weeks'
  | 'w'
  | 'day'
  | 'days'
  | 'd'
  | 'hour'
  | 'hours'
  | 'h'
  | 'minute'
  | 'minutes'
  | 'm'
  | 'second'
  | 'seconds'
  | 's'
  | 'millisecond'
  | 'milliseconds'
  | 'ms';

export interface DateOptions {
  manipulate?: {
    amount?: number;
    unit?: DateUnit;
    startOf?: DateUnit;
    endOf?: DateUnit;
  };
}

export interface TimezoneChangeEvent {
  name: string;
}

export interface DefaultTimezoneChangeEvent {
  name: string;
}

export interface LocaleChangeEvent {
  locale: string;
}

@Injectable({
  providedIn: 'root'
})
export class DateService {
  // tslint:disable-next-line: variable-name
  private _changedTimezone: EventEmitter<
    TimezoneChangeEvent
  > = new EventEmitter<TimezoneChangeEvent>();
  // tslint:disable-next-line: variable-name
  private _changedDefaultTimezone: EventEmitter<
    DefaultTimezoneChangeEvent
  > = new EventEmitter<DefaultTimezoneChangeEvent>();
  // tslint:disable-next-line: variable-name
  private _changedLocale: EventEmitter<LocaleChangeEvent> = new EventEmitter<
    LocaleChangeEvent
  >();

  private onLangChangeSubscription: Subscription;

  // tslint:disable-next-line: variable-name
  private _defaultTimezone: string;
  // tslint:disable-next-line: variable-name
  private _currentTimezone: string;
  // tslint:disable-next-line: variable-name
  private _currentLocale: string;

  get changedTimezone(): EventEmitter<TimezoneChangeEvent> {
    return this._changedTimezone;
  }

  get changedDefaultTimezone() {
    return this._changedDefaultTimezone;
  }

  get changedLocale() {
    return this._changedLocale;
  }

  /**
   * The default lang to fallback when translations are missing on the current lang
   */
  get defaultTimezone(): string {
    return this._defaultTimezone;
  }

  set defaultTimezone(defaultTimezone: string) {
    this._defaultTimezone = defaultTimezone;
  }

  /**
   * The lang currently used
   */
  get currentTimezone(): string {
    return this._currentTimezone;
  }

  set currentTimezone(currentTimezone: string) {
    this._currentTimezone = currentTimezone;
  }

  /**
   * The locale currently used
   */
  get currentLocale(): string {
    return this._currentLocale;
  }

  set currentLocale(currentLocale: string) {
    this._currentLocale = currentLocale;
  }

  constructor(private translateService: TranslateService) {
    this.onLangChangeSubscription = this.translateService.onLangChange.subscribe(
      (event: LangChangeEvent) => {
        if (this._currentLocale !== event.lang) {
          this.changeLocale(event.lang);
        }
      }
    );
  }

  /**
   * Sets the default language to use as a fallback
   */
  setDefaultTimezone(timezone: string): void {
    if (timezone === this.defaultTimezone) {
      return;
    }

    this.changeDefaultTimezone(timezone);
  }

  /**
   * Gets the default language used
   */
  getDefaultTimezone(): string {
    return this.defaultTimezone;
  }

  /**
   * Sets the default language to use as a fallback
   */
  setLocale(locale: string): void {
    if (locale === this.currentLocale) {
      return;
    }

    this.changeLocale(locale);
  }

  /**
   * Gets the default language used
   */
  getLocale(): string {
    return this.currentLocale;
  }

  /**
   * Changes the lang currently used
   */
  public use(timezone: string): void {
    // don't change the language if the language given is already selected
    if (timezone === this.currentTimezone) {
      return;
    }

    this.changeTimezone(timezone);
  }

  /**
   * Gets the translated value of a key (or an array of keys)
   * @returns the translated key, or an object of translated keys
   */
  public get(date: any, format: string = 'L', options?: DateOptions): string {
    if (!date || 0 === date.length) {
      throw new Error(`Parameter "date" required`);
    }

    let d = moment.tz(date, 'Asia/Seoul').utc();

    d = d.tz(this.currentTimezone);

    if (!!options) {
      if (!!options.manipulate) {
        if (!!options.manipulate.amount && !!options.manipulate.unit) {
          d = d.add(options.manipulate.amount, options.manipulate.unit);
        }

        if (!!options.manipulate.startOf) {
          d = d.startOf(options.manipulate.startOf);
        }
        if (!!options.manipulate.endOf) {
          d = d.startOf(options.manipulate.endOf);
        }
      }
    }

    if ('C' === format) {
      return this.chatFormat(d);
    }

    return d.format(format);
  }

  /**
   * Changes the current lang
   */
  private changeTimezone(timezone: string): void {
    this.currentTimezone = timezone;
    this.changedTimezone.emit({ name: timezone });

    // if there is no default lang, use the one that we just set
    if (!this.defaultTimezone) {
      this.changeDefaultTimezone(timezone);
    }
  }

  /**
   * Changes the default lang
   */
  private changeDefaultTimezone(timezone: string): void {
    this.defaultTimezone = timezone;
    this.changedDefaultTimezone.emit({ name: timezone });
  }

  /**
   * Changes the default lang
   */
  private changeLocale(locale: string): void {
    this.currentLocale = locale;
    moment.locale(locale);
    this.changedLocale.emit({ locale });
  }

  private chatFormat(m: moment.Moment): string {
    const today = moment();
    const yesterday = moment().subtract(1, 'day');

    if (m.isSame(today, 'day')) {
      return m.format('a hh:mm');
    } else if (m.isSame(yesterday, 'day')) {
      return moment
        .localeData()
        .calendar('lastDay')
        .replace('LT', '')
        .replace('[', '')
        .replace(']', '')
        .replace('at', '')
        .trim();
    } else {
      return m.format('MM.DD');
    }
  }
}
