import { IPageContent, IPageContentCell, IPageCellStyle } from './Content';
import { IPage } from './Page';
import { unwatchFile } from 'fs';
import Utils, { IUtils } from '../Utils';
import Helpers, { IHelpers } from '../Helpers';
import { IActionsButton } from '../Actions/Buttons';

export interface IParserSorting {
  field: string;
  direction: string;
  enabled: boolean;
  type: string;
  [key: string]: any;
}

export interface IParserFilter {
  field: string;
  enabled: boolean;
  exp: string;
  type: string;
  value: string;
}

export interface IParser {
  grouped: boolean;
  filters: IParserFilter[];
  sorting: IParserSorting | null;
  content: IPageContent;
  visibleGroups: IParserVisibleGroups;
  utils: IUtils;
  groupedContent: IParserGroupedContent;
  defaultCellStyle: IPageCellStyle;
  columnDefinitions: IParserColumnDefinitions;
  groupTagged: boolean;
  parse(): IParserContent;
  toggleGroup(index: number): void;
  setColumnDefinitions(definitions: any[]): void;
  //   setContent(content: IPageContent): void;
  setDefaultCellStyleProperties(style: IPageCellStyle): void;
  headerRenderer(params: IParserParams): IParserCellRenderer | null;
  cellRenderer(params: IParserParams): IParserCellRenderer | null;
}

interface IParserVisibleGroups extends Array<any> {
  [index: number]: any;
}

export interface IParserGroupedContent extends Array<any> {
  [index: number]: IParserData[];
}

interface IParserData {
  // name: string;
  // field: string;
  // width: number;
  // defaultValue?: string | number;
  // columnStyle?: IPageCellStyle;
  // cellStyle?: IPageCellStyle;
  [key: string]: any;
}

interface IParserColumnDefinitions extends Array<any> {
  [index: number]: IParserColumnDefinition;
}

export interface IParserCellRenderer {
  value?: number | string;
  button?: IActionsButton;
  style?: IPageCellStyle;
}

export interface IParserEnumValues {
  [key: number]: string;
}

export interface IParserColumnDefinition {
  disabled: boolean;
  id: boolean;
  name: string;
  field: string;
  type: string;
  width: number;
  columnStyle: IPageCellStyle;
  cellStyle: IPageCellStyle;
  defaultValue: string | number | null;
  enumValues: IParserEnumValues;
  ignoreCellRenderer: boolean;
  render(params: IParserParams): IParserCellRenderer | null;
  getDefaultValue(): string | number;
  [key: string]: any;
}

// export interface ISorting {
//   col: number;
//   direction: string;
//   enabled: boolean;
//   type: Types;
// }

// export interface IFilter {
//   col: number;
//   enabled: boolean;
//   exp: string;
//   type: Types;
//   value: string;
// }

// interface IButton {
//   type: string;
//   options: any[];
// }

export interface IPageCellStyle extends IPageCellStyle {
  width: string;
}

// interface IParams {
//   data: IData;
//   row: number;
//   col: number;
//   definition: IColumnDefinition;
//   expanded: boolean;
//   group: number;
// }

export interface IParserContent extends Array<any> {
  [index: number]: IParserCell[];
}

export interface IParserCell extends IPageContentCell {
  data: IParserData;
  // button: IActionsButton | null;
  definition: IParserColumnDefinition;
  id: boolean;
  group: number;
  groupIndex: number;
  header?: boolean;
}

export interface IParserParams {
  data: IParserData;
  definition: IParserColumnDefinition;
  row: number;
  col: number;
  group: number;
  groupIndex: number;
  expanded: boolean;
  header: boolean;
  value: any;
}

enum Types {
  String = 'string',
  Number = 'number',
  Date = 'date',
}

export class Parser implements IParser {
  public grouped: boolean = false;
  public filters: IParserFilter[] = [];
  public sorting: IParserSorting | null = null;
  public content: IPageContent = [[]];
  public groupedContent: IParserGroupedContent = [[]];
  public columnDefinitions: IParserColumnDefinitions = [];
  public visibleGroups: IParserVisibleGroups = [];
  public utils: IUtils;
  public defaultCellStyle: IPageCellStyle;
  public helpers: IHelpers;
  public groupTagged: boolean = false;
  constructor() {
    this.helpers = new Helpers();
    this.utils = new Utils();
    this.defaultCellStyle = this.utils.getDefaultCellStyle();
  }
  public toggleGroup(index: number): void {
    if (this.visibleGroups.indexOf(index) > -1) {
      this.visibleGroups.splice(this.visibleGroups.indexOf(index), 1);
    } else {
      this.visibleGroups.push(index);
    }
  }
  public parse(): IParserContent {
    if (!this.columnDefinitions.length) {
      throw new Error('No column definitions found');
    }
    let content: IParserContent = [];
    let rowIndex = 0;

    // TODO table header
    let row: IPageContentCell[] = [];
    this.columnDefinitions.forEach((definition: IParserColumnDefinition, colIndex: number) => {
      let data: any = {};
      data[definition.field] = definition.name;
      let cell: IParserCell = this.createCell({
        definition,
        data,
        col: colIndex,
        row: rowIndex,
        header: true,
        expanded: this.visibleGroups.indexOf(-1) > -1,
        group: -1,
        groupIndex: -1,
        value: definition.name,
      });

      row.push(cell);
    });
    content.push(row);
    rowIndex++;

    // sort?
    let tmpContent: IParserGroupedContent = this.applyFilters();
    if (tmpContent.length) tmpContent = this.applySorting(tmpContent);

    // table rows
    tmpContent.forEach((group, groupIndex: number) => {
      group.forEach((data: IParserData, index: number) => {
        // row
        let row: IPageContentCell[] = [];

        // check if group is expanded
        let expanded: boolean = this.groupTagged ?  this.visibleGroups.indexOf(data._group) > -1 : this.visibleGroups.indexOf(groupIndex) > -1;
        if (index > 0 && !expanded) return;
        // check if parents visible. ergh!
        if (this.groupTagged && data._group && this.visibleGroups.indexOf(data._group) > -1) {
          let indexes = data._group_index.split('-');
          indexes.pop();
          let group = '';
          let visible = true;
          indexes.forEach((index: string, i: number) => {
              group = indexes.slice(0, i + 1).join('-');
              if (this.visibleGroups.indexOf(group) < 0) visible = false;
          });
          if (!visible) return;
      }

        this.columnDefinitions.forEach((definition: IParserColumnDefinition, colIndex: number) => {
          if (this.groupTagged) {
            expanded = this.visibleGroups.indexOf(data._group_index) > -1;
          }
          let cell = this.createCell({
            data,
            definition,
            row: rowIndex,
            col: colIndex,
            group: groupIndex,
            groupIndex: index,
            expanded,
            header: false,
            value: '',
          });

          row.push(cell);
        });
        content.push(row);
        rowIndex++;
      });
    });
    // headers
    // content
    return content;
  }
  public setDefaultCellStyleProperties(style: IPageCellStyle): void {
    this.defaultCellStyle = Object.assign(this.defaultCellStyle, style);
  }
  public setColumnDefinitions(definitions: any[] = []): void {
    this.columnDefinitions = [];
    definitions.forEach(definition => {
      if (definition.disabled) return;
      this.columnDefinitions.push(definition instanceof ParserColumnDefinition ? definition : new ParserColumnDefinition(definition, this.defaultCellStyle));
    });
  }
  public headerRenderer(params: IParserParams): IParserCellRenderer | null {
    return null;
  }
  public cellRenderer(params: IParserParams): IParserCellRenderer | null {
    return null;
  }
  private createCell(params: IParserParams): IParserCell {
    let value: string | number =
      params.data[params.definition.field] === undefined ? params.definition.getDefaultValue() : params.data[params.definition.field];

    params.value = value;

    let cell: IParserCell = {
      data: params.data,
      id: params.definition.id,
      value: value,
      style: Object.assign({}, params.definition.cellStyle),
      index: {
        row: params.row,
        col: params.col,
      },
      button: undefined,
      group: params.group,
      groupIndex: params.groupIndex,
      definition: params.definition,
    };

    // updated value
    params.value = cell.value;

    // custom cell renderer
    if (params.header) {
      cell = this.mergeCells(cell, this.headerRenderer(params));
    } else if (!params.definition.ignoreCellRenderer) {
      cell = this.mergeCells(cell, this.cellRenderer(params))
    }

    // custom column renderer
    if (!params.header) {
      cell = this.mergeCells(cell, params.definition.render(params));
    }    

    // value
    cell.formatted_value = cell.value;

    return cell;
  }
  private applySorting(content: IParserGroupedContent): IParserGroupedContent {
    if (!this.sorting || !this.sorting.enabled) {
      return content;
    }
    let sorting: IParserSorting = this.sorting;
    let exp = sorting.direction.toUpperCase() === 'DESC' ? '>' : '<';
    let opp = sorting.direction.toUpperCase() === 'DESC' ? '<' : '>';
    return content.concat().sort((a, b) => {
      let aData: IParserData = a[0];
      let bData: IParserData = b[0];
      if (!sorting || !aData[sorting.field] || !bData[sorting.field]) {
        return 1;
      }
      let aValue: any = aData[sorting.field];
      let bValue: any = bData[sorting.field];
      let aVal = sorting.type === 'number' ? parseFloat(aValue) || 0 : `"${aValue}"`;
      let bVal = sorting.type === 'number' ? parseFloat(bValue) || 0 : `"${bValue}"`;

      if (eval(`${aVal} ${exp} ${bVal}`)) {
        return -1;
      }
      if (eval(`${aVal} ${opp} ${bVal}`)) {
        return 1;
      }
      return 0;
    });
  }
  private applyFilters(): IParserGroupedContent {
    // let originalData = JSON.parse(JSON.stringify(content));
    if (!this.groupedContent.length || !Object.keys(this.filters).length) {
      return this.groupedContent;
    }
    // let data = [];

    let groupsToRemove: number[] = [];
    let content: IParserGroupedContent = this.groupedContent.concat();
    // let rowOffset = this.Grid.Settings.headings ? 1 : 0;

    for (let rowIndex = 0; rowIndex < this.groupedContent.length; rowIndex++) {
      for (let f in this.filters) {
        let filter: IParserFilter = this.filters[f];
        let data: IParserData = this.groupedContent[rowIndex][0];
        // if (!col || rowsToRemove.indexOf(rowIndex) > -1) continue;
        if (data[filter.field] === undefined) {
          groupsToRemove.push(rowIndex);
          continue;
        }
        let colValue: any = filter.type === 'number' ? parseFloat(data[filter.field]) : `${data[filter.field]}`;
        let filterValue: any = filter.type === 'number' ? parseFloat(filter.value) : `${filter.value}`;

        let validValue = false;
        switch (filter.type) {
          case 'number':
            validValue = eval(`${colValue} ${filter.exp} ${filterValue}`);
            break;
          case 'date':
            let dates = filterValue.split(',');
            let fromDate = new Date(dates[0]).toISOString();
            let toDate = new Date(dates[1]).toISOString();
            let date = new Date(this.helpers.parseDateExcel(colValue)).toISOString();
            validValue = date >= fromDate && date <= toDate;
            break;
          default:
            filterValue.split(',').forEach((value: any) => {
              if (validValue) {
                return;
              }
              switch (filter.exp) {
                case 'contains':
                  validValue = colValue.toLowerCase().indexOf(value.toLowerCase()) > -1;
                  break;
                case 'starts_with':
                  validValue = colValue.toLowerCase().substring(0, value.length) === value.toLowerCase();
                  break;
                case '!=':
                  validValue = colValue != value; // eval(`"${colValue}" == "${value}"`);
                  break;
                default:
                  validValue = colValue == value; // eval(`"${colValue}" == "${value}"`);
                  break;
              }
            });
            break;
        }
        if (!validValue && groupsToRemove.indexOf(rowIndex) < 0) groupsToRemove.push(rowIndex);
        // if (!validValue) {
        //   this.content.splice(rowIndex, 1);
        // }
      }
    }
    for (let i = groupsToRemove.length - 1; i >= 0; i--) content.splice(groupsToRemove[i], 1);

    return content;
    // return data;
  }
  private mergeCells(cell: IParserCell, overides: IParserCellRenderer | null): IParserCell {
    if (!overides) return cell;
    if (overides.style) {
      cell.style = Object.assign({}, cell.style, overides.style);
      delete overides.style;
    }
    if (overides.button) {
      cell.button = Object.assign({}, cell.button, overides.button);
      delete overides.button;
    }
    return Object.assign({}, cell, overides);    
  }
}

export class ParserColumnDefinition implements IParserColumnDefinition {
  public id: boolean = false;
  public disabled: boolean = false;
  public name: string = '';
  public field: string = '';
  public type: string = '';
  public width: number = 64;
  public columnStyle: IPageCellStyle = {
    width: `64px`,
  };
  public cellStyle: IPageCellStyle = {
    width: `64px`,
  };
  public defaultValue: string | number | null = null;
  public enumValues: IParserEnumValues = {};
  public ignoreCellRenderer: boolean = false;
  constructor(data: any, styleDefaults?: IPageCellStyle) {
    if (!data) return;
    let styles: IPageCellStyle = Utils.DEFAULT_CELL_STYLE;
    if (styleDefaults) {
      styles = Object.assign({}, styles, styleDefaults);
    }
    Object.keys(styles).forEach(key => {
      (<IParserColumnDefinition>this).columnStyle[key] = styles[key];
      (<IParserColumnDefinition>this).cellStyle[key] = styles[key];
    });
    ['id', 'name', 'field', 'type', 'width', 'enumValues', 'defaultValue', 'render', 'ignoreCellRenderer', 'disabled'].forEach(key => {
      if (data[key] === undefined) return;
      (<IParserColumnDefinition>this)[key] = data[key];
    });
    if (data.columnStyle) {
      this.columnStyle = Object.assign({}, this.columnStyle, data.columnStyle);
    }
    this.columnStyle.width = `${this.width}px`;
    if (data.cellStyle) {
      this.cellStyle = Object.assign({}, this.cellStyle, data.cellStyle);
    }
    this.cellStyle.width = `${this.width}px`;
  }
  public render(params?: IParserParams): IParserCellRenderer | null {
    return null;
  }
  public getDefaultValue(): string | number {
    return this.defaultValue === null ? '#ERROR!' : this.defaultValue;
  }
}

export class ParserWrapper {
  constructor() {
    return Parser;
  }
}
