import {
  AppConfigService,
  Event,
  InputControl,
  MessagesService,
  NotNull,
  UnsupportedOperationError,
  Visible,
} from "@webbpm/base-package";
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef,} from "@angular/core";
import {FileItem, FileLikeObject, FileUploader} from "ng2-file-upload";

@Component({
             selector: 'ervu-file-upload',
             moduleId: module.id,
             templateUrl: './../../../../../src/resources/template/ervu-dashboard/ErvuFileUpload.html',
             changeDetection: ChangeDetectionStrategy.OnPush
           })
export class ErvuFileUpload extends InputControl {
  private static readonly BACKEND_URL: string = "backend.context";

  @NotNull("true") public selectFileFieldText: string;
  @NotNull("true") public selectFileButtonName: string;
  @NotNull("true") public removeFileButtonName: string;
  @NotNull("true") public url: string;
  public maxFileSizeMb: number;
  public extensionFilter: string[] = [];
  public maxFilesToUpload: number;
  @NotNull("true") public displayFileSize: boolean = false;
  @NotNull("true") public displayProgressBar: boolean = false;
  @Visible("false") public uploader: FileUploader;

  @Visible("false") public fileAddedEvent: Event<any> = new Event<any>();
  @Visible("false") public fileDeletedEvent: Event<any> = new Event<any>();
  @Visible("false") public fileUploadStartEvent: Event<any> = new Event<any>();
  @Visible("false") public fileUploadEndEvent: Event<any> = new Event<any>();
  @Visible("false") public fileUploadFailedEvent: Event<any> = new Event<any>();

  public isDropZoneVisible: boolean = true;
  public isFilesListVisible: boolean = true;
  public isProgressBarVisible: boolean = false;

  private fileInputEl: HTMLInputElement;
  private messagesService: MessagesService;
  private isUploadErrorOccurred = false;
  private appConfigService: AppConfigService;

  constructor(el: ElementRef, cd: ChangeDetectorRef) {
    super(el, cd);
    this.uploader = new FileUploader({url: this.url});
  }

  initialize() {
    super.initialize();
    this.messagesService = this.injector.get(MessagesService);
    this.appConfigService = this.injector.get(AppConfigService);
    this.url =
        `/${this.appConfigService.getParamValue(ErvuFileUpload.BACKEND_URL)}${this.url}`;

    this.uploader.setOptions({
                               url: this.url,
                               autoUpload: false,
                               filters: [{
                                 name: 'extension', fn: (item: any): boolean => {
                                   if (!this.extensionFilter.length) {
                                     return true;
                                   }
                                   const fileExtension = item.name.substring(
                                       item.name.lastIndexOf('.') + 1, item.name.length);
                                   for (let ext of this.extensionFilter) {
                                     if (ext.toUpperCase() === fileExtension.toUpperCase()) {
                                       return true;
                                     }
                                   }
                                   return false;
                                 }
                               }],
                               maxFileSize: this.maxFileSizeMb
                                            ? this.maxFileSizeMb * 1024 * 1024
                                            : undefined,
                               queueLimit: this.maxFilesToUpload
                                           ? this.maxFilesToUpload
                                           : undefined,
                               headers: [{
                                 name: "Client-Time-Zone",
                                 value: Intl.DateTimeFormat().resolvedOptions().timeZone
                               }]
                             });

    this.setUploaderMethods();
  }

  ngAfterViewInit() {
    super.ngAfterViewInit();
    this.fileInputEl = this.el.nativeElement.querySelector('.file-input');
  }

  openFileChooseDialog() {
    this.fileInputEl.click();
  }

  @Visible() uploadFiles() {
    this.uploader.uploadAll();
  }

  removeFile(item: FileItem) {
    item.remove();
    this.fileInputEl.value = null;
    this.fileDeletedEvent.trigger();
    this.cd.markForCheck();
  }

  private setUploaderMethods() {
    this.uploader.onBeforeUploadItem = (fileItem: FileItem) => {

      //refresh headers
      this.uploader.setOptions({
                                 headers: [{
                                   name: "Client-Time-Zone",
                                   value: Intl.DateTimeFormat().resolvedOptions().timeZone
                                 }]
                               });
      this.fileUploadStartEvent.trigger();
      this.isDropZoneVisible = false;
      this.isFilesListVisible = false;
      this.isProgressBarVisible = true;
      this.cd.markForCheck();
    };

    this.uploader.onErrorItem = (item: FileItem, response: string) => {
      this.fileUploadFailedEvent.trigger();
      this.uploader.cancelAll();
      this.messagesService.error(`Не удалось отправить следующие файлы: ${item.file.name},` +
                                 ` ${this.uploader.getNotUploadedItems()
                                     .map(notUploadeditem => notUploadeditem.file.name)
                                     .join(', ')}.`);
      this.uploader.clearQueue();
      this.isDropZoneVisible = true;
      this.isFilesListVisible = true;
      this.isProgressBarVisible = false;
      this.isUploadErrorOccurred = true;
      this.cd.markForCheck();
    };

    this.uploader.onCompleteAll = () => {
      if (!this.isUploadErrorOccurred) {
        this.uploader.clearQueue();
        this.fileUploadEndEvent.trigger();
        this.isProgressBarVisible = false;
        this.cd.markForCheck();
      }
    };

    this.uploader.onAfterAddingFile = (fileItem: FileItem) => {
      this.fileAddedEvent.trigger();
    }

    this.uploader.onWhenAddingFileFailed = (item: FileLikeObject, filter: any, options: any) => {
      switch (filter.name) {
        case "fileSize":
          this.messagesService.error(
              `Размер файла ${item.name} превышает предельно допустимый = ${this.maxFileSizeMb} MB`);
          break;
        case "queueLimit":
          this.messagesService.error(`Не удалось добавить файл ${item.name}. ` +
                                     `Достигнуто максимальное количество файлов для загрузки = ${this.maxFilesToUpload}`);
          break;
        case "extension":
          this.messagesService.error(`Файл ${item.name} имеет недопустимое расширение.`);
          break;
        default:
          this.messagesService.error(`Не удалось добавить файл ${item.name}.`);
      }
      this.fileInputEl.value = null;
      this.cd.markForCheck();
    };
  }

  @Visible() getExtensions(): string {
    if (this.extensionFilter) {
      return this.extensionFilter
          .map(s => '.' + s)
          .join(',')
    }
    return undefined;
  }

  subscribeToModelChange() {
    //empty because there is no ngModel here
  }

  unsubscribeToModelChange() {
    //empty because there is no ngModel here
  }

  @Visible() getPresentationValue(): string {
    let fileNames: string = '';
    let fileItems: FileItem[] = this.uploader.queue;
    if (fileItems) {
      fileItems.forEach((fileItem) => {
        fileNames += fileItem.file.name;
      });
    }
    return fileNames;
  }

  @Visible() getValue(): File[] {
    return this.uploader.queue.map(fileItem => fileItem._file);
  }

  getValueAsModel(): any {
    throw new UnsupportedOperationError("Unsupported operation");
  }

  setValue(value: any): any {
    throw new UnsupportedOperationError("Unsupported operation");
  }

  @Visible()
  public reset() {
    //don't use super because there is no ngModel here
    this.uploader.clearQueue();
    this.fileInputEl.value = null;
    this.isDropZoneVisible = true;
    this.isFilesListVisible = true;
    this.isProgressBarVisible = false;
    this.isUploadErrorOccurred = false;
    this.cd.markForCheck();
  }
}
