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

#include <QDebug>

#include "stm32port.h"
//#include "simulator.h"

#define CRL_OFFSET  0x00
#define CRH_OFFSET  0x04
#define IDR_OFFSET  0x08
#define ODR_OFFSET  0x0C
#define BSRR_OFFSET 0x10
#define BRR_OFFSET  0x14
#define LCKR_OFFSET 0x18


Stm32Port::Stm32Port(QemuDevice* mcu, QString n, int number, uint32_t* clk, uint64_t memStart, uint64_t memEnd )
         : QemuModule( mcu, n, number, clk, memStart, memEnd )
         , eElement( n )
{
}
Stm32Port::~Stm32Port(){}

void Stm32Port::reset()
{
    m_pinState = 0;

    writeMem( m_memStart+CRL_OFFSET, 0x44444444 );
    writeMem( m_memStart+CRH_OFFSET, 0x44444444 );
    writeMem( m_memStart+ODR_OFFSET, 0 );
}

void Stm32Port::readRegister()
{
    //qDebug() <<"Stm32Port::readRegister"<< m_name << m_eventAddress << m_eventValue;

    uint64_t offset = m_eventAddress - m_memStart;
    uint32_t val = 0;
    switch( offset )
    {
        //case CRL_OFFSET: break;
        //case CRH_OFFSET: break;
        case IDR_OFFSET:  val = readPort(); break;
        //case ODR_OFFSET: break;
        case BSRR_OFFSET:                   break; // Write only
        case BRR_OFFSET:                    break; // Write only
        //case LCKR_OFFSET: break;
        default:          val = read();     break;
    }
    m_arena->regData = val;
    m_arena->qemuAction = SIM_READ;
}

void Stm32Port::writeRegister()
{
    uint64_t offset = m_eventAddress - m_memStart;

    switch( offset )
    {
        case CRL_OFFSET:
            if( m_eventValue == read() ) break;
            write();
            cofigPort( m_eventValue, 0 );
            break;
        case CRH_OFFSET:
            if( m_eventValue == read() ) break;
            write();
            cofigPort( m_eventValue, 8 ); // shift Pin number by 8
            break;
        case IDR_OFFSET:                 break; // Read only;

        case ODR_OFFSET:
            if( m_eventValue == read() ) break;
            write();
            setPortState( m_eventValue );
            break;
        case BSRR_OFFSET:{
            uint32_t set_mask = m_eventValue & 0x0000FFFF;
            uint32_t reset_mask = ~(m_eventValue >> 16) & 0x0000FFFF;
            uint32_t ODR = readMem( m_memStart+ODR_OFFSET );
            m_eventValue = ( ODR & reset_mask) | set_mask; // Sets take priority over resets, so we do
            if( m_eventValue == ODR ) break;
            writeMem( m_memStart+ODR_OFFSET, m_eventValue );
            setPortState( m_eventValue );
        } break;
        case BRR_OFFSET:{
            uint32_t reset_mask = ~m_eventValue & 0x0000FFFF;
            uint32_t ODR = readMem( m_memStart+ODR_OFFSET );
            m_eventValue = ODR & reset_mask;
            if( m_eventValue == ODR ) break;
            writeMem( m_memStart+ODR_OFFSET, m_eventValue );
            setPortState( m_eventValue );
        } break;
        case LCKR_OFFSET: write(); break; /// TODO: Locking is not implemented

        default: write(); break;
    }
}

void Stm32Port::cofigPort( uint32_t config, uint8_t shift )
{
    //if( config == 0 ) return;
    //qDebug() << "Stm32Port::cofigPort:" << m_name << config << shift;

    for( uint8_t i=shift; i<shift+8; ++i )
    {
        Stm32Pin*  ioPin = m_pins.at( i );
        uint8_t cfgShift = i*4;
        uint32_t cfgMask = 0b1111 << cfgShift;
        uint32_t cfgBits = (config & cfgMask) >> cfgShift;

        uint8_t isOutput = cfgBits & 0b0011;  // 0 = Input

        if( isOutput ) // Output
        {
            uint8_t   open = cfgBits & 0b0100;
            pinMode_t pinMode = open ? openCo : output;
            ioPin->setPinMode( pinMode );
        }
        else          // Input
        {
            ioPin->setPinMode( input );
            uint8_t pull = cfgBits & 0b1000;
            ioPin->setPull( pull>0 );
        }

        uint8_t mode = cfgBits & 0b1100; // Analog if CNF0[1:0] == 0
        ioPin->setAnalog( mode==0 );
        /// TODO: if changing to Not Analog // Restore Port State

        uint8_t alternate = cfgBits & 0b1000;
        if( !ioPin->setAlternate( alternate>0 ) )            // If changing to No Alternate
            ioPin->setPortState( (m_pinState & 1<<i) > 0 );  // Restore Port state
    }
}
    // CNF
    // Input mode:
    //     00: Analog mode
    //     01: Input Floating (reset state)
    //     10: Input with pull-up / pull-down
    //     11: Reserved
    // Output mode:
    // CNF0 0 push-pull
    //      1 Open-drain
    // CNF1 0 General purpose output
    //      1 Alternate function output

void Stm32Port::setPortState( uint16_t state )
{
    //qDebug() << "   Stm32Port::setPortState:               " << m_name << state<< Simulator::self()->circTime();

    for( uint8_t i=0; i<m_pins.size(); ++i )
    {
        Stm32Pin* ioPin = m_pins.at( i );
        ioPin->setPortState( state & (1<<i) );
    }
}

uint32_t Stm32Port::readPort()
{
    uint32_t data = 0;
    for( uint8_t i=0; i<m_pins.size(); ++i )
        if( m_pins[i]->getInpState() ) data |= (1 << i);

    return data;
}

Stm32Pin* Stm32Port::createPin( int i, QString id , QemuDevice* mcu )
{
    Stm32Pin* pin = new Stm32Pin( m_number, i, id, mcu );

    m_pins.emplace_back( pin );

    return pin;
}
