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

import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import link.at17.mid.tushare.data.models.IndexValue;
import link.at17.mid.tushare.data.models.StockValue;
import link.at17.mid.tushare.enums.CrossType;

import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.TreeSet;
import java.util.stream.Collectors;

public class PointsUtil {

    /**
     * 获取 StockValue 极大值所在的位置索引通用方法，默认迭代次数：1
     * @param list
     * @return
     */
    public static List<Integer> getStockValueHighestIndex(List<StockValue> list) {
        return getStockValueHighestIndex(list, 1);
    }

    /**
     * 获取 StockValue 极小值所在的位置索引通用方法，默认迭代次数：1
     * @param list
     * @return
     */
    public static List<Integer> getStockLowestIndex(List<StockValue> list) {
        return getValueLowestIndex(list, 1);
    }
    
    /**
     * 获取 StockValue 极大值所在的位置索引通用方法
     * @param list
     * @param generation 迭代次数
     * @return
     */
    public static List<Integer> getStockValueHighestIndex(List<StockValue> list, int generation) {
        if (CollectionUtils.isEmpty(list) || list.size() < 2) {
            return new ArrayList<Integer>();
        }
        List<Integer> indexList = new ArrayList<>();
        List<StockValue> subList = new ArrayList<>();
        for (int i = 0; i < list.size(); i++) {
            StockValue val = list.get(i);
            if (i == 0) {
                if (val.getHigh().compareTo(list.get(1).getHigh()) == 1) {
                    indexList.add(i);
                    if (generation > 0) {
                        subList.add(val);
                    }
                }
                continue;
            }
            if (i != list.size() - 1 && 
                val.getHigh().compareTo(list.get(i - 1).getHigh()) == 1 &&
                val.getHigh().compareTo(list.get(i + 1).getHigh()) == 1) {
                indexList.add(i);
                if (generation > 0) {
                    subList.add(val);
                }
                continue;
            }
            if (i == list.size() - 1 && 
                val.getHigh().compareTo(list.get(i - 1).getHigh()) == 1) {
                indexList.add(i);
                if (generation > 0) {
                    subList.add(val);
                }
            }
        }
        if (generation-- > 0) {
            List<Integer> subGeneration = getStockValueHighestIndex(subList, generation);
            List<Integer> subResult = new ArrayList<Integer>();
            for (Integer subLowest : subGeneration) {
                subResult.add(indexList.get(subLowest));
            }
            indexList = subResult;
        }
        
        return indexList;
    }

    /**
     * 获取 StockValue 极小值所在的位置索引通用方法
     * @param list
     * @param generation 迭代次数
     * @param compareTo -1 或 1
     * @return
     */
    public static List<Integer> getValueLowestIndex(List<StockValue> list, int generation) {
        if (CollectionUtils.isEmpty(list) || list.size() < 2) {
            return new ArrayList<Integer>();
        }
        List<Integer> indexList = new ArrayList<>();
        List<StockValue> subList = new ArrayList<>();
        for (int i = 0; i < list.size(); i++) {
            StockValue val = list.get(i);
            if (i == 0) {
                if (val.getLow().compareTo(list.get(1).getLow()) == -1) {
                    indexList.add(i);
                    if (generation > 0) {
                        subList.add(val);
                    }
                }
                continue;
            }
            if (i != list.size() - 1 && 
                val.getLow().compareTo(list.get(i - 1).getLow()) == -1 &&
                val.getLow().compareTo(list.get(i + 1).getLow()) == -1) {
                indexList.add(i);
                if (generation > 0) {
                    subList.add(val);
                }
                continue;
            }
            if (i == list.size() - 1 && 
                val.getLow().compareTo(list.get(i - 1).getLow()) == -1) {
                indexList.add(i);
                if (generation > 0) {
                    subList.add(val);
                }
            }
        }
        if (generation-- > 0) {
            List<Integer> subGeneration = getValueLowestIndex(subList, generation);
            List<Integer> subResult = new ArrayList<Integer>();
            for (Integer subLowest : subGeneration) {
                subResult.add(indexList.get(subLowest));
            }
            indexList = subResult;
        }
        
        return indexList;
    }
    
    
    
    
    
    /**
     * 获取极值所在的位置索引通用方法
     * @param list
     * @param generation 迭代次数
     * @param compareTo -1 或 1
     * @return
     */
    public static List<Integer> getExtremeIndex(List<Double> list, int generation, int compareTo) {
        if (CollectionUtils.isEmpty(list) || list.size() < 2) {
            return new ArrayList<Integer>();
        }
        List<Integer> indexList = new ArrayList<>();
        List<Double> subList = new ArrayList<>();
        for (int i = 0, size = list.size(); i < size; i++) {
            Double val = list.get(i);
            if (Objects.isNull(val)) {
                continue;
            }
            if (i == 0) {
                if(val.compareTo(list.get(1)) == compareTo) {
                    indexList.add(i);
                    if (generation > 0) {
                        subList.add(val);
                    }
                }
                continue;
            }
            if (i != size - 1 && 
                Objects.nonNull(list.get(i - 1)) &&
                val.compareTo(list.get(i - 1)) == compareTo &&
                val.compareTo(list.get(i + 1)) == compareTo) {
                indexList.add(i);
                if (generation > 0) {
                    subList.add(val);
                }
                continue;
            }
            if (i == size - 1 && 
                Objects.nonNull(list.get(i - 1)) &&
                val.compareTo(list.get(i - 1)) == compareTo) {
                indexList.add(i);
                if (generation > 0) {
                    subList.add(val);
                }
            }
        }
        if (generation-- > 0) {
            List<Integer> subGeneration = getExtremeIndex(subList, generation, compareTo);
            List<Integer> subResult = new ArrayList<Integer>();
            for (Integer subLowest : subGeneration) {
                subResult.add(indexList.get(subLowest));
            }
            indexList = subResult;
        }
        
        return indexList;
    }

    /**
     * 获取极小值所在的位置索引通用方法，默认迭代次数：1
     * @param list
     * @return
     */
    public static List<Integer> getLowestIndex(List<Double> list) {
        return getLowestIndex(list, 1);
    }
    
    /**
     * 获取极小值所在的位置索引通用方法
     * @param list
     * @param generation 迭代次数
     * @return
     */
    public static List<Integer> getLowestIndex(List<Double> list, int generation) {
        return getExtremeIndex(list, generation, -1);
    }
    
    /**
     * 获取极大值所在的位置索引通用方法，默认迭代次数：1
     * @param list
     * @return
     */
    public static List<Integer> getHighestIndex(List<Double> list) {
        return getHighestIndex(list, 1);
    }
    
    /**
     * 获取极大值所在的位置索引通用方法
     * @param list
     * @param generation 迭代次数
     * @return
     */
    public static List<Integer> getHighestIndex(List<Double> list, int generation) {
        return getExtremeIndex(list, generation, 1);
    }
    
    /**
     * 获取金叉和死叉及其所在索引
     * @param fast 快线
     * @param slow 慢线
     * @return
     */
    public static List<IndexValue<Integer, CrossType>> getCross(List<Double> fast, List<Double> slow) {
        Assert.isTrue(fast != null && slow != null && fast.size() == slow.size(), "快线和慢线不能为 null 且其长度必须相等！");
        TreeSet<IndexValue<Integer, CrossType>> set = new TreeSet<>();
        for (int i = 1; i < fast.size(); i++) {
            Double slowPrev = slow.get(i - 1), slowNow = slow.get(i);
            Double fastPrev = fast.get(i - 1), fastNow = fast.get(i);
            if (slowPrev == null || fastPrev == null || slowNow == null || fastNow == null) {
                continue;
            }
            if ((fastPrev < slowPrev && fastNow >= slowNow) || (fastPrev <= slowPrev && fastNow > slowNow)) {
                set.add(new IndexValue<Integer, CrossType>(i, CrossType.Bullish));
            }
            else if ((fastPrev > slowPrev && fastNow <= slowNow) || (fastPrev >= slowPrev && fastNow < slowNow)) {
                set.add(new IndexValue<Integer, CrossType>(i, CrossType.Bearish));
            }
        }
        return set.stream().collect(Collectors.toList());
    }

    /**
     * 获取多头排列的 Index<br>
     * 一般用于寻找买点或上升趋势。寻找买点时建议 satisfiedCount 严格等于提供的 lists 的个数。
     * @param risePeriod 要连续多少天出现多头才认为是多头，最小 2，默认 2
     * @param satisfiedCount lists 中有多少个 list 满足上涨和多头排列就认为市场是多头，最小 2，默认：lists 经过筛选后的 size
     * @param lists 要对比多头的 list，按照快慢排列，其中任意 list 不能为空，且提供的 lists 的个数至少为 2 组
     * @return
     */
    @SafeVarargs
    public static List<Integer> getBullishArrange(Integer risePeriod, Integer satisfiedCount, List<Double>... lists){
        Assert.isTrue(lists != null && lists.length > 1, "lists 不能为空且至少要有 2 组 List<Double> 存在！");
        if (risePeriod == null || risePeriod < 2) {
            risePeriod = 2;
        }
        List<List<Double>> listList = new ArrayList<>(Arrays.asList(lists));
        Integer length = null;
        for (int i = 0; i < listList.size(); i++) {
            List<Double> list = listList.get(i);
            if (list == null) {
                listList.remove(i--);
                continue;
            }
            if (length == null) {
                length = list.size();
            }
            else {
                Assert.isTrue(length == list.size(), "提供的 lists 内的各 List 长度必须相等！");
            }
        }
        Assert.isTrue(lists.length == listList.size(), "提供的 lists 不允许存在空 list！");
        if (satisfiedCount == null || satisfiedCount < 2 || satisfiedCount > listList.size()) {
            satisfiedCount = listList.size();
        }
        TreeSet<Integer> set = new TreeSet<>();
        if (listList.size() == 0) {
            return set.stream().collect(Collectors.toList());
        }
        
        nextPeriod:
        for (int i = risePeriod - 1; i < length; i++) {
            final int index = i;
            // listList 是最终的去除了全 null lists 的 lists
            // 从中对每个 lists 取对应的 index 索引的值
            // 得到的就是 index 日的所有线的值
            List<Double> values = listList.stream().map(list -> list.get(index)).collect(Collectors.toList());
            // 这里判断 null，判断 list 的个数与 count 有关
            if (values.subList(0, satisfiedCount).stream().anyMatch(d -> d == null)) {
                continue;
            }
            
            // 当天及前 period 天的多线信息，注意最好往前推，或者往后推时要取最后那一个点，不然属于未来函数了
            // 这里选择的是往前推 risePeriod days，再从 index - risePeriod 到 index 日，依次放入 nearValuesList
            List<List<Double>> nearValuesList = new ArrayList<>();
            for (int j = risePeriod - 1; j > 0; j--) {
                final int backwardIndex = j;
                nearValuesList.add(listList.stream().map(list -> list.get(index - backwardIndex)).collect(Collectors.toList()));
            }
            nearValuesList.add(values);
            /** 到这里，假设一开始给定的 List 是 MA5, MA10, MA20，则 nearValuesList 的结构是这样的 **/
            // List<List<Double>> nearValuesList
            // Index of nearValuesList :      0 ,      1 , ...,      period - 1
            // Structure of ...           MA5[0],  MA5[1], ...,  MA5[period - 1]
            //                           MA10[0], MA10[1], ..., MA10[period - 1]
            //                           MA20[0], MA20[1], ..., MA20[period - 1]
            
            // 统计有上涨趋势的线
            List<Integer> upListIndexes = new ArrayList<>();
            nextUpJudge:
            for (int j = 0; j < values.size(); j++) {
                for (int k = 0; k < risePeriod - 1; k++) {
                    List<Double> prevValues = nearValuesList.get(k);
                    if (prevValues.get(j) == null) {
                        continue;
                    }
                    List<Double> currValues = nearValuesList.get(k + 1);
                    if (currValues.get(j) == null) {
                        continue;
                    }
                    // 只要有一次大于，那这根线就不算上涨趋势了，去判断下一根线
                    if (prevValues.get(j) > currValues.get(j)) {
                        continue nextUpJudge;
                    }
                }
                // 一根线跑完一个 period 都没有触发 continue 那这根线认为是上涨趋势
                // 把这根线的索引加到上涨趋势索引列表里
                upListIndexes.add(j);
            }
            if (upListIndexes.size() < satisfiedCount) {
                // 当前 risePeriod 内，满足上涨趋势的线的数量不够，直接去下一个 period
                continue nextPeriod;
            }
            
            // 统计多头排列的线
            // 假设现在有 MA5，MA10，MA20，MA60 四根线
            // count == 4 时，MA5 ≥ MA10 ≥ MA20 ≥ MA60 才认为是多头
            // count == 3 时，MA5 ≥ MA10 ≥ MA20 或 MA5 ≥ MA10 ≥ MA60 或 MA5 ≥ MA20 ≥ MA60 或 MA10 ≥ MA20 ≥ MA60 就可认为是多头
            // 以此类推
            
            int bullCount = 0;
            int minBullCount = (satisfiedCount - 1) * satisfiedCount / 2;
            for (int j = 0; j < values.size() - 1; j++) {
                slowLineJudge:
                for (int k = j + 1; k < values.size(); k++) {
                    for (int m = 0; m < risePeriod; m++) {
                        List<Double> allLineOneDayValue = nearValuesList.get(m);
                        Double fastLineValue = allLineOneDayValue.get(j);
                        Double slowLineValue = allLineOneDayValue.get(k);
                        if (fastLineValue == null || slowLineValue == null) {
                            continue;
                        }
                        if (fastLineValue < slowLineValue) {
                            // 对比下一条被对比线
                            continue slowLineJudge;
                        }
                    }
                    // 这条被对比线对比完了，没出现跳到下一个 slowLineJudge 的情况，可认为这两条线满足多头
                    bullCount++;
                }
            }
            if (bullCount < minBullCount) {
                // 当前 period 内，满足多头排列的线的数量不够，直接去下一个 period
                continue nextPeriod;
            }
            
            // 到这里都满足，加入
            set.add(i);
        }
        
        return set.stream().collect(Collectors.toList());
    }
    /**
     * 获取多头排列的 Index
     * @param lists
     * @return
     */
    @SafeVarargs
    public static List<Integer> getBullishArrange(Integer period, List<Double>... lists){
        if (period == null || period < 1) {
            period = 1;
        }
        List<List<Double>> listList = new ArrayList<>(Arrays.asList(lists));
        Integer length = null;
        for (int i = 0; i < listList.size(); i++) {
            List<Double> list = listList.get(i);
            if (list == null) {
                listList.remove(i--);
                continue;
            }
            if (length == null) {
                length = list.size();
            }
            else {
                Assert.isTrue(length == list.size(), "提供的 List 长度必须相等！");
            }
        }
        TreeSet<Integer> set = new TreeSet<>();
        if (listList.size() == 0) {
            return set.stream().collect(Collectors.toList());
        }
        
        valuesLabel:
        for (int i = period; i < length; i++) {
            final int index = i;
            List<Double> values = listList.stream().map(list -> list.get(index)).collect(Collectors.toList());
            if (values.stream().anyMatch(d -> d == null)) {
                continue;
            }
            
            // 当天及前 period 天的多线信息，注意必须往前推，不然属于未来函数了
            List<List<Double>> prevValuesList = new ArrayList<>();
            for (int j = period; j > 0; j--) {
                final int jIndex = j;
                prevValuesList.add(listList.stream().map(list -> list.get(index - jIndex)).collect(Collectors.toList()));
            }
            prevValuesList.add(values);
            
            // 上涨趋势
            for (int j = 0; j < values.size(); j++) {
                for (int k = 0; k < period - 1; k++) {
                    List<Double> prevValues = prevValuesList.get(k);
                    if (prevValues.get(j) == null) {
                        continue;
                    }
                    List<Double> currValues = prevValuesList.get(k + 1);
                    if (currValues.get(j) == null) {
                        continue;
                    }
                    if (prevValues.get(j) > currValues.get(j)) {
                        continue valuesLabel;
                    }
                }
            }
            
            // 多头排列
            for (int j = 1; j < values.size(); j++) {
                if (j != values.size() - 1) {
                    if (!((values.get(j - 1) > values.get(j) && values.get(j) >= values.get(j + 1)) || (values.get(j - 1) >= values.get(j) && values.get(j) > values.get(j + 1)))) {
                        continue valuesLabel;
                    }
                }
                else if (values.get(j - 1) < values.get(j)) {
                    continue valuesLabel;
                }
            }
            set.add(i);
        }
        
        return set.stream().collect(Collectors.toList());
    }
    
    /**
     * 判断 i 是否位于指定的金叉(死叉)~死叉(金叉)区间内
     * @param i
     * @param crossIndexList
     * @param begin
     * @return
     */
    public static Boolean inCrossAreaBetween(int i, List<IndexValue<Integer, CrossType>> crossIndexList, CrossType beginCrossType) {
        for (int k = 0; k < crossIndexList.size(); k++) {
            IndexValue<Integer, CrossType> cross = crossIndexList.get(k), crossNext = k != crossIndexList.size() - 1 ? crossIndexList.get(k + 1) : null;
            if (cross.getValue() == beginCrossType && cross.getIndex() >= i && (crossNext != null && crossNext.getIndex() <= k || crossNext == null)) {
                return true;
            }
        }
        return false;
        
    }

}
