package gui.gantt;

import com.egantt.model.drawing.DrawingState;
import com.egantt.model.drawing.part.ListDrawingPart;
import com.egantt.model.drawing.state.BasicDrawingState;
import com.sleepycat.persist.EntityCursor;
import core.manager.ConstantManager;
import ext.egantt.drawing.module.BasicPainterModule;
import ext.egantt.swing.GanttDrawingPartHelper;
import lombok.Getter;
import lombok.Setter;
import org.jfree.chart.util.GanttParam;
import profile.IProfile;
import store.service.OlapDAO;
import store.StoreManager;
import store.cache.CompositeKeyCache2;
import store.cache.TripleValueCache;
import store.entity.olap.AshAggrMinData15Sec;
import utility.Utils;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

public class GanttDataById2 {
    private StoreManager storeManager;
    private OlapDAO olapDAO;

    private final byte parameterGroupId;

    private long percentPrev = 0;
    private int scaleToggle = 0;

    private double scale = 0.8;
    private int waitClassId = -1;

    @Getter @Setter private IProfile iProfile;

    //private int minNumberOfRows = 100;

    public GanttDataById2(StoreManager storeManager, byte parameterGroupId){
        this.storeManager = storeManager;
        this.olapDAO = storeManager.getOlapDAO();
        this.parameterGroupId = parameterGroupId;
    }

    public void clear(){
        hashmapCache.clear();
    }

    /***********  New version  ************/
    private Map<CompositeKeyCache2, List<TripleValueCache>> hashmapCache = new HashMap<>();

    public void computeGanttDataFromBDB(GanttParam ganttParameter){
        GanttParam ganttParamTo = ganttParameter;

        long start0 = (long) ganttParamTo.getBeginTime();
        long end0 = (long) ganttParamTo.getEndTime();

        try {
            this.waitClassId = ganttParamTo.getWaitClass().isEmpty()
                    ? -1 : this.getIProfile().getWaitClassId(ganttParamTo.getWaitClass());
        } catch (Exception e){
            e.printStackTrace();
            System.out.println(e.getMessage());
        }

        LocalDateTime dt = LocalDateTime.ofInstant(Instant.ofEpochMilli(start0), ZoneId.systemDefault());

        LocalDateTime begin = LocalDateTime.of(
                dt.getYear(),
                dt.getMonth(),
                dt.getDayOfMonth(),
                dt.getHour(),
                dt.getMinute(),
                dt.getSecond());

        long start1 = begin.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();

        EntityCursor<AshAggrMinData15Sec> cursor =
                this.olapDAO.getAshAggrMinData15SecDAO().getAshAggrEntityCursorRangeQuery(start1, end0 + ConstantManager.CURRENT_WINDOW / ConstantManager.MAX_POINT_PER_GRAPH);
        Iterator<AshAggrMinData15Sec> iterator = cursor.iterator();

        /////////////////////////////////////////////
        while (iterator.hasNext()) {
            AshAggrMinData15Sec sl = iterator.next();
            boolean needToProcess;

            if (this.parameterGroupId == 0
                    & !this.olapDAO.getStrParameterValueById(sl.getCompositeKey().getParamId()).contains(
                        ConstantManager.NULL_VALUE)){ // SQL_ID
                needToProcess = true;
            } else if (this.parameterGroupId == 1 ){ // SessionId + SerialId
                needToProcess = true;
            } else {
                needToProcess = false;
            }

            if (needToProcess) {
                loadAshRowAggrData(sl);
            }
        }
        /////////////////////////////////////////////
        cursor.close();
    }

    public void loadAshRowAggrData(AshAggrMinData15Sec ashAggrMinData){

        /** !!! Refactor it !!! **/
        int paramId = ashAggrMinData.getCompositeKey().getParamId();
        String[] outputParam = this.olapDAO.getStrParameterValueById(ashAggrMinData.getCompositeKey().getParamId()).split("_");

        if (this.parameterGroupId == 0 & !outputParam[0].equalsIgnoreCase(ConstantManager.NULL_VALUE)){
            paramId = this.olapDAO.getParameterIdByStrValue(outputParam[0]);
        } else {
            if (!outputParam[0].equalsIgnoreCase(ConstantManager.NULL_VALUE)){
                int paramId0 = olapDAO.getCheckOrLoadParameter(ConstantManager.NULL_VALUE + "_" + outputParam[1] + "_" + outputParam[2],
                         this.olapDAO.getAdditStrArrayParameters(paramId)); // 1 sec

                paramId = paramId0;
            }
        }
        /** !!! Refactor it !!! **/

        CompositeKeyCache2 compositeKeyCache2
                = new CompositeKeyCache2(paramId, this.parameterGroupId);

        Map<Integer, Integer> allWaitId1 =
                ConstantManager.zipToMap(ashAggrMinData.getWaitId(waitClassId), //mtrx[0]::waitId;
                        ashAggrMinData.getSum(waitClassId));                    //mtrx[2]::sum;

        if (allWaitId1.size() == 0)
            return;

        if (hashmapCache.containsKey(compositeKeyCache2)){
            List<TripleValueCache> tripleValueCache = hashmapCache.get(compositeKeyCache2);

            allWaitId1.entrySet().forEach(e ->{

                if (tripleValueCache.stream().anyMatch(k -> k.waitEventId == e.getKey())) {

                    if (tripleValueCache.stream()
                            .filter(k -> k.waitEventId == e.getKey()).findAny().isPresent()) {
                        tripleValueCache.stream()
                                .filter(k -> k.waitEventId == e.getKey())
                                .findAny().get().setSum(e.getValue());
                    } else {
                        tripleValueCache.add(new TripleValueCache(e.getKey(),
                                (byte) this.olapDAO.getEventGrp(e.getKey()),
                                e.getValue()));
                    }
                } else {
                    tripleValueCache.add(new TripleValueCache(e.getKey(),
                            (byte) this.olapDAO.getEventGrp(e.getKey()),
                            e.getValue()));
                }
            });

        } else {
            hashmapCache.put(compositeKeyCache2, new ArrayList<>());
            List<TripleValueCache> tripleValueCache = hashmapCache.get(compositeKeyCache2);

            allWaitId1.entrySet().forEach(e ->{
                tripleValueCache.add(new TripleValueCache(e.getKey(),
                        (byte) this.olapDAO.getEventGrp(e.getKey()),
                        e.getValue()));
            });
        }
    }

    /***********  New version  ************/

    /**
     * @param columnCnt count of fields
     * @return
     */
    public Object[][] getDataToGantt(int columnCnt) {

        Object[][] data = new Object[hashmapCache.size()][columnCnt];

        final GanttDrawingPartHelper partHelper = new GanttDrawingPartHelper();

        long countOfAllRowsId = this.hashmapCache.entrySet()
                .stream()
                .flatMap(f -> f.getValue().stream())
                .mapToInt(TripleValueCache::getSum)
                .sum();

        AtomicInteger atomicInteger = new AtomicInteger(0);
        this.hashmapCache.entrySet()
                .stream()
                .sorted((f1, f2) ->
                        Integer.compare(
                                f2.getValue().stream().mapToInt(TripleValueCache::getSum).sum(),
                                f1.getValue().stream().mapToInt(TripleValueCache::getSum).sum()
                        ))
                .forEach(e -> {
                    int rowNumber = atomicInteger.getAndIncrement();
                    data[rowNumber][0] =
                            createDrawingState(partHelper, e.getValue(), countOfAllRowsId);

                    AtomicInteger atomicIntegerInter = new AtomicInteger(1);

                    this.loadDataByRow(data, rowNumber, atomicIntegerInter, e.getKey());

                });

        return data;
    }

    private void loadDataByRow(Object[][] data, int rowNumber, AtomicInteger atomicIntegerInter, CompositeKeyCache2 key){
        String[] outputParam = this.olapDAO.getStrParameterValueById(key.getParamId()).split("_");
        String[] addParam = this.olapDAO.getAdditStrArrayParameters(key.getParamId());

        if (this.parameterGroupId == 0){ // SqlId
            data[rowNumber][atomicIntegerInter.getAndIncrement()] = outputParam[0];
            data[rowNumber][atomicIntegerInter.getAndIncrement()] = addParam[0];

        } else { // SessionId
            data[rowNumber][atomicIntegerInter.getAndIncrement()] = outputParam[1];
            data[rowNumber][atomicIntegerInter.getAndIncrement()] = outputParam[2];

            int userId = 0;

            if (!Objects.equals(addParam[1], ConstantManager.NULL_VALUE)){
                userId = Integer.valueOf(addParam[1]);
            }

            data[rowNumber][atomicIntegerInter.getAndIncrement()] =
                    storeManager.getOlapDAO().getUsername(userId);
            data[rowNumber][atomicIntegerInter.getAndIncrement()] =
                    addParam[2].equalsIgnoreCase(ConstantManager.NULL_VALUE) ? "" : addParam[2];

        }
    }

    private DrawingState createDrawingState(GanttDrawingPartHelper helper,
                                            List<TripleValueCache> me,
                                            double countOfSqls) {

        BasicDrawingState state = helper.createDrawingState();
        ListDrawingPart part = helper.createDrawingPart(false);
        ListDrawingPart textLayer = helper.createDrawingPart(true);

        double countPerSqlID = me.stream().mapToInt(TripleValueCache::getSum).sum();

        double percent = Utils.round(countPerSqlID / countOfSqls * 100, 2);

        String percentText = "" + percent + "%";

			/* < 30  => 2
             * 30-70  => 1
			 * > 70    => 0 */
        if (percentPrev == 0) {
            if (percent > 70) {
                scaleToggle = 0;
            } else if (percent < 70 && percent > 30) {
                scaleToggle = 1;
            } else if (percent < 30) {
                scaleToggle = 2;
            }
        }

        if (percent < 0.6) {
            // Show only percent
            helper.createActivityEntry(new StringBuffer(percentText),
                    new Date(0), new Date(100),
                    BasicPainterModule.BASIC_STRING_PAINTER, ConstantManager.TEXT_PAINTER, textLayer);

            state.addDrawingPart(part);
            state.addDrawingPart(textLayer);
            return state;
        }


        long start = 0;
        scale = Utils.getScale(scaleToggle, percent);

        Map<Integer, Integer> preOrdered = me.stream()
                .collect(
                        Collectors.groupingBy(
                                waitClassId == -1 ?
                                        TripleValueCache::getWaitClassIdInt :
                                        TripleValueCache::getWaitEventId,
                                Collectors.summingInt(TripleValueCache::getSum))
                ).entrySet()
                .stream()
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

        Map<Integer, Integer> resultOrdered = new LinkedHashMap<>();

        preOrdered.entrySet().stream()
                .sorted(Map.Entry.<Integer, Integer>comparingByValue().reversed())
                .forEachOrdered(x -> resultOrdered.put(x.getKey(), x.getValue()));

        for (Map.Entry<Integer, Integer> entry : resultOrdered.entrySet()) {
            Integer key0 = entry.getKey();
            Integer value0 = entry.getValue();

            String keyString = waitClassId == -1
                    ? iProfile.getWaitClass(key0.byteValue())
                    : this.olapDAO.getEventStrValueForWaitEventId(key0);
            double value = value0;

            // Show only not zero activities.
            if (value != 0) {

                double currentGroupPercentNotScale = (value / countPerSqlID) * percent;
                double currentGroupPercent = currentGroupPercentNotScale * scale;

                if (currentGroupPercent < 1.0 && currentGroupPercent >= 0.6) {
                    currentGroupPercent = Utils.round(currentGroupPercent, 0);
                }

                long currGroupPercentL = (long) Utils.round(currentGroupPercent, 0);

                // Set tooltip
                final StringBuffer o = new StringBuffer();
                {
                    o.append("<HTML>");
                    o.append("<b>" + keyString + " " + Utils.round(currentGroupPercentNotScale, 2) + "%" + "</b>");
                    o.append("</HTML>");
                }

                // Exit when prev. egantt < than current egantt graph
                if (percentPrev != 0 &&
                        (start + currGroupPercentL) > percentPrev) {
                    currGroupPercentL = percentPrev - start;
                    helper.createActivityEntry(o, new Date(start), new Date(start + currGroupPercentL), keyString, part);
                    start = start + currGroupPercentL;
                    break;
                }

                // If row only one
                if (currentGroupPercent == 100) {
                    helper.createActivityEntry(o, new Date(start), new Date(currGroupPercentL), keyString, part);
                } else {
                    helper.createActivityEntry(o, new Date(start), new Date(start + currGroupPercentL), keyString, part);
                    start = start + currGroupPercentL;
                }
            }
        }

        // Show percent
        helper.createActivityEntry(new StringBuffer(percentText), new Date(start), new Date(100),
                BasicPainterModule.BASIC_STRING_PAINTER, ConstantManager.TEXT_PAINTER, textLayer);

        state.addDrawingPart(part);
        state.addDrawingPart(textLayer);

        percentPrev = start;


        return state;
    }

    public List<String> getListClassAndEvents(){
        ArrayList<String> arrayList = new ArrayList<>();

        this.hashmapCache.entrySet()
                .stream()
                .forEach(e -> {
                    e.getValue()
                            .stream()
                            .forEach(k -> {
                                if (this.waitClassId == -1) {
                                    if(!arrayList.contains(iProfile.getWaitClass(k.getWaitClassId()))){
                                        arrayList.add(
                                                iProfile.getWaitClass(k.getWaitClassId()));
                                    }
                                } else {
                                    if (!arrayList.contains(this.olapDAO.getEventStrValueForWaitEventId(k.getWaitEventId()))) {
                                        arrayList.add(
                                                this.olapDAO.getEventStrValueForWaitEventId(k.getWaitEventId()));
                                    }
                                }
                            });

                });

        return arrayList;
    }

}
