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

#include<cmath>

#include "shiftreg.h"
#include "itemlibrary.h"
#include "simulator.h"
#include "iopin.h"

#include "intprop.h"
#include "boolprop.h"

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

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

LibraryItem* ShiftReg::libraryItem()
{
    return new LibraryItem(
        tr( "Shift Register" ),
        "Arithmetic",
        "1to3.png",
        "ShiftReg",
        ShiftReg::construct );
}

ShiftReg::ShiftReg( QString type, QString id )
        : LogicComponent( type, id )
{
    m_width  = 4;
    m_height = 9;
    m_tristate = true;
    m_parallelIn = false;
    m_ldInps = false;
    m_bidir = false;

    m_dinPin = new IoPin( 0, QPoint(0,0), id+"-in0", 0, this, input ); // Will be configured later
    m_otherPin.emplace_back( m_dinPin );
    m_clkPin = new IoPin( 0, QPoint(0,0), id+"-in1", 0, this, input );
    m_otherPin.emplace_back( m_clkPin );
    m_rstPin = new IoPin( 0, QPoint(0,0), id+"-in2", 0, this, input );
    m_otherPin.emplace_back( m_rstPin );
    m_serPin = new IoPin( 0, QPoint(0,0), id+"-in4", 0, this, input );
    m_otherPin.emplace_back( m_serPin );
    m_serPin->setInverted( true );
    m_serPin->setVisible( false );
    m_dilPin = new IoPin( 0, QPoint(0,0), id+"-in5", 0, this, input );
    m_otherPin.emplace_back( m_dilPin );
    m_dilPin->setVisible( false );
    m_dirPin = new IoPin( 0, QPoint(0,0), id+"-in6", 0, this, input );
    m_otherPin.emplace_back( m_dirPin );
    m_dirPin->setVisible( false );

    m_otherPin.emplace_back( m_dinPin );

    createOePin("IU02OE ", id+"-in3"); //createOePin( "IU01OE ", id+"-in3");
    setBits( 8 );
    setupPin( m_dinPin, "L03DI" );
    setupPin( m_dilPin, "L04DL" );
    setupPin( m_clkPin, "L05>" );
    setupPin( m_dirPin, "L06DIR" );
    setupPin( m_rstPin, "L07Rst" );
    setupPin( m_serPin, "L00S" );

    m_rstPin->setInverted( true ); //setResetInv( true );    // Invert Reset Pin

    addPropGroup( { tr("Main"), {
        new IntProp <ShiftReg>("Bits", tr("Size"),"_bits"
                            , this, &ShiftReg::bits, &ShiftReg::setBits, propNoCopy,"uint" ),

        new BoolProp<ShiftReg>("Parallel_input", tr("Parallel Input"),""
                              , this, &ShiftReg::parallelIn, &ShiftReg::setParallelIn, propNoCopy ),

        new BoolProp<ShiftReg>("Bidirectional", tr("Bidirectional"),""
                              , this, &ShiftReg::bidirectional, &ShiftReg::setBidirectional, propNoCopy ),

        //new BoolProp<ShiftReg>("Clock_Inverted", tr("Clock Inverted"),""
        //                      , this, &ShiftReg::clockInv, &ShiftReg::setClockInv ),

        //new BoolProp<ShiftReg>("Reset_Inverted", tr("Reset Inverted"),""
        //                      , this, &ShiftReg::resetInv, &ShiftReg::setResetInv ),
    }, groupNoCopy} );

    appendPropGroup( tr("Main"), IoComponent::familyProps() );

    addPropGroup( { tr("Inputs"), IoComponent::inputProps(),0 } );

    addPropGroup( { tr("Outputs")
        , IoComponent::outputProps()
        + IoComponent::outputType()
        + QList<ComProperty*>({
            new BoolProp<ShiftReg>("Tristate", tr("Tristate"),""
                                  , this, &ShiftReg::tristate, &ShiftReg::setTristate ),
        })
    ,0 } );

    addPropGroup( { tr("Timing")  , IoComponent::edgeProps(),0 } );
}
ShiftReg::~ShiftReg(){}

bool ShiftReg::propNotFound( QString prop, QString val )
{
    if( prop =="Clock_Inverted" ) // Old circuits
    {
        m_clkPin->setInverted( val == "true" );
    }
    else if( prop =="Reset_Inverted" ) // Old circuits
    {
        bool invert = (val == "true");
        m_rstPin->setInverted( invert );
    }
    else return false;

    return true;
}

void ShiftReg::stamp()
{
    m_clkPin->changeCallBack( this );
    m_rstPin->changeCallBack( this ); // Reset
    m_serPin->changeCallBack( this, m_parallelIn ); // Serial/Parallel changes
    LogicComponent::stamp();
}

void ShiftReg::voltChanged()
{
    if( m_tristate ) updateOutEnabled();
    updateClock();

    bool clkRising = (m_clkState == Clock_Rising);// Get Clk to don't miss any clock changes
    bool     reset = m_rstPin->getInpState();

    bool ldInps = false;
    if( m_parallelIn )
    {
        ldInps = !m_serPin->getInpState(); // High = load parallel

        if( m_ldInps != ldInps ){
            m_ldInps = ldInps;
            for( IoPin* pin : m_inpPin )
                pin->changeCallBack( this, ldInps );
        }
    }

    if( reset ) m_nextOutVal = 0;        // Reset shift register
    else if( m_parallelIn && m_ldInps )  // Load parallel
    {
        m_nextOutVal = 0;
        for( uint i=0; i<m_inpPin.size(); ++i )
            if( m_inpPin[i]->getInpState() ) m_nextOutVal |= 1<<i;
    }
    else if( clkRising )                 // Shift
    {
        bool right = m_bidir ? m_dirPin->getInpState() : false;

        if( right )    // Rotate Right
        {
            m_nextOutVal >>= 1;
            bool data = m_dilPin->getInpState();
            if( data ) m_nextOutVal |= 1<<(m_bits-1);
        }
        else           // Rotate Left
        {
            m_nextOutVal <<= 1;
            bool data = m_dinPin->getInpState();
            if( data ) m_nextOutVal |= 1;
        }
        m_nextOutVal &= m_bitMask;
    }
    scheduleOutPuts( this );
}

void ShiftReg::setBits( int b )
{
    if( b < 1 ) b = 1;
    m_bits = b;
    m_bitMask = pow( 2, m_bits )-1;
    updatePins();
}

void ShiftReg::setParallelIn( bool p )
{
    m_parallelIn = p;
    updatePins();
    if( Simulator::self()->isRunning() ) return; // No changes

    m_serPin->setVisible( p );

    if( p ) voltChanged();
    else{
        m_serPin->removeConnector();
        for( IoPin* pin : m_inpPin )
            pin->changeCallBack( this, false );
    }
}

void ShiftReg::setBidirectional( bool b )
{
    m_bidir = b;
    updatePins();
    m_dilPin->setVisible( b );
    m_dirPin->setVisible( b );

    if( b ) m_dinPin->setLabelText("DR");
    else{
        m_dilPin->removeConnector();
        m_dirPin->removeConnector();
        m_dinPin->setLabelText("DI");
    }
}

void ShiftReg::updatePins()
{
    int inBits = m_parallelIn ? m_bits : 0;
    setNumOuts( m_bits, "Q" );
    setNumInps( inBits, "I", 0, 10 ); // Start at id 10 to leave room for new control inputs
    updtOutPins(); // Fix pin positions

    int start = inBits-m_height/2;
    if( m_parallelIn ) start++;
    m_serPin->setY( 8*(start++) );
    m_dinPin->setY( 8*(start++) );
    if( m_bidir ){
        m_dilPin->setY( 8*(start++) );
        m_dirPin->setY( 8*(start++) );
    }
    m_clkPin->setY( 8*(start++) );
    m_rstPin->setY( 8*(start++) );

    int nBidir  = m_bidir ? 2 : 0;
    int nPins = 3+nBidir;
    int noPara = (m_bits < nPins ) ? nPins-m_bits : 0;
    int deltaH = m_parallelIn ? 4+nBidir : noPara;
    m_area.setHeight(  m_area.height()+deltaH*8 );

    setupPin( m_oePin, "U02OE" ); // Reposition OE pin
}

/*void ShiftReg::setResetInv( bool inv )
{
    m_resetInv = inv;
    m_rstPin->setInverted( inv );
}*/
