/**
 *
 * @file plugins/Distribution/Distribution.cpp
 *
 * @copyright 2008-2024 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
 *                      Univ. Bordeaux. All rights reserved.
 *
 * @author Camille Ordronneau
 * @author Johnny Jazeix
 * @author Mathieu Faverge
 *
 * @date 2024-07-17
 */
#include <cmath>
#include <set>
#include <stack>
#include <vector>
#include <iostream>
#include <sstream>
#include <fstream>
#include <algorithm>
/* -- */
#include <QSlider>
#include <QPainter>
#include <QTextEdit>
#include <QHBoxLayout>
#include <QTreeWidgetItem>
#include <QStackedWidget>
#include <QGraphicsLineItem>
#include <QGraphicsScene>
#include <QGraphicsView>
/* -- */
#include "plugin/Plugin.hpp"
/* -- */
#include "trace/EntityTypes.hpp"
#include "trace/Entitys.hpp"
#include "trace/Container.hpp"
#include "trace/EntityValue.hpp"
#include "trace/StateChange.hpp"
#include "trace/tree/BinaryTree.hpp"
#include "trace/tree/Node.hpp"
#include "trace/Trace.hpp"
/* -- */
#include "common/Message.hpp"

class Distribution : public Plugin
{

    class LogEntry
    {
    public:
        Date start;
        double duration;

        LogEntry(Date _start, double _duration) {
            this->start = std::move(_start);
            this->duration = _duration;
        }

        /* XXX could not this be nicer ? */
        struct compar_date_sort
        {
            bool operator()(LogEntry *e1, LogEntry *e2) {
                return (e1->start < e2->start);
            }
        } compar_date_sort_op;

        struct compar_duration_sort
        {
            bool operator()(LogEntry *e1, LogEntry *e2) {
                return (e1->duration < e2->duration);
            }
        } compar_duration_sort_op;

        void sort_by_duration(std::vector<LogEntry *> &le_list) {
            std::sort(le_list.begin(), le_list.end(), compar_duration_sort_op);
        }

        void sort_by_date(std::vector<LogEntry *> &le_list) {
            std::sort(le_list.begin(), le_list.end(), compar_date_sort_op);
        }
    };

private:
    QTextEdit *_text_info;
    QTreeWidget *_nodes_selected;
    QTreeWidget *_state_names_selected;

    QGraphicsScene *scene;
    QGraphicsView *view;

    QSlider *min_slider;
    QSlider *max_slider;

    /* all names found in the trace */
    std::set<std::string> state_types_names;

    void select_container_rec(QTreeWidgetItem *root_node, Container *current_container) const {
        const std::list<Container *> *children = current_container->get_children();

        // Add the root container names
        QList<QTreeWidgetItem *> items;
        QFlags<Qt::ItemFlag> flg = Qt::ItemIsUserCheckable | Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsAutoTristate;

        for (std::list<Container *>::const_iterator it = children->begin();
             it != children->end();
             ++it) {
            // We create the node and we do the recursivity
            const std::string &name = (*it)->get_name();
            QStringList temp(QString::fromStdString(name));
            QTreeWidgetItem *current_node = new QTreeWidgetItem((QTreeWidgetItem *)root_node, temp);

            current_node->setFlags(flg);
            current_node->setCheckState(0, Qt::Unchecked);
            items.append(current_node);

            select_container_rec(current_node, (*it));
        }
    }

    void select_container() const {
        const std::list<Container *> *root_containers = _trace->get_root_containers();

        // Add the root container names
        QList<QTreeWidgetItem *> items;
        QFlags<Qt::ItemFlag> flg = Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsAutoTristate;

        for (std::list<Container *>::const_iterator it = root_containers->begin(); it != root_containers->end(); ++it) {
            const std::string &name = (*it)->get_name();
            QStringList temp(QString::fromStdString(name));
            QTreeWidgetItem *current_node = new QTreeWidgetItem((QTreeWidgetItem *)0, temp);

            current_node->setFlags(flg);
            current_node->setCheckState(0, Qt::Unchecked);
            items.append(current_node);

            // Recursivity to add the children names
            select_container_rec(current_node, *it);
        }

        (*(items.begin()))->setExpanded(true);
        _nodes_selected->insertTopLevelItems(0, items);
    }

    void browse_statechange_tree(Node<StateChange> *node, QString &text, std::string &container_name, std::ostream &stream,
                                 std::set<std::string> &selected_state_types_names, std::vector<LogEntry *> &found_states) const {
        if (!node)
            return;

        /* handle the node */
        const State *st = node->get_element()->get_left_state();

        if (st) {
            const std::string name = st->get_value()->get_name();

            /* does the name appear in the set of selected names ? */
            std::set<std::string>::const_iterator it = selected_state_types_names.find(name);
            if (it != selected_state_types_names.end()) {
                double length = st->get_duration();
                Date start = st->get_start_time();

                stream << container_name;
                stream << "\t";
                stream << name;
                stream << "\t";
                stream << start.to_string();
                stream << "\t";
                stream << length;
                stream << "\n";

                LogEntry *le = new LogEntry(std::move(start), length);

                found_states.push_back(le);
            }
        }

        /* handle its left child */
        browse_statechange_tree(node->get_left_child(), text, container_name, stream, selected_state_types_names, found_states);

        /* handle its right child */
        browse_statechange_tree(node->get_right_child(), text, container_name, stream, selected_state_types_names, found_states);
    }

    std::string get_container_complete_name(const Container *container) const {
        std::string str;

        if (container->get_parent()) {
            str = get_container_complete_name(container->get_parent());
            str.append("_");
        }
        else {
            str = "";
        }

        const std::string &name = container->get_name();

        str.append(name);

        return str;
    }

    int handle_container(Container *&container,
                         QString &text,
                         std::ostream &stream,
                         std::set<std::string> &selected_state_types_names,
                         std::vector<LogEntry *> &found_states) const {
        std::string name = get_container_complete_name(container);
        text += QString::fromStdString(name);

        Node<StateChange> *root_node = container->get_states()->get_root();
        browse_statechange_tree(root_node, text, name, stream, selected_state_types_names, found_states);

        int localcnt = container->get_state_number();
        text += QStringLiteral("   count: ") + QString::number(localcnt) + QStringLiteral("<br/>");
        return localcnt;
    }

    void get_statechange_distribution(QString &text) const {
        /* count */
        int count = 0;

        text += QStringLiteral("<h2>States:</h2>");

        std::ofstream SaveFile("distribution.txt");

        /* the names that were selected by the application */
        std::set<std::string> selected_state_types_names;
        find_selected_state_types_names(selected_state_types_names);

        /* A vector to store the entries that matched user contraints */
        std::vector<LogEntry *> found_states;

        /* Only handle the containers that the user have selected */
        QTreeWidgetItemIterator it(_nodes_selected);
        while (*it) {
            (*it)->setFlags((*it)->flags() | Qt::ItemIsUserCheckable);

            if ((*it)->checkState(0)) {
                Container *cont = _trace->search_container((*it)->text(0).toStdString());
                std::string name = get_container_complete_name(cont);

                // printf("Container %s was selected\n", name.c_str());
                count += handle_container(cont, text, SaveFile, selected_state_types_names, found_states);
            }

            ++it;
        }

        /* XXX i need C++ courses ;) */
        LogEntry dummy(0, 0);
        dummy.sort_by_duration(found_states);

        /* TODO really take the min and the max ... */
        double min_quantile = 0.01 * (double)min_slider->value();
        double max_quantile = 0.01 * (double)max_slider->value();
        printf("quantiles : min %lf max %lf\n", min_quantile, max_quantile);
        compute_histogram_duration(std::move(found_states), min_quantile, max_quantile, 25);

        text += QStringLiteral("Total count: ") + QString::number(count);
        SaveFile.close();
    }

    /* Fill name_set with the names of the state contained in sub-tree node */
    void get_state_names_list(std::set<std::string> &name_set, Node<StateChange> *node) {
        if (!node)
            return;

        /* handle the node */
        const State *left_st = node->get_element()->get_left_state();
        if (left_st) {
            const std::string name = left_st->get_value()->get_name();
            name_set.insert(name);
        }

        const State *right_st = node->get_element()->get_right_state();
        if (right_st) {
            const std::string name = right_st->get_value()->get_name();
            name_set.insert(name);
        }

        /* handle its left child */
        get_state_names_list(name_set, node->get_left_child());

        /* handle its right child */
        get_state_names_list(name_set, node->get_right_child());
    }

    void select_state_types(void) {
        /* For all containers, get the list of state names */
        std::list<Container *> container_list;
        _trace->get_all_containers(container_list);

        for (std::list<Container *>::const_iterator it = container_list.begin();
             it != container_list.end(); ++it) {
            BinaryTree<StateChange> *states;
            states = (*it)->get_states();
            if (states && states->get_root()) {
                get_state_names_list(state_types_names, states->get_root());
            }
        }

        QList<QTreeWidgetItem *> items;
        QFlags<Qt::ItemFlag> flg = Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsAutoTristate;

        /* Add a tick box per name of event */
        for (std::set<std::string>::const_iterator it = state_types_names.begin();
             it != state_types_names.end(); ++it) {
            QStringList temp(QString::fromStdString(*it));
            QTreeWidgetItem *current_node = new QTreeWidgetItem((QTreeWidgetItem *)0, temp);

            current_node->setFlags(flg);
            current_node->setCheckState(0, Qt::Unchecked);
            items.append(current_node);
        }

        (*(items.begin()))->setExpanded(true);
        _state_names_selected->insertTopLevelItems(0, items);
    }

    void find_selected_state_types_names(std::set<std::string> &selected_state_types_names) const {
        /* Go through the tick box list to find which are the names that have been selected */
        QTreeWidgetItemIterator it(_state_names_selected);
        while (*it) {
            (*it)->setFlags((*it)->flags() | Qt::ItemIsUserCheckable);

            if ((*it)->checkState(0)) {
                std::string name = (*it)->text(0).toStdString();
                selected_state_types_names.insert(name);
                // printf("Name %s was selected\n", name.c_str());
            }

            ++it;
        }
    }

    double compute_quantile_duration(std::vector<LogEntry *> le_list, float alpha) const {
        float sizef = (float)(le_list.size() - 1);

        if (alpha > 1.0)
            alpha = 1.0;

        if (alpha < 0.0)
            alpha = 0.0;

        float indf = alpha * sizef;
        unsigned ind = floor(indf);

        LogEntry *le = le_list[ind];

        return le->duration;
    }

    void compute_histogram_duration(std::vector<LogEntry *> le_list,
                                    float min_quantile, float max_quantile, unsigned nbreaks) const {
        if (le_list.size() == 0)
            return;

        /* Select values in [min_val ; max_val[ */
        double min_val = compute_quantile_duration(le_list, min_quantile);
        double max_val = compute_quantile_duration(le_list, max_quantile);

        if (max_val - min_val == 0.0) {
            printf("Cannot display histogram: not enough different values\n");
            return;
        }

        unsigned i;
        unsigned *cnt = (unsigned *)malloc((nbreaks + 1) * sizeof(unsigned));
        unsigned total_cnt = 0;
        unsigned max_cnt = 0;

        /* start from a blank histogram */
        for (i = 0; i < nbreaks; i++)
            cnt[i] = 0;

        for (i = 0; i < le_list.size(); i++) {
            double val = le_list[i]->duration;
            if ((val >= min_val) && (val < max_val)) {
                /* in which bin does the value go ? */
                unsigned bin = floor((nbreaks * (val - min_val)) / (max_val - min_val));
                cnt[bin]++;
                total_cnt++;
            }
        }

        if (total_cnt == 0) {
            free(cnt);
            return;
        }

        for (i = 0; i < nbreaks; i++) {
            if (max_cnt < cnt[i])
                max_cnt = cnt[i];

            double start = (i * (max_val - min_val)) / nbreaks + min_val;
            double end = ((i + 1) * (max_val - min_val)) / nbreaks + min_val;
            printf("BIN[%u] = %u / %u - [ %lf, %lf [\n", i, cnt[i], total_cnt, start, end);
        }

        /* Start from a blank canva */
        scene->clear();

        /* Display the histogram now */
        int height = 200;
        int width = 100;
        int offsety = 100;

        scene->addItem(new QGraphicsLineItem(0, 0 + offsety, 0, -height + offsety, NULL));
        scene->addItem(new QGraphicsLineItem(0, 0 + offsety, width, 0 + offsety, NULL));

        for (i = 0; i < nbreaks; i++) {
            int xstart = floor(((double)width * i) / nbreaks);
            int xend = floor(((double)width * (i + 1)) / nbreaks);
            int yend = floor(((double)height * cnt[i]) / max_cnt);

            QGraphicsRectItem *rect = new QGraphicsRectItem(xstart, -yend + offsety, xend - xstart, yend, NULL);
            rect->setBrush(QColor(255, 0, 0, 255));
            rect->brush();
            scene->addItem(rect);
        }
        free(cnt);
    }

public:
    Distribution() {
        QHBoxLayout *horizontalLayout = new QHBoxLayout(this);
        _text_info = new QTextEdit();
        horizontalLayout->addWidget(_text_info);

        // QStackedWidget *stackedWidget = new QStackedWidget;
        scene = new QGraphicsScene(0, 0, 100, 100, this);
        view = new QGraphicsView(scene);
        horizontalLayout->addWidget(view);

        _nodes_selected = new QTreeWidget();
        _state_names_selected = new QTreeWidget();

        horizontalLayout->addWidget(_nodes_selected);
        horizontalLayout->addWidget(_state_names_selected);

        min_slider = new QSlider();
        min_slider->setFocusPolicy(Qt::StrongFocus);
        min_slider->setTickPosition(QSlider::TicksBothSides);
        min_slider->setTickInterval(10);
        min_slider->setRange(0, 100);
        min_slider->setSingleStep(1);
        min_slider->setObjectName(QStringLiteral("totp"));
        min_slider->setValue(5);
        horizontalLayout->addWidget(min_slider);

        max_slider = new QSlider();
        max_slider->setFocusPolicy(Qt::StrongFocus);
        max_slider->setTickPosition(QSlider::TicksBothSides);
        max_slider->setTickInterval(10);
        max_slider->setRange(0, 100);
        max_slider->setSingleStep(1);
        max_slider->setValue(95);
        horizontalLayout->addWidget(max_slider);
    }

    void init() {
        //_text_info->clear();
        select_container();
        select_state_types();
    }

    void clear() {
        _text_info->clear();
    }

    void set_arguments(std::map<std::string /*argname*/, QVariant * /*argValue*/>) { }

    std::string get_name() {
        return "Distribution";
    }

public Q_SLOTS:
    void execute() {
        _text_info->clear();
        QString text(QStringLiteral("<center><h1> Distribution </h1></center>"));

        get_statechange_distribution(text);

        _text_info->setHtml(text);
    }
};

extern "C"
#ifdef WIN32
    __declspec(dllexport)
#endif
        Plugin *create() {
    return new Distribution();
}
