package com.gyee.wisdom.dao.golden;

import com.gyee.wisdom.common.data.timeseries.*;
import com.gyee.wisdom.common.exception.WisdomException;
import com.gyee.wisdom.dao.timeseries.GoldenDao;
import com.gyee.wisdom.dao.timeseries.IHistoryDao;
import com.rtdb.api.enums.RtdbType;
import com.rtdb.api.model.RtdbData;
import com.rtdb.enums.RtdbHisMode;
import com.rtdb.enums.ValueType;
import com.rtdb.model.*;
import com.rtdb.service.impl.BaseImpl;
import com.rtdb.service.impl.HistorianImpl;
import com.rtdb.service.impl.ServerImpl;
import com.rtdb.service.impl.SnapshotImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.stream.Collectors;

@Component
@GoldenDao
public class GoldenHistoryDao implements IHistoryDao {

    @Autowired
    private GoldenConfig goldenConfig;

    public List<TsData> getTsDataHistory(TsQuery tsQuery) throws Exception {
        if (tsQuery.getTsPoint().getTsDataType() == TsDataType.DOUBLE) {
            return getDoubleTsDataHistory(tsQuery);
        } else if (tsQuery.getTsPoint().getTsDataType() == TsDataType.BOOLEAN) {
            return getBooleanTsDataHistory(tsQuery);
        } else if (tsQuery.getTsPoint().getTsDataType() == TsDataType.LONG) {
            return getLongTsDataHistory(tsQuery);
        } else if (tsQuery.getTsPoint().getTsDataType() == TsDataType.COORDINATE) {
            return getCoordinateTsDataHistory(tsQuery);
        } else if (tsQuery.getTsPoint().getTsDataType() == TsDataType.BLOB) {
            return getBlobTsDataHistory(tsQuery);
        } else if (tsQuery.getTsPoint().getTsDataType() == TsDataType.STRING) {
            return getStringTsDataHistory(tsQuery);
        } else {
            throw new WisdomException("Golden不支持数据类型：" + tsQuery.getTsPoint().getTsDataType());
        }
    }

    public List<TsData> getDoubleTsDataHistory(TsQuery tsQuery) throws Exception {
        ServerImpl connection = null;
        try {
            connection = goldenConfig.getGoldenConnectionPool().getConnection();
            HistorianImpl his = new HistorianImpl(connection);
            BaseImpl base = new BaseImpl(connection);
            List<MinPoint> lstPoint = base.findPoints(new String[]{tsQuery.getTsPoint().getId()});
            int id = lstPoint.get(0).getId();

            List<DoubleData> lstD = getDoubleHistory(his, tsQuery, id);
            List<TsData> tsDataList = new ArrayList<>();
            for (int i = 0; i < lstD.size(); i++) {
                tsDataList.add(new DoubleTsData(lstD.get(i).getDateTime().getTime(),
                        lstD.get(i).getQuality(),
                        lstD.get(i).getValue()));
            }
            return tsDataList;
        } finally {
            if (connection != null)
                goldenConfig.getGoldenConnectionPool().returnConnection(connection);
        }
    }

    private List<DoubleData> getDoubleHistory(HistorianImpl his, TsQuery tsQuery, int tagId) throws Exception {
        Date startDate = new Date(tsQuery.getStartTs());
        Date endDate = new Date(tsQuery.getEndTs());
        if (tsQuery.getInterpolation() == Interpolation.RAW) {
            return his.getDoubleArchivedValues(tagId, goldenConfig.getQueryHistoryLimit(), startDate, endDate);
        }  else if (tsQuery.getInterpolation() == Interpolation.SNAP) {
            Date[] dateArray = tsQuery.getDateArray();
            if (dateArray != null && dateArray.length > 0) {
                return his.getFloatTimedValues(tagId, dateArray.length, dateArray);
            } else
            {
                throw new WisdomException("无效的查询条件！");
            }
        } else {
            throw new WisdomException("Golden不支持历史数据插值方法：" + tsQuery.getInterpolation());
        }
    }

    public List<TsData> getLongTsDataHistory(TsQuery tsQuery) throws Exception {
        ServerImpl connection = null;
        try {
            connection = goldenConfig.getGoldenConnectionPool().getConnection();
            HistorianImpl his = new HistorianImpl(connection);
            BaseImpl base = new BaseImpl(connection);
            List<MinPoint> lstPoint = base.findPoints(new String[]{tsQuery.getTsPoint().getId()});
            int id = lstPoint.get(0).getId();

            List<IntData> lstD = getIntHistory(his, tsQuery, id);
            List<TsData> tsDataList = new ArrayList<>();
            for (int i = 0; i < lstD.size(); i++) {
                tsDataList.add(new LongTsData(lstD.get(i).getDateTime().getTime(),
                        lstD.get(i).getQuality(),
                        (long) lstD.get(i).getValue()));
            }
            return tsDataList;
        } finally {
            if (connection != null)
                goldenConfig.getGoldenConnectionPool().returnConnection(connection);
        }
    }

    private List<IntData> getIntHistory(HistorianImpl his, TsQuery tsQuery, int tagId) throws Exception {
        Date startDate = new Date(tsQuery.getStartTs());
        Date endDate = new Date(tsQuery.getEndTs());
        if (tsQuery.getInterpolation() == Interpolation.RAW) {
            return his.getIntArchivedValues(tagId, goldenConfig.getQueryHistoryLimit(), startDate, endDate);
        } else if (tsQuery.getInterpolation() == Interpolation.INTERPOLATION) {
            int count = (int) Math.ceil((tsQuery.getEndTs() - tsQuery.getStartTs()) / (tsQuery.getInterval() * 1000));
            return his.getIntInterpoValues(tagId, count, startDate, endDate);
        } else if (tsQuery.getInterpolation() == Interpolation.SNAP) {
            int count = (int) Math.ceil((tsQuery.getEndTs() - tsQuery.getStartTs()) / (tsQuery.getInterval() * 1000));
            return his.getIntIntervalValues(tagId, tsQuery.getInterval(), count, startDate);
        } else {
            throw new WisdomException("Golden不支持历史数据插值方法：" + tsQuery.getInterpolation());
        }
    }

    public List<TsData> getBooleanTsDataHistory(TsQuery tsQuery) throws Exception {
        HistorianImpl his = null;
        BaseImpl base = null;
        try {
            his = new HistorianImpl(goldenConfig.getServerImpl());
            base = new BaseImpl(goldenConfig.getServerImpl());
            List<MinPoint> lstPoint = base.findPoints(new String[]{tsQuery.getTsPoint().getId()});
            int id = lstPoint.get(0).getId();
            Date startDate = new Date(tsQuery.getStartTs());
            Date endDate = new Date(tsQuery.getEndTs());
            if (tsQuery.getInterpolation() == Interpolation.RAW) {
                List<IntData> lstD = his.getIntArchivedValues(id, goldenConfig.getQueryHistoryLimit(), startDate, endDate);
                List<TsData> tsDataList = new ArrayList<>();
                for (int i = 0; i < lstD.size(); i++) {
                    tsDataList.add(new BooleanTsData(lstD.get(i).getDateTime().getTime(),
                            lstD.get(i).getQuality(),
                            lstD.get(i).getValue() != 0));
                }
                return tsDataList;
            } else {
                throw new WisdomException("Golden不支持历史数据插值方法：" + tsQuery.getInterpolation());
            }
        } finally {
            if (base != null)
                base.close();
            if (his != null)
                his.close();
        }
    }

    public List<TsData> getCoordinateTsDataHistory(TsQuery tsQuery) throws Exception {
        HistorianImpl his = null;
        BaseImpl base = null;
        try {
            his = new HistorianImpl(goldenConfig.getServerImpl());
            base = new BaseImpl(goldenConfig.getServerImpl());
            List<MinPoint> lstPoint = base.findPoints(new String[]{tsQuery.getTsPoint().getId()});
            int id = lstPoint.get(0).getId();
            Date startDate = new Date(tsQuery.getStartTs());
            Date endDate = new Date(tsQuery.getEndTs());
            if (tsQuery.getInterpolation() == Interpolation.RAW) {
                List<CoorData> lstD = his.getCoorArchivedValues(id, goldenConfig.getQueryHistoryLimit(), startDate, endDate);
                List<TsData> tsDataList = new ArrayList<>();
                for (int i = 0; i < lstD.size(); i++) {
                    tsDataList.add(new CoordinateTsData(lstD.get(i).getDateTime().getTime(),
                            lstD.get(i).getQuality(),
                            lstD.get(i).getX(),
                            lstD.get(i).getY()));
                }
                return tsDataList;
            } else {
                throw new WisdomException("Golden不支持历史数据插值方法：" + tsQuery.getInterpolation());
            }
        } finally {
            if (base != null)
                base.close();
            if (his != null)
                his.close();
        }
    }

    public List<TsData> getBlobTsDataHistory(TsQuery tsQuery) throws Exception {
        HistorianImpl his = null;
        BaseImpl base = null;
        try {
            his = new HistorianImpl(goldenConfig.getServerImpl());
            base = new BaseImpl(goldenConfig.getServerImpl());
            List<MinPoint> lstPoint = base.findPoints(new String[]{tsQuery.getTsPoint().getId()});
            int id = lstPoint.get(0).getId();
            Date startDate = new Date(tsQuery.getStartTs());
            Date endDate = new Date(tsQuery.getEndTs());
            if (tsQuery.getInterpolation() == Interpolation.RAW) {
                List<BlobData> lstD = his.getArchivedBlobValues(id, goldenConfig.getQueryHistoryLimit(), startDate, endDate);
                List<TsData> tsDataList = new ArrayList<>();
                for (int i = 0; i < lstD.size(); i++) {
                    tsDataList.add(new BlobTsData(lstD.get(i).getDatetime().getTime(),
                            lstD.get(i).getQuality().getNum(),
                            lstD.get(i).getBlob()));
                }
                return tsDataList;
            } else {
                throw new WisdomException("Golden不支持历史数据插值方法：" + tsQuery.getInterpolation());
            }
        } finally {
            if (base != null)
                base.close();
            if (his != null)
                his.close();
        }
    }

    public List<TsData> getStringTsDataHistory(TsQuery tsQuery) throws Exception {
        ServerImpl connection = null;
        try {
            connection = goldenConfig.getGoldenConnectionPool().getConnection();
            HistorianImpl his = new HistorianImpl(connection);
            BaseImpl base = new BaseImpl(connection);
            List<MinPoint> lstPoint = base.findPoints(new String[]{tsQuery.getTsPoint().getId()});
            int id = lstPoint.get(0).getId();
            Date startDate = new Date(tsQuery.getStartTs());
            Date endDate = new Date(tsQuery.getEndTs());
            if (tsQuery.getInterpolation() == Interpolation.RAW) {
                List<StringData> lstD = his.getArchivedStringValues(id, goldenConfig.getQueryHistoryLimit(), startDate, endDate);
                List<TsData> tsDataList = new ArrayList<>();
                for (int i = 0; i < lstD.size(); i++) {
                    tsDataList.add(new StringTsData(lstD.get(i).getDatetime().getTime(),
                            lstD.get(i).getQuality().getNum(),
                            lstD.get(i).getValue()));
                }
                return tsDataList;
            } else {
                throw new WisdomException("Golden不支持历史数据插值方法：" + tsQuery.getInterpolation());
            }
        } finally {
            if (connection != null)
                goldenConfig.getGoldenConnectionPool().returnConnection(connection);
        }
    }

    public List<DoubleStatData> getDoubleStatDataHistory(TsQuery tsQuery) throws Exception {
        if (tsQuery.getTsPoint().getTsDataType() != TsDataType.DOUBLE) {
            throw new WisdomException("无效的数据类型：" + tsQuery.getTsPoint().getTsDataType());
        }
        ServerImpl connection = null;
        try {
            connection = goldenConfig.getGoldenConnectionPool().getConnection();
            HistorianImpl his = new HistorianImpl(connection);
            BaseImpl base = new BaseImpl(connection);
            List<MinPoint> lstPoint = base.findPoints(new String[]{tsQuery.getTsPoint().getId()});
            int id = lstPoint.get(0).getId();
            Date startDate = new Date(tsQuery.getStartTs());
            Date endDate = new Date(tsQuery.getEndTs());
            int count = (int) Math.ceil((tsQuery.getEndTs() - tsQuery.getStartTs()) / (tsQuery.getInterval() * 1000));
            List<SummaryEntity> lstSum = his.getNumberSummaryInBatches(id, count, tsQuery.getInterval()*1000, startDate, endDate);
            List<DoubleStatData> result = new ArrayList<>();
            for (SummaryEntity s : lstSum) {
                DoubleTsData maxData = new DoubleTsData(s.getMaxValueTime().getTime(), (short) 1, s.getMaxValue());
                DoubleTsData minData = new DoubleTsData(s.getMinValueTime().getTime(), (short) 1, s.getMinValue());
                DoubleTsData avgData = new DoubleTsData(s.getMinValueTime().getTime(), (short) 1, s.getCalcAvg());
                DoubleStatData statData = new DoubleStatData(avgData, maxData, minData);
                result.add(statData);
            }

            return result;
        } finally {
            if (connection != null)
                goldenConfig.getGoldenConnectionPool().returnConnection(connection);
        }
    }

    @Override
    public boolean writeHistoryValue(List<TsPointData> dataList) throws Exception {
        ServerImpl connection = null;
        try {
            connection = goldenConfig.getGoldenConnectionPool().getConnection();
            HistorianImpl his = new HistorianImpl(connection);
            BaseImpl base = new BaseImpl(connection);

            RtdbData[] rtdbDataArray = new RtdbData[dataList.size()];

            for (int i = 0; i < dataList.size(); i++) {
                List<MinPoint> pointList = base.findPoints(new String[]{dataList.get(i).getTagName()});
                MinPoint point = null;
                if (pointList.size() > 0)
                    point = pointList.get(0);
                else
                    continue;
                RtdbData rtdbData = new RtdbData();
                rtdbData.setId(point.getId());
                Date dateTime = new Date();
                dateTime.setTime(dataList.get(i).getTsData().getTs());
                rtdbData.setDate(dateTime);
                rtdbData.setGoldenType(ValueType.parseToGoldenType(point.getValueType().getNum()));
                if (dataList.get(i).getTsData().getDoubleValue().isPresent()) {
                    rtdbData.setValue(dataList.get(i).getTsData().getDoubleValue().get());
                } else if (dataList.get(i).getTsData().getStringValue().isPresent()) {
                    rtdbData.setValue(dataList.get(i).getTsData().getStringValue().get());
                } else if (dataList.get(i).getTsData().getLongValue().isPresent()) {
                    rtdbData.setValue(dataList.get(i).getTsData().getLongValue().get());
                } else if (dataList.get(i).getTsData().getBooleanValue().isPresent()) {
                    rtdbData.setValue(dataList.get(i).getTsData().getBooleanValue().get() ? 1 : 0);
                } else if (dataList.get(i).getTsData().getCoordinateValue().isPresent()) {
                    CoorData coor = new CoorData();
                    coor.setId(point.getId());
                    coor.setDateTime(dateTime);
                    coor.setX((float) dataList.get(i).getTsData().getCoordinateValue().get().getLatitude());
                    coor.setY((float) dataList.get(i).getTsData().getCoordinateValue().get().getLongitude());
                    coor.setQuality((short) 0);
                    rtdbData.setValue(coor);
                } else if (dataList.get(i).getTsData().getBooleanValue().isPresent()) {
                    rtdbData.setValue(dataList.get(i).getTsData().getBlobValue().get());
                }
                rtdbData.setQuality((short) 0);

                rtdbDataArray[i] = rtdbData;
            }
            int count = his.putArchivedValues(rtdbDataArray);
            if (count > 0)
                return true;
            else
                return false;

        } catch (Exception e) {
            return false;
        } finally {
            if (connection != null)
                goldenConfig.getGoldenConnectionPool().returnConnection(connection);
        }

    }

    public Map<String, TsData> getHistorySection(List<TsPoint> tsPoints, Long ts) throws Exception {
        Map<TsDataType, List<TsPoint>> pointGroup = tsPoints.stream().collect(Collectors.groupingBy(TsPoint::getTsDataType));
        Map<String, TsData> result = new HashMap<>();
        RtdbHisMode mode = RtdbHisMode.RTDB_NEXT;
        mode = RtdbHisMode.RTDB_PREVIOUS;//获取上一个值
        Date[] dateTime = new Date[tsPoints.size()];
        for (int i = 0; i < tsPoints.size(); i++) {
            Date time = new Date();
            time.setTime(ts);
            dateTime[i] = time;
        }
        for (Map.Entry<TsDataType, List<TsPoint>> entry : pointGroup.entrySet()) {
            String[] tagNames = entry.getValue().stream().map(TsPoint::getId).toArray(String[]::new);
            if (entry.getKey() == TsDataType.DOUBLE)
                result.putAll(getFloatHistorySection(dateTime, mode, tagNames));
            if (entry.getKey() == TsDataType.BOOLEAN)
                result.putAll(getBooleanHistorySection(dateTime, mode, tagNames));
        }
        return result;
    }

    /**
     * @param dateTime 时间点数组
     * @param mode     取值方式： RTDB_NEXT(0)下一个最近的数据, RTDB_PREVIOUS(1)上一个最近的数据, RTDB_EXACT(2)如果没有值抛异常,  RTDB_INTER(3)内插值数据;
     * @param tagName  时间点（秒级）
     * @return
     * @throws Exception
     */
    public Map<String, TsData> getFloatHistorySection(Date[] dateTime, RtdbHisMode mode, String... tagName) throws Exception {
        Map<String, TsData> result = new HashMap<>();
        ServerImpl connection = null;
        try {
            connection = goldenConfig.getGoldenConnectionPool().getConnection();
            HistorianImpl his = new HistorianImpl(connection);
            BaseImpl base = new BaseImpl(connection);
            List<MinPoint> pointList = base.findPoints(tagName);
            List<DoubleData> doubleDataList = his.getFloatCrossSectionValues(pointList.stream().mapToInt(MinPoint::getId).toArray(), dateTime, mode);
            for (int i = 0; i < tagName.length; i++) {
                result.put(tagName[i], new DoubleTsData(doubleDataList.get(i).getDateTime().getTime(),
                        doubleDataList.get(i).getQuality(),
                        doubleDataList.get(i).getValue()));
            }
        } finally {
            if (connection != null)
                goldenConfig.getGoldenConnectionPool().returnConnection(connection);
        }
        return result;
    }

    /**
     * @param dateTime 时间点数组
     * @param mode     取值方式： RTDB_NEXT(0)下一个最近的数据, RTDB_PREVIOUS(1)上一个最近的数据, RTDB_EXACT(2)如果没有值抛异常,  RTDB_INTER(3)内插值数据;
     * @param tagName  时间点（秒级）
     * @return
     * @throws Exception
     */
    public Map<String, TsData> getBooleanHistorySection(Date[] dateTime, RtdbHisMode mode, String... tagName) throws Exception {
        Map<String, TsData> result = new HashMap<>();
        ServerImpl connection = null;
        try {
            connection = goldenConfig.getGoldenConnectionPool().getConnection();
            HistorianImpl his = new HistorianImpl(connection);
            BaseImpl base = new BaseImpl(connection);

            List<MinPoint> pointList = base.findPoints(tagName);

            List<IntData> intDataList = his.getIntCrossSectionValues(pointList.stream().mapToInt(MinPoint::getId).toArray(), dateTime, mode);
            for (int i = 0; i < tagName.length; i++) {
                result.put(tagName[i], new BooleanTsData(intDataList.get(i).getDateTime().getTime(), (short) 0,
                        Boolean.valueOf(String.valueOf(intDataList.get(i).getValue()))));
            }
        } finally {
            if (connection != null)
                goldenConfig.getGoldenConnectionPool().returnConnection(connection);
        }
        return result;
    }

}
