#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
            std::string name = (*it)->get_name().to_string();
            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++) {
		std::string name = (*it)->get_name().to_string();
		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().to_string();

		/* 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 = "";
	}
		
	std::string name = container->get_name().to_string();

	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().to_string();
	    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().to_string();
	    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[%d] = %d / %d - [ %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(); }

