package com.smppw.utils;

import com.smppw.common.pojo.dto.DateValue;
import com.smppw.common.pojo.dto.Interval;
import com.smppw.common.pojo.dto.Value;
import com.smppw.common.pojo.enums.Frequency;
import com.smppw.common.pojo.enums.Indicator;
import org.apache.commons.math3.linear.EigenDecomposition;
import org.apache.commons.math3.linear.MatrixUtils;
import org.joda.time.DateTime;

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.*;
import java.util.Map.Entry;

/**
 * @author Yu Liu
 *
 */
public class CalcUtils {

	// private static Logger log = LoggerFactory.getLogger(CalcUtils.class);

	public static Double maxDrawdown(List<? extends Value> dateValues) {
		if (dateValues == null || dateValues.size() < 2) {
			// 数据条数少于2条，无法计算最大回撤
			return null;
		}
		List<Double> values = new ArrayList<Double>();

		double peak = Double.NEGATIVE_INFINITY;
		for (Value e : dateValues) {
			Double v = e.getValue();

			if (v != null) {
				if (peak < v) {
					peak = v;
					v = 0.0;
				} else {
					v = v / peak - 1;
				}
				values.add(v);
			}
		}

		return CalcUtils.min(values);
	}

	public static Map<Indicator, String> getMaxDrawdownInfo(List<DateValue> dateValues) {
		if (dateValues == null || dateValues.size() == 0)
			return new HashMap<>();
		List<Double> values = new ArrayList<Double>();
		double peak = Double.NEGATIVE_INFINITY;
		String peakDate = null;
		String footDate = null;
		Double[] temp = new Double[dateValues.size()];
		String maxDate = null;//最大峰值日期
        String minDate = null;//最小谷底日期
        double maxValue = 0.0;
        List<DateValue> maxList = new ArrayList<DateValue>();
		for (int i = 0; i < dateValues.size(); i++) {
			DateValue dateValue = dateValues.get(i);
			Double value = dateValue.getValue();
			if (value != null) {
				if (value > peak) {
					peak = value;
					peakDate = dateValue.getDate();
//					maxDate = dateValue.getDate();
					maxList.add(dateValue);
					value = 0.0;
				} else {
					temp[i] = peak;
					value = value / peak - 1;
					if(value < maxValue){
                        maxValue = value;
                        minDate = dateValue.getDate();
                    }
				}
				values.add(value);
			}
		}
		Double maxDrawdown = CalcUtils.min(values);
		Integer maxDrawDownIterval = null;
		String maxDrawDownRecureDay = null;
		Integer maxDrawDownRecureInterval = null;
		Integer maxDrawDownRecureIntervalDays = null;
		if (maxDrawdown != null && maxDrawdown != 0) {
			int footPos = values.indexOf(maxDrawdown);
			double peakReal = temp[footPos];
			// double footNav = dateValues.get(footPos).getValue();
			footDate = dateValues.get(footPos).getDate();
			for (DateValue value : dateValues) {
				if (value.getValue() == peakReal) {
					peakDate = value.getDate();
				}

				if (value.getValue() >= peakReal
						&& DateTime.parse(footDate).isBefore(DateTime.parse(value.getDate()))) {
					maxDrawDownRecureDay = value.getDate();
					break;
				}
			}
			if (footDate != null && maxDrawDownRecureDay != null){
				maxDrawDownRecureInterval = DateUtil.getDateDistance(footDate, maxDrawDownRecureDay, Frequency.Monthly,
						false);
				maxDrawDownRecureIntervalDays = DateUtil.getDateDistance(footDate, maxDrawDownRecureDay, Frequency.Daily,
						false);
			}
			if (peakDate != null && footDate != null)
				maxDrawDownIterval = DateUtil.getDateDistance(peakDate, footDate, Frequency.Monthly, false);
		}
		Map<Indicator, String> map = new HashMap<>();
		map.put(Indicator.MaxDrawdown, CommonUtil.double2String(maxDrawdown == null ? null : maxDrawdown));
		map.put(Indicator.MaxDrawdownInterval, CommonUtil.integer2String(maxDrawDownIterval));
		map.put(Indicator.MaxDrawdownRecureDate, maxDrawDownRecureDay);
		map.put(Indicator.MaxDrawdownRecureInterval, CommonUtil.integer2String(maxDrawDownRecureInterval));
		map.put(Indicator.MaxDrawdownRecureIntervalDays,CommonUtil.integer2String(maxDrawDownRecureIntervalDays));
		
		if(minDate != null){
		    int minDateInt = Integer.valueOf(minDate.replace("-", "")).intValue();
		    for(DateValue dv : maxList){
		        if(Integer.valueOf(dv.getDate().replace("-", "")).intValue() < minDateInt){
		            maxDate = dv.getDate();
		        }
		    }
		}
		map.put(Indicator.MaxDrawdownPeakDate, maxDate);
		map.put(Indicator.MaxDrawdownValleyDate, minDate);
		return map;
	}

	public static double getSDS(List<List<Double>> dataList) {
		Double sum = 0.0;
		int size = dataList.get(0).size();
		for (List<Double> unit : dataList) {
			Double avg = 0.0;

			for (Double value : unit) {
				if (value != null)
					avg += value;
			}
			avg /= size;
			for (Double value : unit) {
				if (value != null)
					sum += Math.pow(value - avg, 2);
			}
		}
		sum = Math.sqrt(sum / (size - 1));
		return sum;
	}

	/**
	 * 
	 * @param list
	 * @return 债券 期货 标准差计算
	 */
	public static double getFV(List<List<Double>> list) {
		double result = 0.0D;
		int size = list.get(0).size();
		int T = list.size();
		for (int i = 0; i < size; i++) {
			double sum = 0.0D;
			double max = list.get(0).get(i);
			double min = max;
			for (List<Double> doubles : list) {
				sum += doubles.get(i);
				if (doubles.get(i) > max) {
					max = doubles.get(i);
				}
				if (doubles.get(i) < min) {
					min = doubles.get(i);
				}
			}
			double avg = sum / T;
			double sumT = 0.0D;
			for (List<Double> doubles : list) {
				Double value = doubles.get(i) - avg;
				if (null != value) {
					sumT += Math.pow(value, 2);
				}
			}
			double molecular = 2 * (Math.sqrt(sumT / (T - 1)));

			result += molecular / (max - min);
		}

		return result;
	}

	public static double datetime2Double(String datetime) {
		if (datetime == null)
			return 0;
		String[] str = datetime.split("-");
		StringBuilder sb = new StringBuilder();
		sb.append(str[0]).append(str[1]).append(str[2]);
		return Double.valueOf(sb.toString());
	}

	public static Double min(List<Double> values) {
		if (values.isEmpty()) {
			return null;
		}
		return Collections.min(values);
	}


	public static List<Double> divide(List<Double> values, double denominator) {
		List<Double> res = new ArrayList<Double>();

		for (Double value : values) {
			res.add(value / denominator);
		}

		return res;
	}

	public static double matrixConditionNumber(double[][] cor) throws Exception {
		EigenDecomposition ed = new EigenDecomposition(MatrixUtils.createRealMatrix(cor));
		double[] eigenArr = ed.getRealEigenvalues();
		List<Double> eigenValues = new ArrayList<>();
		for (double unit : eigenArr)
			eigenValues.add(unit);
		double maxVal = Collections.max(eigenValues);
		double minVal = Collections.min(eigenValues);

		return Math.pow(maxVal / minVal, 0.5);
	}

	public static int roundOff2Integer(double dou) {
		return (int) roundOff(dou, 0);
	}

	public static double roundOff(double dou, int decimal) {
		return round(dou, decimal, BigDecimal.ROUND_HALF_UP);
	}

	public static double round(double dou, int decimal, int model) {
		if (isInvalidateFloat(dou))
			return dou;
		BigDecimal bd = new BigDecimal(dou);
		BigDecimal bd1 = bd.setScale(decimal, model);
		dou = bd1.doubleValue();
		return dou;
	}

	public static boolean isInvalidateFloat(double dou) {
		return Double.isNaN(dou) || Double.isInfinite(dou);
	}

	public static List<Double> calcCulumative(List<Double> list) {
		List<Double> values = new ArrayList<Double>();
		double sum = 0.0;
		for (Double v : list) {
			sum += v;
			values.add(sum);
		}
		return values;
	}

	public static List<Integer> calcCulumativeInteger(List<Integer> list) {
		List<Integer> values = new ArrayList<Integer>();
		int sum = 0;
		for (Integer v : list) {
			sum += v;
			values.add(sum);
		}
		return values;
	}

	public static Double[][] getCorrelationMatrix(Map<String, Map<String, Double>> retMap, List<String> xSecIds,
			List<String> ySecIds) {
		return getCorrelationMatrix(retMap, retMap, xSecIds, ySecIds, 0);
	}

	public static Double[][] getCorrelationMatrix(Map<String, Map<String, Double>> retMap, List<String> xSecIds,
			List<String> ySecIds, int minRetSize) {
		return getCorrelationMatrix(retMap, retMap, xSecIds, ySecIds, minRetSize);
	}

	public static Double[][] getCorrelationMatrix(Map<String, Map<String, Double>> xRetMap,
			Map<String, Map<String, Double>> yRetMap, List<String> xSecIds, List<String> ySecIds, int minRetSize) {
		int dim1 = xSecIds.size();
		int dim2 = ySecIds.size();
		Double[][] corArr = new Double[dim1][dim2];
		boolean isSame = false;
		if (xSecIds == ySecIds)
			isSame = true;
		for (int i = 0; i < dim1; i++) {
			int j = 0;
			if (isSame)
				j = i;
			for (; j < dim2; j++) {
				if (i == j && isSame) {
					corArr[i][j] = 1.0;
					corArr[j][i] = 1.0;
					continue;
				}
				String xId = xSecIds.get(i);
				String yId = ySecIds.get(j);
				Map<String, Double> xRetList = xRetMap.get(xId);
				Map<String, Double> yRetList = yRetMap.get(yId);
				if (xRetList == null || yRetList == null)
					continue;
				List<String> dates = new ArrayList<>();
				dates.addAll(xRetList.keySet());
				dates.retainAll(yRetList.keySet());
				int count = dates.size();
				if (count < minRetSize) {
					corArr[i][j] = null;
					if (isSame)
						corArr[j][i] = null;
					continue;
				}
				List<Double> xValues = new ArrayList<>();
				List<Double> yValues = new ArrayList<>();
				for (String date : dates) {
					xValues.add(xRetList.get(date));
					yValues.add(yRetList.get(date));
				}
				Double cor = Statistics.correlation(xValues, yValues);
				corArr[i][j] = cor;
				if (isSame)
					corArr[j][i] = cor;
			}
		}
		return corArr;
	}

	public static void main(String[] args) {
		List<Double> list = new ArrayList<>();
		list.add(-2D);
		list.add(-3D);
		list.add(2D);
		list.add(5D);
		list.add(100D);
		List<Interval> intervals = generateIntervals(list);
		Map<String, Integer> distributionMap = matchInterval(intervals, list);
		System.out.println(distributionMap);
	}

	private static Map<String, Integer> matchInterval(List<Interval> intervals, List<Double> list) {
		Map<Interval, Integer> map = new LinkedHashMap<>();
		for (Double unit : list) {
			int n = intervals.size();
			for (int i = 0; i < n; i++) {
				Interval interval = intervals.get(i);
				if (!map.containsKey(interval))
					map.put(interval, 0);
				if (unit >= interval.getLb()) {
					if (i == n - 1) {
						if (unit <= interval.getUb())
							map.put(interval, map.get(interval) + 1);
					} else if (unit < interval.getUb())
						map.put(interval, map.get(interval) + 1);
				}
			}
		}
		Map<String, Integer> resMap = new LinkedHashMap<>();
		for (Entry<Interval, Integer> entry : map.entrySet()) {
			if (entry.getValue() != 0)
				resMap.put(entry.getKey().toString(), entry.getValue());
		}

		return resMap;
	}

	public static List<Interval> generateIntervals(List<Double> rets) {
		List<Interval> intervals = new ArrayList<>();
		if (rets.isEmpty())
			return new ArrayList<>();
		if (rets.size() == 1) {
			intervals.add(new Interval(rets.get(0), rets.get(0) + 1));
			return intervals;
		}
		Double min = Collections.min(rets);
		Double max = Collections.max(rets);

		return generateIntervals(min, max, rets.size());
	}

	public static List<Interval> generateIntervals(double min, double max, int n) {

		return generateIntervals(min, max, n, -1);
	}

	public static List<Interval> generateIntervals(double min, double max, int size, double width) {
		List<Interval> intervals = new ArrayList<>();
		if (min > max) {
			return intervals;
		}

		if (size <= 0) {
			return intervals;
		}

		if (width <= 0) {
			width = getWidth(min, max, size);
			if (width <= 0) {
				return intervals;
			}
		}

		double minEdge = Math.floor(min);
		double maxEdge = Math.ceil(max);
		int seg = (int) Math.ceil((maxEdge - minEdge) / width);
		seg = Math.min(size + 100, seg);

		for (int i = 0; i < seg; i++) {
			double lb = minEdge + i * width;
			double ub = lb + width;
			intervals.add(new Interval(lb, ub));
		}

		return intervals;
	}

	public static double getWidth(double min, double max, int n) {
		if (n <= 1)
			return 1;
		int segmentNum = (int) Math.ceil(Math.log(n) / Math.log(2D));
		double r = (max - min) / segmentNum;
		double b = Math.floor(Math.log10(r));
		double a = Math.ceil(r / Math.pow(10, b));

		return a * Math.pow(10, b);
	}

	public static double format(double d, int num) {
		StringBuilder sb = new StringBuilder("#.");
		for (int i = 1; i <= num; i++)
			sb.append("0");
		DecimalFormat df = new DecimalFormat(sb.toString());
		return Double.valueOf(df.format(d));
	}

}
