#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 = _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::ItemIsTristate;

        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::ItemIsTristate;

        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(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 += QString("   count: %1<br/>").arg(localcnt);
        return localcnt;
    }

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

        text += "<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(found_states, min_quantile, max_quantile, 25);

        text += QString("Total count: %1").arg(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::ItemIsTristate;

            /* 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);
                QColor col(0, 0, 255, 127);
                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("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 slots:
    void execute() {
        _text_info->clear();
        QString text ("<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(); }
