package com.sagacloud.endpoints;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.opengamma.strata.math.impl.interpolation.PiecewiseCubicHermiteSplineInterpolator;
import com.sagacloud.exception.DepthExceededException;
import com.sagacloud.exception.ExpressionException;
import com.sagacloud.utils.*;
import org.apache.commons.lang.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * Created by wo on 11/28/19.
 */
@Path("servicedata")
public class ServiceData {
    private static final Logger log = LoggerFactory.getLogger(ServiceData.class);

    private final static String[] PERIODS = {"1min", "5min"};

    @Path("/query")
    @POST
    @Produces(MediaType.APPLICATION_JSON)
    public String queryPeriodData(@QueryParam(Const.PROJECT_ID) String projectId, String jsonStr) {
        try {
            JSONObject json = JSONObject.parseObject(jsonStr);
            if (json == null)
                return DpUtils.errorJsonMsg("json格式不正确.");
            json = (JSONObject) json.get("criteria");
            if (json == null || json.get("id") == null || json.get("code") == null || json.get("period") == null || json.get("receivetime") == null)
                return DpUtils.errorJsonMsg("json格式不正确.");

            String id = json.get("id").toString();
            JSONObject criteria = new JSONObject();
            criteria.put("criteria", new JSONObject());
            ((JSONObject) criteria.get("criteria")).put("id", id);
            Object metFunStr = CommonUtil.getInfoById(projectId.substring(2, 12), id, (String) json.get("code"));
            if (metFunStr == null) {
                return DpUtils.errorJsonMsg("Assigned code does not exist");
            }
            if (metFunStr instanceof JSONObject) {
                JSONObject calcJSON = (JSONObject) metFunStr;
                if (calcJSON.get(Const.EXPRESSION) == null || calcJSON.get(Const.VARIABLES) == null || !(calcJSON.get(Const.VARIABLES) instanceof JSONObject))
                    return DpUtils.errorJsonMsg("信息点值错误!");
                // 读expr, 返回变量.
                Calculator calc = new Calculator();
                String[] vars = calc.readExpression(calcJSON.get(Const.EXPRESSION).toString());
                // 匹配变量是否存在.
                for (int i = 0; i < vars.length; ++i)
                    if (!((JSONObject) calcJSON.get(Const.VARIABLES)).containsKey(vars[i]))
                        throw new Exception("变量中不存在" + vars[i]);
                LinkedHashMap<String, LinkedHashMap<String, Double>> varValue = new LinkedHashMap<>();
                // 获取各个变量的值.
                LinkedHashMap<String, Double> lastMap = null;
                String data_time_str = null;
                for (int i = 0; i < vars.length; ++i) {
                    JSONObject var = (JSONObject) ((JSONObject) calcJSON.get(Const.VARIABLES)).get(vars[i]);
                    if (var == null || ((var.get("id") == null || var.get("code") == null) && var.getString("meterfunc") == null))
                        throw new Exception("变量" + vars[i] + "的值配置错误");
                    JSONArray arr = getMeterFuncPeriodData(projectId, var, json);
                    LinkedHashMap<String, Double> timeValueMap = new LinkedHashMap<>();
                    if (arr == null || arr.size() == 0) {
                    	timeValueMap.put(data_time_str, 0.0);
                    	if(!varValue.containsKey(vars[i])) {
                    		varValue.put(vars[i], timeValueMap);
                    	}
                    	continue;
                    	//return DpUtils.returnJSONArray(new JSONArray(), "");
                    }
                    for (int j = 0; j < arr.size(); ++j) {
                        JSONObject obj = (JSONObject) arr.get(j);
                        data_time_str = obj.get("data_time").toString();
                        if (lastMap == null) {
                            timeValueMap.put(data_time_str, (Double) obj.get("data_value"));
                        } else {
                            if (lastMap.containsKey(data_time_str)) {
                                timeValueMap.put(data_time_str, (Double) obj.get("data_value"));
                            }
                        }
                    }
                    lastMap = timeValueMap;
                    if (timeValueMap.size() == 0)
                        return DpUtils.returnJSONArray(new JSONArray(), "");
                    varValue.put(vars[i], timeValueMap);
                }
                // 依次匹配相同时间点, 计算, 添加到return里并返回.
                JSONArray result = new JSONArray();
                if(null != lastMap) {
                	for (String time : lastMap.keySet()) {
                		JSONObject jsonObject = new JSONObject();
                		jsonObject.put("time_period", json.get("period"));
                		jsonObject.put("data_time", time);
                		result.add(jsonObject);
                		ArrayList<Double> values = new ArrayList<>();
                		for (int i = 0; i < vars.length; ++i) {
                			Map<String, Double> singleVar = varValue.get(vars[i]);
                			values.add(singleVar.get(time));
                		}
                		Double re = calc.compute(vars, values);
                		jsonObject.put("data_value", re);
                	}
                }
                return DpUtils.returnJSONArray(result, "");
            } else if (metFunStr instanceof String)
                return this.innerQueryPeriodData(projectId, metFunStr.toString(), json).toString();
            else
                return DpUtils.errorJsonMsg("信息点值有错误!");
        } catch (ExpressionException eex) {
            log.error(eex.getMessage(), eex);
            return DpUtils.errorJsonMsg(eex.getMessage());
        } catch (DepthExceededException deex) {
            log.error(deex.getMessage(), deex);
            return DpUtils.errorJsonMsg(deex.getMessage());
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return DpUtils.errorJsonMsg(e.getMessage() + " : " + Arrays.toString(e.getStackTrace()));
        }
    }

    private JSONArray getMeterFuncPeriodData(String pjId, JSONObject var, JSONObject json) throws Exception {
        Object metFunStr = null;
        String id = var.getString("id");
        String code = var.getString("code");
        if (var.getString("meterfunc") != null && var.getString("meterfunc").length() > 0) {
            metFunStr = var.getString("meterfunc");
        } else {
            metFunStr = CommonUtil.getInfoById(pjId.substring(2, 12), id, code);
        }
        if (metFunStr == null) {
            throw new Exception("信息点不存在:" + id + ":" + code);
        }
        if (metFunStr instanceof JSONObject)
            throw new DepthExceededException();
        if (!(metFunStr instanceof String))
            throw new Exception("数据错误:" + id + ":" + code);
        return (JSONArray) this.innerQueryPeriodData(pjId, metFunStr.toString(), json).get("Content");
    }

    private JSONObject innerQueryPeriodData(String projectId, String metFunStr, JSONObject json) throws Exception {
        JSONObject queryData = new JSONObject();

        JSONObject newCriteria = this.generateMetFun(metFunStr, "-");
        String period = json.get("period").toString().trim();
        Boolean interpolation = json.getBoolean("interpolation");
        if(interpolation == null){
            interpolation = true;
        }
        int stepLength = this.rewritePeriod(projectId, period, newCriteria, false);
        newCriteria.put("data_time", json.get("receivetime"));

        String tableSuffix = newCriteria.getString("time_period");
        newCriteria.remove("time_period");

        queryData.put("criteria", newCriteria);

//        TreeMap<String, JSONObject> sortedMap = new TreeMap();

        return CommonMethods.queryData(projectId, queryData.toJSONString(), "data_servicedata_" + tableSuffix);
/*        JSONObject jsonObject1 = CommonMethods.queryData(projectId, queryData.toJSONString(), "fjd_1_near_" + tableSuffix);
        JSONObject jsonObject0 = CommonMethods.queryData(projectId, queryData.toJSONString(), "fjd_0_near_" + tableSuffix);
        JSONObject[] jsonObjects = {jsonObject0, jsonObject1, jsonObject2};
        Arrays.stream(jsonObjects).forEach(reJson -> {
            JSONArray arr = (JSONArray) reJson.get("Content");
            if (arr == null) {
                return;
            }
            for (int i = 0; i < arr.size(); i++) {
                JSONObject item = (JSONObject) arr.get(i);
                item.put("time_period", tableSuffix);
                sortedMap.put(item.get("data_time").toString(), item);
            }
        });

        JSONObject result = new JSONObject();
        JSONArray arr = new JSONArray();
        sortedMap.descendingKeySet().stream().forEach(key -> arr.add(sortedMap.get(key)));
        result.put("Result", "success");
        result.put("Count", (long) arr.size());
        result.put("Content", arr);
        result.put("ResultMsg", "");
        if (stepLength < 1) {
            return result;
        } else {
            return this.interpolate(stepLength, result);
        }*/

    }

    private JSONObject interpolate(int stepLength, JSONObject rawRe) {
        JSONArray content = (JSONArray) rawRe.get("Content");
        if (content == null || content.size() < 2) {
            return rawRe;
        }
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
        Map<LocalDateTime, JSONObject> map = new TreeMap<>();
        JSONObject tmpItem = null;
        for (int i = 0; i < content.size(); i++) {
            tmpItem = (JSONObject) content.get(i);
            map.put(LocalDateTime.parse(tmpItem.get("data_time").toString(), formatter), tmpItem);
        }
        int rawStepLenth = Integer.parseInt(tmpItem.get("time_period").toString().replace("min", ""));
        List<LocalDateTime> timeArr = new ArrayList<>(map.keySet());
        JSONArray array = new JSONArray();
        PiecewiseCubicHermiteSplineInterpolator pchip = new PiecewiseCubicHermiteSplineInterpolator();
        List<Double> X = new ArrayList<>();
        List<Double> Y = new ArrayList<>();
        int start = -1;
        for (int i = 0; i < timeArr.size(); i++) {
            if (i == 0) {
                start = 0;
                X.add((double) timeArr.get(i).toInstant(ZoneOffset.ofTotalSeconds(0)).toEpochMilli());
                Y.add(map.get(timeArr.get(i)).getDouble("data_value"));
                continue;
            }
            if (timeArr.get(i).minusMinutes(rawStepLenth * 2).compareTo(timeArr.get(i - 1)) > 0 || i == timeArr.size() - 1) {
                if (timeArr.get(i).minusMinutes(rawStepLenth * 2).compareTo(timeArr.get(i - 1)) < 0 && i == timeArr.size() - 1) {
                    X.add((double) timeArr.get(i).toInstant(ZoneOffset.ofTotalSeconds(0)).toEpochMilli());
                    Y.add(map.get(timeArr.get(i)).getDouble("data_value"));
                }
                LocalDateTime tmpDateTime = timeArr.get(start);
                if (X.size() == 1) {
                    array.add(map.get(tmpDateTime));
                }
                while (true) {
                    if (tmpDateTime.compareTo(timeArr.get(i)) > 0) {
                        break;
                    }
                    if (map.get(tmpDateTime) != null && !array.contains(map.get(tmpDateTime))) {
                        array.add(map.get(tmpDateTime));
                    } else {
                        double value = pchip.interpolate(ArrayUtils.toPrimitive(X.toArray(new Double[X.size()])), ArrayUtils.toPrimitive(Y.toArray(new Double[Y.size()])), (double) tmpDateTime.toInstant(ZoneOffset.ofTotalSeconds(0)).toEpochMilli());
                        JSONObject jsonObject = JSONObject.parseObject(tmpItem.toString());
                        jsonObject.put("data_time", tmpDateTime.format(formatter));
                        jsonObject.put("data_value", value);
                        jsonObject.put("time_period", String.format("%dmin", stepLength));
                        array.add(jsonObject);
                    }
                    tmpDateTime = tmpDateTime.plusMinutes(stepLength);
                }
                X.clear();
                Y.clear();
                start = i;
                if (timeArr.get(i).minusMinutes(rawStepLenth * 2).compareTo(timeArr.get(i - 1)) > -1) {
                    X.add((double) timeArr.get(i).toInstant(ZoneOffset.ofTotalSeconds(0)).toEpochMilli());
                    Y.add(map.get(timeArr.get(i)).getDouble("data_value"));
                }
            } else {
                X.add((double) timeArr.get(i).toInstant(ZoneOffset.ofTotalSeconds(0)).toEpochMilli());
                Y.add(map.get(timeArr.get(i)).getDouble("data_value"));
            }

        }
        rawRe.put("Content", array);
        rawRe.put("Count", (long) array.size());
        return rawRe;
    }

    public JSONObject generateMetFun(String valueStr, String metFunSep) throws Exception {
        String regex = "";
        if(metFunSep.equalsIgnoreCase("+")){
            regex = "[+]";
        }else {
            regex = metFunSep;
        }
        String[] strs = valueStr.split(regex);
        if (strs.length < 2) {
            throw new Exception(String.format("Assigned code does not has a 'string%slong' style value.", metFunSep));
        }
        if (!strs[strs.length - 1].matches("[0-9]+")) {
            throw new Exception(String.format("Assigned code does not has a 'string%slong' style value.", metFunSep));
        }
        JSONObject criteria = new JSONObject();
        criteria.put("meter", valueStr.replace(metFunSep + strs[strs.length - 1], ""));
        criteria.put("funcid", Long.valueOf(strs[strs.length - 1]));
        return criteria;
    }

    private int rewritePeriod(String projectId, String period, JSONObject criteria, boolean interpolation) {
        if (!Arrays.asList(PERIODS).contains(period) || !interpolation) {
            criteria.put("time_period", period);
            return -1;
        }
        JSONObject queryData = new JSONObject();
        queryData.put("criteria", criteria);
        log.info(queryData.toJSONString());
        JSONObject result = CommonMethods.queryData(projectId, queryData.toJSONString(), "dy_pointlist");
        JSONArray contents = (JSONArray) result.get("Content");
        if (contents == null || contents.size() == 0) {
            criteria.put("time_period", period);
            return -1;
        }
        String minPeriod = ((JSONObject) contents.get(0)).get("collect_cycle").toString();
        try {
            int min = Integer.parseInt(minPeriod.trim().replace("min", ""));
            int intPeriod = Integer.parseInt(period.trim().replace("min", ""));
            if (min > intPeriod) {
                criteria.put("time_period", minPeriod);
                return intPeriod;
            }
        } catch (NumberFormatException e) {
            log.error("collect_cycle : " + minPeriod, e);
        }
        criteria.put("time_period", period);
        return -1;


    }

}
