#include "segmentstimeline.h"
#include "ui_segmentstimeline.h"

SegmentsTimeline::SegmentsTimeline(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::SegmentsTimeline)
{
    ui->setupUi(this);

    highlightSegments = true;
    cursorPosition = 0;
    cursorGrabbed = false;
    cursorVisible = true;
}

SegmentsTimeline::~SegmentsTimeline()
{
    delete ui;
}

void SegmentsTimeline::setSegments(Segments newSegments)
{
    segments = newSegments;
    std::sort(segments.begin(), segments.end());

    cursorPosition = 0;
    cursorGrabbed = false;;

    recomputeDimensions();
    repaint();
}

void SegmentsTimeline::clearSegments()
{
    segments.clear();
    repaint();
}

void SegmentsTimeline::recomputeDimensions()
{
    totalLength = 0;

    for(Segments::iterator it = segments.begin(); it != segments.end(); it++)
    {
        totalLength += it->getLength();
    }
}

double SegmentsTimeline::getScale() const
{
    return (segments.size() > 0)? geometry().width()/totalLength : -1;
}

void SegmentsTimeline::setHighlightSegments(bool doHighlightSegments)
{
    this->highlightSegments = doHighlightSegments;
}

void SegmentsTimeline::setCursorVisible(bool cursorVisible)
{
    this->cursorVisible = cursorVisible;
}

int SegmentsTimeline::getSegmentIdxByPosition(double position, double *positionInsideSegment)
{
    double cumulativeLength = 0;
    int i = 0;

    while(i < (signed)segments.size() && cumulativeLength+segments[i].getLength() < position*totalLength)
    {
        cumulativeLength += segments[i].getLength();
        i++;
    }

    if(i >= (signed)segments.size())
    {
        return -1;
    }

    if(positionInsideSegment != NULL)
    {
        *positionInsideSegment = (position*totalLength - cumulativeLength)/segments[i].getLength();
    }

    return i;
}

void SegmentsTimeline::paintEvent(QPaintEvent *e)
{
    Q_UNUSED(e);

    QPainter painter(this);

    if(segments.size() == 0)
    {
        painter.setPen(Qt::NoPen);
        painter.setBrush(Qt::darkGray);
        painter.drawRect(0, 0, geometry().width(), geometry().height());
    }
    else
    {
        paintSegments(painter);
        paintCursor(painter);
    }
}

void SegmentsTimeline::paintSegments(QPainter & painter)
{
    double scale = getScale();
    if(scale > 0)
    {
        painter.setPen(Qt::NoPen);

        double cumulativeLength = 0;

        for(size_t i = 0; i < segments.size(); i++)
        {
            Segment segment = segments[i];

            painter.setBrush(segment.getColor());

            QRect rectangle(ceil(scale*cumulativeLength), 0, ceil(scale*segment.getLength()), height());
            painter.drawRect(rectangle);

            if(highlightSegments && i == getSegmentIdxByPosition(cursorPosition))
            {
                painter.setBrush(QBrush(Qt::gray, Qt::BDiagPattern));
                painter.drawRect(rectangle);
            }

            cumulativeLength += segment.getLength();
        }
    }
}

void SegmentsTimeline::paintCursor(QPainter & painter)
{
    if(!cursorVisible)
    {
        return;
    }

    int cursorPositionPx = getCursorPositionPx();

    painter.setPen(Qt::SolidLine);
    painter.drawLine(cursorPositionPx, 0, cursorPositionPx, height());

    double triangleSize = 0.3;
    int triangleSideLength = triangleSize*height();

    QPolygon triangleTop;
    triangleTop << QPoint(cursorPositionPx-triangleSideLength, 0) << QPoint(cursorPositionPx+triangleSideLength, 0) << QPoint(cursorPositionPx, triangleSideLength);

    QPolygon triangleBottom;
    triangleBottom << QPoint(cursorPositionPx-triangleSideLength, height()) << QPoint(cursorPositionPx+triangleSideLength, height()) << QPoint(cursorPositionPx, height()-triangleSideLength);

    painter.setPen(Qt::NoPen);
    painter.setBrush(Qt::black);
    painter.drawPolygon(triangleTop);
    painter.drawPolygon(triangleBottom);
}

int SegmentsTimeline::getCursorPositionPx() const
{
   return ceil(cursorPosition*(width()-1));
}

void SegmentsTimeline::setCursorPositionGlobal(double positionGlobal)
{
    if(positionGlobal >= 0 && positionGlobal <=1)
    {
        cursorPosition = positionGlobal;
        repaint();
    }
}

void SegmentsTimeline::setCursorPositionSegment(int segmentIdx, double positionSegment)
{
    if(segmentIdx >= (signed)segments.size())
    {
        // TODO
        throw;
    }

    double cumulativeLength = 0;
    for(int i = 0; i < segmentIdx; i++)
    {
        cumulativeLength += segments[i].getLength();
    }

    setCursorPositionGlobal((cumulativeLength + positionSegment*segments[segmentIdx].getLength())/totalLength);
}

bool SegmentsTimeline::isCursorNear(int x, int y, int neighbourhood) const
{
    int cursorPositionPx = getCursorPositionPx();
    return y >= 0 && y <= height()-1 && x >= cursorPositionPx-neighbourhood && x <= cursorPositionPx+neighbourhood;
}


void SegmentsTimeline::mouseMoveEvent(QMouseEvent *e)
{
    if(cursorGrabbed)
    {
        double newCursorPosition = std::min<double>(std::max<double>(((double)e->x())/(width()-1), 0), 1);
        setCursorPositionGlobal(newCursorPosition);

        double currentSegmentInsidePosition = 0;
        int currentSegmentIdx = getSegmentIdxByPosition(cursorPosition, &currentSegmentInsidePosition);

        if(currentSegmentIdx >= 0)
        {
            emit cursorMoved(currentSegmentIdx, currentSegmentInsidePosition, cursorPosition);
        }
    }
}

void SegmentsTimeline::mousePressEvent(QMouseEvent *e)
{
    if(cursorVisible && isCursorNear(e->x(), e->y(), 4))
    {
        cursorGrabbed = true;
        setMouseTracking(true);
    }
}

void SegmentsTimeline::mouseReleaseEvent(QMouseEvent *e)
{
    Q_UNUSED(e);

    if(cursorGrabbed)
    {
        cursorGrabbed = false;
        setMouseTracking(false);
    }
    else {
        mouseClickEvent(e);
    }
}

void SegmentsTimeline::mouseClickEvent(QMouseEvent *e)
{
    double clickPosition = ((double)e->x())/width();
    double currentSegmentInsidePosition = 0;
    int currentSegmentIdx = getSegmentIdxByPosition(clickPosition, &currentSegmentInsidePosition);

    if(currentSegmentIdx >= 0)
    {
        emit segmentClicked(currentSegmentIdx, currentSegmentInsidePosition, clickPosition);
    }
}

SegmentsTimeline::Segment::Segment(int start, int end, QColor color, void *data = NULL)
{
    this->start = start;
    this->end = end;
    this->length = end-start+1;
    this->color = color;
    this->data = data;
}

int SegmentsTimeline::Segment::getStart() const
{
    return start;
}

int SegmentsTimeline::Segment::getEnd() const
{
    return end;
}

double SegmentsTimeline::Segment::getLength() const
{
    return length;
}

QColor SegmentsTimeline::Segment::getColor() const
{
    return color;
}

void SegmentsTimeline::Segment::setData(void *data)
{
    this->data = data;
}

void *SegmentsTimeline::Segment::getData() const
{
    return data;
}

bool SegmentsTimeline::Segment::operator<(const Segment & second) const
{
    if(this->start < second.start)
    {
        return true;
    }

    return this->start < second.start;
}
