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

#include <QGraphicsProxyWidget>
#include <QFile>

#include "logicanalizer.h"
#include "itemlibrary.h"
#include "connector.h"
#include "circuitwidget.h"
#include "circuit.h"
#include "simulator.h"
#include "lachannel.h"
#include "lawidget.h"
#include "datalawidget.h"
#include "tunnel.h"
#include "e-node.h"
#include "iopin.h"

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

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

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

LibraryItem* LAnalizer::libraryItem()
{
    return new LibraryItem(
        tr( "Logic Analyzer" ),
        "Meters",
        "lanalizer.png",
        "LAnalizer",
        LAnalizer::construct ); 
}

LAnalizer::LAnalizer( QString type, QString id )
         : PlotBase( type, id )
{
    m_numChannels = 8; 
    m_bufferSize = 100000; 

    m_laWidget   = new LaWidget( CircuitWidget::self(), this ); 
    m_dataWidget = new DataLaWidget( nullptr, this ); 
    m_proxy = Circuit::self()->addWidget( m_dataWidget ); 
    m_proxy->setParentItem( this ); 
    m_dataWidget->show(); 
    m_display = m_laWidget->display(); 
    m_display->setPlotBase( this ); 
    m_display->setFixedSize( m_baSizeX+6*8, m_baSizeY+2*8 ); 
    m_display->setChannels( 8 ); 
    m_display->setTracks( 8 ); 

    m_pin.resize(8); 
    m_inPin.resize(8); 
    for( int i=0;  i<8;  ++i )
    {
        m_pin[i] = m_inPin[i] = new IoPin( 180, QPoint(-80-8,-64+16*i ), id+"-Pin"+QString::number(i), 0, this, undef_mode ); 
        double admit = m_connectGnd ? m_inputAdmit : 0; 
        m_inPin[i]->setInputAdmit( admit ); 
        LaChannel* ch = new LaChannel( this, id+"Chan"+QString::number(i) ); 

        ch->m_channel = i; 
        ch->setPin( m_pin[i] ); 
        ch->m_buffer.resize( m_bufferSize ); 
        ch->m_time.resize( m_bufferSize ); 
        m_channel[i] = ch; 

        m_display->setChannel( i, m_channel[i] ); 
        m_display->setColor( i, m_color[i%4] ); 
        m_display->setLimits( i, 1, 0 ); 
        m_display->setVPos( i, -0.5 ); 

        m_dataWidget->setColor( i, m_color[i%4] ); 
    }
    m_updtCount = 0; 
    m_thresholdR = 0; 
    m_thresholdF = 0; 

    setThresholdR( 2.5 ); 
    setThresholdF( 2.5 ); 

    setTimePos( 0 ); 
    setTimeDiv( 1e9 );    // 1 ms
    setVoltDiv( 0.6/4 );  // For Logic values 0 : 1

    setLabelPos(-90,-100, 0); 
    expand( false ); 
    setTrigger( 9 );  // Trigger = None

    addPropGroup( { tr("Export"), {
        new IntProp <LAnalizer>("TimeStep", tr("Base Time Step"), "_ps"
                               , this, &LAnalizer::timeStep,   &LAnalizer::setTimeStep,0,"uint" ),

        new BoolProp<LAnalizer>("AutoExport", tr("Export at pause"),""
                               , this, &LAnalizer::autoExport, &LAnalizer::setAutoExport ),
    },0} ); 

    addPropGroup( { "Hidden1", {
        new DoubProp<LAnalizer>("TresholdR", "", "V"
                               , this, &LAnalizer::thresholdR, &LAnalizer::setThresholdR ),

        new StrProp <LAnalizer>("Bus", "", ""
                               , this, &LAnalizer::busStr, &LAnalizer::setBusStr ),

        new DoubProp<LAnalizer>("TresholdF", "", "V"
                               , this, &LAnalizer::thresholdF, &LAnalizer::setThresholdF )
    }, groupHidden } ); 
}
LAnalizer::~LAnalizer()
{
    m_proxy->setWidget( nullptr ); 
    delete m_dataWidget; 

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

void LAnalizer::updateStep()
{
    if( !Simulator::self()->isPaused() )
    {
        uint64_t simTime = Simulator::self()->circTime();  // free running
        if( m_trigger < 9 )
        {
            uint64_t risEdge; 
            if( m_trigger == 8 ) risEdge = m_risEdge; 
            else                 risEdge = m_channel[m_trigger]->m_risEdge; 

            if( risEdge > 0 ) // We have a Trigger
            {
                if( m_trigger < 8 ) m_channel[m_trigger]->m_risEdge = 0; 
                simTime = risEdge; 
                m_updtCount = 0; 
            }
            else if( ++m_updtCount < 20 ) return; 
        }

        m_display->setTimeEnd( simTime ); 

        for( int i=0;  i<8;  i++ )
        {
            bool connected = m_pin[i]->connector(); 

            if( !connected && !m_pin[i]->isBus() )
            {
                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]->initialize(); 
            m_channel[i]->m_trigIndex = m_channel[i]->m_bufferCounter; 
        }
        m_risEdge = 0; 
    }
    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 LAnalizer::expand( bool e )
{
    m_expand = e; 
    m_screenSizeY = m_baSizeY+2*4; 
    if( e ){
        m_display->setMaximumSize( 9999, 9999 ); 
        m_laWidget->getDispLayout()->insertWidget( 0, m_display ); 
        m_laWidget->setWindowTitle( idLabel() ); 
        m_laWidget->show(); 
        m_screenSizeX = 0; 
    }else{
        m_screenSizeX = m_baSizeX+2*4; 
        m_laWidget->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(); 
}

QString LAnalizer::timPos()
{
    return QString::number( m_timePos ); 
}

void LAnalizer::setTimPos( QString tp )
{
    setTimePos( tp.toLongLong() ); 
}

QString LAnalizer::volDiv()
{
    return QString::number( m_voltDiv ); 
}

void LAnalizer::setVolDiv( QString vd )
{
    setVoltDiv( vd.toDouble() ); 
}

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

void LAnalizer::setThresholdR( double thr )
{
    if( thr < m_thresholdF ) thr = m_thresholdF; 
    m_thresholdR = thr; 
    m_laWidget->updateThresholdR( thr ); 
}

void LAnalizer::setThresholdF( double thr )
{
    if( thr > m_thresholdR ) thr = m_thresholdR; 
    m_thresholdF = thr; 
    m_laWidget->updateThresholdF( thr ); 
}

void LAnalizer::setTimePos( int64_t tp )
{
    m_timePos = tp; 
    for( int i=0;  i<8;  ++i ) m_display->setHPos( i, m_timePos ); 
    m_laWidget->updateTimePosBox( tp ); 
}

void LAnalizer::moveTimePos( int64_t delta )
{
    m_timePos = m_timePos+delta; 
    for( int i=0;  i<8;  ++i ) m_display->setHPos( i, m_timePos ); 
    m_laWidget->updateTimePosBox( m_timePos ); 
}

void LAnalizer::setVoltDiv( double )
{
    m_voltDiv = 0.6/4;  // For Logic values 0 : 1
    for( int i=0;  i<8;  i++ ) m_display->setVTick( i, m_voltDiv ); 
}

void LAnalizer::setTrigger( int ch )
{
    m_trigger = ch; 
    m_laWidget->setTrigger( ch ); 

    bool pauseOnCond = ( m_trigger == 8 );  // Pause on condition
    for( int i=0;  i<8;  i++ ) m_channel[i]->m_pauseOnCond = pauseOnCond; 
}

QString LAnalizer::busStr()
{
    QString list; 
    QString bus; 
    for( int i=0;  i<8;  ++i ){
        bus = m_channel[i]->isBus()? "true":"false"; 
        list.append( bus ).append(","); 
    }
    return list; 
}

void LAnalizer::setBusStr( QString hc )
{
    QStringList list = hc.split(","); 
    for( int i=0;  i<8;  ++i ){
        if( i == list.size() ) break; 
        bool bus = (list.at(i) == "true")? true:false; 
        m_laWidget->setIsBus( i, bus ); 
}   }

void LAnalizer::setConds( QString conds )
{
    m_laWidget->setConds( conds ); 
    updateConds( conds ); 
}

void LAnalizer::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 LAnalizer::dumpData( QString fn )
{
    QChar identifiers[8] = {'*', '"', '#', '$', '%', '&', '(', ')'}; 

    QFile file( fn ); 
    if( !file.open( QIODevice::WriteOnly | QIODevice::Text ) ) return; 
    m_exportFile = fn; 

    QTextStream out( &file ); 
    out.setLocale( QLocale::C ); 

    QMultiMap<uint64_t, sample_t> samples;  // collect timing data, use QMap to implicitely sort it

    uint64_t startTime = m_display->startTime(); 
    if( (int64_t)startTime+m_timePos >= 0 ) startTime += m_timePos; 
    else                                    startTime  = 1; 

    uint64_t endTime = m_display->endTime()+m_timePos; 
    uint64_t lastTime = (endTime-startTime)/m_timeStep; 
    uint64_t pTime = 0; 
    double pVal=-1; 

    QString varDef; 
    QString dumpVars = "\n$dumpvars\n"; 
    uint64_t gcd = 1;   // Problems in Pulseview using gcd

    for( uint ch=0;  ch<8;  ++ch )
    {
        if( !m_channel[ch]->m_connected ) continue; 

        QString name = m_channel[ch]->getChName();              // Get channel name
        if( name.isEmpty() ) name = "D"+QString::number( ch );  // If name is empty set name = Dn

        varDef += "$var wire 1 " + QString( identifiers[ch] )+" "+name+" $end\n"; 

        bool init = false; 
        double initVal = 0; 
        int index = m_channel[ch]->m_bufferCounter;  // Start with the first sample (circular buffer)

        for( int i=0;  i<m_bufferSize;  ++i )
        {
            index++; 
            if( index >= m_bufferSize ) index -= m_bufferSize;  // It's a circular buffer

            double   val  = m_channel[ch]->m_buffer[index]; 
            uint64_t time = m_channel[ch]->m_time[index]; 

            if( pTime == time ) continue;                       // Avoid repeated times
            pTime = time; 

            if( time == 0 ) continue;                             // Empty samples: Simulation times start at 1 ps
            if( time <= startTime ) { initVal = val;  continue;  } // Previous value from first valid sample
            if( time > endTime ) { time = endTime;  val = pVal;  } // Add final value (screen edge)
            if( !init ) {
                dumpVars += QString::number(initVal)+identifiers[ch]+"\n";  // Add initial value
                init = true; 
            }
            pVal = val; 
            time = (time-startTime)/m_timeStep; 

            //if( gcd > 0 ) gcd = getGcd( gcd, time );  // Get Greatest Common Denominator
            //else          gcd = time; 

            samples.insert( time, { val, ch } ); 
            if( time == lastTime ) break;             // All samples before endTime already registered
        }
    }
    dumpVars += "$end\n"; 
    //if( gcd < 1 ) gcd = 1;  // This should not happen

    out <<"$timescale "<< gcd*m_timeStep <<"ps $end"<< Qt::endl<< Qt::endl; 
    out << varDef; 
    out << Qt::endl <<"$enddefinitions $end"<< Qt::endl; 
    out << dumpVars; 

    uint64_t timeStamp; 
    for( uint64_t time : samples.uniqueKeys() )
    {
        timeStamp = time/gcd; 
        out << Qt::endl <<"#"<< timeStamp; 
        for( sample_t sample : samples.values( time ) )
            out <<" "<< sample.value <<identifiers[sample.channel]; 
    }
    out << Qt::endl <<"#"<< timeStamp+1;  // last time stamp
    file.close(); 
}

uint64_t LAnalizer::getGcd( uint64_t a, uint64_t b )  // Greatest Common Denominator
{
    uint64_t h; 
    do {
        h = a % b; 
        a = b; 
        b = h; 
    } while (b != 0); 

    return a; 
}
