#include "Diagram.hpp"

#include <unordered_map>
#include <string>

#include <QtWidgets/qlabel.h>
#include <QtCharts/qbarseries.h>
#include <QtCharts/qchart.h>
#include <QtCharts/qbarset.h>
#include <QtCharts/qstackedbarseries.h>
#include <QtCharts/qbarcategoryaxis.h>
#include <QtCharts/qhorizontalbarseries.h>
#include <QtCharts/qhorizontalstackedbarseries.h>
#include <QtCharts/qvalueaxis.h>
#include <QtCharts/qlineseries.h>

/* -- */
#include "trace/values/Values.hpp"
#include "trace/EntityValue.hpp"
#include "trace/EntityTypes.hpp"
#include "trace/Entitys.hpp"
#include "trace/tree/Interval.hpp"
/* -- */
#include "statistics/Statistic.hpp"

// Helper function to help clean every information on the chart.
void clean_chart(QtCharts::QChart& chart) {
    chart.removeAllSeries();
    auto axes = chart.axes();
    for (auto& x : axes) {
        chart.removeAxis(x);
        delete x;
    }
}

// We must ensure that two distinct containers have distinct names.
// So if some of them are empty we need to construct a unique name we do that by walking the
// container tree and append the name like so: root -> child1 -> grandchild1 -> ... -> leaf
std::string construct_name(Container& container, size_t max_rec = 0) {
    auto parent = container.get_parent();
    std::string res;

    // >TODO(Tackwin) >Perf
    // back to front string construction :( for now it's good.
    while(parent && --max_rec > 0) {
        auto app = parent->get_name() + " -> ";
        res = app + res;
        parent = parent->get_parent();
    }

    return res;
}

// Helper function to check if a name is only whitespace.
bool name_is_whitespace(const std::string& name) {
    for (auto& c : name) if (!std::isspace(c)) return false;
    return true;
}


// Helper function that handles the logic in common for stacked_/vertical/horizontal.
// fill the series and categories with appropriate data. (Time spent on different state type for
// each containers).
void feed_abstract_bar_series(
    const std::vector<Container*>& containers,
    const Interval& interval,
    QtCharts::QAbstractBarSeries* series,
    QStringList& categories
) {
    // The Qt Api dictate that a bar set contains a value for each categories
    // But for us it would be more natural to says at once all the value for each categories
    // so we need to transform our data a little.

    // This struct will contains the data in a QtChart friendly way.
    struct Bar_Set_Data {
        std::vector<double> vec;
        QColor color;
    };

    std::unordered_map<std::string, Bar_Set_Data> data;

    // >Reverse
    // We go end to beginning to order the containers the same way as they are in the
    // principal view.
    for (size_t i = containers.size() - 1; i + 1 > 0; --i) { // for each container
        auto& c = containers[i];
        auto name = c->get_name();
        if (name_is_whitespace(name)) {
            name = construct_name(*c) + std::to_string(i);
        }

        categories << name.c_str(); // add a categorie

        Statistic stat_temp; // get the stats
        c->fill_stat(&stat_temp, interval);
        auto states_temp = stat_temp.get_states();

        for (auto& state : states_temp) { // for each states type
            auto h = state.second->_total_length * 100 / (interval._right - interval._left);
            auto c = state.first->get_used_color();

            // First it might be the first time that we encounter this state type so resize the
            // vector. We don't care if it's done multiple time since containers.size() is a
            // constant. Then add an entry in the corresponding set (meaning at index i)
            data[state.first->get_name()].vec.resize(containers.size());
            data[state.first->get_name()].vec[i] = (std::size_t)(h * 10) / 10.f;
            data[state.first->get_name()].color = {
                (int)(255 * c->get_red()),
                (int)(255 * c->get_green()),
                (int)(255 * c->get_blue())
            };
        }
    }

    // Then we have the data ordered by state type then by container so we can fill naturally the
    // qt objects.
    for (auto& it : data) {
        QtCharts::QBarSet* set = new QtCharts::QBarSet(QString(it.first.c_str()));
        set->setColor(it.second.color);

        // >Reverse for the same reason as before.
        for (size_t i = it.second.vec.size() - 1; i + 1 > 0; --i) {
            *set << it.second.vec[i];
        }

        series->append(set);
        series->setLabelsVisible(true);
        series->setLabelsPosition(QtCharts::QAbstractBarSeries::LabelsPosition::LabelsOutsideEnd);
    }
}

void Diagram::vertical(
	QtCharts::QChart& chart, const std::vector<Container*>& containers, const Interval& interval
) {
    clean_chart(chart);

    QStringList categories;
    auto series = new QtCharts::QBarSeries();
    feed_abstract_bar_series(containers, interval, series, categories);

    chart.addSeries(series);

    auto *axis_x = new QtCharts::QBarCategoryAxis();
    axis_x->append(categories);

    auto *axis_y = new QtCharts::QValueAxis();
    axis_y->setRange(0, 100);

    chart.addAxis(axis_y, Qt::AlignLeft);
    chart.addAxis(axis_x, Qt::AlignBottom);

    series->attachAxis(axis_x);
    series->attachAxis(axis_y);
}

void Diagram::stacked_vertical(
	QtCharts::QChart& chart, const std::vector<Container*>& containers, const Interval& interval
) {
    clean_chart(chart);

    QStringList categories;
    auto series = new QtCharts::QStackedBarSeries();
    feed_abstract_bar_series(containers, interval, series, categories);

    chart.addSeries(series);

    auto *axis_x = new QtCharts::QBarCategoryAxis();
    axis_x->append(categories);

    auto *axis_y = new QtCharts::QValueAxis();
    axis_y->setRange(0, 100);

    chart.addAxis(axis_y, Qt::AlignLeft);
    chart.addAxis(axis_x, Qt::AlignBottom);

    series->attachAxis(axis_x);
    series->attachAxis(axis_y);
}

void Diagram::horizontal(
    QtCharts::QChart& chart, const std::vector<Container*>& containers, const Interval& interval
) {
    clean_chart(chart);

    QStringList categories;
    auto series = new QtCharts::QHorizontalBarSeries();
    feed_abstract_bar_series(containers, interval, series, categories);

    chart.addSeries(series);

    auto *axis_y = new QtCharts::QBarCategoryAxis();
    axis_y->append(categories);

    auto *axis_x = new QtCharts::QValueAxis();
    axis_x->setRange(0, 100);

    chart.addAxis(axis_y, Qt::AlignLeft);
    chart.addAxis(axis_x, Qt::AlignBottom);

    series->attachAxis(axis_x);
    series->attachAxis(axis_y);
}

void Diagram::stacked_horizontal(
    QtCharts::QChart& chart, const std::vector<Container*>& containers, const Interval& interval
) {
    clean_chart(chart);

    QStringList categories;
    auto series = new QtCharts::QHorizontalStackedBarSeries();
    feed_abstract_bar_series(containers, interval, series, categories);

    chart.addSeries(series);

    auto *axis_y = new QtCharts::QBarCategoryAxis();
    axis_y->append(categories);

    auto *axis_x = new QtCharts::QValueAxis();
    axis_x->setRange(0, 100);

    chart.addAxis(axis_y, Qt::AlignLeft);
    chart.addAxis(axis_x, Qt::AlignBottom);

    series->attachAxis(axis_x);
    series->attachAxis(axis_y);
}


void Diagram::counter(
    QtCharts::QChart& chart, const std::vector<Container*>& containers, const Interval& interval
) {
    clean_chart(chart);

    // we have essentially the same logic than in feed_abstract_series.
    // For this line graph we want to have a "step" function since our counter are discretized but
    // a line graph is continuous.
    struct Line_Data {
        std::vector<double> xs;
        std::vector<double> ys;
    };

    std::unordered_map<std::string, Line_Data> data;

    QStringList categories;
    for (size_t i = 0; i < containers.size(); ++i) {
        auto& c = containers[i];
        auto name = c->get_Name().to_string();
        categories << name.c_str();

        auto& var = *c->get_variables();
        for (auto& first_it : var) {
            auto& v = first_it.second;
            Line_Data d;

            // It's possible that we have more than one counter by containers so we need to
            // construct an unique name.
            auto name = c->get_name() + " : " + v->get_type()->get_name();

            for (auto& second_it : *v->get_values()) {
                auto& t = second_it.first;
                auto& y = second_it.second;
                data[name].xs.push_back(t.get_value());
                data[name].ys.push_back(y.get_value());
            }
        }
    }

    for (auto& it : data) {
        auto* series = new QtCharts::QLineSeries();
        series->setName(QString(it.first.c_str()));

        // with this last_y and double append we construct the "step" function.
        double last_y = 0;
        for (size_t i = 0; i < it.second.xs.size(); ++i) {
            series->append(it.second.xs[i], last_y);
            last_y = it.second.ys[i];
            series->append(it.second.xs[i], it.second.ys[i]);
        }
        chart.addSeries(series);
    }

    chart.createDefaultAxes();
}
