/*
 * This file is part of the DSView project.
 * DSView is based on PulseView.
 *
 * Copyright (C) 2012 Joel Holdsworth <joel@airwebreathe.org.uk>
 * Copyright (C) 2013 DreamSourceLab <support@dreamsourcelab.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 */

#include "xcursor.h"
#include <QPainter>
#include "view.h"
#include "ruler.h"
#include "dsosignal.h"
#include "../log.h"
#include "ruler.h"
  
using namespace std;

namespace pv {
namespace view {

XCursor::XCursor(View &view, int order, double value0, double value1) :
	_view(view),
    _yvalue(0.5),
    _value0(value0),
    _value1(value1),
    _grabbed(XCur_None),
    _order(order)
{
    _dsoSig = NULL;
    _sig_index = -1;
    _colour = Qt::blue;
    
    for(auto s : _view.session().get_signals()) {        
        if (s->signal_type() == SR_CHANNEL_DSO){
            DsoSignal *dsoSig = (DsoSignal*)s;
            if (dsoSig->enabled()) {
                _dsoSig = dsoSig;
                break;
            }
        }
    }

    if (_dsoSig != NULL){
        connect(_dsoSig, SIGNAL(sig_released(void*)), this, SLOT(on_signal_deleted(void*)));
        _sig_index = _dsoSig->get_index();
    }
}

XCursor::XCursor(const XCursor &x) :
	QObject(),
    _view(x._view)
{
    _dsoSig = x._dsoSig;
    _yvalue = x._yvalue;
    _value0 = x._value0;
    _value1 = x._value1;
    _grabbed = XCur_None;
    _colour = x._colour;
    _sig_index = -1;

    if (_dsoSig != NULL){
        connect(_dsoSig, SIGNAL(sig_released(void*)), this, SLOT(on_signal_deleted(void*)));
        _sig_index = _dsoSig->get_index();
    }
}

QColor XCursor::colour()
{
    return get_color();
}

void XCursor::set_colour(QColor color)
{
    _colour = color;
}

/**
 * Gets/Sets the mapping channel of the marker
 */
DsoSignal* XCursor::channel()
{
    return _dsoSig;
}

void XCursor::set_channel(DsoSignal *sig)
{
    _dsoSig = sig;

    if (_dsoSig != NULL){
        connect(_dsoSig, SIGNAL(sig_released(void*)), this, SLOT(on_signal_deleted(void*)));
        _sig_index = _dsoSig->get_index();
    }
}

void XCursor::on_signal_deleted(void *o)
{
    if (o == _dsoSig){
        _dsoSig = NULL;
    }   
}

enum XCursor::XCur_type XCursor::grabbed()
{
    return _grabbed;
}

void XCursor::set_grabbed(XCur_type type, bool grabbed)
{
    if (_grabbed == XCur_None && grabbed)
        _grabbed = type;
    else if (_grabbed == type && !grabbed)
        _grabbed = XCur_None;
}

void XCursor::rel_grabbed()
{
    _grabbed = XCur_None;
}

double XCursor::value(XCur_type type)
{
    if (type == XCur_Y)
        return _yvalue;
    else if (type == XCur_X0)
        return _value0;
    else if (type == XCur_X1)
        return _value1;
    else
        return 0;
}

void XCursor::set_value(XCur_type type, double value)
{
    if (type == XCur_Y)
        _yvalue = value;
    else if (type == XCur_X0)
        _value0 = value;
    else if (type == XCur_X1)
        _value1 = value;
    value_changed();
}

void XCursor::paint(QPainter &p, const QRect &rect, XCur_type highlight)
{   
    // Attach the channel
    if (_dsoSig == NULL && _sig_index != -1){
        auto sig = _view.session().get_signal_by_index(_sig_index);
        if (sig != NULL){
            _dsoSig = dynamic_cast<DsoSignal*>(sig);

            if (_dsoSig != NULL){
                connect(_dsoSig, SIGNAL(sig_released(void*)), this, SLOT(on_signal_deleted(void*)));
            }
        }
    }

    const int arrow = 3;
    const int x = rect.left() + _yvalue * rect.width();
    const int y0 = rect.top() + _value0 * rect.height();
    const int y1 = rect.top() + _value1 * rect.height();
    QColor color = get_color();
    const bool hit0 = (_grabbed == XCur_X0) | (_grabbed == XCur_None && (highlight == XCur_X0 || highlight == XCur_All));
    p.setPen(hit0 ? QPen(color.lighter(), 2, Qt::DashLine) : QPen(color, 1, Qt::DashLine));
    p.drawLine(QPoint(0, y0), QPoint(rect.right()-_v0_size.width(), y0));
    const bool hit1 = (_grabbed == XCur_X1) | (_grabbed == XCur_None && (highlight == XCur_X1 || highlight == XCur_All));
    p.setPen(hit1 ? QPen(color.lighter(), 2, Qt::DashLine) : QPen(color, 1, Qt::DashLine));
    p.drawLine(QPoint(0, y1), QPoint(rect.right()-_v1_size.width(), y1));

    if (_dsoSig) {
        if ((_grabbed == XCur_Y) | (_grabbed == XCur_None && (highlight == XCur_Y || highlight == XCur_All)))
            p.setPen(QPen(_dsoSig->get_colour(), 2, Qt::DashLine));
        else
            p.setPen(QPen(_dsoSig->get_colour(), 1, Qt::DashLine));
        p.drawLine(QPoint(x, y0), QPoint(x, y1));
        p.drawLine(QPoint(x-arrow, y0 + ((y0 > y1) ? -arrow : arrow)), QPoint(x, y0));
        p.drawLine(QPoint(x+arrow, y0 + ((y0 > y1) ? -arrow : arrow)), QPoint(x, y0));
        p.drawLine(QPoint(x-arrow, y1 + ((y0 > y1) ? arrow : -arrow)), QPoint(x, y1));
        p.drawLine(QPoint(x+arrow, y1 + ((y0 > y1) ? arrow : -arrow)), QPoint(x, y1));

        QString vol = " Δ:" + _dsoSig->get_voltage(qAbs(y0 - y1), 2, true);
        const QSizeF delta_size = p.boundingRect(rect, Qt::AlignLeft | Qt::AlignTop, vol).size();
        const QRectF delta_rect = QRectF(x, (y0+y1-delta_size.height())/2, delta_size.width(), delta_size.height());
        p.drawText(delta_rect, Qt::AlignCenter, vol);

        vol = _dsoSig->get_voltage((_dsoSig->get_zero_ratio() - _value0)*rect.height(), 2, true);
        _v0_size = p.boundingRect(rect, Qt::AlignLeft | Qt::AlignTop, vol).size();
        p.drawText(QRectF(rect.right()-_v0_size.width(), y0-_v0_size.height()/2, _v0_size.width(), _v0_size.height()),
                   Qt::AlignLeft | Qt::AlignVCenter,
                   vol);

        vol = _dsoSig->get_voltage((_dsoSig->get_zero_ratio() - _value1)*rect.height(), 2, true);
        _v1_size = p.boundingRect(rect, Qt::AlignLeft | Qt::AlignTop, vol).size();
        p.drawText(QRectF(rect.right()-_v1_size.width(), y1-_v1_size.height()/2, _v1_size.width(), _v1_size.height()),
                   Qt::AlignLeft | Qt::AlignVCenter,
                   vol);
    }

    paint_label(p, rect);
}

/**
 * Gets the map label rectangle.
 * @param rect The rectangle of the xcursor area.
 * @return Returns the map label rectangle.
 */
QRect XCursor::get_map_rect(const QRect &rect)
{
    const int width = 10;
    const int64_t y = rect.top() + _value0 * rect.height() - width/2;
    return QRect(rect.right()+1, y, width, width);
}

/**
 * Gets the close label rectangle.
 * @param rect The rectangle of the xcursor area.
 * @return Returns the close label rectangle.
 */
QRect XCursor::get_close_rect(const QRect &rect)
{
    const int width = 10;
    const int64_t y = rect.top() + _value1 * rect.height() - width/2;
    return QRect(rect.right()+1, y, width, width);
}

/**
 * Paints the labels to the xcursor.
 * @param p The painter to draw with.
 * @param rect The rectangle of the xcursor area.
 */
void XCursor::paint_label(QPainter &p, const QRect &rect)
{
    if (_dsoSig) {
        QRect map_rect = get_map_rect(rect);
        p.setPen(Qt::NoPen);
        p.setBrush(_dsoSig->get_colour());
        p.drawRect(map_rect);
        p.setPen(Qt::white);
        p.drawText(map_rect, Qt::AlignCenter | Qt::AlignHCenter, QString::number(_dsoSig->get_index()));
    }

    QRect close = get_close_rect(rect);
    p.setPen(Qt::NoPen);

    if (close.contains(QPoint(_view.hover_point().x(), _view.hover_point().y())))
        p.setBrush(View::Red);
    else if (_dsoSig != NULL)
        p.setBrush(_dsoSig->get_colour());
    else
        p.setBrush(View::Blue);

    p.drawRect(close);
    p.setPen(Qt::black);
    p.drawLine(close.left() + 2, close.top() + 2, close.right() - 2, close.bottom() - 2);
    p.drawLine(close.left() + 2, close.bottom() - 2, close.right() - 2, close.top() + 2);
}

QColor XCursor::get_color()
{   
    if (_order > 0){
        return Ruler::GetColorByCursorOrder(_order);
    }
    return _colour;    
}

} // namespace view
} // namespace pv
