/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { GraphConfig } from '@zeppelin/sdk';

// @ts-ignore: TODO - Should write type declaration file.
import { DataSet } from '@antv/data-set';
import { get } from 'lodash';

import { TableData } from './table-data';
import { Transformation } from './transformation';

export class PivotTransformation extends Transformation {
  constructor(config: GraphConfig) {
    super(config);
  }

  removeUnknown(array: Array<{ name: string }>, tableData: TableData): void {
    for (let i = 0; i < array.length; i++) {
      // remove non existing column
      let found = false;
      const a = array[i];
      for (const b of tableData.columns) {
        if (a.name === b) {
          found = true;
          break;
        }
      }
      if (!found) {
        array.splice(i, 1);
        i--;
      }
    }
  }

  setDefaultConfig(tableData: TableData) {
    const config = this.getConfig();
    config.keys = config.keys || [];
    config.groups = config.groups || [];
    config.values = config.values || [];
    this.removeUnknown(config.keys, tableData);
    this.removeUnknown(config.values, tableData);
    this.removeUnknown(config.groups, tableData);
    if (config.keys.length === 0 && config.groups.length === 0 && config.values.length === 0) {
      if (config.keys.length === 0 && tableData.columns[0]) {
        config.keys = [
          {
            name: tableData.columns[0],
            index: 0,
            aggr: 'sum'
          }
        ];
      }

      if (config.values.length === 0 && tableData.columns[1]) {
        config.values = [
          {
            name: tableData.columns[1],
            index: 1,
            aggr: 'sum'
          }
        ];
      }
    }
  }

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  transform(tableData: TableData): any {
    const config = this.getConfig();
    this.setDefaultConfig(tableData);
    const ds = new DataSet();
    let dv = ds.createView().source(tableData.rows);

    let firstKey = '';
    if (config.keys && config.keys[0]) {
      firstKey = config.keys[0].name;
    }
    let keys: string[];
    let groups: string[];
    let values: string[];
    let aggregates: string[];

    // set values from config
    if (config.mode !== 'scatterChart') {
      keys = config.keys.map(e => e.name);
      groups = config.groups.map(e => e.name);
      values = config.values.map(v => `${v.name}(${v.aggr})`);
      aggregates = config.values.map(v => (v.aggr === 'avg' ? 'mean' : v.aggr));
    } else {
      const xAxis = get(config.setting, 'scatterChart.xAxis.name', tableData.columns[0]);
      const yAxis = get(config.setting, 'scatterChart.yAxis.name', tableData.columns[1]);
      const group = get(config.setting, 'scatterChart.group.name');
      keys = xAxis ? [xAxis] : [];
      values = yAxis ? [yAxis] : [];
      groups = group ? [group] : [];
      aggregates = [];
    }

    // try coercion to number type
    dv.transform({
      type: 'map',
      // @ts-ignore: TODO - Should write type declaration file.
      callback: row => {
        Object.keys(row).forEach(k => {
          if (config.keys.map(e => e.name).indexOf(k) === -1) {
            const numberValue = Number.parseFloat(row[k]);
            row[k] = Number.isFinite(numberValue) ? numberValue : row[k];
          }
        });
        return row;
      }
    });

    // not applicable with type scatter chart
    if (config.mode !== 'scatterChart') {
      // aggregate values
      dv.transform({
        type: 'aggregate',
        fields: config.values.map(v => v.name),
        operations: aggregates,
        as: values,
        groupBy: [...keys, ...groups]
      });

      // fill the rows to keep the charts is continuity
      dv.transform({
        type: 'fill-rows',
        groupBy: [...keys, ...groups],
        fillBy: 'group'
      });

      /**
       * fill the field to keep the charts is continuity
       *
       * before
       * ```
       * [
       *  { x: 0, y: 1 },
       *  { x: 0, y: 2 },
       *  { x: 0, y: 3 },
       *  { x: 0 }
       * ]
       * ```
       * after
       * ```
       * [
       *  { x: 0, y: 1 },
       *  { x: 0, y: 2 },
       *  { x: 0, y: 3 },
       *  { x: 0, y: 0 }
       * //      ^^^^^ filled this
       * ]
       * ```
       */
      config.values
        .map(v => `${v.name}(${v.aggr})`)
        .forEach(field => {
          dv.transform({
            field,
            type: 'impute',
            groupBy: keys,
            method: 'value',
            value: config.mode === 'stackedAreaChart' ? 0 : null
          });
        });
    }

    dv.transform({
      type: 'fold',
      fields: values,
      key: '__key__',
      value: '__value__'
    });

    dv.transform({
      type: 'partition',
      groupBy: groups
    });

    // @ts-ignore: TODO - Should write type declaration file.
    const groupsData = [];
    Object.keys(dv.rows).forEach(groupKey => {
      const groupName = groupKey.replace(/^_/, '');
      // @ts-ignore: TODO - Should write type declaration file.
      dv.rows[groupKey].forEach(row => {
        const getKey = () => {
          if (config.mode !== 'pieChart') {
            return groupName ? `${row.__key__}.${groupName}` : row.__key__;
          } else {
            const keyName = keys.map(k => row[k]).join('.');
            return groupName ? `${keyName}.${groupName}` : keyName;
          }
        };
        groupsData.push({
          ...row,
          __key__: getKey()
        });
      });
    });

    // @ts-ignore: TODO - Should write type declaration file.
    groupsData.sort(
      (a, b) =>
        // @ts-ignore: TODO - Should write type declaration file.
        dv.origin.findIndex(o => o[firstKey] === a[firstKey]) - dv.origin.findIndex(o => o[firstKey] === b[firstKey])
    );

    // @ts-ignore: TODO - Should write type declaration file.
    console.log(groupsData);
    dv = ds
      .createView({
        state: {
          filterData: null
        }
      })
      // @ts-ignore: TODO - Should write type declaration file.
      .source(groupsData);

    if (config.mode === 'stackedAreaChart' || config.mode === 'pieChart') {
      dv.transform({
        type: 'percent',
        field: '__value__',
        dimension: '__key__',
        groupBy: keys,
        as: '__percent__'
      });
    }
    return dv;
  }
}
