/*
 * 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 <dreamsourcelab@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 "view.h"
#include "viewport.h"
#include "ruler.h"

#include "signal.h"
#include "dsosignal.h"
#include "logicsignal.h"
#include "../device/devinst.h"
#include "../data/logic.h"
#include "../data/logicsnapshot.h"
#include "../sigsession.h"

#include <QMouseEvent>
#include <QStyleOption>

#include <boost/foreach.hpp>

using namespace boost;
using namespace std;

namespace pv {
namespace view {

Viewport::Viewport(View &parent) :
	QWidget(&parent),
        _view(parent),
    _total_receive_len(0),
    _zoom_rect_visible(false),
    _measure_shown(false),
    _cur_sample(0),
    _nxt_sample(1),
    _cur_preX(0),
    _cur_aftX(1),
    _cur_midY(0)
{
	setMouseTracking(true);
	setAutoFillBackground(true);
	setBackgroundRole(QPalette::Base);

    //setFixedSize(QSize(600, 400));
    _mm_width = "#####";
    _mm_period = "#####";
    _mm_freq = "#####";
    _mm_duty = "#####";
    _measure_en = true;
    triggered = false;
    timer_cnt = 0;

    connect(&_view, SIGNAL(traces_moved()),
        this, SLOT(on_traces_moved()));
    connect(&trigger_timer, SIGNAL(timeout()),
            this, SLOT(on_trigger_timer()));
}

int Viewport::get_total_height() const
{
	int h = 0;

    const vector< shared_ptr<Trace> > traces(_view.get_traces());
    BOOST_FOREACH(const shared_ptr<Trace> t, traces) {
        assert(t);
        h += (int)(t->get_signalHeight());
    }
    h += 2 * View::SignalMargin;

	return h;
}

QPoint Viewport::get_mouse_point() const
{
    return _mouse_point;
}

void Viewport::paintEvent(QPaintEvent *event)
{
    (void)event;

    using pv::view::Signal;

    QStyleOption o;
    o.initFrom(this);
    QPainter p(this);
    style()->drawPrimitive(QStyle::PE_Widget, &o, &p, this);

    const vector< shared_ptr<Trace> > traces(_view.get_traces());
    BOOST_FOREACH(const shared_ptr<Trace> t, traces)
    {
        assert(t);
        t->paint_back(p, 0, _view.get_view_width());
    }

    p.setRenderHint(QPainter::Antialiasing, false);
    if (_view.session().get_device()->dev_inst()->mode == LOGIC ||
        _view.session().get_instant()) {
        switch(_view.session().get_capture_state()) {
        case SigSession::Init:
            break;

        case SigSession::Stopped:
            paintSignals(p);
            break;

        case SigSession::Running:
            //p.setRenderHint(QPainter::Antialiasing);
            paintProgress(p);
            break;
        }
    } else {
        paintSignals(p);
    }

    BOOST_FOREACH(const shared_ptr<Trace> t, traces)
    {
        assert(t);
        if (t->enabled())
            t->paint_fore(p, 0, _view.get_view_width());
    }

    //p.setRenderHint(QPainter::Antialiasing, false);
    if (_view.get_signalHeight() != _curSignalHeight)
            _curSignalHeight = _view.get_signalHeight();

	p.end();
}

void Viewport::paintSignals(QPainter &p)
{
    const vector< shared_ptr<Trace> > traces(_view.get_traces());
    if (_view.scale() != _curScale ||
        _view.offset() != _curOffset ||
        _view.get_signalHeight() != _curSignalHeight ||
        _view.need_update()) {
        _curScale = _view.scale();
        _curOffset = _view.offset();
        _curSignalHeight = _view.get_signalHeight();

        pixmap = QPixmap(size());
        pixmap.fill(Qt::transparent);
        QPainter dbp(&pixmap);
        dbp.initFrom(this);
        //p.setRenderHint(QPainter::Antialiasing, false);
        BOOST_FOREACH(const shared_ptr<Trace> t, traces)
        {
            assert(t);
            if (t->enabled())
                t->paint_mid(dbp, 0, _view.get_view_width());
        }

        _view.set_need_update(false);
    }
    p.drawPixmap(0, 0, pixmap);

    // plot cursors
    if (_view.cursors_shown()) {
        list<Cursor*>::iterator i = _view.get_cursorList().begin();
        double cursorX;
        while (i != _view.get_cursorList().end()) {
            cursorX = ((*i)->time() - _view.offset()) / _view.scale();
            if (rect().contains(_view.hover_point().x(), _view.hover_point().y()) &&
                    qAbs(cursorX - _view.hover_point().x()) <= HitCursorMargin)
                (*i)->paint(p, rect(), 1);
            else
                (*i)->paint(p, rect(), 0);
            i++;
        }
    }
    if (_view.trig_cursor_shown()) {
        _view.get_trig_cursor()->paint(p, rect(), 0);
    }
    if (_view.search_cursor_shown()) {
        _view.get_search_cursor()->paint(p, rect(), 0);
    }

    // plot zoom rect
    if (_zoom_rect_visible) {
        p.setPen(Qt::NoPen);
        p.setBrush(Trace::dsLightBlue);
        p.drawRect(_zoom_rect);
    }

    //plot measure arrow
    if (_measure_shown) {
        paintMeasure(p);
    }
}

void Viewport::paintProgress(QPainter &p)
{
    using pv::view::Signal;

    const quint64 _total_sample_len = _view.session().get_device()->get_sample_limit();
    double progress = -(_total_receive_len * 1.0f / _total_sample_len * 360 * 16);
    int captured_progress = 0;

    p.setPen(Qt::gray);
    p.setBrush(Qt::NoBrush);
    const QPoint cenPos = QPoint(_view.get_view_width() / 2, height() / 2);
    const int radius = min(0.3 * _view.get_view_width(), 0.3 * height());
    p.drawEllipse(cenPos, radius - 2, radius - 2);
    p.setPen(QPen(Trace::dsGreen, 4, Qt::SolidLine));
    p.drawArc(cenPos.x() - radius, cenPos.y() - radius, 2* radius, 2 * radius, 180 * 16, progress);

    p.setPen(Qt::gray);
    const QPoint logoPoints[] = {
        QPoint(cenPos.x() - 0.75 * radius, cenPos.y()),
        QPoint(cenPos.x() - 0.75 * radius, cenPos.y() + 0.15 * radius),
        QPoint(cenPos.x() - 0.6 * radius, cenPos.y()),
        QPoint(cenPos.x() - 0.6 * radius, cenPos.y() + 0.3 * radius),
        QPoint(cenPos.x() - 0.45 * radius, cenPos.y()),
        QPoint(cenPos.x() - 0.45 * radius, cenPos.y() + 0.45 * radius),
        QPoint(cenPos.x() - 0.3 * radius, cenPos.y()),
        QPoint(cenPos.x() - 0.3 * radius, cenPos.y() + 0.3 * radius),
        QPoint(cenPos.x() - 0.15 * radius, cenPos.y()),
        QPoint(cenPos.x() - 0.15 * radius, cenPos.y() + 0.15 * radius),
        QPoint(cenPos.x() + 0.15 * radius, cenPos.y()),
        QPoint(cenPos.x() + 0.15 * radius, cenPos.y() - 0.15 * radius),
        QPoint(cenPos.x() + 0.3 * radius, cenPos.y()),
        QPoint(cenPos.x() + 0.3 * radius, cenPos.y() - 0.3 * radius),
        QPoint(cenPos.x() + 0.45 * radius, cenPos.y()),
        QPoint(cenPos.x() + 0.45 * radius, cenPos.y() - 0.45 * radius),
        QPoint(cenPos.x() + 0.6 * radius, cenPos.y()),
        QPoint(cenPos.x() + 0.6 * radius, cenPos.y() - 0.3 * radius),
        QPoint(cenPos.x() + 0.75 * radius, cenPos.y()),
        QPoint(cenPos.x() + 0.75 * radius, cenPos.y() - 0.15 * radius)
    };
    const int logoRadius = 10;
    p.drawLine(logoPoints[0], logoPoints[1]);
    p.drawLine(logoPoints[2], logoPoints[3]);
    p.drawLine(logoPoints[4], logoPoints[5]);
    p.drawLine(logoPoints[6], logoPoints[7]);
    p.drawLine(logoPoints[8], logoPoints[9]);
    p.drawLine(logoPoints[10], logoPoints[11]);
    p.drawLine(logoPoints[12], logoPoints[13]);
    p.drawLine(logoPoints[14], logoPoints[15]);
    p.drawLine(logoPoints[16], logoPoints[17]);
    p.drawLine(logoPoints[18], logoPoints[19]);
    p.drawEllipse(logoPoints[1].x() - 0.5 * logoRadius, logoPoints[1].y(),
            logoRadius, logoRadius);
    p.drawEllipse(logoPoints[3].x() - 0.5 * logoRadius, logoPoints[3].y(),
            logoRadius, logoRadius);
    p.drawEllipse(logoPoints[5].x() - 0.5 * logoRadius, logoPoints[5].y(),
            logoRadius, logoRadius);
    p.drawEllipse(logoPoints[7].x() - 0.5 * logoRadius, logoPoints[7].y(),
            logoRadius, logoRadius);
    p.drawEllipse(logoPoints[9].x() - 0.5 * logoRadius, logoPoints[9].y(),
            logoRadius, logoRadius);
    p.drawEllipse(logoPoints[11].x() - 0.5 * logoRadius, logoPoints[11].y() - logoRadius,
            logoRadius, logoRadius);
    p.drawEllipse(logoPoints[13].x() - 0.5 * logoRadius, logoPoints[13].y() - logoRadius,
            logoRadius, logoRadius);
    p.drawEllipse(logoPoints[15].x() - 0.5 * logoRadius, logoPoints[15].y() - logoRadius,
            logoRadius, logoRadius);
    p.drawEllipse(logoPoints[17].x() - 0.5 * logoRadius, logoPoints[17].y() - logoRadius,
            logoRadius, logoRadius);
    p.drawEllipse(logoPoints[19].x() - 0.5 * logoRadius, logoPoints[19].y() - logoRadius,
            logoRadius, logoRadius);

    if (!triggered) {
        const int width = _view.get_view_width();
        const QPoint cenLeftPos = QPoint(width / 2 - 0.05 * width, height() / 2);
        const QPoint cenRightPos = QPoint(width / 2 + 0.05 * width, height() / 2);
        const int trigger_radius = min(0.02 * width, 0.02 * height());

        p.setPen(Qt::NoPen);
        p.setBrush((timer_cnt % 3) == 0 ? Trace::dsLightBlue : Trace::dsGray);
        p.drawEllipse(cenLeftPos, trigger_radius, trigger_radius);
        p.setBrush((timer_cnt % 3) == 1 ? Trace::dsLightBlue : Trace::dsGray);
        p.drawEllipse(cenPos, trigger_radius, trigger_radius);
        p.setBrush((timer_cnt % 3) == 2 ? Trace::dsLightBlue : Trace::dsGray);
        p.drawEllipse(cenRightPos, trigger_radius, trigger_radius);

        sr_status status;
        if (sr_status_get(_view.session().get_device()->dev_inst(), &status, SR_STATUS_TRIG_BEGIN, SR_STATUS_TRIG_END) == SR_OK){
            const bool triggred = status.trig_hit & 0x01;
            const uint32_t captured_cnt = (status.captured_cnt0 +
                                          (status.captured_cnt1 << 8) +
                                          (status.captured_cnt2 << 16) +
                                          (status.captured_cnt3 << 24));
            captured_progress = captured_cnt * 100.0 / _total_sample_len;


            p.setPen(Trace::dsLightBlue);
            QFont font=p.font();
            font.setPointSize(10);
            font.setBold(true);
            p.setFont(font);
            QRect status_rect = QRect(cenPos.x() - radius, cenPos.y() + radius * 0.4, radius * 2, radius * 0.5);
            if (triggred)
                p.drawText(status_rect,
                           Qt::AlignCenter | Qt::AlignVCenter,
                           "Triggered! " + QString::number(captured_progress)+"% Captured");
            else
                p.drawText(status_rect,
                           Qt::AlignCenter | Qt::AlignVCenter,
                           "Waiting for Trigger! " + QString::number(captured_progress)+"% Captured");
        }

    } else {
        const int progress100 = ceil(progress / -3.6 / 16);
        p.setPen(Trace::dsGreen);
        QFont font=p.font();
        font.setPointSize(50);
        font.setBold(true);
        p.setFont(font);
        p.drawText(rect(), Qt::AlignCenter | Qt::AlignVCenter, QString::number(progress100)+"%");
    }

    p.setPen(QPen(Trace::dsLightBlue, 4, Qt::SolidLine));
    const int int_radius = max(radius - 4, 0);
    p.drawArc(cenPos.x() - int_radius, cenPos.y() - int_radius, 2* int_radius, 2 * int_radius, 180 * 16, -captured_progress*3.6*16);

}

void Viewport::mousePressEvent(QMouseEvent *event)
{
	assert(event);

	_mouse_down_point = event->pos();
	_mouse_down_offset = _view.offset();

    if (event->buttons() & Qt::LeftButton) {
        if (_view.cursors_shown()) {
            list<Cursor*>::iterator i = _view.get_cursorList().begin();
            double cursorX;
            while (i != _view.get_cursorList().end()) {
                cursorX = ((*i)->time() - _view.offset()) / _view.scale();
                if ((*i)->grabbed())
                    _view.get_ruler()->rel_grabbed_cursor();
                else if (qAbs(cursorX - event->pos().x()) <= HitCursorMargin) {
                    _view.get_ruler()->set_grabbed_cursor(*i);
                    break;
                }
                i++;
            }

        }
//        if (!_view.get_ruler()->get_grabbed_cursor()) {
//            _zoom_rect_visible = true;
//        }

        const vector< shared_ptr<Signal> > sigs(_view.session().get_signals());
        BOOST_FOREACH(const shared_ptr<Signal> s, sigs) {
            assert(s);
            shared_ptr<DsoSignal> dsoSig;
            if ((dsoSig = dynamic_pointer_cast<DsoSignal>(s)) &&
                 dsoSig->get_trig_rect(0, _view.get_view_width()).contains(_mouse_point)) {
                _drag_sig = s;
                break;
            }
        }

        update();
    }
}

void Viewport::mouseMoveEvent(QMouseEvent *event)
{
	assert(event);
    _mouse_point = event->pos();
    if (event->buttons() & Qt::RightButton) {
        _zoom_rect = QRectF(_mouse_down_point, event->pos());
        _zoom_rect_visible = true;
	}

    if (event->buttons() & Qt::LeftButton) {
        if (_drag_sig) {
            shared_ptr<view::DsoSignal> dsoSig;
            if (dsoSig = dynamic_pointer_cast<view::DsoSignal>(_drag_sig))
                dsoSig->set_trig_vpos(_mouse_point.y());
        } else {
            _view.set_scale_offset(_view.scale(),
                _mouse_down_offset +
                (_mouse_down_point - event->pos()).x() *
                _view.scale());
            measure();
        }
    }

    if (!(event->buttons() || Qt::NoButton)) {
        uint64_t sample_rate = _view.session().get_device()->get_sample_rate();
        TimeMarker* grabbed_marker = _view.get_ruler()->get_grabbed_cursor();
        if (_view.cursors_shown() && grabbed_marker) {
            const double cur_time = _view.offset() + _view.hover_point().x() * _view.scale();
            const double pos = cur_time * sample_rate;
            const double pos_delta = pos - (int)pos;
            if ( pos_delta < 0.5)
                grabbed_marker->set_index(floor(pos));
            else
                grabbed_marker->set_index(ceil(pos));
        }
        measure();
    }

    update();
}

void Viewport::mouseReleaseEvent(QMouseEvent *event)
{
    assert(event);

    if (_zoom_rect_visible) {
        _zoom_rect_visible = false;
        const double newOffset = _view.offset() + (min(event->pos().x(), _mouse_down_point.x()) + 0.5) * _view.scale();
        const double newScale = max(min(_view.scale() * (event->pos().x() - _mouse_down_point.x()) / _view.get_view_width(),
                                        _view.get_maxscale()), _view.get_minscale());
        if (newScale != _view.scale())
            _view.set_scale_offset(newScale, newOffset);
    }

    if(_drag_sig)
        _drag_sig.reset();

    update();
}

void Viewport::mouseDoubleClickEvent(QMouseEvent *event)
{
    assert (event);
    (void)event;

    if (_view.session().get_device()->dev_inst()->mode == LOGIC) {
        if (_view.scale() == _view.get_maxscale())
            _view.set_preScale_preOffset();
        else
            _view.set_scale_offset(_view.get_maxscale(), 0);

        update();
    }
}

void Viewport::wheelEvent(QWheelEvent *event)
{
	assert(event);

	if (event->orientation() == Qt::Vertical) {
		// Vertical scrolling is interpreted as zooming in/out
        const double offset = (_view.session().get_capture_state() == SigSession::Running) ? 0 : event->x();
        _view.zoom(event->delta() / 80, offset);
	} else if (event->orientation() == Qt::Horizontal) {
		// Horizontal scrolling is interpreted as moving left/right
		_view.set_scale_offset(_view.scale(),
				       event->delta() * _view.scale()
				       + _view.offset());
	}

    measure();
}

void Viewport::leaveEvent(QEvent *)
{
    _measure_shown = false;
    _mouse_point = QPoint(-1, -1);
    //_view.show_cursors(false);
    update();
}

void Viewport::on_traces_moved()
{
	update();
}

void Viewport::set_receive_len(quint64 length)
{
    if (length == 0) {
        _total_receive_len = 0;
        start_trigger_timer(333);
    } else {
        stop_trigger_timer();
        if (_total_receive_len + length > _view.session().get_device()->get_sample_limit())
            _total_receive_len = _view.session().get_device()->get_sample_limit();
        else
            _total_receive_len += length;
    }
    update();
}

void Viewport::measure()
{
   const uint64_t sample_rate = _view.session().get_device()->get_sample_rate();
   const vector< boost::shared_ptr<Signal> > sigs(_view.session().get_signals());
   BOOST_FOREACH(const boost::shared_ptr<Signal> s, sigs) {
       assert(s);
       shared_ptr<view::LogicSignal> logicSig;
       if (logicSig = dynamic_pointer_cast<view::LogicSignal>(s)) {
           if (logicSig->measure(_view.hover_point(), _cur_sample, _nxt_sample, _thd_sample)) {
               _measure_shown = true;

               _mm_width = _view.get_ruler()->format_real_time(_nxt_sample - _cur_sample, sample_rate);
               _mm_period = _thd_sample != 0 ? _view.get_ruler()->format_real_time(_thd_sample - _cur_sample, sample_rate) : "#####";
               _mm_freq = _thd_sample != 0 ? _view.get_ruler()->format_real_freq(_thd_sample - _cur_sample, sample_rate) : "#####";

               const double pixels_offset =  _view.offset() / _view.scale();
               const double samples_per_pixel = sample_rate * _view.scale();
               _cur_preX = _cur_sample / samples_per_pixel - pixels_offset;
               _cur_aftX = _nxt_sample / samples_per_pixel - pixels_offset;
               _cur_thdX = _thd_sample / samples_per_pixel - pixels_offset;
               _cur_midY = logicSig->get_y();

               _mm_duty = _thd_sample != 0 ? QString::number((_nxt_sample - _cur_sample) * 100.0f / (_thd_sample - _cur_sample), 'f', 2)+"%" :
                                             "#####";
               mouse_measure();
               return;
           } else {
               _mm_width = "#####";
               _mm_period = "#####";
               _mm_freq = "#####";
               _mm_duty = "#####";
           }
           mouse_measure();
       }
    }

    _measure_shown = false;
    return;
}

void Viewport::paintMeasure(QPainter &p)
{
    p.setPen(QColor(17, 133, 209,  255));
    p.drawLine(QLineF(_cur_preX, _cur_midY, _cur_aftX, _cur_midY));
    p.drawLine(QLineF(_cur_preX, _cur_midY, _cur_preX + 2, _cur_midY - 2));
    p.drawLine(QLineF(_cur_preX, _cur_midY, _cur_preX + 2, _cur_midY + 2));
    p.drawLine(QLineF(_cur_aftX - 2, _cur_midY - 2, _cur_aftX, _cur_midY));
    p.drawLine(QLineF(_cur_aftX - 2, _cur_midY + 2, _cur_aftX, _cur_midY));
    if (_thd_sample != 0) {
        p.drawLine(QLineF(_cur_aftX, _cur_midY, _cur_thdX, _cur_midY));
        p.drawLine(QLineF(_cur_aftX, _cur_midY, _cur_aftX + 2, _cur_midY - 2));
        p.drawLine(QLineF(_cur_aftX, _cur_midY, _cur_aftX + 2, _cur_midY + 2));
        p.drawLine(QLineF(_cur_thdX - 2, _cur_midY - 2, _cur_thdX, _cur_midY));
        p.drawLine(QLineF(_cur_thdX - 2, _cur_midY + 2, _cur_thdX, _cur_midY));
    }

    if (_measure_en) {
        double typical_width = p.boundingRect(0, 0, INT_MAX, INT_MAX,
            Qt::AlignLeft | Qt::AlignTop, _mm_width).width() + 150;
        QRectF measure_rect = QRectF(_view.hover_point().x(), _view.hover_point().y(),
                                     (double)typical_width, 80.0);
        QRectF measure1_rect = QRectF(_view.hover_point().x(), _view.hover_point().y(),
                                     (double)typical_width, 20.0);
        QRectF measure2_rect = QRectF(_view.hover_point().x(), _view.hover_point().y() + 20,
                                     (double)typical_width, 20.0);
        QRectF measure3_rect = QRectF(_view.hover_point().x(), _view.hover_point().y() + 40,
                                     (double)typical_width, 20.0);
        QRectF measure4_rect = QRectF(_view.hover_point().x(), _view.hover_point().y() + 60,
                                     (double)typical_width, 20.0);

        p.setPen(Qt::NoPen);
        p.setBrush(QColor(17, 133, 209,  150));
        p.drawRect(measure_rect);

        p.setPen(Qt::black);
        p.drawText(measure1_rect, Qt::AlignRight | Qt::AlignVCenter,
                   "Width: " + _mm_width);
        p.drawText(measure2_rect, Qt::AlignRight | Qt::AlignVCenter,
                   "Period: " + _mm_period);
        p.drawText(measure3_rect, Qt::AlignRight | Qt::AlignVCenter,
                   "Frequency: " + _mm_freq);
        p.drawText(measure4_rect, Qt::AlignRight | Qt::AlignVCenter,
                   "Duty Cycle: " + _mm_duty);
    }
}

QString Viewport::get_measure(QString option)
{
    if(option.compare("width") == 0)
        return _mm_width;
    else if (option.compare("period") == 0)
        return _mm_period;
    else if (option.compare("frequency") == 0)
        return _mm_freq;
    else if (option.compare("duty") == 0)
        return _mm_duty;
    else
        return "#####";
}

void Viewport::set_measure_en(int enable)
{
    if (enable == 0)
        _measure_en = false;
    else
        _measure_en = true;
}

void Viewport::start_trigger_timer(int msec)
{
    assert(msec > 0);
    triggered = false;
    timer_cnt = 0;
    trigger_timer.start(msec);
}

void Viewport::stop_trigger_timer()
{
    triggered = true;
    timer_cnt = 0;
    trigger_timer.stop();
}

void Viewport::on_trigger_timer()
{
    timer_cnt++;
    update();
}

} // namespace view
} // namespace pv