#include "AP_HAL.h"

extern const AP_HAL::HAL &hal;

uint32_t AP_HAL::RCOutput::DSHOT_BIT_WIDTH_TICKS = DSHOT_BIT_WIDTH_TICKS_DEFAULT;
uint32_t AP_HAL::RCOutput::DSHOT_BIT_0_TICKS = DSHOT_BIT_0_TICKS_DEFAULT;
uint32_t AP_HAL::RCOutput::DSHOT_BIT_1_TICKS = DSHOT_BIT_1_TICKS_DEFAULT;

// helper function for implementation of get_output_mode_banner
const char* AP_HAL::RCOutput::get_output_mode_string(enum output_mode out_mode) const
{
    // convert mode to string
    switch (out_mode) {
    case MODE_PWM_NONE:
        return "None";
    case MODE_PWM_NORMAL:
        return "PWM";
    case MODE_PWM_ONESHOT:
        return "OneS";
    case MODE_PWM_ONESHOT125:
        return "OS125";
    case MODE_PWM_BRUSHED:
        return "Brush";
    case MODE_PWM_DSHOT150:
        return "DS150";
    case MODE_PWM_DSHOT300:
        return "DS300";
    case MODE_PWM_DSHOT600:
        return "DS600";
    case MODE_PWM_DSHOT1200:
        return "DS1200";
    case MODE_NEOPIXEL:
        return "NeoP";
    case MODE_PROFILED:
        return "ProfiLED";
    }

    // we should never reach here but just in case
    return "Unknown";
}

// convert output mode to string.  helper function for implementation of get_output_mode_banner
void AP_HAL::RCOutput::append_to_banner(char banner_msg[], uint8_t banner_msg_len, output_mode out_mode, uint8_t low_ch, uint8_t high_ch) const
{
    const char* mode_str = get_output_mode_string(out_mode);

    // make copy of banner_msg
    char banner_msg_temp[banner_msg_len];
    memcpy(banner_msg_temp, banner_msg, banner_msg_len);

    if (low_ch == high_ch) {
        // handle single channel case
        hal.util->snprintf(banner_msg, banner_msg_len, "%s %s:%u", banner_msg_temp, mode_str, (unsigned)low_ch);
    } else {
        // the general case
        hal.util->snprintf(banner_msg, banner_msg_len, "%s %s:%u-%u", banner_msg_temp, mode_str, (unsigned)low_ch, (unsigned)high_ch);
    }
}

/*
  true when the output mode is of type dshot
*/
bool AP_HAL::RCOutput::is_dshot_protocol(const enum output_mode mode)
{
    switch (mode) {
    case MODE_PWM_DSHOT150:
    case MODE_PWM_DSHOT300:
    case MODE_PWM_DSHOT600:
    case MODE_PWM_DSHOT1200:
        return true;
    default:
        return false;
    }
}

/*
 * calculate the prescaler required to achieve the desire bitrate
 */
uint32_t AP_HAL::RCOutput::calculate_bitrate_prescaler(uint32_t timer_clock, uint32_t target_frequency, bool is_dshot)
{
    uint32_t prescaler;
    if (is_dshot) {
        // original prescaler calculation from betaflight. bi-dir dshot is incredibly sensitive to the bitrate
        prescaler = uint32_t(lrintf((float) timer_clock / target_frequency + 0.01f) - 1);
    } else {
        // adjust frequency to give an allowed value given the clock, erring on the high side
        prescaler = timer_clock / target_frequency;
        while ((timer_clock / prescaler) < target_frequency && prescaler > 1) {
            prescaler--;
        }
    }

    const uint32_t freq = timer_clock / prescaler;
    // if using dshot then always pick the high value. choosing low seems to not agree with some
    // ESCs despite the fact that BLHeli32 is supposed not to care what the bitrate is
    if (is_dshot) {
        if (freq < target_frequency) {
            prescaler--;
        }
    } else {
        // find the closest value
        const float delta = fabsf(float(freq) - target_frequency);
        if (freq > target_frequency
            && delta > fabsf(float(timer_clock / (prescaler+1)) - target_frequency)) {
            prescaler++;
        } else if (freq < target_frequency
            && delta > fabsf(float(timer_clock / (prescaler-1)) - target_frequency)) {
            prescaler--;
        }
    }

    return prescaler;
}

