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

#pragma once

#include <vector>
#include <inttypes.h>

class CallBackBase
{
        friend class McuSignal; 

    public:
        CallBackBase(){; }
        virtual ~CallBackBase(){; }

        virtual void call( uint8_t ){; }

        CallBackBase* nextCallBack; 

    private:
        uint8_t m_mask; 
}; 

template <class Obj>
class CallBack : public CallBackBase
{
        friend class McuSignal; 

    public:
        CallBack( Obj* object, void (Obj::*func)(uint8_t) )
        : CallBackBase()
        {
            m_object = object; 
            m_func = func; 
            nextCallBack = nullptr; 
        }
        ~CallBack() {; }

        virtual void call( uint8_t val ) override
        { (m_object->*m_func)(val);  }

    private:
        Obj* m_object; 
        void (Obj::*m_func)(uint8_t); 
}; 

class McuSignal
{
    public:
        McuSignal()
        {
            m_slot = nullptr; 
        }
        ~McuSignal()
        {
            CallBackBase* slot = m_slot; 
            while( slot ) // delete slots
            {
                CallBackBase* slotDel = slot; 
                slot = slot->nextCallBack; 
                delete slotDel; 
        }   }

        template <class Obj>
        void connect( Obj* obj, void (Obj::*func)(uint8_t), uint8_t mask=0xFF )
        {
            CallBack<Obj>* slot = new CallBack<Obj>( obj, func ); 
            slot->m_mask = mask; 

            // New slots are prepended (LIFO)
            // This means Interrupt flag clearing after register write callback
            // Because Interrupts are created first
            slot->nextCallBack = m_slot; 
            m_slot = slot; 
        }

        template <class Obj>
        void disconnect( Obj* obj, void (Obj::*func)(uint8_t) )
        {
            CallBackBase* preSlot = 0; 
            CallBackBase* posSlot = m_slot; 
            while( posSlot )
            {
                CallBack<Obj>* cb = dynamic_cast<CallBack<Obj>*>(posSlot); 

                if( (cb->m_object == obj) && (cb->m_func == func))
                {
                    if( preSlot ) preSlot->nextCallBack = posSlot->nextCallBack; 
                    else          m_slot = posSlot->nextCallBack; 
                    delete posSlot; 
                    break; 
                }
                preSlot = posSlot; 
                posSlot = posSlot->nextCallBack; 
        }   }

        void emitValue( uint8_t val ) // Calls all connected functions with masked val.
        {
            CallBackBase* slot = m_slot; 
            while( slot )
            {
                slot->call( val & slot->m_mask ); 
                slot = slot->nextCallBack; 
        }   }

    private:
        CallBackBase* m_slot; 
}; 
