package cz.utb.tadis.charts.util;

import cz.utb.tadis.charts.model.*;
import org.springframework.stereotype.Component;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.List;

@Component
public class ChartDataUtil {

    @PersistenceContext
    private EntityManager em;

    public List<ChartDataPoint> getChartData(Chart chart){
        String queryString = "";
        ChartData d = chart.getChartData();
        switch(chart.getType()){
            case COLUMN:
            case PIE:
            case LINE:
                queryString = "SELECT NEW cz.utb.tadis.charts.model.UnprocessedChartDataPoint(count(a), "+d.getX().getColumn()+", "+d.getY().getColumn()+") FROM "+d.getX().getSourceTables()+" WHERE "+d.getX().getConstraint()+" AND "+d.getY().getConstraint()+" AND a.date >= to_date('"+chart.getFromDate()+"', 'yyyy-MM-dd') AND a.date <= to_date('"+chart.getToDate()+"', 'yyyy-MM-dd') GROUP BY "+ d.getX().getColumn() +" ORDER BY "+d.getX().getColumn();
                break;
            case BUBBLE:
                queryString = "SELECT NEW cz.utb.tadis.charts.model.UnprocessedChartDataPoint(count(a), "+d.getX().getColumn()+", "+d.getY().getColumn()+", "+ d.getZ().getColumn() + ") FROM "+d.getX().getSourceTables()+" WHERE "+d.getX().getConstraint()+" AND "+d.getY().getConstraint()+" AND "+d.getZ().getConstraint()+" AND a.date >= to_date('"+chart.getFromDate()+"', 'yyyy-MM-dd') AND a.date <= to_date('"+chart.getToDate()+"', 'yyyy-MM-dd') GROUP BY "+ d.getX().getColumn() +" ORDER BY "+d.getX().getColumn();
                break;
            case BAR:
                queryString = "SELECT NEW cz.utb.tadis.charts.model.UnprocessedChartDataPoint(count(a), "+d.getX().getColumn()+", "+d.getY().getColumn()+") FROM "+d.getY().getSourceTables()+" WHERE "+d.getX().getConstraint()+" AND "+d.getY().getConstraint()+" AND a.date >= to_date('"+chart.getFromDate()+"', 'yyyy-MM-dd') AND a.date <= to_date('"+chart.getToDate()+"', 'yyyy-MM-dd') GROUP BY "+ d.getY().getColumn() +" ORDER BY "+d.getY().getColumn();
                break;
            default:
                throw new IllegalArgumentException("Invalid chart type");
        }

        TypedQuery<UnprocessedChartDataPoint> query = em.createQuery(queryString, UnprocessedChartDataPoint.class);
        List<UnprocessedChartDataPoint> queryResults = query.getResultList();

        List<UnprocessedChartDataPoint> groupedResults = new ArrayList<>();
        if(ChartAxisType.NUMERIC_VALUE.equals(d.getX().getType()) && d.getxInterval() != null && d.getxInterval() >= 1 && queryResults.size() > 0){

            int lowestX = getLowestXDataPoint(queryResults);
            int highestX = lowestX + d.getxInterval() - 1;

            UnprocessedChartDataPoint currentDataPoint = null;

            for (UnprocessedChartDataPoint point : queryResults){

                if ((Integer)point.getX() > highestX){
                    currentDataPoint.setX(lowestX+" - "+highestX);
                    groupedResults.add(currentDataPoint);

                    lowestX = ((int)Math.floor(((Integer)point.getX() - lowestX)/d.getxInterval()) * d.getxInterval()) + lowestX;
                    highestX = lowestX + d.getxInterval() - 1;

                    currentDataPoint = null;
                }

                if (currentDataPoint == null)
                    currentDataPoint = new UnprocessedChartDataPoint(point.getCount(), point.getX(), point.getY(), point.getZ());
                else {
                    if (d.getY() != null) {
                        if (ChartAxisType.AVG.equals(d.getY().getType())) {
                            currentDataPoint.setY((((Double) currentDataPoint.getY() * currentDataPoint.getCount()) + ((Double) point.getY() * point.getCount())) / (currentDataPoint.getCount() + point.getCount()));
                        }
                        if (ChartAxisType.SUM.equals(d.getY().getType()) || ChartAxisType.COUNT.equals(d.getY().getType())) {
                            currentDataPoint.setY((Long) currentDataPoint.getY() + (Long) point.getY());
                        }
                    }
                    if (d.getZ() != null) {
                        if (ChartAxisType.AVG.equals(d.getZ().getType())) {
                            currentDataPoint.setZ((((Double) currentDataPoint.getZ() * currentDataPoint.getCount()) + ((Double) point.getZ() * point.getCount())) / (currentDataPoint.getCount() + point.getCount()));
                        }
                        if (ChartAxisType.SUM.equals(d.getZ().getType()) || ChartAxisType.COUNT.equals(d.getZ().getType())) {
                            currentDataPoint.setZ((Long) currentDataPoint.getZ() + (Long) point.getZ());
                        }
                    }
                    currentDataPoint.setCount(currentDataPoint.getCount() + point.getCount());
                }
            }

            currentDataPoint.setX(lowestX+" - "+highestX);
            groupedResults.add(currentDataPoint);
        }
        else
            groupedResults = queryResults;

        List<ChartDataPoint> finalResults = new ArrayList<>();
        for (UnprocessedChartDataPoint dp: groupedResults){
            finalResults.add(new ChartDataPoint(dp.getX(), dp.getY(), dp.getZ()));
        }
        return finalResults;
    }

    private Integer getLowestXDataPoint(List<UnprocessedChartDataPoint> dataPoints){
        if (dataPoints.size() < 1 || dataPoints.get(0) == null || !(dataPoints.get(0).getX() instanceof Number))
            return null;
        return (Integer)dataPoints.get(0).getX();
    }







}
