package ru.yandex.wmconsole.data.wrappers.highchart;

import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Set;
import java.util.TreeMap;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;

import ru.yandex.wmtools.common.data.plot.DateNumberPlotHelper;
import ru.yandex.wmtools.common.data.plot.PlotHelper;
import ru.yandex.wmtools.common.util.XmlDataWrapper;

/**
 * User: azakharov
 * Date: 01.02.13
 * Time: 14:15
 */
public class JsonHighchartPlotDataWrapper<K, V> extends XmlDataWrapper<List<NavigableMap<K, V>>> {

    private static final String TAG_JSON = "json";

    private static final String FIELD_SERIES = "series";
    private static final String FIELD_NAME = "name";
    private static final String FIELD_DATA = "data";

    private final PlotHelper<K, V> plotHelper;
    private final String title;
    private final List<String> labels;

    public JsonHighchartPlotDataWrapper(String title, List<NavigableMap<K, V>> graphs, List<String> labels, PlotHelper<K, V> plotHelper) {
        super(graphs);
        this.title = title;
        this.plotHelper = plotHelper;
        this.labels = labels;
    }

    @Override
    protected void doToXml(StringBuilder result) {
        StringWriter writer = new StringWriter();
        JsonFactory jsonFactory = new JsonFactory();
        try {
            JsonGenerator jg = jsonFactory.createJsonGenerator(writer);
            jg.writeStartObject();
            jg.writeArrayFieldStart(FIELD_SERIES);

            boolean outputLabels = labels != null && data.size() == labels.size();
            Iterator<String> labelIterator = outputLabels ? labels.iterator() : null;

            List<NavigableMap<K, V>> correctedGraphs = preparePlotData();

            for (NavigableMap<K, V> graph : correctedGraphs) {
                jg.writeStartObject();

                if (outputLabels) {
                    String label = labelIterator.next();
                    jg.writeStringField(FIELD_NAME, label);
                }

                jg.writeArrayFieldStart(FIELD_DATA);
                for (Map.Entry<K, V> entry : graph.entrySet()) {
                    jg.writeStartArray();
                    jg.writeRawValue(plotHelper.formatKey(entry.getKey()));
                    if (entry.getValue() != null) {
                        jg.writeRawValue(plotHelper.formatValue(entry.getValue()));
                    } else {
                        jg.writeNull();
                    }
                    jg.writeEndArray();
                }
                jg.writeEndArray();                  // end of data array

                jg.writeEndObject();                 // end of seria object
            }
            jg.writeEndArray();                  // end of series array
            jg.writeEndObject();                 // end of root object
            jg.close();
        } catch (IOException e) {
            throw new RuntimeException("Exception while converting to json", e);
        }
        putTag(result, TAG_JSON, writer.toString());
    }

    private List<NavigableMap<K, V>> preparePlotData() {
        List<NavigableMap<K, V>> correctedGraphs = new ArrayList<NavigableMap<K, V>>();
        for (NavigableMap<K, V> graph : data) {
            NavigableMap<K, V> correctedGraph = new TreeMap<K, V>();
            correctedGraphs.add(correctedGraph);
            if (graph.isEmpty()) {
                continue;
            }
            if (graph.size() == 1) {
                correctedGraph.putAll(graph);
                K key = graph.lastKey();
                K next = plotHelper.nextKey(key);
                K prev = plotHelper.prevKey(key);
                correctedGraph.put(prev, null);
                correctedGraph.put(next, null);
                continue;
            }
            K lastExistingKey = null;
            for (K key : allPossibleKeys(graph)) {
                V value = graph.get(key);
                if (value != null) { // all good
                    correctedGraph.put(key, value);
                    lastExistingKey = key;
                    continue;
                }

                K nextExistingKey = graph.higherKey(key);
                if (nextExistingKey == null) {
                    break;
                }

                if (lastExistingKey == null) {
                    continue;
                }

                value = plotHelper.interpolate(lastExistingKey, graph.get(lastExistingKey),
                        nextExistingKey, graph.get(nextExistingKey),
                        key);
                if (value != null) { // interpolated in real value, all good
                    correctedGraph.put(key, value);
                    continue;
                }
                if (!plotHelper.isNeedGaps()) { // get null, but doesn't use gaps
                    continue;
                }
                if (correctedGraph.isEmpty() || correctedGraph.lastEntry().getValue() == null) { // drawing gap, but already put down null
                    continue;
                }
                correctedGraph.put(key, null);
            }
        }

        //optimizing 0 values:
        if (plotHelper instanceof DateNumberPlotHelper) {
            DateNumberPlotHelper numberPlotHelper = (DateNumberPlotHelper) plotHelper;
            boolean first = true;
            Set<K> allKeysToRemove = new HashSet<K>();
            for (NavigableMap<K, V> graph : correctedGraphs) {
                Set<K> keysToRemove = new HashSet<K>();
                for (Iterator<K> it = graph.navigableKeySet().iterator(); it.hasNext(); ) {
                    K key = it.next();
                    K prev = graph.lowerKey(key);
                    K next = graph.higherKey(key);
                    if (numberPlotHelper.getZero().equals(graph.get(key)) && // Current value == 0
                            (prev != null && numberPlotHelper.getZero().equals(graph.get(prev))) && // Prev value == 0
                            (next != null && numberPlotHelper.getZero().equals(graph.get(next)))) { // Next value == 0
                        //it.remove();
                        keysToRemove.add(key);
                    }
                }
                if (first) {
                    allKeysToRemove = keysToRemove;
                    first = false;
                } else {
                    allKeysToRemove.retainAll(keysToRemove);
                }
            }
            if (!allKeysToRemove.isEmpty()) {
                for (NavigableMap<K, V> graph : correctedGraphs) {
                    graph.keySet().removeAll(allKeysToRemove);
                }
            }
        }

        return correctedGraphs;
    }

    private Iterable<K> allPossibleKeys(NavigableMap<K, V> graph) {
        final K min = graph.isEmpty() ? null : graph.firstKey();
        final K max = graph.isEmpty() ? null : graph.lastKey();
        return new Iterable<K>() {
            @Override
            public Iterator<K> iterator() {
                return new Iterator<K>() {
                    private K current;

                    @Override
                    public boolean hasNext() {
                        if (min == null) {
                            return false;
                        }
                        return current == null || plotHelper.compare(current, max) < 0;
                    }

                    @Override
                    public K next() {
                        if (current == null) {
                            current = min;
                        } else {
                            current = plotHelper.nextKey(current);
                        }
                        return current;
                    }

                    @Override
                    public void remove() {
                        throw new UnsupportedOperationException();
                    }
                };
            }
        };
    }
}
