<template>
  <echart-base :options="[options]" :empty="isEmpty" v-model:loading="loading"></echart-base>
</template>

<script lang="ts">
import { defineComponent, PropType, ref, watch, watchEffect } from 'vue';
import { QueryHistoryDatasRsp, QueryQuoteDayRsp, QueryHistoryDatas, CycleType } from '@/services/go/quote/interface';
import { QueryHistoryDatas as queryHistoryDatas } from '@/services/go/quote';
import { _debounce } from '@/utils/time';
import EchartBase from '../echart-base/index.vue';
import { handleEchart, Source } from './setup';
import moment from 'moment';

export default defineComponent({
  name: 'EchartKline',
  components: {
    EchartBase,
  },
  props: {
    // 实时行情数据
    quoteData: {
      type: Object as PropType<QueryQuoteDayRsp>,
      required: true,
    },
    // 周期类型
    cycleType: {
      type: Number as PropType<CycleType>,
      required: true,
    },
    // 指标类型
    seriesType: {
      type: String,
      default: 'MACD',
    },
  },
  setup(props) {
    const loading = ref(true);
    const isEmpty = ref(false);
    const historyIndexs: number[] = []; // 行情历史数据中所有非补充数据的索引位置(用于计算均线)
    const { chartData, options, updateOptions, initOptions } = handleEchart();

    // 处理图表数据
    const handleData = (rawData: QueryHistoryDatasRsp[]): void => {
      const { source } = chartData.value;
      source.length = 0;
      historyIndexs.length = 0;

      rawData.forEach((item, index) => {
        const { o, c, h, l, ts, tv } = item;
        source.push({
          date: moment(ts).format('YYYY-MM-DD HH:mm:ss'),
          open: o,
          close: c,
          lowest: l,
          highest: h,
          ma5: '-',
          ma10: '-',
          ma15: '-',
          vol: tv,
          macd: '-',
          dif: '-',
          dea: '-',
          k: '-',
          d: '-',
          j: '-',
          cci: '-',
        });
        if (!item.f) historyIndexs.push(index); // 排除补充数据
      });

      calcMA('ma5', 5);
      calcMA('ma10', 10);
      calcMA('ma15', 15);
      calcMACD();
      calcKDJ();
      clacCCI();
    };

    // 计算平均线
    const calcMA = (key: keyof Source, count: number) => {
      const { source } = chartData.value;
      let result: Source[keyof Source] = '-';

      if (source.length >= count) {
        // 均线起始位置
        const startIndex = historyIndexs[count - 1];
        for (let i = 0; i < source.length; i++) {
          if (startIndex !== undefined && i > startIndex) {
            const j = historyIndexs.findIndex((val) => val === i);
            // 判断是否补充数据
            if (j === -1) {
              result = source[i - 1][key]; // 取上个平均值
            } else {
              // 向后取MA数
              const maIndexs = historyIndexs.slice(j - (count - 1), j + 1);
              // 计算总价
              const total = maIndexs.reduce((sum, val) => sum + source[val].close, 0);
              // 计算均线
              result = (total / count).toFixed(2);
            }
          }
          (<typeof result>source[i][key]) = result;
        }
      }
    };

    // 计算EMA
    const calcEMA = (close: number[], n: number) => {
      const ema: number[] = [],
        a = 2 / (n + 1); // 平滑系数
      for (let i = 0; i < close.length; i++) {
        if (i === 0) {
          //第一个EMA(n)是前n个收盘价代数平均
          const result = close.slice(0, n).reduce((sum, val) => sum + val, 0) / n;
          ema.push(result);
        } else {
          // EMA(n) = α × Close + (1 - α) × EMA(n - 1)
          const result = a * close[i] + (1 - a) * ema[i - 1];
          ema.push(result);
        }
      }
      return ema;
    };

    // 计算DEA
    const calcDEA = (dif: number[]) => {
      return calcEMA(dif, 9);
    };

    // 计算DIF
    const calcDIF = (close: number[]) => {
      const dif: number[] = [],
        emaShort = calcEMA(close, 12),
        emaLong = calcEMA(close, 26);

      for (let i = 0; i < close.length; i++) {
        const result = emaShort[i] - emaLong[i];
        dif.push(result);
      }
      return dif;
    };

    // 计算MACD
    const calcMACD = () => {
      const { source } = chartData.value,
        close = source.map((item) => item.close),
        dif = calcDIF(close),
        dea = calcDEA(dif);

      for (let i = 0; i < source.length; i++) {
        source[i].dif = dif[i].toFixed(2);
        source[i].dea = dea[i].toFixed(2);
        source[i].macd = ((dif[i] - dea[i]) * 2).toFixed(2);
      }
    };

    // 计算KDJ
    const calcKDJ = () => {
      const { source } = chartData.value;
      for (let i = 0; i < source.length; i++) {
        const item = source[i];
        if (i < 8) {
          item.k = '-';
          item.d = '-';
          item.j = '-';
        } else {
          let rsv = 50; // 如果最低价等于最高价，RSV默认值为50
          if (item.lowest !== item.highest) {
            const n9 = source.slice(i - 8, i + 1).map((item) => item.close), // 取前9个收盘价
              max = Math.max(...n9),
              min = Math.min(...n9);
            // 计算RSV
            rsv = ((item.close - min) / (max - min)) * 100;
          }

          const yestK = Number(source[i - 1].k); // 取前一日K值
          const yestD = Number(source[i - 1].d); // 取前一日D值

          if (isNaN(yestK) || isNaN(yestD)) {
            // 如果前一日的K值或D值不存在则默认值为50
            item.k = '50';
            item.d = '50';
            item.j = '50';
          } else {
            const k = (2 / 3) * yestK + (1 / 3) * rsv,
              d = (2 / 3) * yestD + (1 / 3) * yestK,
              j = 3 * k - 2 * d;

            item.k = k.toFixed(2);
            item.d = d.toFixed(2);
            item.j = j.toFixed(2);
          }
        }
      }
    };

    // 计算CCI
    const clacCCI = () => {
      const { source } = chartData.value;
      for (let i = 0; i < source.length; i++) {
        const item = source[i];
        if (i < 13) {
          item.cci = '-';
        } else {
          const tp = (item.close + item.lowest + item.highest) / 3, // (收盘价 + 最低价 + 最高价) / 3
            n14 = source.slice(i - 13, i + 1), // 取前14条数据
            ma = n14.reduce((sum, e) => sum + (e.close + e.lowest + e.highest) / 3, 0) / 14, // 计算前14条数据的(TP)价总和÷N
            md = n14.reduce((sum, e) => sum + Math.abs(ma - (e.close + e.lowest + e.highest) / 3), 0) / 14, // 计算前14条数据的(MA-TP)价总和÷N
            result = (tp - ma) / md / 0.015;

          item.cci = result.toFixed(2);
        }
      }
    };

    // 更新图表K线数据
    const updateChartData = () => {
      const { source } = chartData.value,
        lastIndex = source.length - 1, // 历史行情最后索引位置
        lastTime = moment(source[lastIndex].date), // 历史行情最后时间
        newTime = moment(props.quoteData.lasttime), // 实时行情最新时间
        newPrice = props.quoteData.last; // 实时行情最新价

      let cycleMilliseconds = 60 * 1000; // 周期毫秒数

      switch (props.cycleType) {
        case CycleType.minutes5:
          cycleMilliseconds *= 5;
          break;
        case CycleType.minutes30:
          cycleMilliseconds *= 30;
          break;
        case CycleType.minutes60:
          cycleMilliseconds *= 60;
          break;
        case CycleType.hours2:
          cycleMilliseconds *= 2 * 60;
          break;
        case CycleType.Hours4:
          cycleMilliseconds *= 4 * 60;
          break;
        case CycleType.days:
          cycleMilliseconds *= 24 * 60;
          break;
      }

      const diffTime = newTime.valueOf() - lastTime.valueOf(); // 计算时间差

      if (diffTime > cycleMilliseconds * 2) {
        // 时间间隔超过两个周期，重新请求历史数据
      } else {
        // 判断时间差是否大于周期时间
        if (diffTime > cycleMilliseconds) {
          lastTime.add(cycleMilliseconds, 'ms');
          // 添加历史行情
          source.push({
            date: lastTime.format('YYYY-MM-DD HH:mm:ss'),
            open: newPrice,
            close: newPrice,
            lowest: newPrice,
            highest: newPrice,
            ma5: '-',
            ma10: '-',
            ma15: '-',
            vol: 0,
            macd: '-',
            dif: '-',
            dea: '-',
            k: '-',
            d: '-',
            j: '-',
            cci: '-',
          });
          historyIndexs.push(lastIndex + 1); // 添加历史行情索引
        } else {
          const lastData = source[lastIndex];
          if (lastData.lowest > newPrice) {
            lastData.lowest = newPrice; //更新最低价
          }
          if (lastData.highest < newPrice) {
            lastData.highest = newPrice; //更新最高价
          }
          lastData.close = newPrice; //更新收盘价
        }

        calcMA('ma5', 5);
        calcMA('ma10', 10);
        calcMA('ma15', 15);
        calcMACD();
        calcKDJ();
        clacCCI();

        // 延迟图表更新，减少卡顿
        _debounce(() => {
          updateOptions(props.seriesType);
        }, 1000);
      }
    };

    // 监听行情最新价推送
    watch(
      () => props.quoteData.last,
      () => {
        if (!loading.value) {
          updateChartData();
        }
      }
    );

    // 监听指标类型
    watch(
      () => props.seriesType,
      (val) => {
        if (!loading.value) {
          updateOptions(val);
        }
      }
    );

    // 监听周期选择变化
    watchEffect(() => {
      loading.value = true;
      const params: QueryHistoryDatas = {
        cycleType: props.cycleType,
        goodsCode: props.quoteData.goodscode.toUpperCase(),
        count: 1440,
      };
      // 查询K线数据
      queryHistoryDatas(params)
        .then((res) => {
          if (res.length) {
            isEmpty.value = false;
            // 日期升序排序
            const kdata = res.sort((a, b) => moment(a.ts).valueOf() - moment(b.ts).valueOf());
            handleData(kdata);
          } else {
            isEmpty.value = true;
          }
          initOptions(props.seriesType);
        })
        .catch(() => {
          isEmpty.value = true;
        })
        .finally(() => {
          loading.value = false;
        });
    });

    return {
      loading,
      isEmpty,
      options,
    };
  },
});
</script>