#include "graphwidget.h"

#include <QScrollBar>

GraphWidget::GraphWidget(QWidget* parent) :
    QWidget(parent),
    m_view(NULL),
    m_label(NULL),
    m_axisTop(NULL),
    m_axisLeft(NULL),
    m_axisRight(NULL),
    m_axisBottom(NULL),
    m_horizontalScrollbar(NULL),
    m_horizontalMin(0),
    m_horizontalMax(0),
    m_horizontalStart(0),
    m_horizontalEnd(0),
    m_horizontalScrollbarPolicy(Qt::ScrollBarAlwaysOff),
    m_verticalScrollbar(NULL),
    m_verticalMin(0),
    m_verticalMax(0),
    m_verticalStart(0),
    m_verticalEnd(0),
    m_verticalScrollbarPolicy(Qt::ScrollBarAlwaysOff)
{
    m_selection.type = SelectionState::None;

    m_verticalScrollbar = new QScrollBar(this);
    m_verticalScrollbar->setOrientation(Qt::Vertical);
    m_verticalScrollbar->hide();
    m_verticalScrollbar->resize(m_verticalScrollbar->sizeHint());

    m_horizontalScrollbar = new QScrollBar(this);
    m_horizontalScrollbar->setOrientation(Qt::Horizontal);
    m_horizontalScrollbar->hide();
    m_horizontalScrollbar->resize(m_horizontalScrollbar->sizeHint());

    updateLayout();
    setAutoFillBackground(true);
}


GraphView* GraphWidget::view()
{
    return m_view;
}


GraphLabelWidget* GraphWidget::label()
{
    return m_label;
}


GraphAxisWidget* GraphWidget::axis(AxisPosition pos)
{
    switch(pos) {
        case AxisTop:
            return m_axisTop;

        case AxisLeft:
            return m_axisLeft;

        case AxisRight:
            return m_axisRight;

        case AxisBottom:
            return m_axisBottom;

        default:
            return NULL;
    }
}


void GraphWidget::setView(GraphView* view)
{
    delete m_view;
    m_view = view;

    updateConnections();

    m_view->setSelectionState(&m_selection);
    m_view->show();
    m_view->update();
}


void GraphWidget::setLabel(GraphLabelWidget* label)
{
    delete m_label;
    m_label = label;
}


void GraphWidget::setAxis(AxisPosition pos, GraphAxisWidget* axis)
{
    switch(pos) {
        case AxisTop:
            delete m_axisTop;
            m_axisTop = axis;
            m_axisTop->setOrientation(GraphAxisWidget::Horizontal);
            m_axisTop->setSelectionState(&m_selection);
            break;

        case AxisLeft:
            delete m_axisLeft;
            m_axisLeft = axis;
            m_axisLeft->setOrientation(GraphAxisWidget::Vertical);
            m_axisLeft->setSelectionState(&m_selection);
            break;

        case AxisRight:
            delete m_axisRight;
            m_axisRight = axis;
            m_axisRight->setOrientation(GraphAxisWidget::Vertical);
            m_axisRight->setSelectionState(&m_selection);
            break;

        case AxisBottom:
            delete m_axisBottom;
            m_axisBottom = axis;
            m_axisBottom->setOrientation(GraphAxisWidget::Horizontal);
            m_axisBottom->setSelectionState(&m_selection);
            break;
    }

    updateConnections();
    updateSelection();
    axis->show();
}


void GraphWidget::setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy policy)
{
    m_horizontalScrollbarPolicy = policy;
    updateScrollbars();
}


void GraphWidget::setVerticalScrollBarPolicy(Qt::ScrollBarPolicy policy)
{
    m_verticalScrollbarPolicy = policy;
    updateScrollbars();
}


void GraphWidget::resizeEvent(QResizeEvent *e)
{
    updateLayout();
    update();
}


/* Used if a selection would be shared between graphs with different axis */
SelectionState GraphWidget::transformSelectionIn(SelectionState state)
{
    return state;
}


/* Used if a selection would be shared between graphs with different axis */
SelectionState GraphWidget::transformSelectionOut(SelectionState state)
{
    return state;
}


/* Update the scrollbars based on current view */
void GraphWidget::updateScrollbars()
{
    /* Vertical scroll bar */
    qint64 size = (m_verticalMax - m_verticalMin) - (m_verticalEnd - m_verticalStart);

    if (size <= INT_MAX) {
        m_verticalScrollbar->setValue(m_verticalStart - m_verticalMin);
        m_verticalScrollbar->setPageStep(m_verticalEnd - m_verticalStart);
        m_verticalScrollbar->setRange(0, size);
    } else {
        /* QScrollBar only supports up to INT_MAX values,
         * here we must scale our values to match this */
        double curSize = m_verticalEnd - m_verticalStart;
        double pages = (m_verticalMax - m_verticalMin) / curSize;
        double value = (m_verticalStart - m_verticalMin) / curSize;

        m_verticalScrollbar->setValue(value);
        m_verticalScrollbar->setPageStep(1);
        m_verticalScrollbar->setRange(0, pages);
    }

    /* Adhere to scrollbar policy */
    bool visible = false;

    if (m_verticalScrollbarPolicy == Qt::ScrollBarAlwaysOn) {
        visible = true;
    } else if (m_verticalScrollbarPolicy == Qt::ScrollBarAlwaysOff) {
        visible = false;
    } else if (m_verticalScrollbarPolicy == Qt::ScrollBarAsNeeded) {
        visible = m_verticalMin != m_verticalStart || m_verticalMax != m_verticalEnd;
    }

    if (visible != m_verticalScrollbar->isVisible()) {
        m_verticalScrollbar->setVisible(visible);
        updateLayout();
    }

    /* Horizontal scroll bar */
    size = (m_horizontalMax - m_horizontalMin) - (m_horizontalEnd - m_horizontalStart);

    if (size <= INT_MAX) {
        m_horizontalScrollbar->setValue(m_horizontalStart - m_horizontalMin);
        m_horizontalScrollbar->setPageStep(m_horizontalEnd - m_horizontalStart);
        m_horizontalScrollbar->setRange(0, size);
    } else {
        /* QScrollBar only supports up to INT_MAX values,
         * here we must scale our values to match this */
        double dxdv = INT_MAX / (double)size;
        double value = (m_horizontalStart - m_horizontalMin) * dxdv;
        double pageStep = (m_horizontalEnd - m_horizontalStart) * dxdv;

        m_horizontalScrollbar->setValue((int)value);
        m_horizontalScrollbar->setPageStep((int)pageStep);
        m_horizontalScrollbar->setRange(0, INT_MAX);
    }

    /* Adhere to scrollbar policy */
    visible = false;

    if (m_horizontalScrollbarPolicy == Qt::ScrollBarAlwaysOn) {
        visible = true;
    } else if (m_horizontalScrollbarPolicy == Qt::ScrollBarAlwaysOff) {
        visible = false;
    } else if (m_horizontalScrollbarPolicy == Qt::ScrollBarAsNeeded) {
        visible =  m_horizontalMin != m_horizontalStart || m_horizontalMax != m_horizontalEnd;
    }

    if (visible != m_horizontalScrollbar->isVisible()) {
        m_horizontalScrollbar->setVisible(visible);
        updateLayout();
    }
}


/* Update all signal / slot connections */
void GraphWidget::updateConnections()
{
    if (m_view) {
        connect(m_view, SIGNAL(selectionChanged()), this, SLOT(updateSelection()), Qt::UniqueConnection);

        connect(m_view, SIGNAL(horizontalViewChanged(qint64,qint64)), this, SLOT(horizontalViewChange(qint64,qint64)), Qt::UniqueConnection);
        connect(m_view, SIGNAL(horizontalRangeChanged(qint64,qint64)), this, SLOT(horizontalRangeChange(qint64,qint64)), Qt::UniqueConnection);

        connect(m_view, SIGNAL(verticalViewChanged(qint64,qint64)), this, SLOT(verticalViewChange(qint64,qint64)), Qt::UniqueConnection);
        connect(m_view, SIGNAL(verticalRangeChanged(qint64,qint64)), this, SLOT(verticalRangeChange(qint64,qint64)), Qt::UniqueConnection);
    }

    if (m_axisTop) {
        if (m_view) {
            connect(m_view, SIGNAL(horizontalViewChanged(qint64,qint64)), m_axisTop, SLOT(setView(qint64,qint64)), Qt::UniqueConnection);
            connect(m_view, SIGNAL(horizontalRangeChanged(qint64,qint64)), m_axisTop, SLOT(setRange(qint64,qint64)), Qt::UniqueConnection);
        }

        connect(m_axisTop, SIGNAL(selectionChanged()), this, SLOT(updateSelection()), Qt::UniqueConnection);
    }

    if (m_axisLeft) {
        if (m_view) {
            connect(m_view, SIGNAL(verticalViewChanged(qint64,qint64)), m_axisLeft, SLOT(setView(qint64,qint64)), Qt::UniqueConnection);
            connect(m_view, SIGNAL(verticalRangeChanged(qint64,qint64)), m_axisLeft, SLOT(setRange(qint64,qint64)), Qt::UniqueConnection);
        }

        connect(m_axisLeft, SIGNAL(selectionChanged()), this, SLOT(updateSelection()), Qt::UniqueConnection);
    }

    if (m_axisRight) {
        if (m_view) {
            connect(m_view, SIGNAL(verticalViewChanged(qint64,qint64)), m_axisRight, SLOT(setView(qint64,qint64)), Qt::UniqueConnection);
            connect(m_view, SIGNAL(verticalRangeChanged(qint64,qint64)), m_axisRight, SLOT(setRange(qint64,qint64)), Qt::UniqueConnection);
        }

        connect(m_axisRight, SIGNAL(selectionChanged()), this, SLOT(updateSelection()), Qt::UniqueConnection);
    }

    if (m_axisBottom) {
        if (m_view) {
            connect(m_view, SIGNAL(horizontalViewChanged(qint64,qint64)), m_axisBottom, SLOT(setView(qint64,qint64)), Qt::UniqueConnection);
            connect(m_view, SIGNAL(horizontalRangeChanged(qint64,qint64)), m_axisBottom, SLOT(setRange(qint64,qint64)), Qt::UniqueConnection);
        }

        connect(m_axisBottom, SIGNAL(selectionChanged()), this, SLOT(updateSelection()), Qt::UniqueConnection);
    }

    if (m_horizontalScrollbar) {
        connect(m_horizontalScrollbar, SIGNAL(actionTriggered(int)), this, SLOT(horizontalScrollAction(int)));
    }

    if (m_verticalScrollbar) {
        connect(m_verticalScrollbar, SIGNAL(actionTriggered(int)), this, SLOT(verticalScrollAction(int)));
    }
}


/* Recalculate the layout */
void GraphWidget::updateLayout()
{
    int x, y;
    int padX = 0, padY = 0;

    if (m_axisTop) {
        padY += m_axisTop->height();
    }

    if (m_axisBottom) {
        padY += m_axisBottom->height();
    }

    if (m_axisLeft) {
        padX += m_axisLeft->width();
    }

    if (m_axisRight) {
        padX += m_axisRight->width();
    }

    if (m_horizontalScrollbar->isVisible()) {
        padY += m_horizontalScrollbar->height();
    }

    if (m_verticalScrollbar->isVisible()) {
        padX += m_verticalScrollbar->width();
    }

    if (m_axisTop) {
        x = m_axisLeft ? m_axisLeft->width() : 0;
        y = 0;

        m_axisTop->move(x, y);
        m_axisTop->resize(width() - padX, m_axisTop->height());
    }

    if (m_axisBottom) {
        x = m_axisLeft ? m_axisLeft->width() : 0;
        y = height() - m_axisBottom->height();

        if (m_horizontalScrollbar->isVisible())  {
            y -= m_horizontalScrollbar->height();
        }

        m_axisBottom->move(x, y);
        m_axisBottom->resize(width() - padX, m_axisBottom->height());
    }

    if (m_axisLeft) {
        x = 0;
        y = m_axisTop ? m_axisTop->height() : 0;

        m_axisLeft->move(x, y);
        m_axisLeft->resize(m_axisLeft->width(), height() - padY);
    }

    if (m_axisRight) {
        x = width() - m_axisRight->width();
        y = m_axisTop ? m_axisTop->height() : 0;

        if (m_verticalScrollbar->isVisible())  {
            x -= m_verticalScrollbar->width();
        }

        m_axisRight->move(x, y);
        m_axisRight->resize(m_axisRight->width(), height() - padY);
    }

    if (m_view) {
        x = m_axisLeft ? m_axisLeft->width() : 0;
        y = m_axisTop ? m_axisTop->height() : 0;

        m_view->move(x, y);
        m_view->resize(width() - padX, height() - padY);
    }

    if (m_label) {
        if (m_axisTop && m_axisLeft) {
            m_label->move(0, 0);
            m_label->resize(m_axisLeft->width(), m_axisTop->height());
        }
    }

    if (m_verticalScrollbar) {
        m_verticalScrollbar->move(width() - m_verticalScrollbar->width(), 0);

        if (m_horizontalScrollbar) {
            m_verticalScrollbar->resize(m_verticalScrollbar->width(), height() - m_horizontalScrollbar->height());
        } else {
            m_verticalScrollbar->resize(m_verticalScrollbar->width(), height());
        }
    }

    if (m_horizontalScrollbar) {
        m_horizontalScrollbar->move(0, height() - m_horizontalScrollbar->height());

        if (m_verticalScrollbar) {
            m_horizontalScrollbar->resize(width() - m_verticalScrollbar->width(), m_horizontalScrollbar->height());
        } else {
            m_horizontalScrollbar->resize(width(), m_horizontalScrollbar->height());
        }
    }
}


void GraphWidget::setSelection(SelectionState state)
{
    m_selection = transformSelectionIn(state);
    updateSelection(false);
}


void GraphWidget::setHorizontalView(qint64 start, qint64 end)
{
    if (m_view) {
        m_view->setHorizontalView(start, end);
    }
}


void GraphWidget::setVerticalView(qint64 start, qint64 end)
{
    if (m_view) {
        m_view->setVerticalView(start, end);
    }
}


/* Called when the view is translated / zoomed */
void GraphWidget::verticalViewChange(qint64 start, qint64 end)
{
    m_verticalStart = start;
    m_verticalEnd = end;
    updateScrollbars();

    emit verticalViewChanged(start, end);
}


void GraphWidget::verticalRangeChange(qint64 start, qint64 end)
{
    m_verticalMin = start;
    m_verticalMax = end;
    updateScrollbars();

    emit verticalRangeChanged(start, end);
}


void GraphWidget::horizontalViewChange(qint64 start, qint64 end)
{
    m_horizontalStart = start;
    m_horizontalEnd = end;
    updateScrollbars();

    emit horizontalViewChanged(start, end);
}


void GraphWidget::horizontalRangeChange(qint64 start, qint64 end)
{
    m_horizontalMin = start;
    m_horizontalMax = end;
    updateScrollbars();

    emit horizontalRangeChanged(start, end);
}


/* User interaction with horizontal scroll bar */
void GraphWidget::horizontalScrollAction(int /*action*/)
{
    int value = m_horizontalScrollbar->sliderPosition();
    qint64 size = (m_horizontalMax - m_horizontalMin) - (m_horizontalEnd - m_horizontalStart);

    /* Calculate the new scroll values */
    if (size <= INT_MAX) {
        m_horizontalEnd -= m_horizontalStart;
        m_horizontalStart = value + m_horizontalMin;
        m_horizontalEnd += value;
    } else {
        /* QScrollBar only supports up to INT_MAX values, here we must scale
         * our values to match this */
        double dxdv = INT_MAX / (double)size;

        size = m_horizontalEnd - m_horizontalStart;
        m_horizontalStart = value / dxdv + m_horizontalMin;
        m_horizontalEnd = m_horizontalStart + size;
    }

    /* Update view */
    if (m_view) {
        m_view->setHorizontalView(m_horizontalStart, m_horizontalEnd);
    }

    /* Update horizontal axes */
    if (m_axisTop) {
        m_axisTop->setView(m_horizontalStart, m_horizontalEnd);
    }

    if (m_axisBottom) {
        m_axisBottom->setView(m_horizontalStart, m_horizontalEnd);
    }

    /* Inform the world of our changes! */
    emit horizontalViewChanged(m_horizontalStart, m_horizontalEnd);
}


/* User interaction with vertical scroll bar */
void GraphWidget::verticalScrollAction(int /*action*/)
{
    int value = m_verticalScrollbar->sliderPosition();
    qint64 size = (m_verticalMax - m_verticalMin) - (m_verticalEnd - m_verticalStart);

    /* Calculate the new scroll values */
    if (size <= INT_MAX) {
        m_verticalEnd -= m_verticalStart;
        m_verticalStart = value + m_verticalMin;
        m_verticalEnd += value;
    } else {
        /* QScrollBar only supports up to INT_MAX values, here we must scale
         * our values to match this */
        double dxdv = INT_MAX / (double)size;

        size = m_verticalEnd - m_verticalStart;
        m_verticalStart = value / dxdv + m_verticalMin;
        m_verticalEnd = m_verticalStart + size;
    }

    /* Update view */
    if (m_view) {
        m_view->setVerticalView(m_verticalStart, m_verticalEnd);
    }

    /* Update vertical axes */
    if (m_axisLeft) {
        m_axisLeft->setView(m_verticalStart, m_verticalEnd);
    }

    if (m_axisRight) {
        m_axisRight->setView(m_verticalStart, m_verticalEnd);
    }

    /* Inform the world of our changes! */
    emit verticalViewChanged(m_verticalStart, m_verticalEnd);
}


/* Update child elements when selection changes */
void GraphWidget::updateSelection(bool emitSignal)
{
    if (m_view) {
        m_view->update();
    }

    if (m_axisTop) {
        m_axisTop->update();
    }

    if (m_axisLeft) {
        m_axisLeft->update();
    }

    if (m_axisRight) {
        m_axisRight->update();
    }

    if (m_axisBottom) {
        m_axisBottom->update();
    }

    if (emitSignal) {
        emit selectionChanged(transformSelectionOut(m_selection));
    }
}
