/*
 * 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"


namespace eCxx {


//
// Analog comparator user callback function
//
static eCxx::VoidCallback acIntFunc = nullptr;


//
// A simple AVR analog comparator wrapper
//
AnalogComparatorLite::AnalogComparatorLite()
: _initialized(false)
{ reset(); }


bool AnalogComparatorLite::begin()
{
    // Simply exit if the analog comparator is already enabled
    if(_initialized) return false;

    // A constant to indicate that the pin will be unused
    static constexpr uint8_t PinUnused = AVR::InvalidPin - 1;

    // Get and check the pins
    uint8_t pinp = AVR::InvalidPin;
    switch(inputp) {
        case InputP::AIN0    : pinp = AVR::AC_InPos; break;
        case InputP::Bandgap : pinp = PinUnused;     break;
    }
    if(pinp == AVR::InvalidPin) return false;

    uint8_t pinn = AVR::InvalidPin;
    switch(inputn) {
        case InputN::AIN1    : pinn = AVR::AC_InNeg; break;
        case InputN::Bandgap : pinn = PinUnused;     break;
        case InputN::ADCMux  : pinn = PinUnused;     break;
    }
    if(pinn == AVR::InvalidPin) return false;

#if !defined(__AVR_ATmega16U4__) && !defined(__AVR_ATmega32U4__)
    if(inputn == InputN::Bandgap) return false;
#endif

    // Check the ADC multiplexer
    if(inputn == InputN::ADCMux) {
#ifdef MUX5
        if(adcMux > 15) return false;
#else
        if(adcMux > 7) return false;
#endif
    }

    // Configure the positive input pin
    if(pinp != PinUnused) {
        Application::setPinMode(pinp, Application::PinMode::Input);
    }

    // Configure the negative input pin
    if(pinn != PinUnused) {
        Application::setPinMode(pinn, Application::PinMode::Input);
    }

    // Configure the positive input
    ACSR = ( (inputp == InputP::Bandgap) ? _BV(ACBG) : 0x00 );

    // Configure the negative input
    if(inputn == InputN::ADCMux) {
        // Disable the ADC
        ADCSRA &= ~_BV(ADEN);
        // Enable the analog comparator multiplexer
#ifdef SFIOR
        SFIOR  |= _BV(ACME);
#else
        ADCSRB |= _BV(ACME);
#endif
        // Set the ADC channel
#ifdef MUX5
        if(adcMux > 7) ADCSRB |=  _BV(MUX5);
        else           ADCSRB &= ~_BV(MUX5);
#endif
        ADMUX &= 0b11111000;
        ADMUX |= (adcMux & 7);
    }

    // Clear the interrupt flag
    ACSR |= _BV(ACI);

    // Enable interrupt as needed
    if(acIntFunc) ACSR |= _BV(ACIE);

    // Set flag
    _initialized = true;

    // Done
    return true;
}


bool AnalogComparatorLite::end()
{
    // Simply exit if the analog comparator is currently disabled
    if(!_initialized) return false;

    // Disable interrupt
    ACSR &= ~_BV(ACIE);

    // Disable the negative input
    if(inputn == InputN::ADCMux) {
        // Disable the analog comparator multiplexer
#ifdef SFIOR
        SFIOR  &= ~_BV(ACME);
#else
        ADCSRB &= ~_BV(ACME);
#endif
        // Enable the ADC
        ADCSRA |= _BV(ADEN);
    }

    // Reset flag
    _initialized = false;

    // Done
    return true;
}


void AnalogComparatorLite::reset()
{
    end();

    inputp = InputP::AIN0;
#if !defined(__AVR_ATmega16U4__) && !defined(__AVR_ATmega32U4__)
    inputn = InputN::AIN1;
#else
    inputn = InputN::Bandgap;
#endif
    adcMux = 0;
}


bool AnalogComparatorLite::getOutStatus()
{ return ACSR & _BV(ACO); }


void AnalogComparatorLite::attachInterrupt(eCxx::VoidCallback userFunc, Application::InterruptMode mode)
{
    ACSR &= ~_BV(ACIE );
    ACSR &= ~_BV(ACIS1);
    ACSR &= ~_BV(ACIS0);

    acIntFunc = userFunc;

    if(mode != Application::InterruptMode::Change) ACSR |= _BV(ACIS1);
    if(mode == Application::InterruptMode::Rising) ACSR |= _BV(ACIS0);

    ACSR |= _BV(ACI );
    ACSR |= _BV(ACIE);
}


void AnalogComparatorLite::detachInterrupt()
{
    ACSR &= ~_BV(ACIE);

    acIntFunc = nullptr;
}


//
// Analog comparator interrupt vector
//
#if defined(ANA_COMP_vect)
ISR(ANA_COMP_vect)
#elif defined(ANALOG_COMP_vect)
ISR(ANALOG_COMP_vect)
#else
#error "unsupported 'Analog Comparator' interrupt vector name"
#endif
{
    // Call the user callback if specified
    if(acIntFunc) (*acIntFunc)();
}


} // namespace eCxx
