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


#ifndef INCLUDE_ECXX_PLATFORM_ANALOG_JOYSTICK_TPP
#error "this file cannot be included directly"
#endif
#undef INCLUDE_ECXX_PLATFORM_ANALOG_JOYSTICK_TPP


#ifndef ECXX_PLATFORM_ANALOG_JOYSTICK_TPP
#define ECXX_PLATFORM_ANALOG_JOYSTICK_TPP


//
// A simple reader class for two-axis analog joystick with one switch
//
template <typename ADCDriverT, int8_t ToMin, int8_t ToMax, uint16_t FromMin, uint16_t FromMax>
inline AnalogJoystick<ADCDriverT, ToMin, ToMax, FromMin, FromMax>::AnalogJoystick() noexcept
: _switchPin(ADCDriverT::InvalidPin)
{}


template <typename ADCDriverT, int8_t ToMin, int8_t ToMax, uint16_t FromMin, uint16_t FromMax>
inline bool AnalogJoystick<ADCDriverT, ToMin, ToMax, FromMin, FromMax>::begin(uint8_t axis1Pin, uint8_t axis2Pin, uint8_t switchPin, bool reverseAxis1, bool reverseAxis2, ActiveState switchAS) noexcept
{
    // Check the specifications
    if(axis1Pin < ADCDriverT::ADCPinMin || axis1Pin > ADCDriverT::ADCPinMax) return false;
    if(axis2Pin < ADCDriverT::ADCPinMin || axis2Pin > ADCDriverT::ADCPinMax) return false;

    // Copy the specifications
    _axis1Pin  = axis1Pin;
    _axis2Pin  = axis2Pin;
    _switchPin = switchPin;
    _revAxis1  = reverseAxis1;
    _revAxis2  = reverseAxis2;
    _switchAS  = switchAS;

    // Reset the positions
    _posA1 = 0;
    _posA2 = 0;

    _posA1Max = static_cast<uint16_t>(-1);
    _posA2Max = static_cast<uint16_t>(-1);

    // Set pin modes
    const typename ADCDriverT::PinMode switchPinMode = (_switchAS== ActiveState::Low)
                                                     ? ADCDriverT::PinMode::InputPullUp
                                                     : ADCDriverT::PinMode::Input;

    ADCDriverT::setPinMode(_axis1Pin,  ADCDriverT::PinMode::Input);
    ADCDriverT::setPinMode(_axis2Pin,  ADCDriverT::PinMode::Input);
    ADCDriverT::setPinMode(_switchPin, switchPinMode             );

    // Done
    return true;
}


template <typename ADCDriverT, int8_t ToMin, int8_t ToMax, uint16_t FromMin, uint16_t FromMax>
inline void AnalogJoystick<ADCDriverT, ToMin, ToMax, FromMin, FromMax>::end() noexcept
{
    ADCDriverT::setPinMode(_axis1Pin,  ADCDriverT::PinMode::Input);
    ADCDriverT::setPinMode(_axis2Pin,  ADCDriverT::PinMode::Input);
    ADCDriverT::setPinMode(_switchPin, ADCDriverT::PinMode::Input);

    _switchPin == ADCDriverT::InvalidPin;
}


template <typename ADCDriverT, int8_t ToMin, int8_t ToMax, uint16_t FromMin, uint16_t FromMax>
inline void AnalogJoystick<ADCDriverT, ToMin, ToMax, FromMin, FromMax>::setMaxCoordinate(uint16_t maxA1, uint16_t maxA2)
{
    _posA1Max = maxA1;
    _posA2Max = maxA2;
}


template <typename ADCDriverT, int8_t ToMin, int8_t ToMax, uint16_t FromMin, uint16_t FromMax>
inline bool AnalogJoystick<ADCDriverT, ToMin, ToMax, FromMin, FromMax>::readRaw(int16_t& axis1Val, int16_t& axis2Val, bool& switchVal) noexcept
{
    // Read the values
    if( !readRaw_impl() ) return false;

    // Copy the values
    axis1Val  = _axis1Val;
    axis2Val  = _axis2Val;
    switchVal = _switchVal;

    // Done
    return true;
}


template <typename ADCDriverT, int8_t ToMin, int8_t ToMax, uint16_t FromMin, uint16_t FromMax>
inline bool AnalogJoystick<ADCDriverT, ToMin, ToMax, FromMin, FromMax>::readCoordinates(uint16_t& axis1Val, uint16_t& axis2Val, bool& switchVal) noexcept
{
    // Read the values
    if( !readRaw_impl() ) return false;

    // Update the coordinates
    if(_axis1Val < 0) {
        if( _posA1 <= static_cast<uint16_t>( eCxx::abs(_axis1Val) ) ) _posA1  = 0;
        else                                                          _posA1 += _axis1Val;

    }
    else if(_axis1Val > 0) {
        if( (_posA1Max - _posA1) <= static_cast<uint16_t>( _axis1Val ) ) _posA1  = _posA1Max;
        else                                                             _posA1 += _axis1Val;
    }

    if(_axis2Val < 0) {
        if( _posA2 <= static_cast<uint16_t>( eCxx::abs(_axis2Val) ) ) _posA2  = 0;
        else                                                          _posA2 += _axis2Val;
    }
    else if(_axis2Val > 0) {
        if( (_posA2Max - _posA2) <= static_cast<uint16_t>( _axis2Val ) ) _posA2  = _posA2Max;
        else                                                             _posA2 += _axis2Val;
    }

    // Copy the values
    axis1Val  = _posA1;
    axis2Val  = _posA2;
    switchVal = _switchVal;

    // Done
    return true;
}


template <typename ADCDriverT, int8_t ToMin, int8_t ToMax, uint16_t FromMin, uint16_t FromMax>
inline bool AnalogJoystick<ADCDriverT, ToMin, ToMax, FromMin, FromMax>::readRaw_impl() noexcept
{
    // Check if it has not been initialized
    if(_switchPin == ADCDriverT::InvalidPin) return false;

    // Read and scale axis 1
    const int16_t a1FMin = _revAxis1 ? MapFromMax : MapFromMin;
    const int16_t a1FMax = _revAxis1 ? MapFromMin : MapFromMax;

    _axis1Val = eCxx::mapValue<int16_t, true>(
                    ADCDriverT::analogRead(_axis1Pin),
                    a1FMin, a1FMax, MapToMin, MapToMax
                );

    // Read and scale axis 2
    const int16_t a2FMin = _revAxis2 ? MapFromMax : MapFromMin;
    const int16_t a2FMax = _revAxis2 ? MapFromMin : MapFromMax;

    _axis2Val = eCxx::mapValue<int16_t, true>(
                    ADCDriverT::analogRead(_axis2Pin),
                    a2FMin, a2FMax, MapToMin, MapToMax
                );

    // Read the switch
    _switchVal = ADCDriverT::digitalRead(_switchPin);
    if(_switchAS == ActiveState::Low) _switchVal = !_switchVal;

    // Done
    return true;
}


#endif
