#include "profiledialog.h"
#include "profiletablemodel.h"
#include <QSortFilterProxyModel>

#include "graphing/histogramview.h"
#include "graphing/timeaxiswidget.h"
#include "graphing/frameaxiswidget.h"
#include "graphing/heatmapverticalaxiswidget.h"
#include "profileheatmap.h"

/* Handy function to allow selection of a call in main window */
ProfileDialog* g_profileDialog = 0;

void Profiling::jumpToCall(int index)
{
    if (g_profileDialog) {
        g_profileDialog->showCall(index);
    }
}

/* Provides frame numbers based off call index */
class FrameCallDataProvider : public FrameDataProvider {
public:
    FrameCallDataProvider(const trace::Profile* profile)
    {
        m_profile = profile;
    }

    unsigned size() const override {
        return m_profile->frames.size();
    }

    qint64 frameStart(unsigned index) const override {
        return m_profile->frames[index].calls.begin;
    }

    qint64 frameEnd(unsigned index) const override {
        return m_profile->frames[index].calls.end;
    }

private:
    const trace::Profile* m_profile;
};

/* Provides frame numbers based off time */
class FrameTimeDataProvider : public FrameDataProvider {
public:
    FrameTimeDataProvider(const trace::Profile* profile)
    {
        m_profile = profile;
    }

    unsigned size() const override {
        return m_profile->frames.size();
    }

    qint64 frameStart(unsigned index) const override {
        return m_profile->frames[index].cpuStart;
    }

    qint64 frameEnd(unsigned index) const override {
        return m_profile->frames[index].cpuStart + m_profile->frames[index].cpuDuration;
    }

private:
    const trace::Profile* m_profile;
};

ProfileDialog::ProfileDialog(QWidget *parent)
    : QDialog(parent),
      m_profile(0)
{
    setupUi(this);
    g_profileDialog = this;

    /* Gradients for call duration histograms */
    QLinearGradient cpuGradient;
    cpuGradient.setColorAt(0.9, QColor(0, 0, 210));
    cpuGradient.setColorAt(0.0, QColor(130, 130, 255));

    QLinearGradient gpuGradient;
    gpuGradient.setColorAt(0.9, QColor(210, 0, 0));
    gpuGradient.setColorAt(0.0, QColor(255, 130, 130));


    /* Setup heatmap timeline */
    m_timeline->setLabel(new GraphLabelWidget("Frames ", m_timeline));
    m_timeline->label()->setFlags(Qt::AlignVCenter | Qt::AlignRight);

    m_timeline->setView(new HeatmapView(m_timeline));

    m_timeline->setAxis(GraphWidget::AxisTop, new FrameAxisWidget(m_timeline));
    m_timeline->setAxis(GraphWidget::AxisLeft, new HeatmapVerticalAxisWidget(m_timeline));
    m_timeline->axis(GraphWidget::AxisLeft)->resize(80, 0);

    m_timeline->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
    m_timeline->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);


    /* Setup Cpu call duration histogram */
    m_cpuGraph->setLabel(new GraphLabelWidget("CPU", m_cpuGraph));

    m_cpuGraph->setAxis(GraphWidget::AxisTop, new FrameAxisWidget(m_cpuGraph));
    m_cpuGraph->setAxis(GraphWidget::AxisLeft, new TimeAxisWidget(m_cpuGraph));
    m_cpuGraph->axis(GraphWidget::AxisLeft)->resize(80, 0);

    HistogramView* cpuView = new HistogramView(m_cpuGraph);
    cpuView->setSelectedGradient(cpuGradient);
    m_cpuGraph->setView(cpuView);


    /* Setup Gpu call duration histogram */
    m_gpuGraph->setLabel(new GraphLabelWidget("GPU", m_gpuGraph));

    m_gpuGraph->setAxis(GraphWidget::AxisTop, new FrameAxisWidget(m_gpuGraph));
    m_gpuGraph->setAxis(GraphWidget::AxisLeft, new TimeAxisWidget(m_gpuGraph));
    m_gpuGraph->axis(GraphWidget::AxisLeft)->resize(80, 0);

    HistogramView* gpuView = new HistogramView(m_gpuGraph);
    gpuView->setSelectedGradient(gpuGradient);
    m_gpuGraph->setView(gpuView);


    /* Synchronise selections */
    connect(m_timeline, SIGNAL(selectionChanged(SelectionState)), m_cpuGraph, SLOT(setSelection(SelectionState)));
    connect(m_timeline, SIGNAL(selectionChanged(SelectionState)), m_gpuGraph, SLOT(setSelection(SelectionState)));

    connect(m_cpuGraph, SIGNAL(selectionChanged(SelectionState)), m_timeline, SLOT(setSelection(SelectionState)));
    connect(m_cpuGraph, SIGNAL(selectionChanged(SelectionState)), m_gpuGraph, SLOT(setSelection(SelectionState)));

    connect(m_gpuGraph, SIGNAL(selectionChanged(SelectionState)), m_timeline, SLOT(setSelection(SelectionState)));
    connect(m_gpuGraph, SIGNAL(selectionChanged(SelectionState)), m_cpuGraph, SLOT(setSelection(SelectionState)));

    connect(m_timeline, SIGNAL(selectionChanged(SelectionState)), this, SLOT(graphSelectionChanged(SelectionState)));
    connect(m_cpuGraph, SIGNAL(selectionChanged(SelectionState)), this, SLOT(graphSelectionChanged(SelectionState)));
    connect(m_gpuGraph, SIGNAL(selectionChanged(SelectionState)), this, SLOT(graphSelectionChanged(SelectionState)));


    /* Synchronise views between cpuGraph and gpuGraph */
    connect(m_cpuGraph, SIGNAL(horizontalViewChanged(qint64,qint64)), m_gpuGraph, SLOT(setHorizontalView(qint64,qint64)));
    connect(m_gpuGraph, SIGNAL(horizontalViewChanged(qint64,qint64)), m_cpuGraph, SLOT(setHorizontalView(qint64,qint64)));
}


ProfileDialog::~ProfileDialog()
{
    delete m_profile;
}


void ProfileDialog::showCall(int call)
{
    emit jumpToCall(call);
}


void ProfileDialog::tableDoubleClicked(const QModelIndex& index)
{
    ProfileTableModel* model = (ProfileTableModel*)m_table->model();

    if (!model) {
        return;
    }

    const trace::Profile::Call* call = model->getJumpCall(index);

    if (call) {
        emit jumpToCall(call->no);
    } else {
        unsigned program = model->getProgram(index);

        SelectionState state;
        state.type = SelectionState::Vertical;
        state.end = state.start = program;

        m_timeline->setSelection(state);
        m_cpuGraph->setSelection(state);
        m_gpuGraph->setSelection(state);
    }
}


void ProfileDialog::setProfile(trace::Profile* profile)
{

    if (profile && profile->frames.size()) {
        HeatmapVerticalAxisWidget* programAxis;
        FrameAxisWidget* frameAxis;
        HistogramView* histogram;
        HeatmapView* heatmap;


        /* Setup data providers for Cpu graph */
        m_cpuGraph->setProfile(profile);
        histogram = (HistogramView*)m_cpuGraph->view();
        frameAxis = (FrameAxisWidget*)m_cpuGraph->axis(GraphWidget::AxisTop);

        histogram->setDataProvider(new CallDurationDataProvider(profile, false));
        frameAxis->setDataProvider(new FrameCallDataProvider(profile));

        /* Setup data provider for Gpu graph */
        m_gpuGraph->setProfile(profile);
        histogram = (HistogramView*)m_gpuGraph->view();
        frameAxis = (FrameAxisWidget*)m_gpuGraph->axis(GraphWidget::AxisTop);

        histogram->setDataProvider(new CallDurationDataProvider(profile, true));
        frameAxis->setDataProvider(new FrameCallDataProvider(profile));

        /* Setup data provider for heatmap timeline */
        heatmap = (HeatmapView*)m_timeline->view();
        frameAxis = (FrameAxisWidget*)m_timeline->axis(GraphWidget::AxisTop);
        programAxis = (HeatmapVerticalAxisWidget*)m_timeline->axis(GraphWidget::AxisLeft);

        heatmap->setDataProvider(new ProfileHeatmapDataProvider(profile));
        frameAxis->setDataProvider(new FrameTimeDataProvider(profile));
        programAxis->setDataProvider(new ProfileHeatmapDataProvider(profile));

        /* Setup data model for table view */
        ProfileTableModel* model = new ProfileTableModel(m_table);
        model->setProfile(profile);

        delete m_table->model();
        m_table->setModel(model);
        m_table->update(QModelIndex());
        m_table->sortByColumn(2, Qt::DescendingOrder);
        m_table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
        m_table->resizeColumnsToContents();

        /* Reset selection */
        SelectionState emptySelection;
        emptySelection.type = SelectionState::None;
        m_cpuGraph->setSelection(emptySelection);
        m_gpuGraph->setSelection(emptySelection);
        m_timeline->setSelection(emptySelection);
    }

    delete m_profile;
    m_profile = profile;
}


void ProfileDialog::graphSelectionChanged(SelectionState state)
{
    ProfileTableModel* model = (ProfileTableModel*)m_table->model();

    if (!model) {
        return;
    }

    if (state.type == SelectionState::None) {
        model->selectNone();
    } else if (state.type == SelectionState::Horizontal) {
        model->selectTime(state.start, state.end);
    } else if (state.type == SelectionState::Vertical) {
        model->selectProgram(state.start);
    }

    m_table->reset();

    if (state.type == SelectionState::Vertical) {
        m_table->selectRow(model->getRowIndex(state.start));
    }
}
