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

#include "mcuocunit.h"
#include "mcupin.h"
#include "e_mcu.h"
#include "simulator.h"

McuOcUnit::McuOcUnit( eMcu* mcu, QString name )
         : McuModule( mcu, name )
         , eElement( mcu->getId()+"-"+name )
{
    m_timer = nullptr;
    m_ocPin = nullptr;
    m_ocm   = nullptr;
    m_ocPinInv = nullptr;
}
McuOcUnit::~McuOcUnit( ){}

void McuOcUnit::initialize()
{
    m_comMatch = 0;
    m_extMatch = 0;
    m_ocPin->controlPin( false, false );

    clear();
}

void McuOcUnit::clear()
{
    m_enabled = false;
    m_ctrlPin = false;
    m_mode = 0;

    m_comAct = ocNON;
    m_tovAct = ocNON;
}

void McuOcUnit::clockStep( uint16_t count )
{
    if( count == m_extMatch ) runEvent();
}

void McuOcUnit::runEvent()  // Compare match
{
    m_interrupt->raise();   // Trigger interrupt
    if( m_enabled ) drivePin( m_comAct, m_mcu->psInst() );
}

void McuOcUnit::comMatch()
{
    drivePin( m_comAct, m_mcu->psInst() );
}

void McuOcUnit::drivePin( ocAct_t act, uint64_t time )
{
    if( !act ) return;
    bool pinState = false;

    if     ( act == ocTOG ) pinState = !m_ocPin->getOutState();
    else if( act == ocCLR ) pinState = !m_pinSet;
    else if( act == ocSET ) pinState =  m_pinSet;

    setPinSate( pinState, time );
}

void McuOcUnit::setPinSate( bool state, uint64_t time )
{
    m_ocPin->scheduleState( state, time );
}

void McuOcUnit::sheduleEvents( uint32_t ovf, uint32_t countVal, int rot )
{
    if( rot ){
        ovf      <<= rot;  // Used by Pic CCP PWM mode: 8+2 bits (rot=2)
        countVal <<= rot;
    }
    uint64_t match;

    if( m_timer->reverse() )
    {
        match = ovf-m_comMatch;
        m_pinSet = false;
    }else{
        match = m_comMatch;
        m_pinSet = true;
    }
    Simulator::self()->cancelEvents( this );

    if( m_timer->extClocked() ) // Using external clock
    {
        m_extMatch = match;
    }
    else if( match <= ovf && match >= countVal ) // be sure next comp match is still ahead
    {
        uint64_t psPerTick  = m_timer->psPerTick();
        uint64_t timeOffset = m_timer->timeOffset();

        uint64_t time2ovf = (match-countVal)*psPerTick+0.5; // Time in ps
        if( timeOffset ) time2ovf -= psPerTick-timeOffset;

        uint64_t nextTime = time2ovf;
        if( rot ) nextTime >>= rot;
        Simulator::self()->addEvent( nextTime, this );
    }
}

void McuOcUnit::setOcActs( ocAct_t comAct, ocAct_t tovAct )
{
    m_comAct = comAct;
    m_tovAct = tovAct;
}

void McuOcUnit::ocrWriteL( uint8_t val )
{
    m_comMatch = (m_comMatch & 0xFF00) | val;
    if( m_timer->running() ) sheduleEvents( m_timer->ovfMatch(), m_timer->getCount() );
}

void McuOcUnit::ocrWriteH( uint8_t val )
{
    m_comMatch = (m_comMatch & 0x00FF) | (uint16_t)val<<8;
}
