/***************************************************************************
 *   Copyright (C) 2018 by Santiago González                               *
 *                                                                         *
 ***( see copyright.txt file at root folder )*******************************/

#include <QGraphicsProxyWidget>

#include "oscope.h"
#include "connector.h"
#include "circuitwidget.h"
#include "circuit.h"
#include "simulator.h"
#include "itemlibrary.h"
#include "oscopechannel.h"
#include "oscwidget.h"
#include "datawidget.h"
#include "tunnel.h"
#include "e-node.h"
#include "iopin.h"

#include "stringprop.h"
#include "doubleprop.h"
#include "intprop.h"

#define tr(str) simulideTr("Oscope",str)

Component* Oscope::construct( QString type, QString id )
{ return new Oscope( type, id ); }

LibraryItem* Oscope::libraryItem()
{
    return new LibraryItem(
        tr( "Oscope" ),
        "Meters",
        "oscope.png",
        "Oscope",
        Oscope::construct );
}

Oscope::Oscope( QString type, QString id )
      : PlotBase( type, id )
{
    m_numChannels = 4;
    m_trigger = 4;
    m_auto    = 4;

    m_oscWidget  = new OscWidget( CircuitWidget::self(), this );
    m_dataWidget = new DataWidget( nullptr, this );
    m_proxy = Circuit::self()->addWidget( m_dataWidget );
    m_proxy->setParentItem( this );
    m_dataWidget->show();
    m_display = m_oscWidget->display();
    m_display->setPlotBase( this );
    m_display->setFixedSize( m_baSizeX+6*8, m_baSizeY+2*8 );

    m_inPin.resize(5);
    m_pin.resize(5);
    m_pin[4] = m_inPin[4] = new IoPin( 180, QPoint(-80-8, 64 ), id+"-PinG", 0, this, input );

    for( int i=0; i<4; i++ )
    {
        m_pin[i] = m_inPin[i] = new IoPin( 180, QPoint(-80-8,-48+32*i ), id+"-Pin"+QString::number(i), 0, this, undef_mode );
        double admit = m_connectGnd ? m_inputAdmit : 0;
        m_inPin[i]->setInputAdmit( admit );
        m_channel[i] = new OscopeChannel( this, id+"Chan"+QString::number(i) );
        m_channel[i]->m_channel = i;
        m_channel[i]->m_ePin[0] = m_pin[i];
        m_channel[i]->m_ePin[1] = m_pin[4]; // Ref Pin
        m_channel[i]->m_buffer.resize( m_bufferSize );
        m_channel[i]->m_time.resize( m_bufferSize );

        m_hideCh[i] = false;

        m_display->setChannel( i, m_channel[i] );
        m_display->setColor( i, m_color[i] );
        m_dataWidget->setColor( i, m_color[i] );

        setTimePos( i, 0 );
        setVoltDiv( i, 1 );
        setVoltPos( i, 0 );
    }
    setFilter( 0.1 );
    setTimeDiv( 1e9 ); // 1 ms
    setLabelPos(-90,-100, 0);
    expand( false );

    addPropGroup( { "Hidden1", {
        new DoubProp<Oscope>("Filter", "", "V"
                            , this, &Oscope::filter, &Oscope::setFilter ),

        new IntProp <Oscope>("AutoSC" ,"", ""
                            , this, &Oscope::autoSC, &Oscope::setAutoSC ),

        new IntProp <Oscope>("Tracks","", ""
                            , this, &Oscope::tracks, &Oscope::setTracks ),

        new StrProp <Oscope>("HideCh","", ""
                            , this, &Oscope::hideCh, &Oscope::setHideCh ),

        new StrProp <Oscope>("VolPos","", ""
                            , this, &Oscope::volPos, &Oscope::setVolPos )
    }, groupHidden } );
}
Oscope::~Oscope()
{
    m_proxy->setWidget( nullptr );
    delete m_dataWidget;

    m_oscWidget->setParent( nullptr );
    m_oscWidget->close();
    delete m_oscWidget;
}

void Oscope::updateStep()
{
    uint64_t period = 0;
    uint64_t timeFrame = m_timeDiv*10;
    uint64_t simTime;

    if( !Simulator::self()->isPaused() )
    {
        if( m_trigger < 4  ) period = m_channel[m_trigger]->m_period; // We want a trigger

        if( period > 10 ) // We have a Trigger
        {
            uint64_t risEdge = m_channel[m_trigger]->m_risEdge;

            uint64_t nCycles = timeFrame/period;
            if( timeFrame%period ) nCycles++;
            if( nCycles%2 )        nCycles++;

            uint64_t delta = nCycles*period/2-timeFrame/2;
            if( delta > risEdge ) delta = risEdge;
            simTime = risEdge-delta;
        }
        else simTime = Simulator::self()->circTime(); // free running

        m_display->setTimeEnd( simTime );

        for( int i=0; i<4; i++ )
        {
            bool connected = m_pin[i]->connector();
            if( !connected )
            {
                QString chTunnel = m_channel[i]->m_chTunnel;

                eNode* enode = Tunnel::getEnode( chTunnel );
                m_pin[i]->setEnode( enode );
                if( enode )
                {
                    enode->voltChangedCallback( m_channel[i] );
                    connected = true;
                }
                display()->connectChannel( i, connected );
            }
            m_channel[i]->m_connected = connected;
            if( connected ) m_channel[i]->updateStep();
            m_channel[i]->m_trigIndex = m_channel[i]->m_bufferCounter;
        }
    }
    m_display->update();

    if( m_changed ){
        m_changed = false;
        double admit = m_connectGnd ? m_inputAdmit : 0;
        for( IoPin* pin : m_inPin ){
            pin->setInputAdmit( admit );
            pin->stampAdmitance( admit );
        }
    }
}

void Oscope::expand( bool e )
{
    m_expand = e;
    m_screenSizeY = m_baSizeY+2*4;
    if( e ){
        m_display->setMaximumSize( 9999, 9999 );
        m_oscWidget->getLayout()->addWidget( m_display );
        m_oscWidget->setWindowTitle( idLabel() );
        m_oscWidget->show();
        m_screenSizeX = 0;
    }else{
        m_screenSizeX = m_baSizeX+2*4;
        m_oscWidget->hide();
        m_dataWidget->getLayout()->addWidget( m_display );
        m_display->setMaximumSize( m_screenSizeX, m_screenSizeY );
    }
    m_display->setMinimumSize( m_screenSizeX, m_screenSizeY );

    int widgetSizeX = m_screenSizeX+68+4;
    int widgetSizeY = m_screenSizeY+4;
    int centerY = widgetSizeY/2;
    m_dataWidget->setFixedSize( widgetSizeX, widgetSizeY );
    m_proxy->setPos( -80+2, -centerY-2+4 );
    m_area = QRectF( -80, -centerY, widgetSizeX+4, widgetSizeY+4+2 );

    m_display->setExpand( e );
    m_display->updateValues();
    Circuit::self()->update();
}

void Oscope::setFilter( double filter )
{
    m_filter = filter;
    m_oscWidget->setFilter( filter );
    for( int i=0; i<4; i++ ) m_channel[i]->setFilter( filter );
}

void Oscope::setTrigger( int ch )
{
    m_trigger = ch;
    m_oscWidget->setTrigger( ch );

    if( ch > 3 ) return;
    for( int i=0; i<4; ++i ){
        if( ch == i ) m_channel[i]->m_trigger = true;
        else          m_channel[i]->m_trigger = false;
}   }

void Oscope::setTunnels( QString tunnels )
{
    QStringList list = tunnels.split(",");
    for( int i=0; i<list.size(); i++ ){
        if( i >= m_numChannels ) break;
        m_channel[i]->m_chTunnel = list.at(i);
        m_dataWidget->setTunnel( i, list.at(i) );
}   }

void Oscope::setAutoSC( int ch )
{
    m_auto = ch;
    m_oscWidget->setAuto( ch );
}

QString Oscope::hideCh()
{
    QString list;
    QString hide;
    for( int i=0; i<4; ++i ){
        hide = m_hideCh[i]? "true":"false";
        list.append( hide ).append(",");
    }
    return list;
}

void Oscope::setHideCh( QString hc )
{
    QStringList list = hc.split(",");
    for( int i=0; i<4; ++i ){
        if( i == list.size() ) break;
        bool hide = (list.at(i) == "true")? true:false;
        hideChannel( i, hide );
}   }

void Oscope::hideChannel( int ch, bool hide )
{
    if( ch > 3 ) return;
    m_hideCh[ch] = hide;
    m_display->hideChannel( ch , hide );
    m_oscWidget->hideChannel( ch, hide );
}

int Oscope::tracks() { return m_display->tracks(); }
void Oscope::setTracks( int tracks )
{
    m_display->setTracks( tracks );
    m_oscWidget->setTracks( tracks );
}

void Oscope::setTimeDiv( uint64_t td )
{
    if( td < 1 ) td = 1;
    PlotBase::setTimeDiv( td );
    m_oscWidget->updateTimeDivBox( td );
}

QString Oscope::timPos()
{
    QString list;
    for( int i=0; i<4; ++i ) list.append( QString::number( m_timePos[i] )).append(",");
    return list;
}

void Oscope::setTimPos( QString tp )
{
    QStringList list = tp.split(",");
    for( int i=0; i<4; ++i ){
        if( i == list.size() ) break;
        setTimePos( i, list.at(i).toLongLong() );
}   }

QString Oscope::volDiv()
{
    QString list;
    for( int i=0; i<4; ++i ) list.append( QString::number( m_voltDiv[i] )).append(",");
    return list;
}

void Oscope::setVolDiv( QString vd )
{
    QStringList list = vd.split(",");
    for( int i=0; i<4; ++i ){
        if( i == list.size() ) break;
        setVoltDiv( i, list.at(i).toDouble() );
}   }

QString Oscope::volPos()
{
    QString list;
    for( int i=0; i<4; ++i ) list.append( QString::number( m_voltPos[i] )).append(",");
    return list;
}

void Oscope::setVolPos( QString vp )
{
    QStringList list = vp.split(",");
    for( int i=0; i<4; ++i ){
        if( i == list.size() ) break;
        setVoltPos( i, list.at(i).toDouble() );
}   }

void Oscope::setTimePos( int ch, int64_t tp )
{
    m_timePos[ch] = tp;
    m_display->setHPos( ch, tp );
    m_oscWidget->updateTimePosBox( ch, tp );
}

void Oscope::moveTimePos( int64_t delta )
{
    if( m_trigger < 4  )
    {
        uint64_t period = m_channel[m_trigger]->m_period; // check wave period
        if( period > 10 ) return;                // We have a Trigger: Don't Zoom at mouse pos.
    }
    for( int i=0; i<4; ++i )
    {
        m_timePos[i] = m_timePos[i]+delta;
        m_display->setHPos( i, m_timePos[i] );
        m_oscWidget->updateTimePosBox( i, m_timePos[i] );
    }
}

void Oscope::setVoltDiv( int ch, double vd )
{
    m_voltDiv[ch] = vd;
    m_display->setVTick( ch, vd );
    m_oscWidget->updateVoltDivBox( ch, vd );
}

void Oscope::setVoltPos( int ch, double vp )
{
    m_voltPos[ch] = vp;
    m_display->setVPos( ch, vp );
    m_oscWidget->updateVoltPosBox( ch, vp );
}
