/* Copyright (c) 2023 Skyward Experimental Rocketry
 * Authors: Emilio Corigliano, Alberto Nidasio
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#pragma once

#include <assert.h>
#include <drivers/timer/GeneralPurposeTimer.h>

#include "PWM.h"
#include "utils/Debug.h"

namespace Boardcore
{

/**
 * @brief This class generates a PWM signal for a chosen number of pulses.
 *
 * Two timers are used: PulseTimer and CounterTimer.
 *
 * +------------+  OverflowEvent   UpdateCounter  +--------------+
 * | PulseTimer | ------------------------------> | CounterTimer |
 * |            |                                 |              |
 * |            |                                 |              |
 * | PWM, GATED | <------------------------------ |  ExtClkMode  |
 * +------------+  StopCounter     OverflowEvent  +--------------+
 *
 * As seen in the previous scheme, PulseTimer generates the PWM signal. This
 * is seen both as output on the respective GPIO and as input for the
 * CounterTimer. So, in this way, CounterTimer counts the pulses generated and,
 * when a threshold is passed, the CounterTimer changes its output polarity.
 * Since PulseTimer has as GATED input the CounterTimer, when the N pulses are
 * exceeded, the PulseTimer is stopped and no other cycles are generated. This
 * works because since we disable the PulseTimer, the CounterTimer doesn't
 * exceed the Capture and Compare value set (which corresponds to the wanted
 * pulses).
 *
 * The requirement for this technique to work is that the two timers should have
 * an "internal trigger input" (ITR) connection to each other.
 */
class CountedPWM
{
public:
    /**
     * @brief Constructor that initializes the timers.
     *
     * In particular, the pulseTimer will be used to create the output PWM
     * signal while the counterTimer will just count for the occurred pulses.
     * The PWM signal will be transmitted on the `pulseChannel` channel while
     * the counterTimer signal will be accessible on the `counterChannel`
     * channel (for debug purposes).
     * @param pulseTimer Timer used for the generation of the PWM signal.
     * @param pulseChannel Channel of pulseTimer on which the PWM signal will be
     * generated.
     * @param pulseTriggerSource The corresponding ITR mode of the pulseTimer
     * for having as external trigger input the counterTimer.
     * @param counterTimer Timer used for counting the pulses generated by the
     * PWM.
     * @param counterChannel Channel of pulseTimer on which the counter signal
     * will be visible.
     * @param counterTriggerSource The corresponding ITR mode of the
     * counterTimer for having as external trigger input the pulseTimer.
     */
    CountedPWM(TIM_TypeDef* const pulseTimer,
               TimerUtils::Channel const pulseChannel,
               TimerUtils::TriggerSource const pulseTriggerSource,
               TIM_TypeDef* const counterTimer,
               TimerUtils::Channel const counterChannel,
               TimerUtils::TriggerSource const counterTriggerSource);

    ~CountedPWM();

    void setFrequency(unsigned int pulseFrequency);

    /**
     * @brief Sets the duty cycle for the specified channel.
     *
     * Changes the duty cycle only if the specified value is in the range [0,1].
     *
     * @param dutyCycle Relative duty cycle, ranges from 0 to 1.
     */
    void setDutyCycle(float dutyCycle);

    /**
     * @brief Sets the granularity of the PulseTimer duty cycle. So it sets the
     * Auto Reload Register of the PulseTimer.
     */
    void setDutyCycleResolution(unsigned int dutyCycleResolution);

    /**
     * @brief Triggers the generation of a specific number of PWM periods.
     *
     * Note that the function returns immediately and the PWM signal is
     * generated in background by the timers.
     *
     * @param pulses The number of pulses to generate.
     */
    void generatePulses(uint16_t pulses);

    /**
     * @brief Returns whether the timers are generating the PWM signal or not.
     */
    bool isGenerating();

    /**
     * @brief Allows to update the target periods count while the timers are
     * generating the signal.
     */
    void updateTargetCount(uint16_t pulses);

    /**
     * @brief Allows to get the target periods count while the timers are
     * generating the signal.
     */
    uint16_t getTargetCount();

    /**
     * @brief Allows to update the current periods count while the timers are
     * generating the signal.
     */
    void updateCurrentCount(uint16_t count);

    /**
     * @brief Returns the number of periods counted until now.
     */
    uint16_t getCurrentCount();

    /**
     * @brief Stops the PWM signal generation if it is ongoing.
     */
    void stop();

private:
    // This class is not copyable!
    CountedPWM& operator=(const CountedPWM&) = delete;
    CountedPWM(const CountedPWM& p)          = delete;

    void configureTimers();

    GP16bitTimer pulseTimer;
    TimerUtils::Channel pulseChannel;
    TimerUtils::TriggerSource pulseTriggerSource;

    GP16bitTimer counterTimer;
    TimerUtils::Channel counterChannel;
    TimerUtils::TriggerSource counterTriggerSource;

    unsigned int pulseFrequency = 50;   ///< Frequency of the pulses [Hz]
    float dutyCycle             = 0.5;  ///< DutyCycle of the pulses [%]
    unsigned int dutyCycleResolution =
        20000;  ///< Basically this is the Auto Reload Register of the
                ///< PulseTimer
};

inline void CountedPWM::updateTargetCount(uint16_t pulses)
{
    counterTimer.setCaptureCompareRegister(counterChannel, pulses);
}

inline uint16_t CountedPWM::getTargetCount()
{
    return counterTimer.readCaptureCompareRegister(counterChannel);
}

inline void CountedPWM::updateCurrentCount(uint16_t count)
{
    return counterTimer.setCounter(count);
}

inline uint16_t CountedPWM::getCurrentCount()
{
    return counterTimer.readCounter();
}

inline void CountedPWM::stop()
{
    counterTimer.setCaptureCompareRegister(counterChannel, getCurrentCount());
}

}  // namespace Boardcore