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

#include <QDebug>

#include "mcuport.h"
//#include "mcupin.h"
#include "mcu.h"
#include "e_mcu.h"
#include "mcuinterrupts.h"
#include "datautils.h"

McuPort::McuPort( eMcu* mcu, QString name )
       : McuModule( mcu, name )
{
    m_numPins = 0; 

    m_shortName = "P"+ name.right(1); 

    m_outReg = nullptr; 
    m_dirReg = nullptr; 
    m_inReg  = nullptr; 

    m_intMask = 0; 
    m_outAddr = 0; 
    m_inAddr  = 0; 
    m_dirAddr = 0; 
    m_dirInv  = false; 
    m_rstIntMask = true; 
}
McuPort::~McuPort(){}

void McuPort::reset()
{
    m_pinState = 0; 
    if( m_rstIntMask ) m_intMask = 0; 
    /// for( McuPin* pin : m_pins ) pin->reset(); 
}

void McuPort::pinChanged( uint8_t pinMask, uint8_t val ) // Pin number in pinMask
{
    uint8_t pinState = (m_pinState & ~pinMask) | (val & pinMask); 

    if( pinState == m_pinState ) return; 
    m_pinState = pinState; 

    if( (m_intMask & pinMask) && m_interrupt ) m_interrupt->raise();  // Pin change interrupt

    if( m_sleeping ) return; 
    if( m_inAddr ) *m_inReg = m_pinState;  //m_mcu->writeReg( m_inAddr, m_pinState, false );  // Write to RAM
}

void McuPort::outChanged( uint8_t val )
{
    uint8_t changed = *m_outReg ^ val;  // See which Pins have actually changed
    if( changed == 0 ) return; 
    *m_outReg = val; 

    for( int i=0;  i<m_numPins;  ++i ){
        if( changed & (1<<i) ) m_pins[i]->setPortState( val & (1<<i) );  // Pin changed
}   }

void McuPort::dirChanged( uint8_t val )
{
    uint8_t changed = *m_dirReg ^ val;   // See which Pins have actually changed
    if( changed == 0 ) return; 

    if( m_dirInv ) val = ~val;    // defaul: 1 for outputs, inverted: 0 for outputs (PICs)

    for( int i=0;  i<m_numPins;  ++i ){
        if( changed & 1<<i) m_pins[i]->setDirection( val & (1<<i) );  // Pin changed
}   }

void McuPort::readPort( uint8_t )
{
    if( m_inAddr ) *m_inReg = getInpState(); 
}

void McuPort::intChanged( uint8_t val )
{
    if( m_intBits.reg ) m_intMask = getRegBitsVal( val, m_intBits ); 
    else                m_intMask = val; 
}
// Direct Control---------------------------------------------------

void McuPort::controlPort( bool outCtrl, bool dirCtrl )
{
    for( int i=0;  i<m_numPins;  ++i )
        m_pins[i]->controlPin( outCtrl, dirCtrl ); 
}

void McuPort::setDirection( uint val )
{
    for( int i=0;  i<m_numPins;  ++i )
        m_pins[i]->setDirection( val & (1<<i) ); 
}

void McuPort::setOutState( uint val )
{
    for( int i=0;  i<m_numPins;  ++i )
    {
        bool state = (val & 1<<i) > 0; 
        if( state != m_pins[i]->getOutState() )
            m_pins[i]->setOutState( state ); 
    }
}

uint McuPort::getInpState()
{
    uint data = 0; 
    for( int i=0;  i<m_numPins;  ++i )
        if( m_pins[i] && m_pins[i]->getInpState() ) data |= (1 << i); 
    return data; 
}

void McuPort::setPinMode( pinMode_t mode )
{
    for( int i=0;  i<m_numPins;  ++i ) m_pins[i]->setPinMode( mode ); 
}
//-------------------------------------------------------------------

void McuPort::setPullups( uint8_t puMask )
{
    for( int i=0;  i<m_numPins;  ++i )
        if( m_pins[i] ) m_pins[i]->setPullup( (puMask & 1<<i) ? 1e5 : 0 ); 
}

void McuPort::setAllPullups( uint8_t val )
{
    uint8_t puMask = val ? 255 : 0; 
    setPullups( puMask ); 
}

void McuPort::clearAllPullups( uint8_t val )
{
    uint8_t puMask = val ? 0 : 255; 
    setPullups( puMask ); 
}

void McuPort::createPins( Mcu* mcuComp, QString pins, uint32_t pinMask )
{
    m_numPins = pins.toUInt(0,0); 
    if( m_numPins )
    {
        m_pins.resize( m_numPins ); 

        for( int i=0;  i<m_numPins;  ++i )
        {
            if( pinMask & 1<<i )
                m_pins[i] = createPin( i, mcuComp->getUid()+"-"+m_name+QString::number(i) , mcuComp ); //new McuPin( this, i, m_name+QString::number(i), mcuComp ); 
        }
    }else{
        QStringList pinList = pins.split(","); 
        pinList.removeAll(""); 
        for( QString pinName : pinList )
        {
            McuPin* pin = createPin( m_numPins, mcuComp->getUid()+"-"+m_name+pinName , mcuComp ); //new McuPin( this, i, m_name+pinName, mcuComp ); 
            m_pins.emplace_back( pin ); 
            m_numPins++; 
        }
    }
}

McuPin* McuPort::createPin( int i, QString id , Component* mcu )
{
    return new McuPin( this, i, id, mcu ); 
}

McuPin* McuPort::getPinN( uint8_t i )
{
    if( i >= m_pins.size() ) return nullptr; 
    return m_pins[i]; 
}

McuPin* McuPort::getPin( QString pinName )
{
    McuPin* pin = nullptr; 

    if( pinName.startsWith( m_name ) || pinName.startsWith( m_shortName ) )
    {
        QString pinId = pinName.remove( m_name ).remove( m_shortName ); 
        int pinNumber = pinId.toInt(); 
        pin = getPinN( pinNumber ); 
    }else{
        for( McuPin* mcuPin : m_pins )
        {
            if( !mcuPin ) continue; 
            QString pid = mcuPin->pinId(); 
            pid = pid.split("-").last().remove( m_name ); 
            if( pid == pinName ) return mcuPin; 
        }
    }
    //if( !pin )
    //    qDebug() << "ERROR: McuPort::getPin NULL Pin:"<< pinName; 
    return pin; 
}

// ---- Script Engine -------------------
#include "angelscript.h"
QStringList McuPort::registerScript( asIScriptEngine* engine )
{
    QStringList memberList; 
    engine->RegisterObjectType("McuPort", 0, asOBJ_REF | asOBJ_NOCOUNT ); 

    memberList << "controlPort( bool outCtrl, bool dirCtrl )"; 
    engine->RegisterObjectMethod("McuPort", "void controlPort( bool o, bool d )"
                                   , asMETHODPR( McuPort, controlPort, (bool, bool), void)
                                   , asCALL_THISCALL ); 

    memberList << "setDirection( uint direction )"; 
    engine->RegisterObjectMethod("McuPort", "void setDirection( uint d )"
                                   , asMETHODPR( McuPort, setDirection, (uint), void)
                                   , asCALL_THISCALL ); 

    memberList << "getInpState()"; 
    engine->RegisterObjectMethod("McuPort", "uint getInpState()"
                                   , asMETHODPR( McuPort, getInpState, (), uint)
                                   , asCALL_THISCALL ); 

    memberList << "setOutState( uint state )"; 
    engine->RegisterObjectMethod("McuPort", "void setOutState(uint s)"
                                   , asMETHODPR( McuPort, setOutState, (uint), void)
                                   , asCALL_THISCALL ); 

    return memberList; 
}
