/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */

/*
 * ----------------------------------------------------------------------------------
 * Most of the code in this file is copied (with a some modifications) from:
 *     WInterrupts.c
 * ----------------------------------------------------------------------------------
 * Copyright (C) 2004 - 2005 Hernando Barragan
 *
 * Part of the Wiring project - http://wiring.uniandes.edu.co
 *
 * This library is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any later
 * version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along
 * with this library; if not, write to the Free Software Foundation, Inc., 51
 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 *
 * Modified 24 November 2006 by David A. Mellis
 * Modified  1 August   2010 by Mark Sproul
 * ----------------------------------------------------------------------------------
 */


#include "../AVRCommon/Internal.h"


namespace eCxx {

namespace AVR {


//
// Determine the number of external interrupts based on the MCU type
//
#if defined(__AVR_ATmega8__  ) || defined(__AVR_ATmega8A__  ) ||                                                                                                \
    defined(__AVR_ATmega48__ ) || defined(__AVR_ATmega48A__ ) || defined(__AVR_ATmega48P__ ) || defined(__AVR_ATmega48PA__ ) || defined(__AVR_ATmega48PB__ ) || \
    defined(__AVR_ATmega88__ ) || defined(__AVR_ATmega88A__ ) || defined(__AVR_ATmega88P__ ) || defined(__AVR_ATmega88PA__ ) || defined(__AVR_ATmega88PB__ ) || \
    defined(__AVR_ATmega168__) || defined(__AVR_ATmega168A__) || defined(__AVR_ATmega168P__) || defined(__AVR_ATmega168PA__) || defined(__AVR_ATmega168PB__) || \
    defined(__AVR_ATmega328__) ||                                defined(__AVR_ATmega328P__) ||                                 defined(__AVR_ATmega328PB__)

    #define NUM_OF_EXT_INTS 2

#elif defined(__AVR_ATmega16__  ) || defined(__AVR_ATmega16A__ ) ||                                                                 \
                                     defined(__AVR_ATmega164A__) || defined(__AVR_ATmega164P__ ) || defined(__AVR_ATmega164PA__) || \
      defined(__AVR_ATmega32__  ) || defined(__AVR_ATmega32A__ ) ||                                                                 \
                                     defined(__AVR_ATmega324A__) || defined(__AVR_ATmega324P__ ) || defined(__AVR_ATmega324PA__) || \
      defined(__AVR_ATmega644__ ) || defined(__AVR_ATmega644A__) || defined(__AVR_ATmega644P__ ) || defined(__AVR_ATmega644PA__) || \
      defined(__AVR_ATmega1284__) ||                                defined(__AVR_ATmega1284P__)

    #define NUM_OF_EXT_INTS 3

#elif defined(__AVR_ATmega64__) || defined(__AVR_ATmega64A__) || defined(__AVR_ATmega128__) ||  defined(__AVR_ATmega128A__)

    #define NUM_OF_EXT_INTS 8

#elif defined(__AVR_ATmega640__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)

    #define NUM_OF_EXT_INTS 8

#elif defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__)

    #define NUM_OF_EXT_INTS 7

    #define NO_EXT_INT4
    #define NO_EXT_INT5

#elif defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB1286__)

    #define NUM_OF_EXT_INTS 8

#else

#error "unsupported AVR MCU"

#endif


//
// Pointer to interrupt handlers for each external interrupts
//
static void gpio_nop_isr(void*)
{}


static volatile VoidFuncPtrArg intFunc[NUM_OF_EXT_INTS] = {
#if (NUM_OF_EXT_INTS > 7)
    &gpio_nop_isr,
#endif
#if (NUM_OF_EXT_INTS > 6)
    &gpio_nop_isr,
#endif
#if (NUM_OF_EXT_INTS > 5)
    &gpio_nop_isr,
#endif
#if (NUM_OF_EXT_INTS > 4)
    &gpio_nop_isr,
#endif
#if (NUM_OF_EXT_INTS > 3)
    &gpio_nop_isr,
#endif
#if (NUM_OF_EXT_INTS > 2)
    &gpio_nop_isr,
#endif
#if (NUM_OF_EXT_INTS > 1)
    &gpio_nop_isr,
#endif
#if (NUM_OF_EXT_INTS > 0)
    &gpio_nop_isr
#endif
};


static void* intFuncArg[NUM_OF_EXT_INTS] = { nullptr };


//
// GPIO interrupt vector
//
#define IMPLEMENT_ISR(VECTOR_NAME, EINT_NUM) ISR(VECTOR_NAME)                       \
{                                                                                   \
    if(intFuncArg[EINT_NUM]) (*intFunc[EINT_NUM])(intFuncArg[EINT_NUM]);            \
    else                     (*reinterpret_cast<VoidFuncPtr>(intFunc[EINT_NUM]))(); \
}

#if (NUM_OF_EXT_INTS > 7)
IMPLEMENT_ISR(INT7_vect, 7)
#endif
#if (NUM_OF_EXT_INTS > 6)
IMPLEMENT_ISR(INT6_vect, 6)
#endif
#if (NUM_OF_EXT_INTS > 5) && !defined(NO_EXT_INT5)
IMPLEMENT_ISR(INT5_vect, 5)
#endif
#if (NUM_OF_EXT_INTS > 4) && !defined(NO_EXT_INT4)
IMPLEMENT_ISR(INT4_vect, 4)
#endif
#if (NUM_OF_EXT_INTS > 3)
IMPLEMENT_ISR(INT3_vect, 3)
#endif
#if (NUM_OF_EXT_INTS > 2)
IMPLEMENT_ISR(INT2_vect, 2)
#endif
#if (NUM_OF_EXT_INTS > 1)
IMPLEMENT_ISR(INT1_vect, 1)
#endif
#if (NUM_OF_EXT_INTS > 0)
IMPLEMENT_ISR(INT0_vect, 0)
#endif


//
// Helper functions
//
static bool attachInterruptArg_impl(uint8_t eintNum, VoidFuncPtrArg userFunc, void* arg, uint8_t mode)
{
    // Check if the external interrupt number is too large
    if(eintNum >= NUM_OF_EXT_INTS) return false;

    // Save the user callback
    intFunc   [eintNum] = userFunc;
    intFuncArg[eintNum] = arg;

    // Enable the interrupt
    switch(eintNum) {

        #if defined(EICRA) && defined(EICRB) && defined(EIMSK)
        case 0:
            EICRA  = ( EICRA & ~( _BV(ISC00) | _BV(ISC01) ) ) | (mode << ISC00);
            EIMSK |= _BV(INT0);
            break;
        case 1:
            EICRA  = ( EICRA & ~( _BV(ISC10) | _BV(ISC11) ) ) | (mode << ISC10);
            EIMSK |= _BV(INT1);
            break;
        case 2:
            EICRA  = ( EICRA & ~( _BV(ISC20) | _BV(ISC21) ) ) | (mode << ISC20);
            EIMSK |= _BV(INT2);
            break;
        case 3:
            EICRA  = ( EICRA & ~( _BV(ISC30) | _BV(ISC31) ) ) | (mode << ISC30);
            EIMSK |= _BV(INT3);
            break;
        case 4:
            EICRB  = ( EICRB & ~( _BV(ISC40) | _BV(ISC41) ) ) | (mode << ISC40);
            EIMSK |= _BV(INT4);
            break;
        case 5:
            EICRB  = ( EICRB & ~( _BV(ISC50) | _BV(ISC51) ) ) | (mode << ISC50);
            EIMSK |= _BV(INT5);
            break;
        case 6:
            EICRB  = ( EICRB & ~( _BV(ISC60) | _BV(ISC61) ) ) | (mode << ISC60);
            EIMSK |= _BV(INT6);
            break;
        case 7:
            EICRB  = ( EICRB & ~( _BV(ISC70) | _BV(ISC71) ) ) | (mode << ISC70);
            EIMSK |= _BV(INT7);
            break;
        #else
        case 0:
            #if defined(EICRA) && defined(EIMSK) && defined(ISC00)
                EICRA  = ( EICRA & ~( _BV(ISC00) | _BV(ISC01) ) ) | (mode << ISC00);
                EIMSK |= _BV(INT0);
            #elif defined(MCUCR) && defined(GICR) && defined(ISC00)
                MCUCR  = ( MCUCR & ~( _BV(ISC00) | _BV(ISC01) ) ) | (mode << ISC00);
                GICR  |= _BV(INT0);
            #elif defined(MCUCR) && defined(GIMSK) && defined(ISC00)
                MCUCR  = ( MCUCR & ~( _BV(ISC00) | _BV(ISC01) ) ) | (mode << ISC00);
                GIMSK |= _BV(INT0);
            #else
                #error attachInterrupt_impl(0) is not implemented for this MCU
            #endif
            break;
        case 1:
            #if defined(EICRA) && defined(EIMSK) && defined(ISC10) && defined(ISC11)
                EICRA  = ( EICRA & ~( _BV(ISC10) | _BV(ISC11) ) ) | (mode << ISC10);
                EIMSK |= _BV(INT1);
            #elif defined(MCUCR) && defined(GICR) && defined(ISC10) && defined(ISC11)
                MCUCR  = ( MCUCR & ~( _BV(ISC10) | _BV(ISC11) ) ) | (mode << ISC10);
                GICR  |= _BV(INT1);
            #elif defined(MCUCR) && defined(GIMSK) && defined(ISC10) && defined(ISC11)
                MCUCR  = ( MCUCR & ~( _BV(ISC10) | _BV(ISC11) ) ) | (mode << ISC10);
                GIMSK |= _BV(INT1);
            #else
                #error attachInterrupt_impl(1) is not implemented for this MCU
            #endif
            break;
        #if NUM_OF_EXT_INTS > 2
        case 2:
            #if defined(EICRA) && defined(EIMSK) && defined(ISC20) && defined(ISC21)
                EICRA  = ( EICRA & ~( _BV(ISC20) | _BV(ISC21) ) ) | (mode << ISC20);
                EIMSK |= _BV(INT2);
            #elif defined(MCUCR) && defined(GICR) && defined(ISC20) && defined(ISC21)
                MCUCR  = ( MCUCR & ~( _BV(ISC20) | _BV(ISC21) ) ) | (mode << ISC20);
                GICR  |= _BV(INT2);
            #elif defined(MCUCR) && defined(GIMSK) && defined(ISC20) && defined(ISC21)
                MCUCR  = ( MCUCR & ~( _BV(ISC20) | _BV(ISC21) ) ) | (mode << ISC20);
                GIMSK |= _BV(INT2);
            #elif defined(MCUCSR) && defined(GICR) && defined(ISC2)
                if( mode == static_cast<uint8_t>(Application::InterruptMode::Change) ) return false;
                MCUCSR  = ( MCUCSR & ~_BV(ISC2) ) | ( (mode & 0x01) << ISC2 );
                GICR   |= _BV(INT2);
            #else
                #error attachInterrupt_impl(2) is not implemented for this MCU
            #endif
            break;
        #endif
        #endif

    } // switch eintNum

    // Done
    return true;
}


static bool detachInterrupt_impl(uint8_t eintNum)
{
    // Check if the external interrupt number is too large
    if(eintNum >= NUM_OF_EXT_INTS) return false;

    // Disable the interrupt
    switch (eintNum) {

        #if defined(EICRA) && defined(EICRB) && defined(EIMSK)
        case 0:
            EIMSK &= ~_BV(INT0);
            break;
        case 1:
            EIMSK &= ~_BV(INT1);
            break;
        case 2:
            EIMSK &= ~_BV(INT2);
            break;
        case 3:
            EIMSK &= ~_BV(INT3);
            break;
        case 4:
            EIMSK &= ~_BV(INT4);
            break;
        case 5:
            EIMSK &= ~_BV(INT5);
            break;
        case 6:
            EIMSK &= ~_BV(INT6);
            break;
        case 7:
            EIMSK &= ~_BV(INT7);
            break;
        #else
        case 0:
            #if defined(EIMSK) && defined(INT0)
                EIMSK &= ~_BV(INT0);
            #elif defined(GICR) && defined(ISC00)
                GICR  &= ~_BV(INT0);
            #elif defined(GIMSK) && defined(INT0)
                GIMSK &= ~_BV(INT0);
            #else
                #error detachInterrupt_impl(0) is not implemented for this MCU
            #endif
            break;
        case 1:
            #if defined(EIMSK) && defined(INT1)
                EIMSK &= ~_BV(INT1);
            #elif defined(GICR) && defined(INT1)
                GICR  &= ~_BV(INT1);
            #elif defined(GIMSK) && defined(INT1)
                GIMSK &= ~_BV(INT1);
            #else
                #error detachInterrupt_impl(1) is not implemented for this MCU
            #endif
            break;
        #if NUM_OF_EXT_INTS > 2
        case 2:
            #if defined(EIMSK) && defined(INT2)
                EIMSK &= ~_BV(INT2);
            #elif defined(GICR) && defined(INT2)
                GICR  &= ~_BV(INT2);
            #elif defined(GIMSK) && defined(INT2)
                GIMSK &= ~_BV(INT2);
            #elif defined(INT2)
                #error detachInterrupt_impl(2) is not implemented for this MCU
            #endif
            break;
        #endif
        #endif

    }

    // Clear the user callback
    intFunc   [eintNum] = &gpio_nop_isr;
    intFuncArg[eintNum] = nullptr;

    // Done
    return true;
}


static inline uint8_t pinNumberToIntNumber(uint8_t pin) __ECXX_ATTR_ALWAYS_INLINE;
static inline uint8_t pinNumberToIntNumber(uint8_t pin)
{
#if defined(__AVR_ATmega8__  ) || defined(__AVR_ATmega8A__  ) ||                                                                                                \
    defined(__AVR_ATmega48__ ) || defined(__AVR_ATmega48A__ ) || defined(__AVR_ATmega48P__ ) || defined(__AVR_ATmega48PA__ ) || defined(__AVR_ATmega48PB__ ) || \
    defined(__AVR_ATmega88__ ) || defined(__AVR_ATmega88A__ ) || defined(__AVR_ATmega88P__ ) || defined(__AVR_ATmega88PA__ ) || defined(__AVR_ATmega88PB__ ) || \
    defined(__AVR_ATmega168__) || defined(__AVR_ATmega168A__) || defined(__AVR_ATmega168P__) || defined(__AVR_ATmega168PA__) || defined(__AVR_ATmega168PB__) || \
    defined(__AVR_ATmega328__) ||                                defined(__AVR_ATmega328P__) ||                                 defined(__AVR_ATmega328PB__)

    switch(pin) {
        case 2  : return 0;
        case 3  : return 1;
        default : return Application::InvalidPin;
    }

#elif defined(__AVR_ATmega16__  ) || defined(__AVR_ATmega16A__ ) ||                                                                 \
                                     defined(__AVR_ATmega164A__) || defined(__AVR_ATmega164P__ ) || defined(__AVR_ATmega164PA__) || \
      defined(__AVR_ATmega32__  ) || defined(__AVR_ATmega32A__ ) ||                                                                 \
                                     defined(__AVR_ATmega324A__) || defined(__AVR_ATmega324P__ ) || defined(__AVR_ATmega324PA__) || \
      defined(__AVR_ATmega644__ ) || defined(__AVR_ATmega644A__) || defined(__AVR_ATmega644P__ ) || defined(__AVR_ATmega644PA__) || \
      defined(__AVR_ATmega1284__) ||                                defined(__AVR_ATmega1284P__)

    switch(pin) {
        case  2 : return 0;
        case  3 : return 1;
        case 10 : return 2;
        default : return Application::InvalidPin;
    }

#elif defined(__AVR_ATmega64__) || defined(__AVR_ATmega64A__) || defined(__AVR_ATmega128__) ||  defined(__AVR_ATmega128A__)

    switch(pin) {
        case 18 : return 0;
        case 19 : return 1;
        case 20 : return 2;
        case 21 : return 3;
        case  2 : return 6;
        case  3 : return 7;
        case  6 : return 4;
        case  7 : return 5;
        default : return Application::InvalidPin;
    }

#elif defined(__AVR_ATmega640__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)

    switch(pin) {
        case 21 : return 0;
        case 20 : return 1;
        case 19 : return 2;
        case 18 : return 3;
        case  2 : return 4;
        case  3 : return 5;
        case 71 : return 6; // Not connected to any header pin in Arduino variant mega
        case 72 : return 7; // Not connected to any header pin in Arduino variant mega
        default : return Application::InvalidPin;
    }

#elif defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__)

    switch(pin) {
        case 3  : return 0;
        case 2  : return 1;
        case 0  : return 2;
        case 1  : return 3;
        case 7  : return 6;
        default : return Application::InvalidPin;
    }

#elif defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB1286__)

    switch(pin) {
        case  0  : return 2;
        case  1  : return 3;
        case  2  : return 4;
        case  3  : return 5;
        case  5  : return 0;
        case  6  : return 1;
        case 37  : return 6;
        case 38  : return 7;
        default : return Application::InvalidPin;
    }

#else

#error "pinNumberToIntNumber() is not implemented for this MCU"

#endif
}


//
// Interrupt-related functions
//
extern bool attachInterrupt(uint8_t pin, VoidFuncPtr userFunc, InterruptMode mode)
{
    return attachInterruptArg_impl(
        pinNumberToIntNumber(pin),
        reinterpret_cast<VoidFuncPtrArg>(userFunc),
        nullptr,
        static_cast<uint8_t>(mode)
    );
}


extern bool attachInterruptArg(uint8_t pin, VoidFuncPtrArg userFunc, void* arg, InterruptMode mode)
{
    return attachInterruptArg_impl(
        pinNumberToIntNumber(pin),
        userFunc,
        arg,
        static_cast<uint8_t>(mode)
    );
}


extern bool detachInterrupt(uint8_t pin)
{ return detachInterrupt_impl( pinNumberToIntNumber(pin) ); }


} // namespace AVR

} // namespace eCxx
