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

#include <QDebug>

#include "mcuinterrupts.h"
#include "cpu8bits.h"
#include "mcupin.h"
#include "e_mcu.h"
#include "datautils.h"

Interrupt::Interrupt( QString name, uint16_t vector, eMcu* mcu )
{
    m_mcu  = mcu; 
    m_name = name; 
    m_vector = vector; 
    m_wakeup = 0; 
    m_autoClear = true; 
    m_remember  = true;       /// Remember by deafult: find out exceptions
    m_nextInt = nullptr; 
    m_intPin  = nullptr; 

    m_ram = mcu->getRam(); 
}
Interrupt::~Interrupt(){}

void Interrupt::reset()
{
    //m_mode    = 0; 
    m_enabled = 0; 
    m_raised  = false; 
    m_continuous = false; 
}

void Interrupt::enableFlag( uint8_t en )
{
    if( m_enabled == en ) return; 
    m_enabled = en; 

    if( en ) // If not enabled m_remember it until reenabled
    {
        if( m_raised && m_remember ) m_interrupts->addToPending( this );  // Add to pending interrupts
    }
    else m_interrupts->remFromPending( this ); 
}

void Interrupt::clearFlag()
{
    if( !m_raised ) return; 
    m_raised = false; 

    m_ram[m_flagReg] &= ~m_flagMask;  // Clear Interrupt flag

    m_interrupts->remFromPending( this ); 
}

void Interrupt::flagCleared( uint8_t )  // Interrupt flag was cleared by software
{
    if( !m_raised ) return; 
    m_raised = false; 
    m_interrupts->remFromPending( this ); 
}

void Interrupt::writeFlag( uint8_t v ) // Clear Interrupt flag by writting 1 to it
{
    int overrided = m_mcu->m_regOverride; 
    if( overrided > 0 ) v = overrided;       // Get previous overrides

    if( v & m_flagMask ){
        m_mcu->m_regOverride = v & ~m_flagMask;  // Clear flag
        flagCleared();       // Pin INT continuous while low level
    }
    else if( m_ram[m_flagReg] & m_flagMask )
    {
        m_mcu->m_regOverride = v | m_flagMask;  // Keep flag
    }
}

void Interrupt::setContinuous( bool c ) // Pin INT
{
    if( m_continuous == c ) return; 
    m_continuous = c; 
    m_autoClear = !c; 
    clearFlag(); 
}

void Interrupt::raise( uint8_t v )
{
    if( v )
    {
        if( !m_callBacks.isEmpty() ) { for( McuModule* mod : m_callBacks ) mod->callBack();  }

        if( m_raised ) return; 
        m_raised = true; 

        if( !m_continuous )                  // Set Interrupt flag
        {                                    // If Continuous don't set the flag (AVR Pin INT)
             m_ram[m_flagReg] |= m_flagMask; 
        }

        if( m_enabled )
        {
            m_interrupts->addToPending( this );  // Add to pending interrupts
            if( m_intPin ) m_intPin->setOutState( false ); 

            if( m_mcu->state() == mcuSleeping
             && (m_wakeup & m_mcu->sleepMode()) )
                m_mcu->sleep( false );  // Exit sleep
        }

    }
    else if( m_autoClear || m_continuous ) clearFlag(); 
}

void Interrupt::execute()
{
    m_interrupts->writeGlobalFlag( 0 );  // Disable Global Interrupts
    if( m_vector ) m_mcu->cpu()->INTERRUPT( m_vector ); 
}

void Interrupt::exitInt() // Exit from this interrupt
{
    if( m_autoClear ) clearFlag(); 
    if( !m_exitCallBacks.isEmpty() ) { for( McuModule* mod : m_exitCallBacks ) mod->callBack();  }
}

void Interrupt::callBack( McuModule* mod, bool call ) // Add Modules to be called at Interrupt raise
{
    if( call )
    { if( !m_callBacks.contains( mod ) ) m_callBacks.append( mod );  }
    else m_callBacks.removeAll( mod ); 
}

void Interrupt::exitCallBack( McuModule* mod, bool call )
{
    if( call )
    { if( !m_exitCallBacks.contains( mod ) ) m_exitCallBacks.append( mod );  }
    else m_exitCallBacks.removeAll( mod ); 
}

//------------------------               ------------------------
//---------------------------------------------------------------

Interrupts::Interrupts( eMcu* mcu )
{
    m_mcu = mcu; 
}
Interrupts::~Interrupts(){}

void Interrupts::resetInts()
{
    if( m_enGlobalFlag.regAddr ) m_enabled = 0; 
    else                         m_enabled = 1;  // keep enabled if no enable flag
    m_reti    = false; 
    m_active  = nullptr; 
    m_pending = nullptr; 
    m_running = nullptr; 

    for( QString inte : m_intList.keys() ) m_intList.value( inte )->reset(); 
}

void Interrupts::runInterrupts()
{
    if( m_reti )                                // RETI
    {
        m_reti = false; 

        if( !m_active ) {
            qDebug() << "Interrupts::retI Error: No active Interrupt";  return;  }
        m_active->exitInt(); 

        if( m_running )                         // Some interrupt was interrupted by this one
        {
            m_active  = m_running; 
            m_running = m_running->m_nextInt;    // Remove from running list
        }
        else{
            if( m_active->isContinuous() && m_active->raised() ) addToPending( m_active ); 
            m_active = nullptr; 
        }
        writeGlobalFlag( 1 );                    // Enable Global Interrupts
        return; 
    }
    /// if( m_enabled > 1 ){ m_enabled -= 1;  return;  }// Execute interrupts some cycles later

    if( !m_enabled ) return;                     // Global Interrupts disabled
    if( !m_pending ) return;                     // No Interrupts pending to execute; 

    if( m_active )                              // An interrupt is running,
    {
        if( m_pending->priority() > m_active->priority() )// Only interrupt other Interrupts with lower priority
        {
            m_active->m_nextInt = m_running; 
            m_running = m_active;                // An interrupt being interrupted, add to running list.
        }
        else return; 
    }
    m_pending->execute(); 
    m_active  = m_pending; 
    m_pending = m_pending->m_nextInt; 
}

void Interrupts::writeGlobalFlag( uint8_t flag )
{
    if( m_enGlobalFlag.regAddr ) writeRegBits( m_enGlobalFlag, flag );    // Set/Clear Enable Global Interrupts flag

    m_enabled = flag;                        // Enable/Disable interrupts
}

void Interrupts::enableGlobal( uint8_t en )
{
    m_enabled = en; 
}

void Interrupts::remove()
{
    for( QString inte : m_intList.keys() ) delete m_intList.value( inte ); 
}

void Interrupts::addToPending( Interrupt* newInt )
{
    Interrupt* preInt = nullptr; 
    Interrupt* posInt = m_pending; 
    while( posInt )
    {
        if( posInt == newInt ) // Interrupt already in the list
        { return;  } // ERROR
        if( newInt->priority() <= posInt->priority() )  // High priority first & last In, las Out
        {                                               // Keep iterating
            preInt = posInt; 
            posInt = posInt->m_nextInt; 
        }
        else break; 
    }
    newInt->m_nextInt = posInt; 
    if( preInt ) preInt->m_nextInt = newInt; 
    else         m_pending = newInt; 

}

void Interrupts::remFromPending( Interrupt* remInt )
{
    Interrupt* preInt = nullptr; 
    Interrupt* posInt = m_pending; 
    while( posInt )
    {
        if( posInt == remInt )
        {
            if( preInt ) preInt->m_nextInt = posInt->m_nextInt; 
            else         m_pending = posInt->m_nextInt; 
            break; 
        }
        preInt = posInt; 
        posInt = posInt->m_nextInt; 
}   }

Interrupt* Interrupts::getInterrupt( QString name )
{
    return m_intList.value( name ); 
}
