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

#include <QDebug>

#include "stm32timer.h"
#include "stm32ocunit.h"
#include "stm32pin.h"

#define CR1_OFFSET   0x00
#define CR2_OFFSET   0x04
#define SMCR_OFFSET  0x08
#define DIER_OFFSET  0x0c
#define SR_OFFSET    0x10
#define EGR_OFFSET   0x14
#define CCMR1_OFFSET 0x18
#define CCMR2_OFFSET 0x1c
#define CCER_OFFSET  0x20
#define CNT_OFFSET   0x24
#define PSC_OFFSET   0x28
#define ARR_OFFSET   0x2c
#define RCR_OFFSET   0x30
#define CCR1_OFFSET  0x34
#define CCR2_OFFSET  0x38
#define CCR3_OFFSET  0x3c
#define CCR4_OFFSET  0x40
#define BDTR_OFFSET  0x44
#define DCR_OFFSET   0x48
#define DMAR_OFFSET  0x4C

#define CR1_CEN 1<<0
#define CR1_OPM 1<<3


Stm32Timer::Stm32Timer( QemuDevice* mcu, QString name, int n, uint32_t* clk, uint64_t memStart, uint64_t memEnd )
          : QemuTimer( mcu, name, n, clk, memStart, memEnd )
{
    for( int i=0; i<4; ++i )
    {
        m_channel[i] = new Stm32OcUnit( this, name+"-OcUnit"+QString::number(i), i );
    }
}
Stm32Timer::~Stm32Timer() {}

void Stm32Timer::reset()
{
    for( int i=0; i<4; ++i ) m_channel[i]->reset();
}

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

    if( offset != 0 ) return;

    int ch = m_eventValue & 0xFF;
    int state = m_eventValue & 1<<8;

    m_channel[ch]->m_pin->setOutState( state );
    //qDebug() << "Stm32Timer::writeRegister" << m_number << ch << m_channel[ch]->m_pin->pinId();

    //switch( offset )
    //{
    //case CR1_OFFSET:   writeCR1();                         break;
    //case CR2_OFFSET:   m_cr2  = m_eventValue & 0x00F8;     break;
    //case SMCR_OFFSET:  m_smcr = m_eventValue;              break;
    //case DIER_OFFSET:  m_dier = m_eventValue & 0x5F5F;     break;
    //case SR_OFFSET:    updateUIF();                        break;
    //case EGR_OFFSET:   writeEGR();                         break;
    //case CCMR1_OFFSET: writeCCMR( 0 );                     break;
    //case CCMR2_OFFSET: writeCCMR( 1 );                     break;
    //case CCER_OFFSET:  writeCCER();                        break;
    //case CNT_OFFSET:   setCount();                         break;
    //case PSC_OFFSET:   updtFreq();                         break;
    //case ARR_OFFSET:   updtPeriod();                       break;
    //case RCR_OFFSET:   m_rcr = m_eventValue;               break;
    //case CCR1_OFFSET:  m_channel[0]->m_CCR = m_eventValue; break;
    //case CCR2_OFFSET:  m_channel[1]->m_CCR = m_eventValue; break;
    //case CCR3_OFFSET:  m_channel[2]->m_CCR = m_eventValue; break;
    //case CCR4_OFFSET:  m_channel[3]->m_CCR = m_eventValue; break;
    //case BDTR_OFFSET:  m_bdtr = m_eventValue;              break;
    //case DCR_OFFSET:   m_dcr  = m_eventValue;              break;
    //case DMAR_OFFSET:  m_dmar = m_eventValue;              break;
    //default:           write();                            break;
    //}
}

void Stm32Timer::readRegister()
{
    uint64_t offset = m_eventAddress - m_memStart;

    uint64_t val = 0;

    switch( offset )
    {
    case CR1_OFFSET:   val = m_cr1;               break;
    case CR2_OFFSET:   val = m_cr2;               break;
    case SMCR_OFFSET:  val = m_smcr;              break;
    case DIER_OFFSET:  val = m_dier;              break;
    case SR_OFFSET:    val = m_sr;                break;
    case EGR_OFFSET:   val = m_egr;               break;
    case CCMR1_OFFSET: val = m_ccmr[0];           break;
    case CCMR2_OFFSET: val = m_ccmr[1];           break;
    case CCER_OFFSET:  val = m_ccer;              break;
    case CNT_OFFSET:   val = getCount();          break;
    case PSC_OFFSET:   val = m_psc;               break;
    case ARR_OFFSET:   val = m_arr;               break;
    case RCR_OFFSET:   val = m_rcr;               break;
    case CCR1_OFFSET:  val = m_channel[0]->m_CCR; break;
    case CCR2_OFFSET:  val = m_channel[1]->m_CCR; break;
    case CCR3_OFFSET:  val = m_channel[2]->m_CCR; break;
    case CCR4_OFFSET:  val = m_channel[3]->m_CCR; break;
    case BDTR_OFFSET:  val = m_bdtr;              break;
    case DCR_OFFSET:   val = m_dcr;               break;
    case DMAR_OFFSET:  val = m_dmar;              break;
    default:           val = read();              break;
    }
    m_arena->regData = val;
    m_arena->qemuAction = SIM_READ;
}

uint32_t Stm32Timer::getCount()
{
    uint32_t val = 0;

    return val;
}

void Stm32Timer::setCount()
{
    //uint32_t count =  m_eventValue;
}

void Stm32Timer::writeCR1()
{
    uint16_t cr1 = m_eventValue & 0x3FF;

    if( m_cr1 == cr1 ) return;
    m_cr1 = cr1;

    uint8_t enabled = cr1 & CR1_CEN;
    uint8_t oneShot = cr1 & CR1_OPM;

    if( m_enabled != enabled || m_oneShot != oneShot )
    {
        m_enabled = enabled;
        m_oneShot = oneShot;

        if( !enabled ) //Can't switch from edge-aligned to center-aligned if enabled (CEN=1)
        {
            uint8_t CMS = (cr1 & 0b1100000) >> 5;

            switch( CMS ) {
            case 0: m_bidirectional = 0; break;
            case 1: //break;
            case 2: //break;
            case 3: m_bidirectional = 1; break;
            default: break;
            }
        }

        // if( enabled ) ptimer_run( s->timer, s->oneShot ); // DPRINTF("%s Enabling timer\n", stm32_periph_name(s->periph));
        // else          ptimer_stop( s->timer );            // DPRINTF("%s Disabling timer\n", stm32_periph_name(s->periph));
    }
    if( !m_bidirectional )
    {
        m_direction = m_cr1 & 1<<4; // DIR bit
    }
}

void Stm32Timer::writeCCER()
{
    uint16_t ccer = m_eventValue;

    if( m_ccer == ccer ) return;
    m_ccer = ccer;

    for( int i=0; i<4; ++i )
    {
        m_channel[i]->writeCCER( ccer & 0x0F );
        ccer >>= 4;
    }
}

void Stm32Timer::writeEGR()
{
    uint16_t egr = m_eventValue & 0x1E;
    if( m_egr == egr ) return;
    m_egr = egr;
    if( m_eventValue & 0x40 ) m_sr |= 0x40;      // TG bit
    if( m_eventValue & 0x1  ) updtPeriod();      // UG bit - reload count
}

void Stm32Timer::writeCCMR( int i )
{
    uint16_t ccmr = m_eventValue;
    if( m_ccmr[i] == ccmr ) return;
    m_ccmr[i] = ccmr;

    int ch = i*2;
    m_channel[ch]->writeCCMR( ccmr );
    m_channel[ch+1]->writeCCMR( ccmr>>8 );
}

void Stm32Timer::updateUIF()
{
    m_sr ^= (m_eventValue ^ 0xFFFF);
    m_sr &= 0x1EFF;

    //uint8_t value = m_sr & 1;

    //if( !(m_sr & 1) )
    //{
    //    //s->sr &= ~0x1; /* update interrupt flag in status reg */
    //    s->sr |= (value & 0x1);
    //    s->dier |= 0x01;                 //FIXME what value must be used? RESET = 0
    //    qemu_set_irq( s->irq, value );
    //}
}

void Stm32Timer::updtFreq()
{
    m_psc = m_eventValue;
}

void Stm32Timer::updtPeriod()
{
    m_arr = m_eventValue;
}

void Stm32Timer::setOcPins( Stm32Pin* oc0Pin, Stm32Pin* oc1Pin, Stm32Pin* oc2Pin , Stm32Pin *oc3Pin)
{
    //qDebug() << "Stm32Timer::setOcPins" << m_number << oc0Pin->pinId() << oc1Pin->pinId() << oc2Pin->pinId() << oc3Pin->pinId();
    m_channel[0]->m_pin = oc0Pin;
    m_channel[1]->m_pin = oc1Pin;
    m_channel[2]->m_pin = oc2Pin;
    m_channel[3]->m_pin = oc3Pin;
}
