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

#include <QPainter>
#include <QDebug>

#include "esp32pin.h"
#include "qemumodule.h"
#include "simulator.h"

Esp32Pin::Esp32Pin( int i, QString id, QemuDevice* mcu, IoPin* dummyPin )
        : IoPin( 0, QPoint(0,0), mcu->getId()+"-"+id, i, mcu, input )
        //, QemuModule( mcu, i )
{
    //m_id     = id;

    m_pullAdmit = 1e5; // 10k

    double vdd = 3.3; //m_port->getMcu()->vdd();
    m_outHighV = vdd;
    m_inpHighV = vdd/2;
    m_inpLowV  = vdd/2;

    m_pullUp = 0;
    m_pullDown = 0;

    m_pinMask = 1<<i;

    m_dummyPin =  dummyPin;

    m_pinLabel = id;

    for( int i=0; i<6; ++i ) m_iomuxFuncs[i] = { nullptr, nullptr, "- -" };
}
Esp32Pin::~Esp32Pin() {}

void Esp32Pin::initialize()
{
    //Pin::setLabelText( m_pinLabel );
    //Pin::setLabelColor( QColor( 250, 250, 200 ) );

    //m_isAnalog = false;
    ////m_portState = false;

    //double vdd = 3.3; //m_port->getMcu()->vdd();
    //m_outHighV = vdd;
    //m_inpHighV = vdd/2;
    //m_inpLowV  = vdd/2;
    //

    IoPin::initialize();
}

void Esp32Pin::updateStep()
{
    IoPin::updateStep();
    Pin::setLabelText( m_iomuxFuncs[m_iomuxIndex].label );
    //Simulator::self()->remFromUpdateList( this );
}

void Esp32Pin::stamp()
{
    //m_alternate = false;
    //m_analog = false;
    //m_pull = false;

    m_pullUp   = 0;
    m_pullDown = 0;
    m_inputEn  = 0;
    setPinMode( input );

    m_iomuxIndex = -1;
    selectIoMuxFunc( 0 );

    //setPull( true );
    //updateStep();

    //update();

    IoPin::stamp();
}

void Esp32Pin::voltChanged()
{
    bool oldState = m_inpState;
    bool newState = IoPin::getInpState();

    if( oldState == newState ) return;

    // while( m_arena->qemuAction )        // Wait for previous action executed
    // {
    //     ; /// TODO: add timeout
    // }
    // m_arena->data8 = m_port-1;
    // m_arena->mask8 = m_number;
    // m_arena->data16 = newState;
    // m_arena->qemuAction = SIM_GPIO_IN;
}

void Esp32Pin::setPinMode( pinMode_t mode )
{
    IoPin::setPinMode( mode );
    changeCallBack( this, mode == input );
}

//void Esp32Pin::setPull( bool p )
//{
//    if( m_pull == p ) return;
//    m_pull = p;
//    setOutState( m_outState );
//}
//
//bool Esp32Pin::setAlternate( bool a ) // If changing to Not Alternate, return false
//{
//    if( m_alternate == a ) return true;
//    m_alternate = a;
//    if( a ) qDebug() << "Esp32Pin::setAlternate" << this->m_id;
//    return a;
//}
//
//void Esp32Pin::setAnalog( bool a ) /// TODO: if changing to Not Analog, return false
//{
//    if( m_analog == a ) return;
//    m_analog = a;
//}

void Esp32Pin::setPortState(  bool high ) // Set output from Port register
{
    //if( m_alternate ) return;
    setPinState( high );
}

void Esp32Pin::setOutState( bool high ) // Set output from Alternate (peripheral)
{
    //if( m_alternate )
        setPinState( high );
}

void Esp32Pin::scheduleState( bool high, uint64_t time )
{
    //if( m_alternate )
        IoPin::scheduleState( high, time );
}

void Esp32Pin::setPinState( bool high ) // Set Output to Hight or Low
{
    m_outState = m_nextState = high;
    //if( m_pinMode < openCo  || m_stateZ ) return;

    if( m_inverted ) high = !high;

    switch( m_pinMode )
    {
        case undef_mode: return;
        case input:
            /// if( m_pull ){
            ///     m_outVolt = high ? m_outHighV : m_outLowV;
            ///     ePin::stampCurrent( m_outVolt*m_pullAdmit );
            /// }
            break;
        case output:
            m_outVolt = high ? m_outHighV : m_outLowV;
            ePin::stampCurrent( m_outVolt*m_admit );
            break;
        case openCo:
            m_gndAdmit = high ? 1/1e8 : 1/m_outputImp;
            updtState();
            break;
        default: return;
    }
}

void Esp32Pin::setIoMuxFuncs( QList<funcPin> functions ) // Set IO_MUX functions for this pad
{
    for( int i=0; i<6; ++i )
    {
        funcPin fp = functions.at(i);
        if( fp.label.isEmpty() ) fp.label = m_pinLabel;

        m_iomuxFuncs[i] = fp;
    }
}

void Esp32Pin::selectIoMuxFunc( uint8_t func ) // Select IO_MUX function
{
    if( func > 5 ){
        qDebug() << this->pinId() << "Selected func ERROR"<< func;
        return;
    }
    if( m_iomuxIndex < 6 ){
        if( m_iomuxFuncs[m_iomuxIndex].pinPointer )
            *m_iomuxFuncs[m_iomuxIndex].pinPointer = m_dummyPin;

        QemuModule* mod = m_iomuxFuncs[m_iomuxIndex].module;
        if( mod ) mod->connected( false );
    }
    if( m_iomuxFuncs[func].label == "GPIO" ) m_iomuxFuncs[func].label = m_pinLabel;


    Simulator::self()->addToUpdateList( this ); /// FIXME

    if( m_iomuxFuncs[func].pinPointer )
    {
        //qDebug() << this->pinId() << "Selected func"<< func << m_iomuxPin[func].label;
        *m_iomuxFuncs[func].pinPointer = this;

        QemuModule* mod = m_iomuxFuncs[func].module;
        if( mod ) mod->connected( true );
    }
    if( m_iomuxFuncs[func].pinPointer || m_iomuxFuncs[func].label == m_pinLabel ){
        Pin::setLabelColor( QColor( 255, 255, 100 ) );
    }else{
        Pin::setLabelColor( QColor( 100, 100, 100 ) );
    }
    m_iomuxIndex = func;
    //qDebug() << this->pinId() << "Selected func"<< func << m_iomuxFuncs[func].label;
    //update();
}

void Esp32Pin::setMatrixFunc( uint16_t val, funcPin func ) // Set Function for GPIO Matrix: index=2
{
    //qDebug() << this->pinId() << "Matrix function"<< func.label<< (val & 0b111000000000);
    m_iomuxFuncs[2] = func;
    if( m_iomuxIndex == 2 ) selectIoMuxFunc( 2 );
}

void Esp32Pin::writeIoMuxReg( uint16_t value )
{
    uint64_t puld = (value >> 7) & 1;

    // Sleep bits 0-6
    // PD bit 7
    // PU bit 8
    // IE bit 9
    // Drive bits 10-11
    // function bits 12-14

    if( m_pullDown != puld ){
        m_pullDown = puld;
        /// TODO: iplement IoPin::setPulldown
    }

    uint8_t pulu = (value >> 8) & 1;
    if( m_pullUp != pulu ){
        m_pullUp = pulu;
        double pullup = pulu ? m_pullAdmit : 0;
        IoPin::setPullup( pullup );
    }

    m_inputEn = (value >> 9) & 1;

    uint8_t func = (value >> 12) & 7;
    if( m_iomuxIndex == func ) return;
    selectIoMuxFunc( func );
}

void Esp32Pin::writePinReg( uint32_t value )
{

}

void Esp32Pin::paint( QPainter* p, const QStyleOptionGraphicsItem* o, QWidget* w )
{
    if( !isVisible() ) return;
    Pin::paint( p, o, w );

    if( !m_pullUp && !m_pullDown ) return;
    if( m_pinMode > openCo ) return;

    // Draw pullUp/Down dot

    if( m_pullUp ) p->setBrush( QColor( 255, 180,   0 ) );
    else           p->setBrush( QColor(   0, 180, 255 ) );

    QPen pen = p->pen();
    pen.setWidthF( 0 );
    p->setPen(pen);
    int start = (m_length > 4) ? m_length-4.5 : 3.5;
    QRectF rect( start+0.6,-1.5, 3, 3 );
    p->drawEllipse(rect);
}
