<template>
  <div class="table-div">
    <el-table
      row-key="id"
      :scroll="scroll"
      :data="cData"
      :stripe="stripe"
      :border="border"
      :row-class-name="rowClassName.class || function(){return null}"
      @clearSelection="onChange"
      @toggleRowSelection="onChange"
      @toggleAllSelection="onChange"
      @toggleRowExpansion="onChange"
      @setCurrentRow="onChange"
      @clearSort="onChange"
      @clearFilter="onChange"
      @doLayout="onChange"
      @sort="onChange"
    >
      <el-table-column v-for="column in columns" 
        v-bind="column">
        <template v-if="!column.prop" #default>
          <el-button v-if="isFlatAction" 
            v-for="action in cActions" 
            v-bind="getActionProps(action)"
            @click="callback"
            >{{ action.name }}
          </el-button>

          <!-- TODO: 增加下拉选择的多按钮 -->
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>
<script>
import _ from 'lodash';
import JSONfn from '/@/utils/jsonfn';
import {
  getAction,
  postAction,
  deleteAction,
  putAction,
} from "/@/request/http";

class HmTable {
  constructor(columns, data, combined) {
    this.columns = columns;
    this.data = data;
    this.combined = combined || [];
  }

  /**
   * 自动计算合并单元格
   */
  getCombinedColumns() {
    let self = this;
    if (!self.combined || !self.combined.length) {
      return _.cloneDeep(self.columns);
    }

    // 计算要进行数据合并的范围
    let ranges = self.getCombinedRanges();
    // 对this.columns进行处理，完成合并操作
    _.each(ranges, (range) => {
      self.combineRangeOnColumns(range);
    });
    return _.cloneDeep(self.columns);
  }

  /**
   * 计算要合并的数据范围。可以是多个联通区域。
   * ["r1c1:r10c1", "r2c2:r10c2", "r1:r2", "c4"] ；用户输入的类似的数据，转换成如下格式的数据
   * [
   *   {
   *     cols: [1, 2],
   *     rows: [1, 100]
   *   }
   * ]
   */
  getCombinedRanges() {
    let self = this;
    let ranges = [];
    if (!this.combined || !this.combined.length) {
      return [];
    }

    let rowNum = this.data.length;
    let colNum = this.getColNum();

    _.each(this.combined, (item) => {
      // 计算行范围
      let rows = [];
      let rowRegex = /r\d+/g;
      let rowMatch = item.match(rowRegex);
      if (!rowMatch) {
        rows = [1, rowNum];
      } else if (rowMatch.length === 1) {
        let row = parseInt(rowMatch[0].replace("r", ""));
        rows = [row, row];
      } else if (rowMatch.length === 2) {
        let rowStart = parseInt(rowMatch[0].replace("r", ""));
        let rowEnd = parseInt(rowMatch[1].replace("r", ""));
        rows = [rowStart, rowEnd];
      } else {
        console.warn(`非法表达式：${item}`);
      }

      let cols = [];
      let colRegex = /c\d+/g;
      let colMatch = item.match(colRegex);
      if (!colMatch) {
        cols = [1, colNum];
      } else if (colMatch.length === 1) {
        let col = parseInt(colMatch[0].replace("c", ""));
        cols = [col, col];
      } else if (colMatch.length === 2) {
        let colStart = parseInt(colMatch[0].replace("c", ""));
        let colEnd = parseInt(colMatch[1].replace("c", ""));
        cols = [colStart, colEnd];
      } else {
        console.warn(`非法表达式：${item}`);
      }

      let subRanges = self.getSameValueSubMatrix({ rows: rows, cols: cols });
      ranges = _.concat(ranges, subRanges);
    });

    return ranges;
  }

  getColNum() {
    return this.getFlattenedColumns().length;
  }

  getFlattenedColumns(columns) {
    let self = this;
    if (typeof columns === "undefined") {
      columns = this.columns;
    }
    let flattenedColumns = [];

    _.each(columns, (column) => {
      if (typeof column.dataIndex !== "undefined") {
        flattenedColumns.push(column);
        return;
      }
      if (column.children && column.children.length > 0) {
        flattenedColumns = _.concat(
          flattenedColumns,
          self.getFlattenedColumns(column.children)
        );
      }
    });
    return flattenedColumns;
  }

  /**
   * 计算矩阵内值相同的子矩阵
   */
  getSameValueSubMatrix(range) {
    let self = this;
    let subRanges = [];
    let values = self.getDistinctValues(range);
    _.each(values, (value) => {
      subRanges = _.concat(subRanges, self.getSubRangesOfValue(range, value));
    });
    return subRanges;
  }

  /**
   * 计算一个子矩阵内的所有可能值
   */
  getDistinctValues(range) {
    let values = [];

    for (let r = range.rows[0]; r <= range.rows[1]; r++) {
      for (let c = range.cols[0]; c <= range.cols[1]; c++) {
        let value = this.getValueByIndex(r, c);
        if (values.indexOf(value) < 0) {
          values.push(value);
        }
      }
    }
    return values;
  }

  /**
   * 计算子矩阵中值为value的所有最大子矩阵
   */
  getSubRangesOfValue(range, value) {
    let self = this;
    let subRanges = [];
    for (let r_s = range.rows[0]; r_s <= range.rows[1]; r_s++) {
      for (let c_s = range.cols[0]; c_s <= range.cols[1]; c_s++) {
        // 如果左上点的值不等于value则跳过
        if (self.getValueByIndex(r_s, c_s) !== value) {
          continue;
        }

        let maxCount = 1;
        let endPoint = [];
        for (let r_e = r_s; r_e <= range.rows[1]; r_e++) {
          for (let c_e = c_s; c_e <= range.cols[1]; c_e++) {
            // 如果右下点的值不等于value则跳过
            if (self.getValueByIndex(r_e, c_e) !== value) {
              continue;
            }

            // 如果当前区域跟subRanges有交集，那么跳过
            if (
              self.hasIntersectionWithRanges(
                { rows: [r_s, r_e], cols: [c_s, c_e] },
                subRanges
              )
            ) {
              continue;
            }

            // 计算左上和右下点之间的所有为value的值的点数
            let sum = self.countMatchedValue(
              {
                rows: [r_s, r_e],
                cols: [c_s, c_e],
              },
              value
            );

            let shouldBeSum = (r_e - r_s + 1) * (c_e - c_s + 1);
            if (shouldBeSum === sum && sum > maxCount) {
              maxCount = sum;
              endPoint = [r_e, c_e];
            }
          }
        }

        if (endPoint.length) {
          subRanges.push({
            rows: [r_s, endPoint[0]],
            cols: [c_s, endPoint[1]],
          });
        }
      }
    }
    return subRanges;
  }

  hasIntersection(range1, range2) {
    return (
      Math.max(range1.rows[0], range2.rows[0]) <=
      Math.min(range1.rows[1], range2.rows[1]) &&
      Math.max(range1.cols[0], range2.cols[0]) <=
      Math.min(range1.cols[1], range2.cols[1])
    );
  }

  countMatchedValue(range, value) {
    let count = 0;
    for (let r = range.rows[0]; r <= range.rows[1]; r++) {
      for (let c = range.cols[0]; c <= range.cols[1]; c++) {
        if (this.getValueByIndex(r, c) === value) {
          count++;
        }
      }
    }
    return count;
  }

  /**
   * 计算range和ranges是否有交集
   * @param {*} range
   * @param {*} ranges
   */
  hasIntersectionWithRanges(range, ranges) {
    let self = this;
    return _.reduce(
      ranges,
      (result, value, key) => {
        return result || self.hasIntersection(range, value);
      },
      false
    );
  }

  /**
   * 根据行列标号，取到对应单元格的值
   */
  getValueByIndex(row, col) {
    let flattenedColumns = this.getFlattenedColumns();
    let colIndex = flattenedColumns[col - 1].dataIndex;
    return this.data[row - 1][colIndex];
  }

  getColumnByIndex(col) {
    let flattenedColumns = this.getFlattenedColumns();
    return flattenedColumns[col - 1];
  }

  getColumnsByColRange(colRange) {
    let columns = [];
    for (let c = colRange[0]; c <= colRange[1]; c++) {
      columns.push(this.getColumnByIndex(c));
    }
    return columns;
  }

  /**
   * 合并联通的区域
   */
  combineRangeOnColumns(range) {
    if (
      !range ||
      !range.rows ||
      range.rows.length !== 2 ||
      !range.cols ||
      range.cols.length !== 2
    ) {
      return this.columns;
    }

    let relatedColumns = this.getColumnsByColRange(range.cols);

    for (let i = 0; i < relatedColumns.length; i++) {
      if (i === 0) {
        relatedColumns[i].customRender = ({ text, index }) => {
          const obj = {
            children: text,
            props: {},
          };

          if (index === range.rows[0] - 1) {
            obj.props.rowSpan = range.rows[1] - range.rows[0] + 1;
            obj.props.colSpan = range.cols[1] - range.cols[0] + 1;
          } else if (index > range.rows[0] - 1 && index < range.rows[1]) {
            obj.props.rowSpan = 0;
            obj.props.colSpan = 0;
          }

          return obj;
        };
      } else {
        relatedColumns[i].customRender = ({ text, index }) => {
          const obj = {
            children: text,
            props: {},
          };

          if (index >= range.rows[0] - 1 && index < range.rows[1]) {
            obj.props.rowSpan = 0;
            obj.props.colSpan = 0;
          }

          return obj;
        };
      }
    }

    return this.columns;
  }
}

export default {
  name: "HmElementTable",
  components: {},
  props: {
    /**
     * 列定义
     */
    columns: {
      type: Array,
      default: function () {
        return [
          {
            label: "姓名",
            prop: "name",
            columnKey: "name",
            fixed: true,
            width: 100
          },
          {
            label: "年龄",
            prop: "age",
            columnKey: "age",
            width: 80,
          },
          {
            label: "地址",
            prop: "address",
            columnKey: "address",
            ellipsis: true,
          },
          {
            label: "性别",
            prop: "sexual",
            columnKey: "sexual",
          },
          {
            label: "毕业院校",
            prop: "school",
            columnKey: "school",
          },
          {
            label: "操作",
            prop: "",
          },
        ];
      },
    },
    /**
     * 数据
     */
    data: {
      type: Array,
      default: function () {
        return [
          {
            key: "1",
            name: "John Brown",
            age: 32,
            address: "New York No. 1 Lake Park, New York No. 1 Lake Park",
            sexual: "男",
            school: "加里敦大学",
          },
          {
            key: "2",
            name: "Jim Green",
            age: 42,
            address: "London No. 2 Lake Park, London No. 2 Lake Park",
            sexual: "男",
            school: "加里敦大学",
          },
          {
            key: "3",
            name: "Joe Black",
            age: 32,
            address: "Sidney No. 1 Lake Park, Sidney No. 1 Lake Park",
            sexual: "男",
            school: "加里敦大学",
          },
        ];
      },
    },
    /**
     * 是否为斑马纹
     */
    stripe: {
      type: Boolean,
      default: false
    },
    /**
     * 是否纵向边框
     */
    border: {
      type: Boolean,
      default: false
    },
    /**
     * 隐藏翻页控件
     */
    paginationHidden: {
      type: Boolean,
      default: false
    },
    /**
     * 翻页参数
     */
    pagination: {
      type: Object,
      default: function () {
        return {
          current: 1,
          pageSize: 10,
        };
      },
    },
    /**
     * 显示序号列
     */
    showColumnNo: {
      type: Boolean,
      default: false
    },
    /**
     * GET URL
     */
    url: {
      type: String,
    },
    /**
     * GET Params
     */
    params: {
      type: Object,
    },
    /**
     * 可选择行
     */
    rowSelectFlag: {
      type: Boolean,
      default: false,
    },
    /**
     * 带边框
     */
    bordered: {
      type: Boolean,
      default: true,
    },
    /**
     * 操作按钮
     */
    actions: {
      type: Array,
      default: function () {
        return [
          {
            name: "编辑",
            callback: function (item) {
              console.log("点击编辑: ", item);
            },
          },
          {
            name: "删除",
            callback: function (item) {
              console.log("点击删除: ", item);
            },
          },
        ];
      },
    },
    /**
     * 操作按钮展开
     */
    isFlatAction: {
      type: Boolean,
      default: false,
    },
    /**
     * 固定行列
     */
    scroll: {
      type: Object,
      default: function () {
        return {
          x: "100%",
        };
      },
    },
    /**
     * 自动合并单元
     * @description 数据相同的内容自动合并. 形如 ["r1c1:r10c1", "r2c2:r10c2", "r1:r2", "c4"]，这里的r1c1表示的是第1行（row 1），第1列（column 1）。第1列不包含表格，指的是数据行。
     */
    combined: {
      type: Array,
      default: [],
    },
    /**
     * 清空过滤排序
     */
    clearFiltersAndSortersFlag: {
      type: Boolean,
      default: false
    },
    /**
     * 行高
     */
    rowHeight: {
      type: String,
      default: '42'
    },
    /**
     * 表格背景颜色
     * @type Color
     * */
    backgroundColor: {
      type: String,
      default: ""
    },
    /**
     * 行类名
     */
    rowClassName: {
      type: Object,
      default: function() {
        return {
          class: null
        }
      }
    }
  },
  data() {
    return {
      hmTable: new HmTable(),
      cData: [],
      cColumns: [],
      cActions: [],
      cRowHeight: '42px',
      rowSelection: {},
      cPagination: {
        current: 1,
        pageSize: 10,
      },
      sorter: {},
      filters: {},
      cBackgroundColor: ''
    };
  },
  watch: {
    combined: {
      handler: function (value, oldValue) {
        console.log('watch combined');
        this.hmTable.combined = value;
        this.cColumns = this.hmTable.getCombinedColumns();
        if (this.showColumnNo) {
          this.cColumns.unshift({
            title: "序号",
            dataIndex: "hmNo",
            key: "hmNo",
            width: 60
          })
        }
      },
      deep: true
    },
    clearFiltersAndSortersFlag(value, oldValue) {
      console.log(`清除过滤和排序：`, value, oldValue);
      if (!oldValue && value) {
        this.filters = {};
        this.sorter = {};
        this.getData();
      }
    },
    columns: {
      handler: function (value, oldValue) {
        console.log('watch columns');
        this.hmTable.columns = value;
        this.cColumns = this.hmTable.getCombinedColumns();
        if (this.showColumnNo) {
          this.cColumns.unshift({
            title: "序号",
            dataIndex: "hmNo",
            key: "hmNo",
            width: 60
          })
        }
      },
      deep: true
    },
    data: {
      handler: function (value, oldValue) {
        console.log('watch data');
        this.cData = JSONfn.parse(JSONfn.stringify(value));

        if (this.showColumnNo) {
          _.each(self.cData, (item, index) => {
            item.hmNo = index + 1;
          })
        }

        this.hmTable.data = this.cData;
      },
      deep: true
    },
    actions: {
      handler: function (value, oldValue) {
        console.log('watch actions');
        this.cActions = this.convertActions(value);
      },
      deep: true
    },
    url(value) {
      this.getData(value);
    },
    params: {
      handler: function (value, oldValue) {
        if (JSON.stringify(value) == JSON.stringify(oldValue)) {
          // 修复相等的情况下也调用watch的bug
          return;
        }
        console.log('watch params');
        this.getData(null, value);
      },
      deep: true
    },
    pagination: {
      handler(value) {
        if (Object.keys(value).length === 0) {
          this.cPagination = false;
        } else {
          this.cPagination = Object.assign(this.cPagination, this.pagination);
        }
        console.log(`table watch pagination: `, this.cPagination, this.pagination);
        // this.getData();
      },
      deep: true
    },
    rowHeight(value) {
      this.cRowHeight = this.getCssUnit(value);
    },
    backgroundColor(value) {
      this.cBackgroundColor = value;
    }
  },
  computed: {
  },
  mounted() {
    console.log('mounted');
    // this.cBackgroundColor = this.backgroundColor;
    // this.cRowHeight = this.getCssUnit(this.rowHeight);
    // this.hmTable = new HmTable(this.columns, this.data, this.combined);
    // this.cColumns = this.hmTable.getCombinedColumns();

    // this.cActions = this.convertActions(this.actions);
    // this.cData = JSONfn.parse(JSONfn.stringify(this.data));

    // if (this.showColumnNo) {
    //   this.cColumns.unshift({
    //     title: "序号",
    //     dataIndex: "hmNo",
    //     key: "hmNo",
    //     width: 60
    //   })
    //   _.each(this.cData, (item, index) => {
    //     item.hmNo = index + 1;
    //   })
    // }

    // if (Object.keys(this.pagination).length === 0) {
    //   this.cPagination = false;
    // } else {
    //   this.cPagination = Object.assign(this.cPagination, this.pagination);
    // }

    // console.log(`table mounted: `, this.cPagination, this.pagination);
    // this.getData();

    // this.rowSelection = {
    //   onChange: this.onSelectChange,
    //   onSelect: this.onSelect,
    //   onSelectAll: this.onSelectAll,
    // };
  },
  methods: {
    getData(url, params) {
      let self = this;
      self._getData(url, params);
    },
    _getData(url, params) {
      let self = this;
      url = url || this.url;
      params = params || (this.params ? JSON.parse(JSON.stringify(this.params)) : {});

      // 跟表格自带的翻页过滤排序功能结合
      // 翻页
      if (typeof this.cPagination !== 'boolean') {
        params = Object.assign(params, {
          pageNo: this.cPagination.current,
          pageSize: this.cPagination.pageSize,
        });
      }


      // 排序
      if (this.sorter && this.sorter.field) {
        params.column = this.sorter.field;
        params.order = this.sorter.order === "descend" ? "desc" : "asc";
      }

      // 过滤
      if (this.filters) {
        params = Object.assign(params, this.filters);
      }

      getAction(url, params).then((resp) => {
        console.log(`get table data: `, resp);
        self.cData = [];
        setTimeout(() => {
          if (resp.result) {
            self.cData = resp.result.records || resp.result;
            if (typeof self.cPagination !== "boolean") {
              self.cPagination.total = resp.result.total || self.cPagination.pageSize;
            }
          }
          if (resp.data) {
            self.cData = resp.data;
            if (typeof self.cPagination !== "boolean") {
              self.cPagination.total = resp.total || self.cPagination.pageSize;
            }
          };
          _.each(self.cData, (item, index) => {
            item.hmNo = index + 1;
          })
          self.hmTable.data = self.cData;
        }, 0)
        // self.cColumns = self.hmTable.getCombinedColumns();
      });
    },
    convertActions(actions) {
      let ret = [];
      _.each(actions, (action) => {
        if (
          action.callback &&
          typeof action.callback === "string" &&
          action.callback.indexOf("function") === 0
        ) {
          try {
            action.callback = eval(`(${action.callback})`);
          } catch (error) {
            console.error('action.callback error: ', action);
          }
        }
        ret.push(action);
      });
      console.log(`cActions: `, ret);
      return ret;
    },
    onSelectChange(selectedRowKeys, selectedRows) {
      console.log(
        `onSelectChange: selectedRowKeys: ${selectedRowKeys}`,
        "selectedRows: ",
        selectedRows
      );
      this.$emit("onSelectChange", { selectedRowKeys, selectedRows });
    },
    onSelect(record, selected, selectedRows) {
      console.log(
        `onSelect: record: ${record}, selected: ${selected}, selectedRows: ${selectedRows}`
      );
      this.$emit("onSelect", { record, selected, selectedRows });
    },
    onSelectAll(selected, selectedRows, changeRows) {
      console.log(
        `onSelectAll: selected: ${selected}, selectedRows: ${selectedRows}, changeRows: ${changeRows}`
      );
      this.$emit("onSelectAll", { selected, selectedRows, changeRows });
    },
    onChange(pagination, filters, sorter, { currentDataSource }) {
      console.log(
        `pagination: `,
        pagination,
        "filters: ",
        filters,
        "sorter: ",
        sorter,
        "currentDataSource: ",
        currentDataSource,
        new Date().getTime()
      );
      if (Object.keys(this.pagination).length === 0) {
        this.cPagination = false
      } else {
        this.cPagination = Object.assign(this.cPagination, pagination);
      }
      this.sorter = sorter;
      this.filters = filters;
      this.getData();
      this.$emit("change", pagination, filters, sorter, { currentDataSource });
    },

    /**
     * 字段过滤项, input类型
     */
    handleFilterInput(selectedKeys, confirm, column) {
      console.log(`handleFilterInput: `, selectedKeys, confirm, column);
      if (!selectedKeys || !selectedKeys.length || selectedKeys[0] === null) {
        this.getData();
        return;
      }
      this.filters[column.dataIndex] = `*${selectedKeys[0]}*`;
      this.getData();
    },

    /**
     * 字段过滤项, Date类型
     */
    handleFilterDate(selectedKeys, confirm, column) {
      console.log(`handleFilterDate: `, selectedKeys, confirm, column);
      if (!selectedKeys || !selectedKeys.length || (!selectedKeys[0] && !selectedKeys[1])) {
        this.getData();
        return;
      }

      // 时间范围
      let format = 'YYYY-MM-DD';
      if (selectedKeys[0]) {
        this.filters[`${column.dataIndex}_begin`] = selectedKeys[0].format(format);
      } else {
        delete this.filters[`${column.dataIndex}_begin`];
      }

      if (selectedKeys[1]) {
        this.filters[`${column.dataIndex}_end`] = selectedKeys[1].format(format);
      } else {
        delete this.filters[`${column.dataIndex}_end`];
      }

      this.getData();
    },

    /**
     * 字段过滤项, DateTime类型
     */
    handleFilterDateTime(selectedKeys, confirm, column) {
      console.log(`handleFilterDateTime: `, selectedKeys, confirm, column);
      if (!selectedKeys.length || (!selectedKeys[0] && !selectedKeys[2])) {
        this.getData();
        return;
      }

      // 时间范围
      let dateFormat = 'YYYY-MM-DD';
      let timeFormat = 'HH:mm:ss';
      if (selectedKeys[0]) {
        this.filters[`${column.dataIndex}_begin`] = `${selectedKeys[0].format(dateFormat)} ${selectedKeys[1] ? selectedKeys[1].format(timeFormat) : '00:00:00'}`;
      } else {
        delete this.filters[`${column.dataIndex}_begin`];
      }

      if (selectedKeys[2]) {
        this.filters[`${column.dataIndex}_end`] = `${selectedKeys[2].format(dateFormat)} ${selectedKeys[3] ? selectedKeys[3].format(timeFormat) : '23:59:59'}`;
      } else {
        delete this.filters[`${column.dataIndex}_end`];
      }

      this.getData();
    },

    /**
     * 重置过滤项
     */
    handleReset(clearFilters, column) {
      console.log(`handleReset: `, clearFilters, column);
      clearFilters();
      delete this.filters[column.dataIndex]
      this.getData();
    },
    getCssUnit(value) {
      if (isNaN(Number(value))) {
        return value;
      }
      return `${value}px`;
    },
    getActionProps(action) {
      let actionProps = _.cloneDeep(action);
      delete actionProps.name;
      delete actionProps.callback;
      return actionProps;
    }
  },
};
</script>
<style scoped>
.table-div {
  position: relative;
  display: flex;
  align-items: center;
  min-width: 300px;
  width: 100%;
}

.table-div /deep/ .ant-table-wrapper {
  width: 100% !important;
}

.table-div /deep/ .filter-dropdown-date .ant-calendar-picker {
  width: 188px;
}

.table-div /deep/ .filter-dropdown-datetime .ant-calendar-picker {
  width: 130px;
}

.table-div /deep/ .filter-dropdown-datetime .ant-time-picker {
  width: 130px;
}

.fa.fa-angle-down {
  margin-left: 5px;
  font-size: 16px;
}

.action-btn,
.action-icon {
  margin: 5px;
}

.action-icon:hover {
  cursor: pointer;
}

.table-div /deep/ .ant-table-thead > tr > th,
.table-div /deep/ .ant-table-tbody > tr > td {
  height: v-bind(cRowHeight);
  padding: 0;
}

.table-div /deep/ .ant-table-thead > tr > th > span,
.table-div /deep/ .ant-table-tbody > tr > td {
  padding-left: 16px;
}

.table-div >>> div {
  background-color: v-bind(cBackgroundColor);
}
.table-div >>> .ant-table-thead > tr > th {
  background-color: v-bind(cBackgroundColor);
}
.table-div >>> .ant-pagination-prev > a {
  background-color: v-bind(cBackgroundColor);
}
.table-div >>> .ant-pagination-item {
  background-color: v-bind(cBackgroundColor);
}
.table-div >>> .ant-pagination-next > a {
  background-color: v-bind(cBackgroundColor);
}
</style>
