package link.at17.mid.tushare.data.util;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;

import org.apache.commons.lang3.ArrayUtils;
import org.springframework.util.Assert;

import link.at17.mid.tushare.data.models.CostDistribution;
import link.at17.mid.tushare.data.models.PivotPoint;
import link.at17.mid.tushare.data.models.StockValue;
import link.at17.mid.tushare.data.models.StockValueEx;
import link.at17.mid.tushare.data.models.CostDistribution.PercentChips;
import link.at17.mid.tushare.enums.PivotPointType;

/**
 * 指标库
 * @author Barry
 *
 */
public final class Indicators {
    
    /**
     * 计算移动平均线
     * @param values
     * @param count
     * @return
     */
    public static <T extends StockValue> List<Double> getMA(List<T> values, int count) {
        return getMA(values, v -> v.getClose(), count);
    }
    
    /**
     * 计算移动平均线
     * @param values
     * @param count
     * @return
     */
    public static <T> List<Double> getMA(List<T> values, Function<T, Double> valueFunc, int count) {
        if (Objects.isNull(values) || values.size() < count) {
            return null;
        }
        int size = values.size();
        List<Double> result = new ArrayList<>(size);
        double[] tmp = new double[size - count + 1];
        for (int i = 0; i < size; i++) {
            if (i > count - 1) {
                // sum 直接等于之前的 sum - 头天 + 当天
                double sum =
                    tmp[i - count] 
                    - valueFunc.apply(values.get(i - count))
                    + valueFunc.apply(values.get(i));
                tmp[i - count + 1] = sum;
                result.add(sum / count);
                continue;
            }
            if (i < count - 1) {
                result.add(null);
                continue;
            }
            if (i == count - 1) {
                double sum = 0;
                for (int j = 0; j < count; j++) {
                    sum += valueFunc.apply(values.get(i - j));
                }
                tmp[i - count + 1] = sum;
                result.add(sum / count);
            }
        }
        return result;
    }

    /**
     * 计算 CCI 专用 TP 移动平均线
     * @param values
     * @param count
     * @return
     */
    public static <T extends StockValue> List<Double> getTPMA(List<T> values, int count) {
        return getMA(values, v -> (v.getHigh() + v.getLow() + v.getClose()) / 3, count);
    }
    
    /**
     * 使用 StockValue 列表获取 CCI
     * @param values
     * @param count
     * @return
     */
    public static <T extends StockValue> List<Double> getCCI(List<T> values, int count) {
        if (Objects.isNull(values) || values.size() < count) {
            return null;
        }
        int size = values.size();
        List<Double> cci = new ArrayList<>(size);
        List<Double> sma = getTPMA(values, count);
        for (int i = 0; i < size; i++) {
            if (i < count - 1) {
                cci.add(null);
                continue;
            }
            StockValue sv = values.get(i);
            double tp = getTP(sv), md = 0;
            for (int j = i - count + 1; j <= i; j++) {
                md += Math.abs(getTP(values.get(j)) - sma.get(i));
            }
            md /= count;
            cci.add((tp - sma.get(i)) / md / 0.015);
        }
        return cci;
    }
    
    /** 计算 StockValue 的 高低收 平均值 **/
    private static double getTP(StockValue value) {
        return (value.getHigh() + value.getLow() + value.getClose()) / 3;
    }
    
    /**
     * 使用 StockValue 列表获取 CYW
     * @param values
     * @param count
     * @return
     */
    public static <T extends StockValue> List<Double> getCYW(List<T> values, int count) {
        if (Objects.isNull(values) || values.size() < count) {
            return null;
        }
        int size = values.size();
        List<Double> cyw = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            if (i < count - 1) {
                cyw.add(null);
                continue;
            }
            StockValue sv = values.get(i);
            double sum = 0;
            for (int j = i - count + 1; j <= i; j++) {
                sum += sv.getHigh() > sv.getLow() ? (
                    ((sv.getClose() - sv.getLow()) / (sv.getHigh() - sv.getLow()) +
                    (sv.getClose() - sv.getHigh()) / (sv.getHigh() - sv.getLow()))
                    * sv.getVol() / 10000
                ) : 0;
            }
            cyw.add(sum);
        }
        return cyw;
    }
    
    /**
     * 收盘价计算 EXPMA
     * @param values
     * @param period
     * @param multiplier
     * @return
     */
    public static <T extends StockValue> List<Double> getEXPMA(List<T> values, int period) {
        return getEXPMA(values, v -> v.getClose(), period);
    }
    
    /**
     * 计算 EXPMA(EMA)，提供非主流算法，只需提供好对应获取计算值的方法即可
     * @param <T>
     * @param values 值列表
     * @param valueFunc 获取值的 Function
     * @param period 周期
     * @return
     * 例：获取常规版 EXPMA
     * <br><code>getEXPMA(values, v -> v.getClose(), period) where values is List&ltStockValue> 
     */
    public static <T> List<Double> getEXPMA(List<T> values, Function<T, Double> valueFunc, int period) {
        if (Objects.isNull(values)) {
            return null;
        }
        int size = values.size();
        List<Double> ema = new ArrayList<>(size);
        Double k = 2 / (period + 1.0);
        ema.add(valueFunc.apply(values.get(0)));
        for (int i = 1; i < size; i++) {
            ema.add(valueFunc.apply(values.get(i)) * k + ema.get(i - 1) * (1 - k));
        }
        return ema;
    }

    /**
     * 收盘价计算 SMA
     * @param values
     * @param period
     * @param multiplier
     * @return
     */
    public static <T extends StockValue> List<Double> getSMA(List<T> values, int period, int m) {
        return getSMA(values, v -> v.getClose(), period, m);
    }
    
    /**
     * 简单移动平均线 SMA
     * @param <T>
     * @param values
     * @param valueFunc
     * @param period 周期
     * @param m 平滑系数
     * @return
     */
    public static <T> List<Double> getSMA(List<T> values, Function<T, Double> valueFunc, int period, int m) {
        Assert.isTrue(period > m, "周期必须大于平滑系数！");
        if (Objects.isNull(values)) {
            return null;
        }
        int size = values.size();
        List<Double> sma = new ArrayList<>(size);
        Double k = m * 1.0 / period;
        sma.add(valueFunc.apply(values.get(0)));
        for (int i = 1; i < size; i++) {
            sma.add(valueFunc.apply(values.get(i)) * k + sma.get(i - 1) * (1 - k));
        }
        return sma;
    }
    
    /**
     * 针对 StockValue 及其子类的动态异动平均线 DMA
     * @param <T>
     * @param values StockValue 或其子类的列表
     * @param k 权重系数
     * @return
     */
    public static <T extends StockValue> List<Double> getDMA(List<T> values, double k) {
        return getDMA(values, v -> v.getClose(), k);
    }
    
    /**
     * 动态移动平均线 DMA
     * @param <T>
     * @param values
     * @param valueFunc
     * @param k 权重系数
     * @return
     */
    public static <T> List<Double> getDMA(List<T> values, Function<T, Double> valueFunc, double k) {
        Assert.isTrue(k >= 0 && k <= 1, "权重系数须介于 [0, 1] 之间！");
        if (Objects.isNull(values) || values.size() == 0) {
            return null;
        }
        int size = values.size();
        List<Double> ema = new ArrayList<>(size);
        ema.add(valueFunc.apply(values.get(0)));
        for (int i = 1; i < size; i++) {
            ema.add(valueFunc.apply(values.get(i)) * k + ema.get(i - 1) * (1 - k));
        }
        return ema;
    }
    
    /**
     * 通达信 MCST 指标拟合版
     * @param <T>
     * @param values
     * @return
     */
    public static <T extends StockValueEx> List<Double> getMCST(List<T> values) {
        if (Objects.isNull(values) || values.size() == 0) {
            return null;
        }
        int size = values.size();
        List<Double> mcst = new ArrayList<>(size);
        T first = values.get(0);
        mcst.add((first.getHigh() + first.getLow() + first.getClose())/3);
        for (int i = 1; i < size; i++) {
            T curr = values.get(i);
            double k = curr.getVol() / curr.getFloatShare() / 100;
            // 成交额单位是千元，成交量单位是手，1000/100 = 10，所以 * 10
            mcst.add(curr.getAmount() * 10 / curr.getVol() * k + mcst.get(i - 1) * (1 - k));
        }
        return mcst;
    }
    
    /**
     * 成交量加权平均线 (VWMA)，默认采用 func = CLOSE * VOL 进行计算
     * @param <T>
     * @param values
     * @param period
     * @return
     * @see Indicators#getVWMA(List, Function, int)
     */
    public static <T extends StockValue> List<Double> getVWMA(List<T> values, int period) {
        return getVWMA(values, v -> v.getVol() * v.getClose(), period);
    }

    /**
     * 成交量加权平均线 (VWMA)，可指定 func 进行计算
     * @param <T>
     * @param values
     * @param func
     * @param period
     * @return
     */
    public static <T extends StockValue> List<Double> getVWMA(List<T> values, Function<T, Double> func, int period) {
        if (Objects.isNull(values)) {
            return null;
        }
        int size = values.size();
        List<Double> vwma = new ArrayList<>(size);
        List<Double> closeMulVol = values.stream().map(func).toList();
        List<Double> vol = values.stream().map(v -> v.getVol()).toList();
        for (int i = 0; i < size; i++) {
            vwma.add(closeMulVol.subList(Math.max(0, i - period + 1), i + 1).stream().mapToDouble(v -> v).sum()/
                    vol.subList(Math.max(0, i - period + 1), i + 1).stream().mapToDouble(v -> v).sum());
        }
        return vwma;
    }
    
    /**
     * 成交量价格确认指标 VPCI
     * @param <T>
     * @param values
     * @param m
     * @param n
     * @return
     */
    public static <T extends StockValue> List<Double> getVPCI(List<T> values, int n, int m) {
        Assert.isTrue(m < n, "m 值必须小于 n 值！");
        if (Objects.isNull(values) || values.size() == 0) {
            return null;
        }
        int size = values.size();
        /**
          VWMA:=SUM(C*V,N)/SUM(V,N);
          VPC:=VWMA - MA(C,N);
          VPR:=(SUM(C*V,M)/SUM(V,M))/MA(C,M);
          VM:=MA(V,M)/MA(V,N);
          VPCI:VPC*VPR*VM,COLORCYAN;
         */
        List<Double> vwma = getVWMA(values, n);
        List<Double> vpr = getVWMA(values, m);
        List<Double> vpcma = getMA(values, n);
        List<Double> vprma = getMA(values, m);
        List<Double> vmFast = getMA(values, m);
        List<Double> vmSlow = getMA(values, n);
        List<Double> vpci = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            Double vwmai = vwma.get(i), vpcmai = vpcma.get(i),
                   vprmai = vprma.get(i), vmFastI = vmFast.get(i),
                   vmSlowI = vmSlow.get(i);
            Double vpcI = null, vprI = null, vmI = null;
            if (vwmai != null && vpcmai != null) {
                vpcI = vwmai - vpcmai;
                if (vprmai != null) {
                    vprI = vpr.get(i) / vprmai;
                    if (vmFastI != null && vmSlowI != null) {
                        vmI = vmFastI / vmSlowI;
                        if (vmI != null) {
                            vpci.add(vpcI * vprI * vmI * 100);
                            continue;
                        }
                    }
                }
            }
            vpci.add(null);
        }
        return vpci;
    }
    
    
    /**
     * TRIX 指标
     * @param values
     * @param n
     * @return
     */
    public static <T extends StockValue> List<Double> getTRIX(List<T> values, int n) {
        List<Double> em3 = getEXPMA(getEXPMA(getEXPMA(values, v -> v.getClose(), n), v -> v, n), v -> v, n);
        if (Objects.isNull(em3)) {
            return null;
        }
        int size = values.size();
        List<Double> trix = new ArrayList<>(size);
        trix.add(0d);
        for (int i = 1; i < em3.size(); i++) {
            trix.add((em3.get(i) - em3.get(i - 1)) / em3.get(i - 1) * 100);
        }
        return trix;
    }

    /**
     * TRIX 指标
     * @param values
     * @param func
     * @param m
     * @return
     */
    public static <T extends StockValue> List<Double> getTRIX(List<T> values, Function<T, Double> func, int n) {
        List<Double> em3 = getEXPMA(getEXPMA(getEXPMA(values, func, n), v -> v, n), v -> v, n);
        if (Objects.isNull(em3)) {
            return null;
        }
        int size = values.size();
        List<Double> trix = new ArrayList<>(size);
        trix.add(0d);
        for (int i = 1; i < em3.size(); i++) {
            trix.add((em3.get(i) - em3.get(i - 1)) / em3.get(i - 1) * 100);
        }
        return trix;
    }

    /**
     * LON 指标，默认快线 10，慢线 20
     * @param values
     * @return
     */
    public static <T extends StockValue> List<Double> getLON(List<T> values) {
        return getLON(values, 10, 20);
    }
    
    /**
     * LON 指标
     * @param values
     * @return
     */
    public static <T extends StockValue> List<Double> getLON(List<T> values, int fast, int slow) {
        Assert.isTrue(fast < slow, "fast 周期必须小于 slow！");
        if (Objects.isNull(values)) {
            return null;
        }
        int size = values.size();
        List<Double> lon = new ArrayList<>(size);
        lon.add(0d);
        List<Double> rcList = new ArrayList<>();
        List<Double> longSum = new ArrayList<>();
        longSum.add(0d);
        for (int i = 1; i < values.size(); i++) {
            StockValue prev = values.get(i - 1), now = values.get(i);
            
            Double vid = (prev.getVol() + now.getVol()) / ((Math.max(prev.getHigh(), now.getHigh()) - Math.min(prev.getLow(), now.getLow())) * 100);
            rcList.add((now.getClose() - prev.getClose()) * vid);
            longSum.add(rcList.stream().reduce(Double::sum).get());
            Double dif = getSMA(longSum, v -> v, fast, 1).get(longSum.size() - 1);
            Double dea = getSMA(longSum, v -> v, slow, 1).get(longSum.size() - 1);
            lon.add(dif - dea);
        }
        return lon;
    }
    

    /**
     * 非主流 LON 指标
     * @param values
     * @return
     */
    public static <T extends StockValue> List<Double> getFZLLON(List<T> values, int fast, int slow) {
        Assert.isTrue(fast < slow, "fast 周期必须小于 slow！");
        if (Objects.isNull(values)) {
            return null;
        }
        int size = values.size();
        List<Double> lon = new ArrayList<>(size);
        lon.add(0d);
        List<Double> rcList = new ArrayList<>();
        List<Double> longSum = new ArrayList<>();
        longSum.add(0d);
        for (int i = 1; i < values.size(); i++) {
            StockValue prev = values.get(i - 1), now = values.get(i);
            PivotPoint prevPivot = new PivotPoint(prev, PivotPointType.DeMarks);
            PivotPoint nowPivot = new PivotPoint(now, PivotPointType.DeMarks);
            Double vid = (prev.getVol() + now.getVol()) / ((Math.max(prevPivot.getResistance1(), now.getHigh()) - Math.min(prevPivot.getSupport1(), now.getLow())) * 100);
            rcList.add((nowPivot.getPivotPoint() - prevPivot.getPivotPoint()) * vid);
            longSum.add(rcList.stream().reduce(Double::sum).get());
            Double dif = getSMA(longSum, v -> v, fast, 1).get(longSum.size() - 1);
            Double dea = getSMA(longSum, v -> v, slow, 1).get(longSum.size() - 1);
            lon.add(dif - dea);
        }
        return lon;
    }
    
    /**
     * 根据给定的股价列表，计算指定 index 处的成本分布
     * @param values 股价列表
     * @param index 指定的 index
     * @param factor 系数，指成本分布的最大数量，一般取 150
     * @param range 取多少个历史交易日进行计算（包括当日），一般取 120
     * @return
     */
    public static CostDistribution getCostDistribution(List<StockValueEx> values, int index, int factor, int range) {
        CostDistribution cost = new CostDistribution();
        StockValueEx current = values.get(index);
        List<StockValueEx> subValues = values.subList(Math.max(0, index - range + 1), Math.max(1, index + 1));
        Double historyMax = subValues.stream().mapToDouble(StockValue::getHigh).max().getAsDouble();
        Double historyMin = subValues.stream().mapToDouble(StockValue::getLow).min().getAsDouble();
        double normFactor = Math.max(0.01, (historyMax - historyMin) / (factor - 1));
        double[] distributionY = new double[factor];
        for (int i = 0; i < factor; i++) {
            distributionY[i] = historyMin + normFactor * i;
        }
        double[] distributionX = new double[factor];
        for (int i = 0; i < subValues.size(); i++) {
            StockValueEx stockValueEx = subValues.get(i);
            double open = stockValueEx.getOpen();
            double close = stockValueEx.getClose();
            double high = stockValueEx.getHigh();
            double low = stockValueEx.getLow();
            double ohlcAvg = (open + close + high + low) / 4;
            double turnoverRate = Math.min(1, stockValueEx.getTurnoverRate() / 100);
            int currDistUpper = (int) Math.floor((high - historyMin) / normFactor);
            int currDistLower = (int) Math.ceil((low - historyMin) / normFactor);
            double v0 = high == low ? (factor - 1) : (2 / (high - low));
            int currDistIndex = (int) Math.floor((ohlcAvg - historyMin) / normFactor);
            for (int x = 0; x < factor; x++) {
                distributionX[x] *= 1 - turnoverRate;
            }
            if (high == low) {
                // 一字板
                distributionX[currDistIndex] += v0 * turnoverRate / 2;
            }
            else {
                for (int I = currDistLower; I <= currDistUpper; I++) {
                    double b = historyMin + normFactor * I;
                    if (b <= ohlcAvg) {
                        if (Math.abs(ohlcAvg - low) < 1e-8) {
                            distributionX[I] += v0 * turnoverRate;
                        }
                        else {
                            distributionX[I] += (b - low) / (ohlcAvg - low) * v0 * turnoverRate;
                        }
                    }
                    else {
                        if (Math.abs(high - ohlcAvg) < 1e-8) {
                            distributionX[I] += v0 * turnoverRate;
                        }
                        else {
                            distributionX[I] += (high - b) / (high - ohlcAvg) * v0 * turnoverRate;
                        }
                    }
                }
            }
        }
        double w = current.getClose();
        double distributionXSum = 0;
        for (int i = 0; i < factor; i++) {
            distributionXSum += distributionX[i];
        }
        Function<Double, Double> R = (t) -> {
            Double e = 0d, i = 0d;
            for (int n = 0; n < factor; n++) {
                if (t < i + distributionX[n]) {
                    e = historyMin + n * normFactor;
                    break;
                }
                i += distributionX[n];
            }
            return e;
        };
        final double finalSum = distributionXSum;
        Function<Double, PercentChips> computePercentChips = (t1) -> {
            double e0 = (1 - t1) / 2,
                    e1 = (1 + t1) / 2,
                    upper = R.apply(finalSum * e1),
                    lower = R.apply(finalSum * e0);
            return new PercentChips()
                    .setPriceLower(lower)
                    .setPriceUpper(upper)
                    .setConcentration(Math.abs(lower + upper) < 1e-8 ? 0 : ((upper - lower)/(upper + lower)));
        };
        Function<Double, Double> getBenifitPart = (t) -> {
            double e = 0;
            for (int i = 0; i < factor; i++) {
                if (historyMin + i * normFactor <= t) {
                    e += distributionX[i];
                }
            }
            return 0 == finalSum ? 0 : (e / finalSum);
        };
        cost.setX(ArrayUtils.toObject(distributionX)).setY(ArrayUtils.toObject(distributionY))
            .setDate(current.getDate())
            .setBenifitPart(getBenifitPart.apply(w))
            .setAvgCost(R.apply(.5 * distributionXSum))
            .setPrecentChips90(computePercentChips.apply(.9))
            .setPrecentChips70(computePercentChips.apply(.7));
        return cost;
    }
    
    /**
     * 根据给定的股价列表，计算指定范围的成本分布
     * @param values 股价列表
     * @param from 起始索引，包括
     * @param to 结束索引，不包括
     * @param factor 系数，指成本分布的最大数量，一般取 150
     * @param range 取多少个历史交易日进行计算（包括当日），一般取 120
     * @return
     */
    public static List<CostDistribution> getCostDistributions(List<StockValueEx> values, int from, int to, int factor, int range) {
        List<CostDistribution> cds = new ArrayList<>();
        for (int i = from; i < to; i++) {
            cds.add(getCostDistribution(values, i, factor, range));
        }
        return cds;
    }
}
