/*
 * Copyright (C) 2021 Robert Bosch GmbH Copyright (C) 2021 Robert Bosch Manufacturing Solutions GmbH, Germany. All rights reserved.
 */

import { ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core';
import { BciMultiFileSelectIntl } from '../multi-file-select/multi-file-select-intl';
import { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { BOSCH_DDS_COLORS } from '../../core/core-configuration/bosch-dds-colors';
import { FileValidationTypeError } from './file-validation-type-error';
import { FileValidationError } from './file-validation-error';

@Component({
    selector: 'bci-multi-file-select',
    templateUrl: './multi-file-select.component.html',
    styleUrls: ['./multi-file-select.component.scss']
})
export class MultiFileSelectComponent  {

    /**
   * The type of files (.json, .csv, ...) which are allowed to be uploaded
   */
  @Input() allowedFileTypes?: Array<string>;

  /**
   * The maximum amount of files a user can upload
   */
  @Input() maximumFiles?: number;

  /**
   * Allow multiple file selection
   */
  @Input() allowMultipleFileSelection?: boolean;


  /**
   * The maximum size for a single file a user can upload
   */
  @Input() maximumSizePerFileMB?: number;

  /**
   * Restrict files to have a unique name.
   */
  @Input() uniqueFileNames = false;

  /**
   * The event emitter which propagates the uploaded files after a successful validation
   */
  @Output() selectedFilesChange: EventEmitter<Array<File>> = new EventEmitter<Array<File>>();

  /**
   * The event emitter which propagates errors of type FileValidationError in case an uploaded file is invalid
   */
  @Output() fileValidationError: EventEmitter<FileValidationError[]> = new EventEmitter<FileValidationError[]>();

  private readonly BYTE_TO_MEGABYTE_FACTOR = 1024 * 1024;
  private readonly FILE_TYPE_SEPARATOR = '.';

  readonly JOIN_STRING_SEPARATOR = ', ';
  selectedFiles: Array<File> = new Array<File>();

  private ngUnsubscribe = new Subject<void>();

  constructor(public bciMultiFileSelectIntl: BciMultiFileSelectIntl, private changeDetector: ChangeDetectorRef) {}

  ngOnInit() {
    this.bciMultiFileSelectIntl.changes
      .pipe(takeUntil(this.ngUnsubscribe))
      .subscribe(() => this.changeDetector.detectChanges());

    this.allowedFileTypes = this.allowedFileTypes?.map((type) => type.toLowerCase());
    document.addEventListener('dragover', this.preventBrowserDefault, false);
    document.addEventListener('drop', this.preventBrowserDefault, false);
  }

  ngAfterViewInit() {
    this.setupDragDropStyling();
  }

  ngOnDestroy(): void {
    this.ngUnsubscribe.next();
    this.ngUnsubscribe.complete();
    document.removeEventListener('dragover', this.preventBrowserDefault);
    document.removeEventListener('drop', this.preventBrowserDefault);
  }

  setupDragDropStyling() {
    const dropContainer = document.getElementById('multiFileSelect.dropContainer');
    dropContainer.addEventListener("dragover", this.activateHoverEffect, false);
    dropContainer.addEventListener("dragleave", this.deactivateHoverEffect, false);
    dropContainer.addEventListener("drop", this.deactivateHoverEffect, false);
  }

  private activateHoverEffect() {
    document.getElementById('multiFileSelect.dropContainer').style.borderColor = BOSCH_DDS_COLORS.boschBlue40;
    document.getElementById('multiFileSelect.dropContainerEmptyState').style.color = BOSCH_DDS_COLORS.boschBlue40;
  }

  private deactivateHoverEffect() {
    if (document.getElementById('multiFileSelect.dropContainer') !== null) {
      document.getElementById('multiFileSelect.dropContainer').style.borderColor = BOSCH_DDS_COLORS.boschBlack;
      document.getElementById('multiFileSelect.dropContainerEmptyState').style.color = BOSCH_DDS_COLORS.boschBlack;
    }
  }

  private preventBrowserDefault(e) {
    e.preventDefault();
  }

  handleFileDrop(event) {
    event.stopPropagation();
    event.preventDefault();
    
    this.processFiles(event.dataTransfer.files);
  }
  
  handleFileChange(event) {
    this.processFiles(event.target.files);
    // detect changes to resize the height of the drag area correctly
    this.changeDetector.detectChanges();
  }

  processFiles(fileList: FileList) {    
    const validationMessages: FileValidationError[] = [];

    const validFiles = [];  
    for (const selectedFile of Array.from(fileList)) {                
      const validationError = this.validateFile(selectedFile);
      if (!validationError) {
        if (this.maximumFiles && validFiles.length + this.selectedFiles.length >= this.maximumFiles) {
          validationMessages.push({
            name: selectedFile.name,
            validationError: FileValidationTypeError.EXCEEDED_FILE_NUMBER
          });
          continue;  
        }

        if (this.uniqueFileNames && (this.selectedFiles.find(file => file.name === selectedFile.name) || 
            validFiles.find(file => file.name === selectedFile.name))            
        ) {
          validationMessages.push({
            name: selectedFile.name,
            validationError: FileValidationTypeError.DUPLICATED_FILE
          });
          continue;  
        }
        
        validFiles.push(selectedFile);
      } else {
        validationMessages.push({
          name: selectedFile.name,
          validationError
        });
      }      
    }

    if (validFiles.length > 0) {
      this.selectedFiles = this.selectedFiles.concat(validFiles);
      this.selectedFilesChange.emit(this.selectedFiles);
    }      
    
    if (validationMessages.length > 0) {
      this.fileValidationError.emit(validationMessages);
    }
  }

  handleDragOver(event) {    
    event.stopPropagation();
    event.preventDefault();
    event.dataTransfer.dropEffect = 'move';    
  }  

  removeFile(file: File) {
    this.selectedFiles = this.selectedFiles.filter((item) => item !== file);
    this.selectedFilesChange.emit(this.selectedFiles);
    // detect changes to resize the height of the drag area correctly
    this.changeDetector.detectChanges();
    // setup the drag and drop event listeners again after the drop area was removed from the dom
    this.setupDragDropStyling();
  }

  getFileTypeExtensionFromFileName(fileName: string): string {
    const fileNameParts = fileName.split(this.FILE_TYPE_SEPARATOR);
    if (fileNameParts.length === 1) {
      return null;
    }
    return fileNameParts.pop().toLocaleLowerCase();
  }

  isFileSelectionDisabled(): boolean {
    return this.selectedFiles.length >= this.maximumFiles;
  }

  private validateFile(file: File): FileValidationTypeError | null {
    const sizeValid: boolean = this.validateFileSize(file);
    const typeValid: boolean = this.validateFileType(file);
    const hasFileExtension: boolean = this.validateFileHasExtension(file);

    if (!sizeValid && !typeValid) {
      return FileValidationTypeError.EXCEEDED_FILE_SIZE_AND_INVALID_FILE_TYPE;
    } else if (!sizeValid && !hasFileExtension) {
      return FileValidationTypeError.EXCEEDED_FILE_SIZE_AND_MISSING_EXTENSION
    } else if (!hasFileExtension) {
      return FileValidationTypeError.MISSING_FILE_EXTENSION;
    } else if (!sizeValid) {
      return FileValidationTypeError.EXCEEDED_FILE_SIZE;
    } else if (!typeValid) {
      return FileValidationTypeError.INVALID_FILE_TYPE;
    }
    
    return null;
  }

  private validateFileType(file: File): boolean {
    return (
      !this.allowedFileTypes ||
      this.allowedFileTypes.includes(this.FILE_TYPE_SEPARATOR + this.getFileTypeExtensionFromFileName(file.name))
    );
  }

  private validateFileHasExtension(file: File): boolean {
    return !!this.getFileTypeExtensionFromFileName(file.name);
  }

  private validateFileSize(file: File): boolean {
    return !this.maximumSizePerFileMB || file.size <= this.maximumSizePerFileMB * this.BYTE_TO_MEGABYTE_FACTOR;
  }  
}