import {Component, ElementRef, isDevMode, OnInit, TemplateRef, ViewChild} from '@angular/core';
import {ApiService} from '../services/api.service';
import {AutocompleteService} from '../services/autocomplete.service';
import cloneDeep from 'lodash/cloneDeep';
import pick from 'lodash/pick';
import omit from 'lodash/omit';
import {SampleModel} from '../models/sample.model';
import {LoginService} from '../services/login.service';
import {ModalService} from '@inst-iot/bosch-angular-ui-components';
import {DataService} from '../services/data.service';
import {LocalStorageService} from 'angular-2-local-storage';
import {Router} from '@angular/router';



interface LoadSamplesOptions {
  toPage?: number;
  event?: Event;
  firstPage?: boolean;
}
interface KeyInterface {
  id: string;
  label: string;
  active: boolean;
  sortable: boolean;
}

@Component({
  selector: 'app-samples',
  templateUrl: './samples.component.html',
  styleUrls: ['./samples.component.scss']
})

export class SamplesComponent implements OnInit {

  @ViewChild('pageSizeSelection') pageSizeSelection: ElementRef<HTMLElement>;
  @ViewChild('linkarea') linkarea: ElementRef<HTMLTextAreaElement>;

  downloadSpectra = false;    // download options
  downloadCondition = false;
  downloadFlatten = true;
  samples: SampleModel[] = [];  // all samples to display
  totalSamples = 0;  // total number of samples
  csvUrl = '';  // store url separate so it only has to be generated when clicking the download button
  filters = {
    status: {new: true, validated: true, deleted: false},
    pageSize: 25,
    toPage: 0,
    sort: 'added-asc',
    no: {condition: false, measurements: false},
    filters: [
      {field: 'number', label: 'Number', active: false, autocomplete: [], mode: 'eq', values: ['']},
      {field: 'material.name', label: 'Product name', active: false, autocomplete: [], mode: 'eq', values: ['']},
      {field: 'material.supplier', label: 'Supplier', active: false, autocomplete: [], mode: 'eq', values: ['']},
      {field: 'material.group', label: 'Material', active: false, autocomplete: [], mode: 'eq', values: ['']},
      {field: 'material.glass_fiber', label: 'GF', active: false, autocomplete: [], mode: 'eq', values: ['']},
      {field: 'material.carbon_fiber', label: 'CF', active: false, autocomplete: [], mode: 'eq', values: ['']},
      {field: 'material.mineral', label: 'M', active: false, autocomplete: [], mode: 'eq', values: ['']},
      {field: 'type', label: 'Type', active: false, autocomplete: [], mode: 'eq', values: ['']},
      {field: 'color', label: 'Color', active: false, autocomplete: [], mode: 'eq', values: ['']},
      {field: 'batch', label: 'Batch', active: false, autocomplete: [], mode: 'eq', values: ['']},
      {field: 'notes.comment', label: 'Comment', active: false, autocomplete: [], mode: 'eq', values: ['']},
      {field: 'added', label: 'Added', active: false, autocomplete: [], mode: 'eq', values: ['']}
    ]
  };
  page = 1;   // current page
  pages = 1;  // total number of pages
  loadSamplesQueue = [];  // arguments of queued up loadSamples() calls
  keys: KeyInterface[] = [
    {id: 'number', label: 'Number', active: true, sortable: true},
    {id: 'material.numbers', label: 'Material numbers', active: false, sortable: false},
    {id: 'material.name', label: 'Product name', active: true, sortable: true},
    {id: 'material.supplier', label: 'Supplier', active: false, sortable: true},
    {id: 'material.group', label: 'Material', active: true, sortable: true},
    {id: 'type', label: 'Type', active: true, sortable: true},
    {id: 'color', label: 'Color', active: false, sortable: true},
    {id: 'batch', label: 'Batch', active: true, sortable: true},
    {id: 'notes.comment', label: 'Comment', active: false, sortable: false},
    {id: 'notes', label: 'Notes', active: false, sortable: false},
    {id: 'status', label: 'Status', active: false, sortable: true},
    {id: 'added', label: 'Added', active: true, sortable: true}
  ];
  isActiveKey: {[key: string]: boolean} = {};  // object to check if key is currently active
  activeKeys: KeyInterface[] = [];  // list of active keys
  activeTemplateKeys = {material: [], condition: [], measurements: []};
  sampleDetailsSample: any = null;  // sample for the sample details dialog
  sampleSelect = 0;  // modes: 0 - no selection, 1 - sample edit selection, 2 - validation selection
  loading = 0;  // number of loading instances


  constructor(
    private api: ApiService,
    public autocomplete: AutocompleteService,
    public login: LoginService,
    private modalService: ModalService,
    public d: DataService,
    private storage: LocalStorageService,
    private window: Window,
    private router: Router
  ) {
  }

  ngOnInit(): void {
    this.loading = 8;
    const onLoad = () => {
      if ((--this.loading) <= 0) {
        this.loadSamples();
      }
    };

    this.calcFieldSelectKeys();
    this.d.load('materials', () => {
      this.filters.filters.find(e => e.field === 'material.name').autocomplete = this.d.arr.materials.map(e => e.name);
      onLoad();
    });
    this.d.load('materialSuppliers', () => {
      this.filters.filters.find(e => e.field === 'material.supplier').autocomplete = this.d.arr.materialSuppliers;
      onLoad();
    });
    this.d.load('materialGroups', () => {
      this.filters.filters.find(e => e.field === 'material.group').autocomplete = this.d.arr.materialGroups;
      onLoad();
    });
    this.d.load('userKey', onLoad);
    this.d.load('conditionTemplates', onLoad);
    this.loadTemplateKeys('material', 'type', onLoad);
    this.loadTemplateKeys('condition', 'notes.comment', onLoad);
    this.loadTemplateKeys('measurement', 'status', onLoad);
  }

  loadTemplateKeys(collection, insertBefore, f) {
    this.d.load(collection + 'Templates', () => {
      const templateKeys = [];
      this.d.arr[collection + 'Templates'].forEach(item => {
        item.parameters.forEach(parameter => {
          const parameterName = encodeURIComponent(parameter.name);
          // exclude spectrum and duplicates
          if (parameter.name !== 'dpt' && !templateKeys.find(e => new RegExp('.' + parameterName + '$').test(e.id))) {
            const collectionNames = {
              material: 'material.properties',
              condition: 'condition',
              measurement: 'measurements.' + encodeURIComponent(item.name)
            };
            templateKeys.push({
              id: `${collectionNames[collection]}.${parameterName}`,
              label: `${this.ucFirst(item.name)} ${parameter.name}`,
              active: false,
              sortable: true
            });
            this.filters.filters.push({
              field: `${collectionNames[collection]}.${parameterName}`,
              label: `${this.ucFirst(item.name)} ${parameter.name}`,
              active: false,
              autocomplete: [],
              mode: 'eq',
              values: ['']
            });
          }
        });
      });
      this.keys.splice(this.keys.findIndex(e => e.id === insertBefore), 0, ...templateKeys);
      this.keys = [...this.keys];  // complete overwrite array to invoke update in rb-multiselect
      this.loadPreferences();
      f();
    });
  }

  // set toPage to null to reload first page, queues calls
  loadSamples(options: LoadSamplesOptions = {}, event = null) {
    if (event) {  // adjust active keys
      this.keys.forEach(key => {
        if (event.hasOwnProperty(key.id)) {
          key.active = event[key.id];
        }
      });
      const sortId = this.filters.sort.replace(/(-asc|-desc)/, '');
      if (event.hasOwnProperty(sortId) && !event[sortId]) {  // reset sort if sort field was unselected
        this.setSort('_id-asc');
      }
      this.updateActiveKeys();
    }
    this.loadSamplesQueue.push(options);
    if (this.loadSamplesQueue.length <= 1) {  // nothing queued up
      this.sampleLoader(this.loadSamplesQueue[0]);
    }
    this.storePreferences();
  }

  private sampleLoader(options: LoadSamplesOptions) {  // actual loading of the sample, do not call directly
    this.loading ++;
    this.api.get(this.sampleUrl({paging: true, pagingOptions: options}), (sData, err, headers) => {
      this.loading --;
      if (err) {  // remove stored options on error to avoid loop
        this.storage.remove('samplesPreferences');
        this.api.requestError(err);
      }
      else {
        if (!options.toPage && headers['x-total-items']) {
          this.totalSamples = headers['x-total-items'];
        }
        this.pages = Math.ceil(this.totalSamples / this.filters.pageSize);
        this.samples = sData as any;
        this.loadSamplesQueue.shift();
        if (this.loadSamplesQueue.length > 0) {  // execute next queue item
          this.sampleLoader(this.loadSamplesQueue[0]);
        }
      }
    });
  }

  sampleUrl(options: {
    paging?: boolean,
    pagingOptions?: {
      firstPage?: boolean,
      toPage?: number,
      event?: Event
    },
    csv?: boolean,
    export?: boolean,
    host?: boolean
  }) {  // return url to fetch samples
    // keys which should always be added if export = false
    const additionalTableKeys = ['material_id', '_id', 'user_id'];
    const query: string[] = [];
    query.push(...Object.keys(this.filters.status).filter(e => this.filters.status[e]).map(e => 'status[]=' + e));
    if (options.paging) {
      if (this.samples[0]) {  // do not include from-id when page size was changed
        if (!options.pagingOptions.firstPage) {
          query.push('from-id=' + this.samples[0]._id);
        }
        else {
          this.page = 1;
        }
      }
      if (options.pagingOptions.toPage) {
        query.push('to-page=' + options.pagingOptions.toPage);
      }
      query.push('page-size=' + this.filters.pageSize);
    }
    query.push('sort=' + this.filters.sort);
    if (options.export) {  // append API key on export
      query.push('key=' + this.d.d.userKey.key);
    }
    this.keys.forEach(key => {
      // do not load material properties for table
      if (key.active && (options.export || (!options.export && key.id.indexOf('material.') < 0))) {
        query.push('fields[]=' + key.id);
      }
    });

    query.push(...cloneDeep(this.filters.filters)
      .map(e => {
        e.values = e.values.filter(el => el !== '');  // do not include empty values
        if (e.field === 'added') {  // correct timezone
          e.values = e.values.map(
            el => new Date(new Date(el).getTime() - new Date(el).getTimezoneOffset() * 60000).toISOString()
          );
        }
        if (e.mode === 'null') {  // handle null mode
          e.mode = 'in';
          e.values = [null, ''];
        }
        return e;
      })
      .filter(e => e.active && e.values.length > 0)
      .map(e => 'filters[]=' + encodeURIComponent(JSON.stringify(pick(e, ['mode', 'field', 'values']))))
    );
    if (this.filters.no.condition) {
      query.push('filters[]=' + encodeURIComponent( JSON.stringify({mode: 'eq', field: 'condition', values: [{}]})));
    }
    if (this.filters.no.measurements) {
      query.push('filters[]=' +
        encodeURIComponent( JSON.stringify( {mode: 'eq', field: 'measurements', values: [null]})));
    }
    if (!options.export) {
      additionalTableKeys.forEach(key => {
      if (query.indexOf('fields[]=' + key) < 0) {  // add key if not already added
        query.push('fields[]=' + key);
      }
      });
    }
    else {  // export options
      if (options.csv) {
        query.push('output=csv');
      }
      else if (this.downloadFlatten) {
        query.push('output=flatten');
      }
      if (this.downloadSpectra) {
        query.push('fields[]=measurements.spectrum.dpt');
      }
      if (this.downloadCondition) {
        query.push('fields[]=condition');
      }
    }
    return (options.host && isDevMode() ? this.window.location.host : '') +
      (options.export ? this.api.hostName : '') +
      '/samples?' + query.join('&');
  }

  loadPage(delta) {
    if (!/[0-9]+/.test(delta) || this.page + delta < 1 || this.page + delta > this.pages) {  // invalid delta
      return;
    }
    this.page += delta;
    this.loadSamples({toPage: delta});
  }

  storePreferences() {
    const store = {
      filters: {
        ...pick(this.filters, ['status', 'pageSize', 'toPage', 'sort']),
        filters: this.filters.filters.map(e => pick(e, ['field', 'active', 'mode', 'values']))
      },
      keys: this.keys.map(e => pick(e, ['id', 'active']))
    };
    this.storage.set('samplesPreferences', store);
  }

  loadPreferences() {
    const store: any = this.storage.get('samplesPreferences');
    if (store) {
      this.filters = {...this.filters, ...pick(store.filters, ['status', 'pageSize', 'toPage', 'sort'])};
      store.filters.filters.forEach(filter => {
        const filterIndex = this.filters.filters.findIndex(e => e.field === filter.field);
        if (filterIndex >= 0) {
          this.filters.filters[filterIndex] = {...this.filters.filters[filterIndex], ...filter};
        }
      });
      store.keys.forEach(key => {
        const keyIndex = this.keys.findIndex(e => e.id === key.id);
        if (keyIndex >= 0) {
          this.keys[keyIndex].active = key.active;
        }
      });
    }
    this.calcFieldSelectKeys();
    this.updateActiveKeys();
  }

  resetPreferences() {
    this.storage.remove('samplesPreferences');
    this.window.location.reload();
  }

  updateFilterFields(field) {
    const filter = this.filters.filters.find(e => e.field === field);
    filter.active = !(filter.values.length === 1 && filter.values[0] === '');
  }

  setSort(string) {
    this.filters.sort = string;
    this.loadSamples({firstPage: true});
  }

  updateActiveKeys() {  // array with all activeKeys
    this.activeKeys = this.keys.filter(e => e.active);
    this.filters.filters.forEach(filter => {  // disable filters of fields not displayed
      if (!this.isActiveKey[filter.field]) {
        filter.active = false;
      }
    });
    this.activeTemplateKeys.material = this.keys
      .filter(e => e.id.indexOf('material.properties.') >= 0 && e.active)
      .map(e => e.id.split('.')
        .map(el => decodeURIComponent(el)));
    this.activeTemplateKeys.condition = this.keys.filter(e => e.id.indexOf('condition.') >= 0 && e.active)
      .map(e => e.id.split('.')
        .map(el => decodeURIComponent(el)));
    this.activeTemplateKeys.measurements = this.keys.filter(e => e.id.indexOf('measurements.') >= 0 && e.active)
      .map(e => e.id.split('.')
        .map(el => decodeURIComponent(el)));
  }

  calcFieldSelectKeys() {
    this.keys.forEach(key => {
      this.isActiveKey[key.id] = key.active;
    });
  }

  sampleDetails(id: string, modal: TemplateRef<any>) {  // show sample details
    this.sampleDetailsSample = null;
    this.api.get<SampleModel>('/sample/' + id, data => {
      this.sampleDetailsSample = new SampleModel().deserialize(data);
      if (data.notes.custom_fields) {  // convert custom_fields for more optimized display
        this.sampleDetailsSample.notes.custom_fields_entries =
          Object.entries(this.sampleDetailsSample.notes.custom_fields);
      }
      else {
        this.sampleDetailsSample.custom_fields_entries = [];
      }
      if (Object.keys(data.condition).length) {  // convert condition
        this.sampleDetailsSample.condition_entries =
          Object.entries(omit(this.sampleDetailsSample.condition, ['condition_template']))
          .map(e => {
            e[0] = `${this.ucFirst(
              this.d.id.conditionTemplates[this.sampleDetailsSample.condition.condition_template].name
            )} ${e[0]}`;
            return e;
          });
      }
      else {
        this.sampleDetailsSample.condition_entries = [];
      }
      this.sampleDetailsSample.measurement_entries = [];
      // convert measurements for more optimized display without dpt
      this.sampleDetailsSample.measurements.forEach(measurement => {
        const name = this.d.id.measurementTemplates[measurement.measurement_template].name;
        this.sampleDetailsSample.measurement_entries
          .push(...Object.entries(measurement.values).filter(e => e[0] !== 'dpt')
          .map(e => ({name: this.ucFirst(name) + ' ' + e[0], value: e[1]})));
      });
      new Promise(resolve => {
        if (data.notes.sample_references.length) {  // load referenced samples if available
          let loadingCounter = data.notes.sample_references.length;
          this.sampleDetailsSample.notes.sample_references.forEach(reference => {
            this.api.get<SampleModel>('/sample/' + reference.sample_id, rData => {
              reference.number = rData.number;
              loadingCounter --;
              if (!loadingCounter) {
                resolve();
              }
            });
          });
        }
        else {
          resolve();
        }
      }).then(() => {
        this.modalService.open(modal).then(() => {});
      });
    });
  }

  validate() {
    if (this.sampleSelect) {  // do actual validation
      this.samples.forEach(sample => {
        if (sample.selected) {
          this.api.put('/sample/validate/' + sample._id);
        }
      });
      this.loadSamples();
      this.sampleSelect = 0;
    }
    else {  // get into validation mode
      this.sampleSelect = 2;
    }
  }

  batchEdit() {  // redirect to batch edit
    if (this.sampleSelect) {
      this.router.navigate(['/samples/edit/' + this.samples.filter(e => e.selected).map(e => e._id).join(',')]);
      this.sampleSelect = 0;
    }
    else {
      this.sampleSelect = 1;
    }
  }

  restoreSample(id, modal, event) {
    this.stopPropagation(event);
    this.modalService.open(modal).then(res => {
      if (res) {
        this.api.put('/sample/restore/' + id, {}, ignore => {
          this.samples.find(e => e._id === id).status = 'new';
        });
      }
    });
  }

  selectAll(event) {  // toggle select all except deleted samples
    this.samples.forEach(sample => {
      if (sample.status !== 'deleted') {
        sample.selected = event.target.checked;
      }
      else {
        sample.selected = false;
      }
    });
  }

  preventDefault(event, key = 'all') {
    if (key === 'all' || event.key === key) {
      event.preventDefault();
    }
  }

  stopPropagation(event) {
    event.stopPropagation();
  }

  clipboard() {  // copy contents to clipboard
    this.linkarea.nativeElement.select();
    this.linkarea.nativeElement.setSelectionRange(0, 99999);
    document.execCommand('copy');
  }

  ucFirst(string) {  // convert first character of string to uppercase
    return string[0].toUpperCase() + string.slice(1);
  }


}
