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


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

#include <eCxx/Platform/AVRX/PinMapping.h>


namespace eCxx {

namespace AVR {


//
// Pointer to interrupt handlers for each external interrupts
//
static volatile VoidFuncPtrArg intFunc   [numOfExtInt] = { nullptr };
static void*                   intFuncArg[numOfExtInt] = { nullptr };


//
// Helper functions
//
static inline bool checkIntPin(uint8_t pin)
{
    // For some reason, using switch-case here would make the code compile incorrectly

    if(pin == AVR::EINT0) return true;
    if(pin == AVR::EINT1) return true;
    if(pin == AVR::EINT2) return true;
    if(pin == AVR::EINT3) return true;
    if(pin == AVR::EINT4) return true;
    if(pin == AVR::EINT5) return true;

#if defined(__AVR_ATmega808__) || defined(__AVR_ATmega1608__) || defined(__AVR_ATmega3208__) || defined(__AVR_ATmega4808__)

    // This part is intentionally empty

#elif defined(__AVR_ATmega809__) || defined(__AVR_ATmega1609__) || defined(__AVR_ATmega3209__) || defined(__AVR_ATmega4809__)

    if(pin == AVR::EINT6) return true;
    if(pin == AVR::EINT7) return true;
    if(pin == AVR::EINT8) return true;

#else

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

#endif

    return false;
}


static inline uint8_t intPinToSlotNum(uint8_t pin)
{
    // For some reason, using switch-case here would make the code compile incorrectly

    if(pin == AVR::EINT0) return 0;
    if(pin == AVR::EINT1) return 1;
    if(pin == AVR::EINT2) return 2;
    if(pin == AVR::EINT3) return 3;
    if(pin == AVR::EINT4) return 4;
    if(pin == AVR::EINT5) return 5;

#if defined(__AVR_ATmega808__) || defined(__AVR_ATmega1608__) || defined(__AVR_ATmega3208__) || defined(__AVR_ATmega4808__)

    // This part is intentionally empty

#elif defined(__AVR_ATmega809__) || defined(__AVR_ATmega1609__) || defined(__AVR_ATmega3209__) || defined(__AVR_ATmega4809__)

    if(pin == AVR::EINT6) return 6;
    if(pin == AVR::EINT7) return 7;
    if(pin == AVR::EINT8) return 8;

#else

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

#endif

   return Application::InvalidPin;
}


static inline uint8_t portNumAndBitPosToSlotNum(uint8_t portNum, uint8_t bitPos)
{
    // For some reason, using switch-case here would make the code compile incorrectly

#if defined(__AVR_ATmega808__) || defined(__AVR_ATmega1608__) || defined(__AVR_ATmega3208__) || defined(__AVR_ATmega4808__)

    // Make sure these value pairs match those in "PinMapping.h"
    if(portNum == ECXX_PA && bitPos == 2) return 0;
    if(portNum == ECXX_PA && bitPos == 6) return 1;
    if(portNum == ECXX_PC && bitPos == 2) return 2;
    if(portNum == ECXX_PD && bitPos == 2) return 3;
    if(portNum == ECXX_PD && bitPos == 6) return 4;
    if(portNum == ECXX_PF && bitPos == 2) return 5;

#elif defined(__AVR_ATmega809__) || defined(__AVR_ATmega1609__) || defined(__AVR_ATmega3209__) || defined(__AVR_ATmega4809__)

    // Make sure these value pairs match those in "PinMapping.h"
    if(portNum == ECXX_PA && bitPos == 2) return 0;
    if(portNum == ECXX_PB && bitPos == 2) return 1;
    if(portNum == ECXX_PC && bitPos == 6) return 2;
    if(portNum == ECXX_PD && bitPos == 2) return 3;
    if(portNum == ECXX_PE && bitPos == 2) return 4;
    if(portNum == ECXX_PF && bitPos == 2) return 5;
    if(portNum == ECXX_PA && bitPos == 6) return 6;
    if(portNum == ECXX_PC && bitPos == 2) return 7;
    if(portNum == ECXX_PD && bitPos == 6) return 8;
    if(portNum == ECXX_PF && bitPos == 6) return 9;

#else

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

#endif

    return Application::InvalidPin;
}


static bool attachInterruptArg_impl(uint8_t pin, VoidFuncPtrArg userFunc, void* arg, InterruptMode mode)
{
    // Get and check the slot number
    const uint8_t slotNum = intPinToSlotNum(pin);
    if(slotNum == Application::InvalidPin) return false;

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

    // Get the bit position
    const uint8_t bitPos = pinNumberToBitPosition(pin);

    // Get the port structure and control register
    PORT port = pinNumberToPort(pin);
    REG8 creg = getPinControlRegister(port, bitPos);

    // Clear interrupt setting
    *creg &= ~PORT_ISC_gm;

    // Apply new setting
    switch(mode) {
        case InterruptMode::Rising  : *creg |= PORT_ISC_RISING_gc;    break;
        case InterruptMode::Falling : *creg |= PORT_ISC_FALLING_gc;   break;
        case InterruptMode::Change  : *creg |= PORT_ISC_BOTHEDGES_gc; break;
    }

    // Done
    return true;
}


static bool detachInterrupt_impl(uint8_t pin)
{
    // Get the bit position
    const uint8_t bitPos = pinNumberToBitPosition(pin);

    // Get and check the slot number
    const uint8_t slotNum = intPinToSlotNum(pin);
    if(slotNum == Application::InvalidPin) return false;

    // Get the port structure and control register
    PORT port = pinNumberToPort(pin);
    REG8 creg = getPinControlRegister(port, bitPos);

    // Clear interrupt setting
    *creg &= ~PORT_ISC_gm;

    // clear the user callback
    intFunc   [slotNum] = nullptr;
    intFuncArg[slotNum] = nullptr;

    // Done
    return true;
}


//
// GPIO interrupt vector
//
static void gpio_isr_handler(uint8_t portNum)
{
    // Get the port structure
    PORT port = portNumberToPort(portNum);

    // Copy the interrupt flags
    const uint8_t intFlags = port->INTFLAGS;

    // Iterate through flags
    for(uint8_t p = PIN0_bp; p <= PIN7_bp; ++p) {
        // Skip if the flag is no raised
        if( !( intFlags & _BV(p) ) ) continue;
        // Get and check the slot number
        const uint8_t slotNum = portNumAndBitPosToSlotNum(portNum, p);
        if(slotNum == Application::InvalidPin) continue;
        // Call the user callback as needed
        if( !intFunc[slotNum] ) continue;
        (*intFunc[slotNum])(intFuncArg[slotNum]);
    }

    // Clear the interrupt flags
    port->INTFLAGS = intFlags;
}


#define IMPLEMENT_ISR(VECTOR_NAME, PORT) ISR(VECTOR_NAME) { gpio_isr_handler(PORT); }

IMPLEMENT_ISR(PORTA_PORT_vect, ECXX_PA)
IMPLEMENT_ISR(PORTC_PORT_vect, ECXX_PC)
IMPLEMENT_ISR(PORTD_PORT_vect, ECXX_PD)
IMPLEMENT_ISR(PORTF_PORT_vect, ECXX_PF)

#if defined(__AVR_ATmega808__) || defined(__AVR_ATmega1608__) || defined(__AVR_ATmega3208__) || defined(__AVR_ATmega4808__)

// This part is intentionally empty

#elif defined(__AVR_ATmega809__) || defined(__AVR_ATmega1609__) || defined(__AVR_ATmega3209__) || defined(__AVR_ATmega4809__)

IMPLEMENT_ISR(PORTB_PORT_vect, ECXX_PB)
IMPLEMENT_ISR(PORTE_PORT_vect, ECXX_PE)

#else

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

#endif


//
// Interrupt-related functions
//
extern bool attachInterrupt(uint8_t pin, VoidFuncPtr userFunc, InterruptMode mode)
{
    if( !checkIntPin(pin) ) return false;

    return attachInterruptArg_impl(
        pin,
        reinterpret_cast<VoidFuncPtrArg>(userFunc),
        nullptr,
        mode
    );
}


extern bool attachInterruptArg(uint8_t pin, VoidFuncPtrArg userFunc, void* arg, InterruptMode mode)
{
    if( !checkIntPin(pin) ) return false;

    return attachInterruptArg_impl(
        pin,
        userFunc,
        arg,
        mode
    );
}


extern bool detachInterrupt(uint8_t pin)
{
    if( !checkIntPin(pin) ) return false;

    return detachInterrupt_impl(pin);
}


} // namespace AVR

} // namespace eCxx
