package store.service;

import com.sleepycat.je.DatabaseException;
import com.sleepycat.persist.*;
import core.manager.ConstantManager;
import gui.chart.CategoryTableXYDatasetRDA;
import gui.chart.panel.NameChartDataset;
import gui.chart.panel.StackChartPanel;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.jfree.chart.util.GanttParam;
import profile.IProfile;
import store.BerkleyDB;
import store.dao.olap.*;
import store.entity.olap.*;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Slf4j
@Singleton
public class OlapDAO extends QueryService {
    private BerkleyDB berkleyDB;
    private EntityStore store;

    @Getter @Setter private IProfile iProfile;

    @Getter public IAshAggrMinDataDAO ashAggrMinDataDAO;
    @Getter public IAshAggrMinData15SecDAO ashAggrMinData15SecDAO;
    @Getter public IAshAggrMinData1MinDAO ashAggrMinData1MinDAO;

    private PrimaryIndex<Integer, AshParameter> ashParameterPrimaryIndex;
    private PrimaryIndex<Integer, AshWaitEvent> ashWaitEventPrimaryIndex;
    private PrimaryIndex<Integer, AshUser> ashUserPrimaryIndex;

    private SecondaryIndex<String, Integer, AshParameter> ashParameterSecondaryIndexStrValue;

    private SecondaryIndex<String, Integer, AshWaitEvent> ashWaitEventSecondaryIndexStrValue;
    private SecondaryIndex<Byte, Integer, AshWaitEvent> ashWaitEventSecondaryIndexByteValue;

    private SecondaryIndex<String, Integer, AshUser> ashUserSecondaryIndexStrValue;

    @Inject
    public OlapDAO(BerkleyDB berkleyDB) throws DatabaseException {
        this.berkleyDB = berkleyDB;
    }

    public void init () throws DatabaseException {
        this.store = this.berkleyDB.getStore();

        this.ashAggrMinDataDAO = new AshAggrMinDataDAO(this.store, this);
        this.ashAggrMinData15SecDAO = new AshAggrMinData15SecDAO(this.store, this);
        this.ashAggrMinData1MinDAO = new AshAggrMinData1MinDAO(this.store, this);

        this.ashParameterPrimaryIndex = store.getPrimaryIndex(Integer.class, AshParameter.class);
        this.ashWaitEventPrimaryIndex = store.getPrimaryIndex(Integer.class, AshWaitEvent.class);
        this.ashUserPrimaryIndex = store.getPrimaryIndex(Integer.class, AshUser.class);

        this.ashParameterSecondaryIndexStrValue = store.getSecondaryIndex(ashParameterPrimaryIndex, String.class, "paramValue");

        this.ashWaitEventSecondaryIndexStrValue = store.getSecondaryIndex(ashWaitEventPrimaryIndex, String.class, "eventValue");
        this.ashWaitEventSecondaryIndexByteValue = store.getSecondaryIndex(ashWaitEventPrimaryIndex, Byte.class, "waitClass");

        this.ashUserSecondaryIndexStrValue = store.getSecondaryIndex(ashUserPrimaryIndex, String.class, "userName");
    }

    public void putUserIdUsername(AshUser ashUser){
        this.ashUserPrimaryIndex.putNoOverwrite(ashUser);
    }

    public int getCheckOrLoadParameter(String parameter, String[] additionalParams){
        if (!this.ashParameterSecondaryIndexStrValue.contains(parameter)){
            this.ashParameterPrimaryIndex.putNoOverwrite(
                    new AshParameter(0, parameter, additionalParams)
            );
        }
        return this.ashParameterSecondaryIndexStrValue.get(parameter).getParamId();
    }

    public String getStrParameterValueById(int paramId){
        return this.ashParameterPrimaryIndex.get(paramId).getParamValue();
    }

    public int getParameterIdByStrValue(String paramStrValue){
        return this.ashParameterSecondaryIndexStrValue.get(paramStrValue).getParamId();
    }

    public String[] getAdditStrArrayParameters(int paramId){
        return this.ashParameterPrimaryIndex.get(paramId).getAdditionalParams();
    }

    public int getCheckOrLoadWaitEvent(String waitEvent, byte waitClass){
        if (!this.ashWaitEventSecondaryIndexStrValue.contains(waitEvent)){
            this.ashWaitEventPrimaryIndex.putNoOverwrite(
                    new AshWaitEvent(0, waitEvent, waitClass)
            );
        }
        return this.ashWaitEventSecondaryIndexStrValue.get(waitEvent).getEventId();
    }

    public byte getClassIdForWaitEventId(int waitId){
        return this.ashWaitEventPrimaryIndex.get(waitId).getWaitClass();
    }

    public String getEventStrValueForWaitEventId(int waitEventId){
        return this.ashWaitEventPrimaryIndex.get(waitEventId).getEventValue();
    }

    public String getUsername(int userId){
        Optional<AshUser> opt = Optional.ofNullable(this.ashUserPrimaryIndex.get(userId));

        if (opt.isPresent()){
            return opt.get().getUserName();
        } else {
            return "";
        }
    }

    public int getEventGrp(int eventId){
        if (this.ashWaitEventPrimaryIndex.contains(eventId)){
            return this.ashWaitEventPrimaryIndex.get(eventId).getWaitClass();
        } else {
            return -1;
        }
    }

    public void loadDataToCategoryTableXYDatasetRTVBySqlSessionID(GanttParam ganttParam,
                                                                  CategoryTableXYDatasetRDA categoryTableXYDatasetRDA,
                                                                  StackChartPanel stackChartPanel) {
        long start = (long) ganttParam.getBeginTime();
        long end = (long) ganttParam.getEndTime();

        String paramId;

        if (!ganttParam.getSqlId().isEmpty()) { // SQL_ID
            paramId = ganttParam.getSqlId();
        } else {                                // SessionId + SerialId
            paramId = ganttParam.getSessionId() + "_" + ganttParam.getSerial();
        }

        LinkedHashSet<Integer> uniqueLHashSetEventLst = new LinkedHashSet();
        LinkedHashMap<Long, HashMap<Integer, Integer>> hashMap = new LinkedHashMap();

        double range = (end - start) / ConstantManager.MAX_POINT_PER_GRAPH; // maxPointPerGraph default value is 260

        for (long d = start; d <= end; d += Math.round(range)) {

            long end0 = d + Math.round(range);

            EntityCursor<AshAggrMinData15Sec> cursor =
                    getAshAggrMinData15SecDAO().getAshAggrEntityCursorRangeQuery(d, end0);
            Iterator<AshAggrMinData15Sec> iterator = cursor.iterator();

            hashMap.putIfAbsent(d, new HashMap<>());

            /////////////////////////////////////////////
            while (iterator.hasNext()) {
                AshAggrMinData15Sec sl = iterator.next();
                boolean needToProcess;
                boolean checkParamId = getStrParameterValueById(sl.getCompositeKey().getParamId()).contains(paramId);

                if (!ganttParam.getSqlId().isEmpty()
                        & checkParamId) { // SQL_ID
                    needToProcess = true;
                } else if (!ganttParam.getSessionId().isEmpty()
                        & checkParamId) { // SessionId + SerialId
                    needToProcess = true;
                } else {
                    needToProcess = false;
                }

                if (needToProcess) {

                    int[] waitId = sl.getMatrixValues()[0];
                    int[] waitClass = sl.getMatrixValues()[1];
                    int[] sum = sl.getMatrixValues()[2];

                    for (int i = 0; i < waitClass.length; i++) {
                        int eventId = waitId[i];

                        uniqueLHashSetEventLst.add(eventId);
                        int tmpVal = hashMap.get(d).getOrDefault(eventId, 0);
                        hashMap.get(d).put(eventId, tmpVal + sum[i]);
                    }
                }
            }
            /////////////////////////////////////////////

            cursor.close();
        }

        LinkedHashMap<Integer, String> uniqueLHashSetEventLstStr = new LinkedHashMap<>();
        uniqueLHashSetEventLst.stream().forEach(e -> uniqueLHashSetEventLstStr.put(e, getEventStrValueForWaitEventId(e)));

        uniqueLHashSetEventLstStr.entrySet().stream().forEach(u -> stackChartPanel.getStackedChart().setSeriesPaintDynamicDetail(u.getValue()));

        if (uniqueLHashSetEventLstStr.isEmpty())
            return;

        final CategoryTableXYDatasetRDA catTabXYDtstRTVId = categoryTableXYDatasetRDA;
        hashMap.entrySet().stream().forEach(e -> {
            final double finalD = e.getKey();

            uniqueLHashSetEventLstStr.entrySet().stream().forEach(kk -> {
                catTabXYDtstRTVId.addSeriesValue(finalD,
                        (double) e.getValue().getOrDefault(
                                uniqueLHashSetEventLstStr.entrySet().stream()
                                        .filter(l -> l.getValue().equals(kk.getValue()))
                                        .map(Map.Entry::getKey)
                                        .findFirst()
                                        .orElse(null)
                                , 0) / (range / 1000), kk.getValue());
            });
        });
    }


    public void loadDataToCategoryTableXYDatasetRTVHistoryTA(GanttParam ganttParam,
                                                             CategoryTableXYDatasetRDA categoryTableXYDatasetRDA,
                                                             StackChartPanel stackChartPanel) {

        long start = (long) ganttParam.getBeginTime();
        long end = (long) ganttParam.getEndTime();

        long diffInHours = TimeUnit.MILLISECONDS.toHours(end - start);

        LinkedHashMap<Long, HashMap<Integer, Integer>> hashMap = new LinkedHashMap();

        double range = (end - start) / ConstantManager.MAX_POINT_PER_GRAPH; // maxPointPerGraph default value is 260

        for (long d = start; d <= end; d += Math.round(range)) {

            long end0 = d + Math.round(range);

            if (diffInHours > 1){
                loadDataFrom1MinEntityByWaitClass(hashMap, d, end0);
            } else {
                loadDataFrom1SecEntityByWaitClass(hashMap, d, end0);
            }
        }

        LinkedHashMap<Integer, String> uniqueLHashSetEventLstStr = new LinkedHashMap<>();

        this.iProfile.getUniqueTreeEventListByWaitClass().stream().forEach(m -> {
            uniqueLHashSetEventLstStr.put((int) this.iProfile.getWaitClassId(m), m);
        });

        uniqueLHashSetEventLstStr.entrySet().stream().forEach(u ->
                stackChartPanel.getStackedChart().setSeriesPaintDynamicDetail(u.getValue()));

        if (uniqueLHashSetEventLstStr.isEmpty())
            return;

        final CategoryTableXYDatasetRDA catTabXYDtstRTVId = categoryTableXYDatasetRDA;
        hashMap.entrySet().stream().forEach(e -> {
            final double finalD = e.getKey();

            uniqueLHashSetEventLstStr.entrySet().stream().forEach(kk -> {
                catTabXYDtstRTVId.addSeriesValue(finalD,
                        (double) e.getValue().getOrDefault(
                                uniqueLHashSetEventLstStr.entrySet().stream()
                                        .filter(l -> l.getValue().equals(kk.getValue()))
                                        .map(Map.Entry::getKey)
                                        .findFirst()
                                        .orElse(null)
                                , 0) / (range / 1000), kk.getValue());
            });
        });
    }

    private void loadDataFrom1MinEntityByWaitClass(LinkedHashMap<Long, HashMap<Integer, Integer>> hashMap, long d, long end0){

        EntityCursor<AshAggrMinData1Min> cursor =
                getAshAggrMinData1MinDAO().getAshAggrEntityCursorRangeQuery(d, end0);
        Iterator<AshAggrMinData1Min> iterator = cursor.iterator();

        hashMap.putIfAbsent(d, new HashMap<>());

        while (iterator.hasNext()) {
            AshAggrMinData1Min sl = iterator.next();
            int[] waitClass = sl.getMatrixValues()[1];
            int[] sum = sl.getMatrixValues()[2];

            for (int i = 0; i < waitClass.length; i++) {
                int waitIdI = waitClass[i];

                int tmpVal = hashMap.get(d).getOrDefault(waitIdI, 0);
                hashMap.get(d).put(waitIdI, tmpVal + sum[i]);
            }
        }

        cursor.close();
    }
    
    private void loadDataFrom15SecEntityByWaitClass(LinkedHashMap<Long, HashMap<Integer, Integer>> hashMap, long d, long end0){

        EntityCursor<AshAggrMinData15Sec> cursor =
                getAshAggrMinData15SecDAO().getAshAggrEntityCursorRangeQuery(d, end0);
        Iterator<AshAggrMinData15Sec> iterator = cursor.iterator();

        hashMap.putIfAbsent(d, new HashMap<>());

        while (iterator.hasNext()) {
            AshAggrMinData15Sec sl = iterator.next();
            int[] waitClass = sl.getMatrixValues()[1];
            int[] sum = sl.getMatrixValues()[2];

            for (int i = 0; i < waitClass.length; i++) {
                int waitIdI = waitClass[i];
                int tmpVal = hashMap.get(d).getOrDefault(waitIdI, 0);
                hashMap.get(d).put(waitIdI, tmpVal + sum[i]);
            }
        }

        cursor.close();
    }

    private void loadDataFrom1SecEntityByWaitClass(LinkedHashMap<Long, HashMap<Integer, Integer>> hashMap, long d, long end0){

        EntityCursor<AshAggrMinData> cursor =
            getAshAggrMinDataDAO().getAshAggrEntityCursorRangeQuery(d, end0);
        Iterator<AshAggrMinData> iterator = cursor.iterator();

        hashMap.putIfAbsent(d, new HashMap<>());

        while (iterator.hasNext()) {
            AshAggrMinData sl = iterator.next();
            int[] waitClass = sl.getMatrixValues()[1];
            int[] sum = sl.getMatrixValues()[2];

            for (int i = 0; i < waitClass.length; i++) {
                int waitIdI = waitClass[i];
                int tmpVal = hashMap.get(d).getOrDefault(waitIdI, 0);
                hashMap.get(d).put(waitIdI, tmpVal + sum[i]);
            }
        }

        cursor.close();
    }

    public void loadDataToCategoryTableXYDatasetRTVHistoryDetail(GanttParam ganttParam,
                                                                  LinkedHashSet<NameChartDataset> nameChartDatasetList) {
        long start = (long) ganttParam.getBeginTime();
        long end = (long) ganttParam.getEndTime();

        HashMap<Integer, LinkedHashSet<Integer>> uniqueLHashSetEventLst0 = new HashMap<>();
        this.iProfile.getUniqueTreeEventListByWaitClass().stream().forEach(m -> {
            uniqueLHashSetEventLst0.put((int) this.iProfile.getWaitClassId(m), new LinkedHashSet());
        });

        LinkedHashMap<Long, HashMap<Integer, Integer>> hashMap = new LinkedHashMap();

        double range = (end - start) / ConstantManager.MAX_POINT_PER_GRAPH; // maxPointPerGraph default value is 260

        for (long d = start; d <= end; d += Math.round(range)) {

            long end0 = d + Math.round(range);

            /****/
            EntityCursor<AshAggrMinData15Sec> cursor =
                    getAshAggrMinData15SecDAO().getAshAggrEntityCursorRangeQuery(d, end0);
            Iterator<AshAggrMinData15Sec> iterator = cursor.iterator();

            hashMap.putIfAbsent(d, new HashMap<>());

            while (iterator.hasNext()) {
                AshAggrMinData15Sec sl = iterator.next();
                    int[] waitId = sl.getMatrixValues()[0];
                    int[] waitClass = sl.getMatrixValues()[1];
                    int[] sum = sl.getMatrixValues()[2];

                    for (int i = 0; i < waitClass.length; i++) {
                        int eventId = waitId[i];
                        uniqueLHashSetEventLst0.get(waitClass[i]).add(eventId);
                        int tmpVal = hashMap.get(d).getOrDefault(eventId, 0);
                        hashMap.get(d).put(eventId, tmpVal + sum[i]);
                    }
            }

            cursor.close();
        }

        HashMap<Integer, LinkedHashMap<Integer, String>> uniqueLHashSetEventLstStr0 = new HashMap<>();

        uniqueLHashSetEventLst0.entrySet()
                .stream()
                .forEach(e -> {
                    uniqueLHashSetEventLstStr0.put(e.getKey(), new LinkedHashMap<>());
                    e.getValue().stream().forEach(m -> uniqueLHashSetEventLstStr0.get(e.getKey())
                            .put(m, getEventStrValueForWaitEventId(m)));
                });

        nameChartDatasetList.stream().forEach(e -> {

            LinkedHashMap<Integer, String> uniqueLHashSetEventLstStr =
                    uniqueLHashSetEventLstStr0.entrySet()
                            .stream()
                            .filter(f -> this.iProfile.getWaitClass((byte) (int) f.getKey()).equalsIgnoreCase(e.getName()))
                            .findAny().get().getValue();

            uniqueLHashSetEventLstStr.entrySet()
                    .forEach(u -> {
                        e.getStackChartPanel().getStackedChart().setSeriesPaintDynamicDetail(u.getValue());
                    });

            final CategoryTableXYDatasetRDA catTabXYDtstRTVId = e.getDatasetRDA();

            hashMap.entrySet().stream().forEach(k -> {
                final double finalD = k.getKey();

                uniqueLHashSetEventLstStr.entrySet()
                        .forEach(kk -> {

                            catTabXYDtstRTVId.addSeriesValue(finalD,
                                    (double) k.getValue().getOrDefault(
                                            uniqueLHashSetEventLstStr.entrySet()
                                                    .stream()
                                                    .filter(l -> l.getValue().equals(kk.getValue()))
                                                    .map(Map.Entry::getKey)
                                                    .findFirst()
                                                    .orElse(null)
                                            , 0) / (range / 1000), kk.getValue());

                        });
            });
        });
    }

    public void deleteOlapData(long start, long end){
        try (EntityCursor<AshAggrMinData> cursor = this.ashAggrMinDataDAO.getAshAggrEntityCursorRangeQuery(start, end)) {
            for (AshAggrMinData entity = cursor.first(); entity != null; entity = cursor.next()) {
                cursor.delete();
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        }

        try (EntityCursor<AshAggrMinData15Sec> cursor = this.ashAggrMinData15SecDAO.getAshAggrEntityCursorRangeQuery(start, end)) {
            for (AshAggrMinData15Sec entity = cursor.first(); entity != null; entity = cursor.next()) {
                cursor.delete();
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        }

        try (EntityCursor<AshAggrMinData1Min> cursor = this.ashAggrMinData1MinDAO.getAshAggrEntityCursorRangeQuery(start, end)) {
            for (AshAggrMinData1Min entity = cursor.first(); entity != null; entity = cursor.next()) {
                cursor.delete();
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }

}
