/*
 * Copyright (c) 2013 idealo internet GmbH -- all rights reserved.
 */
package de.idealo.mongodb.slowops.grapher;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.mongodb.DB;
import com.mongodb.MongoException;
import de.idealo.mongodb.slowops.dto.CollectorServerDto;
import de.idealo.mongodb.slowops.dto.SlowOpsDto;
import de.idealo.mongodb.slowops.monitor.MongoDbAccessor;
import de.idealo.mongodb.slowops.util.ConfigReader;
import de.idealo.mongodb.slowops.util.Util;
import org.jongo.Aggregate.ResultsIterator;
import org.jongo.Jongo;
import org.jongo.MongoCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.*;


/**
 *
 *
 * @author kay.agahd
 * @since 04.03.2013
 * @version $Id: $
 * @copyright idealo internet GmbH
 */




public class Grapher {

    private static final Logger LOG = LoggerFactory.getLogger(Grapher.class);

    private final int scale;

    private final MongoCollection coll;

    public Grapher() {
        coll = getProfilingCollection();
        final String scaleStr = ConfigReader.getString(ConfigReader.CONFIG, Util.Y_AXIS_SCALE,  Util.Y_AXIS_MILLISECONDS);

        if(scaleStr.equals(Util.Y_AXIS_MILLISECONDS)){
            scale = 1;
        }else {
            scale = 1000;//default
        }
        LOG.info("scaleStr: {} scale: {}",  scaleStr, scale);
    }

    private MongoCollection getProfilingCollection() {
        CollectorServerDto serverDto = ConfigReader.getCollectorServer();
;
        try{
            MongoDbAccessor mongo = new MongoDbAccessor(60000, -1, true, serverDto.getAdminUser(), serverDto.getAdminPw(), serverDto.getSsl(), serverDto.getHosts());
            DB db = mongo.getMongoDB(serverDto.getDb());
            Jongo jongo = new Jongo(db);
            MongoCollection result =  jongo.getCollection(serverDto.getCollection());

            if(result == null) {
                throw new IllegalArgumentException("Can't continue without profile collection for " + serverDto.getHosts());
            }

            return result;
        } catch (MongoException e) {
            LOG.error("Exception while connecting to: {}", serverDto.getHosts(), e);
        }
        return null;
    }


    public SlowOpsDto aggregateSlowQueries(StringBuffer filter, Object[] params, StringBuffer groupExp, StringBuffer groupTime) {
        final SlowOpsDto result = new SlowOpsDto();
        final String[] customFields = new String[] {"count", "minSec", "maxSec", "sumSec", "stdDevMs",
                "nRet", "minRet", "maxRet", "avgRet", "stdDevRet",
                "len", "minLen", "maxLen", "avgLen", "stdDevLen",
                "rKeys", "rDocs", "wDocs", "memSort"};

        //List<AggregatedProfiling> queryResult = null;
        ResultsIterator<AggregatedProfiling> queryResult = null;

        LOG.debug("filter: {}", filter);
        LOG.debug("groupExp: {}", groupExp);
        LOG.debug("groupTime: {}", groupTime);


        try {
            final Date begin = new Date();
            queryResult = coll.aggregate(filter.toString(), params)
            .and("{$group:{" +

                    "_id:{" +
                        groupExp.toString() +
                        groupTime.toString() +
                        "}," +
                    "count : { $sum : 1 }," +
                    "millis : { $sum : '$millis' }," +
                    "avgMs : { $avg : '$millis' }," +
                    "minMs : { $min : '$millis' }," +
                    "maxMs : { $max : '$millis' }," +
                    "stdDevMs : { $stdDevPop : '$millis' }," +
                    "nRet : { $sum : '$nret' }," +
                    "avgRet : { $avg : '$nret' }," +
                    "minRet : { $min : '$nret' }," +
                    "maxRet : { $max : '$nret' }," +
                    "stdDevRet : { $stdDevPop : '$nret' }," +
                    "len : { $sum : '$resplen' }," +
                    "avgLen : { $avg : '$resplen' }," +
                    "minLen : { $min : '$resplen' }," +
                    "maxLen : { $max : '$resplen' }," +
                    "stdDevLen : { $stdDevPop : '$resplen' }," +
                    "firstts : { $first : '$ts' }," +
                    "keys : { $sum : '$keys' }," +
                    "docs : { $sum : '$docs' }," +
                    "del : { $sum : '$del' }," +
                    "ins : { $sum : '$ins' }," +
                    "mod : { $sum : '$mod' }," +
                    "sortstages : { $addToSet : '$sortstg' }" +
                    "}" +
                    "}" +
                  "}"
            )
            .and("{$sort:{" +
                    "firstts:1" +
                    "}" +
                   "}"
            )
            .as(AggregatedProfiling.class);
            LOG.debug("Duration in ms: {}", ((new Date()).getTime() - begin.getTime()));
        }catch(MongoException e) {
            LOG.warn("MongoException while aggreating.", e);
            result.setErrorMessage(e.getMessage());
            return result;
        }catch(IllegalArgumentException e) {
            LOG.warn("MongoException while aggreating.", e);
            result.setErrorMessage("Please enter only valid values!");
            return result;
        }


        final Map<Calendar, Set<AggregatedProfiling>> timeSeries = new TreeMap<Calendar, Set<AggregatedProfiling>>();
        final HashBiMap<String, Integer> groups = HashBiMap.create();
        final HashMap<String, AggregatedProfiling> labelSeries = new HashMap<String, AggregatedProfiling>();
        int index=0;
        Calendar minCalendar = new GregorianCalendar();
        double maxAvgMs=0;
        double maxMinMs=0;
        double maxMaxMs=0;
        long maxSumMs=0;

        for (AggregatedProfiling entry : queryResult) {
            final AggregatedProfilingId id = entry.getId();
            final String idLabel = id.getLabel(false);

            if(!groups.containsKey(idLabel)) {
                groups.put(idLabel, Integer.valueOf(index++));
            }

            if(!labelSeries.containsKey(idLabel)) {
                labelSeries.put(idLabel, entry.clone());//save copy of first entry for this label
            }else {//sum up the other same-label-entries with this one
                labelSeries.get(idLabel).combine(entry);
            }


            final Calendar calendar = id.getCalendar();

            Set<AggregatedProfiling> serie = timeSeries.get(calendar);

            if(serie == null) {
                serie = new HashSet<AggregatedProfiling>();
                timeSeries.put(calendar, serie);
            }

            serie.add(entry);

            if(minCalendar.after(calendar)) {
                minCalendar = (Calendar)calendar.clone();
            }
            if(maxAvgMs < entry.getAvgMs()) {
                maxAvgMs = entry.getAvgMs();
            }
            if(maxMinMs < entry.getMinMs()) {
                maxMinMs = entry.getMinMs();
            }
            if(maxMaxMs < entry.getMaxMs()) {
                maxMaxMs = entry.getMaxMs();
            }
            if(maxSumMs < entry.getMillis()) {
                maxSumMs = entry.getMillis();
            }
        }

        final StringBuffer labels = new StringBuffer("\"Date\",");
        final BiMap<Integer, String> inversedGroups = groups.inverse();
        for (int i = 0; i < index; i++) {
            labels.append("\"").append(inversedGroups.get(Integer.valueOf(i))).append("\",");
            for (int n = 0; n < customFields.length; n++) {
                labels.append("\"").append(customFields[n]).append("\",");
            }
        }
        if(labels.length() > 0) {
            labels.deleteCharAt(labels.length()-1);//remove last char
        }


        final StringBuffer dataGrid = new StringBuffer("[");
        final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        final DecimalFormatSymbols symbols = new DecimalFormatSymbols();
        symbols.setDecimalSeparator('.');
        final DecimalFormat decimalFormat = new DecimalFormat("#", symbols);

        //write one line to expand the y-axis to a 10th of the negated max value so zooming-in values near 0 becomes easier
        minCalendar.add(Calendar.MINUTE, -1);
        dataGrid.append("[new Date(\"").append(dateFormat.format(minCalendar.getTime())).append("\"),");//first group Date
        dataGrid.append(decimalFormat.format(maxAvgMs/-scale/10)).append(",0,");//first group Avg and Count
        dataGrid.append(decimalFormat.format(maxMinMs/-scale/10)).append(",");//first group MinMs
        dataGrid.append(decimalFormat.format(maxMaxMs/-scale/10)).append(",");//first group MaxMs
        dataGrid.append(decimalFormat.format(maxSumMs/-scale/10)).append(",");//first group SumMs
        dataGrid.append("0,");//first group StdDevMs
        dataGrid.append("0,");//first group NRet
        dataGrid.append("0,");//first group MinRet
        dataGrid.append("0,");//first group MaxRet
        dataGrid.append("0,");//first group AvgRet
        dataGrid.append("0,");//first group StdDevRet
        dataGrid.append("0,");//first group len
        dataGrid.append("0,");//first group MinLen
        dataGrid.append("0,");//first group MaxLen
        dataGrid.append("0,");//first group AvgLen
        dataGrid.append("0,");//first group StdDevLen
        dataGrid.append("0,");//first group rKeys
        dataGrid.append("0,");//first group rDocs
        dataGrid.append("0,");//first group wDocs
        dataGrid.append("0,");//first group memSort

        final int lineSize = (groups.size()-1)*(customFields.length+1);//first group already added, thus -1
        for (int i = 0; i < lineSize; i++) {
            dataGrid.append("0").append(",");
        }
        dataGrid.deleteCharAt(dataGrid.length()-1);//delete last comma

        dataGrid.append("],\n");
        ///


        for (Calendar calendar : timeSeries.keySet()) {
            final Set<AggregatedProfiling> entries = timeSeries.get(calendar);

            dataGrid.append("[new Date(\"").append(dateFormat.format(calendar.getTime())).append("\"),");

            final String[] values = new String[groups.size()*(customFields.length+1)];
            for (AggregatedProfiling entry : entries) {
                final AggregatedProfilingId id = entry.getId();
                final String idLabel = id.getLabel(false);
                final Integer idx = (Integer)groups.get(idLabel);
                final int startIdx = idx.intValue()*(customFields.length+1);
                values[startIdx] = decimalFormat.format(entry.getAvgMs()/scale);//millis to seconds
                values[startIdx + 1] = ""+entry.getCount();
                values[startIdx + 2] = decimalFormat.format(entry.getMinMs()/scale);
                values[startIdx + 3] = decimalFormat.format(entry.getMaxMs()/scale);
                values[startIdx + 4] = decimalFormat.format(entry.getMillis()/scale);
                values[startIdx + 5] = decimalFormat.format(entry.getStdDevMs());
                //NRet:
                values[startIdx + 6] = decimalFormat.format(entry.getNRet());
                values[startIdx + 7] = decimalFormat.format(entry.getMinRet());
                values[startIdx + 8] = decimalFormat.format(entry.getMaxRet());
                values[startIdx + 9] = decimalFormat.format(entry.getAvgRet());
                values[startIdx + 10] = decimalFormat.format(entry.getStdDevRet());
                //RespLen:
                values[startIdx + 11] = decimalFormat.format(entry.getLen());
                values[startIdx + 12] = decimalFormat.format(entry.getMinLen());
                values[startIdx + 13] = decimalFormat.format(entry.getMaxLen());
                values[startIdx + 14] = decimalFormat.format(entry.getAvgLen());
                values[startIdx + 15] = decimalFormat.format(entry.getStdDevLen());
                //Perf:
                values[startIdx + 16] = ""+entry.getKeys();
                values[startIdx + 17] = ""+entry.getDocs();
                values[startIdx + 18] = ""+(entry.getDel() + entry.getIns() + entry.getMod());
                values[startIdx + 19] = entry.hasSortStage()?"1":"0";

            }
            for (int i = 0; i < values.length; i++) {
              dataGrid.append(values[i]==null?"0":values[i]).append(",");
            }
            dataGrid.deleteCharAt(dataGrid.length()-1);//delete last comma

            dataGrid.append("],\n");
        }
        dataGrid.deleteCharAt(dataGrid.length()-1);//delete last comma

        dataGrid.append("];\n");

        final boolean[] visibilityValues = new boolean[groups.size()*(customFields.length+1)];
        int c=0;
        for (int i = 0; i < groups.size(); i++) {
            visibilityValues[c++] = true;
            for (int j = 0; j < customFields.length; j++) {
                visibilityValues[c++] = false;
            }
        }

        //LOG.debug(dataGrid.toString());


        result.setLabels(labels);
        result.setDataGrid(dataGrid);
        result.setVisibilityValues(visibilityValues);
        result.setLabelSeries(labelSeries);
            

        
        return result;

    }
    
    
   public static void main(String[] args) {
       //Grapher g = new Grapher();
       //g.aggregateSlowQueries();
       /*
       HashSet<String> a = new HashSet<String>();
       a.add("a");
       a.add("b");
       
       HashSet<String> b = new HashSet<String>();
       b.add("b");
       b.add("a");
       
       HashSet<HashSet<String>> fields = new HashSet<HashSet<String>>();
       fields.add(a);
       fields.add(b);
       
       for (HashSet<String> strings : fields) {
           for (String string : strings) {
               System.out.println(string);
           }
           
       }
       */
       //boolean[] b = new boolean[] {true, false};
       Boolean[] b = new Boolean[] {true, false};
       System.out.println(""+Arrays.toString(b));
       
   }

     
    
}