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


//
// A simple AVR event system wrapper
//
bool EventSystem::bindChannelToGenerator(uint8_t channel, Generator generator)
{
    // Check the channel number
    if(channel >= EventSystemChannels) return false;

    // Bind channel to generator
    if(generator != Generator::UnbindGenerator) {
        *(&EVSYS.CHANNEL0 + channel) = static_cast<uint8_t>(generator);
    }

    // Unbind channel from generator
    else {
        *(&EVSYS.CHANNEL0 + channel) = 0;
    }

    // Done
    return true;
}


bool EventSystem::bindUserToChannel(User user, uint8_t channel)
{
    // Check the channel number
    if(channel >= EventSystemChannels && channel != UnbindChannel) return false;

    // Adjust the user as needed
    const uint8_t stdEnd = static_cast<uint8_t>(User::__STD_END__);
    const uint8_t altBeg = static_cast<uint8_t>(User::__ALT_BEG__);
    const uint8_t altEnd = static_cast<uint8_t>(User::__ALT_END__);

    uint8_t _user = static_cast<uint8_t>(user);

    if(_user > stdEnd) {
        if(_user < altBeg || _user > altEnd) return false;
        _user = _user - altBeg + static_cast<uint8_t>(User::EVOUTA_Def);
    }

    // Bind user to channel
    if(channel != UnbindChannel) {
        // Configure pin as output first
        if( !configureOutputPin(user, true) ) return false;
        // Bind user to channel
        *(&EVSYS.USERCCLLUT0A + _user) = channel + 1;
    }

    // Unbind user from channel
    else {
        // Configure pin as input first
        if( !configureOutputPin(user, false) ) return false;
        // Unbind user
        *(&EVSYS.USERCCLLUT0A + _user) = 0;
    }

    // Done
    return true;
}


void EventSystem::strobeChannel(uint8_t channel)
{
    // Check the channel number
    if(channel >= EventSystemChannels) return;

    // Strobe the channel
    EVSYS.STROBE |= _BV(channel);
}


bool EventSystem::configureOutputPin(User user, bool enabled)
{
    // A constant to indicate that the pin will be unused
    static constexpr uint8_t PinUnused = AVR::InvalidPin - 1;

    // Get and check the pin number
    uint8_t pin = AVR::InvalidPin;

    switch(user) {
        case User::EVOUTA_Def : pin = AVR::EvOutA_Def; break;
        case User::EVOUTB_Def : pin = AVR::EvOutB_Def; break;
        case User::EVOUTC_Def : pin = AVR::EvOutC_Def; break;
        case User::EVOUTD_Def : pin = AVR::EvOutD_Def; break;
        case User::EVOUTE_Def : pin = AVR::EvOutE_Def; break;
        case User::EVOUTF_Def : pin = AVR::EvOutF_Def; break;
        case User::EVOUTA_Alt : pin = AVR::EvOutA_Alt; break;
        case User::EVOUTB_Alt : pin = AVR::EvOutB_Alt; break;
        case User::EVOUTC_Alt : pin = AVR::EvOutC_Alt; break;
        case User::EVOUTD_Alt : pin = AVR::EvOutD_Alt; break;
        case User::EVOUTE_Alt : pin = AVR::EvOutE_Alt; break;
        case User::EVOUTF_Alt : pin = AVR::EvOutF_Alt; break;
        default               : pin = PinUnused;       break;
    }

    if(pin == AVR::InvalidPin) return false;

    // Configure as needed
    if(pin != PinUnused) Application::setPinMode(
        pin, enabled ? Application::PinMode::Output : Application::PinMode::Input
    );

    // Done
    return true;
}


} // namespace eCxx
