#pragma once

#include <stdbool.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

#define IRDA_COMMON_CARRIER_FREQUENCY      38000
#define IRDA_COMMON_DUTY_CYCLE             0.33

/* if we want to see splitted raw signals during brutforce,
 * we have to have RX raw timing delay less than TX */
#define IRDA_RAW_RX_TIMING_DELAY_US        150000
#define IRDA_RAW_TX_TIMING_DELAY_US        180000

typedef struct IrdaDecoderHandler IrdaDecoderHandler;
typedef struct IrdaEncoderHandler IrdaEncoderHandler;

typedef enum {
    IrdaProtocolUnknown = -1,
    IrdaProtocolNEC = 0,
    IrdaProtocolNECext,
    IrdaProtocolNEC42,
    IrdaProtocolNEC42ext,
    IrdaProtocolSamsung32,
    IrdaProtocolRC6,
    IrdaProtocolRC5,
    IrdaProtocolRC5X,
    IrdaProtocolSIRC,
    IrdaProtocolSIRC15,
    IrdaProtocolSIRC20,
    IrdaProtocolMAX,
} IrdaProtocol;

typedef struct {
    IrdaProtocol protocol;
    uint32_t address;
    uint32_t command;
    bool repeat;
} IrdaMessage;

typedef enum {
    IrdaStatusError,
    IrdaStatusOk,
    IrdaStatusDone,
    IrdaStatusReady,
} IrdaStatus;

/**
 * Initialize decoder.
 *
 * \return      returns pointer to IRDA decoder handler if success, otherwise - error.
 */
IrdaDecoderHandler* irda_alloc_decoder(void);

/**
 * Provide to decoder next timing.
 *
 * \param[in]   handler     - handler to IRDA decoders. Should be acquired with \c irda_alloc_decoder().
 * \param[in]   level       - high(true) or low(false) level of input signal to analyze.
 *                          it should alternate every call, otherwise it is an error case,
 *                          and decoder resets its state and start decoding from the start.
 * \param[in]   duration    - duration of steady high/low input signal.
 * \return      if message is ready, returns pointer to decoded message, returns NULL.
 *              Note: ownership of returned ptr belongs to handler. So pointer is valid
 *              up to next irda_free_decoder(), irda_reset_decoder(),
 *              irda_decode(), irda_check_decoder_ready() calls.
 */
const IrdaMessage* irda_decode(IrdaDecoderHandler* handler, bool level, uint32_t duration);

/**
 * Check whether decoder is ready.
 * Functionality is quite similar to irda_decode(), but with no timing providing.
 * Some protocols (e.g. Sony SIRC) has variable payload length, which means we
 * can't recognize end of message right after receiving last bit. That's why
 * application should call to irda_check_decoder_ready() after some timeout to
 * retrieve decoded message, if so.
 *
 * \param[in]   handler     - handler to IRDA decoders. Should be acquired with \c irda_alloc_decoder().
 * \return      if message is ready, returns pointer to decoded message, returns NULL.
 *              Note: ownership of returned ptr belongs to handler. So pointer is valid
 *              up to next irda_free_decoder(), irda_reset_decoder(),
 *              irda_decode(), irda_check_decoder_ready() calls.
 */
const IrdaMessage* irda_check_decoder_ready(IrdaDecoderHandler* handler);

/**
 * Deinitialize decoder and free allocated memory.
 *
 * \param[in]   handler     - handler to IRDA decoders. Should be acquired with \c irda_alloc_decoder().
 */
void irda_free_decoder(IrdaDecoderHandler* handler);

/**
 * Reset IRDA decoder.
 *
 * \param[in]   handler     - handler to IRDA decoders. Should be acquired with \c irda_alloc_decoder().
 */
void irda_reset_decoder(IrdaDecoderHandler* handler);

/**
 * Get protocol name by protocol enum.
 *
 * \param[in]   protocol    - protocol identifier.
 * \return      string to protocol name.
 */
const char* irda_get_protocol_name(IrdaProtocol protocol);

/**
 * Get protocol enum by protocol name.
 *
 * \param[in]   protocol_name   - string to protocol name.
 * \return      protocol identifier.
 */
IrdaProtocol irda_get_protocol_by_name(const char* protocol_name);

/**
 * Get address length by protocol enum.
 *
 * \param[in]   protocol    - protocol identifier.
 * \return      length of address in bits.
 */
uint8_t irda_get_protocol_address_length(IrdaProtocol protocol);

/**
 * Get command length by protocol enum.
 *
 * \param[in]   protocol    - protocol identifier.
 * \return      length of command in bits.
 */
uint8_t irda_get_protocol_command_length(IrdaProtocol protocol);

/**
 * Checks whether protocol valid.
 *
 * \param[in]   protocol    - protocol identifier.
 * \return      true if protocol is valid, false otherwise.
 */
bool irda_is_protocol_valid(IrdaProtocol protocol);

/**
 * Allocate IRDA encoder.
 *
 * \return      encoder handler.
 */
IrdaEncoderHandler* irda_alloc_encoder(void);

/**
 * Free encoder handler previously allocated with \c irda_alloc_encoder().
 *
 * \param[in]   handler     - handler to IRDA encoder. Should be acquired with \c irda_alloc_encoder().
 */
void irda_free_encoder(IrdaEncoderHandler* handler);

/**
 * Encode previously set IRDA message.
 * Usage:
 *  1) alloc with \c irda_alloc_encoder()
 *  2) set message to encode with \c irda_reset_encoder()
 *  3) call for \c irda_encode() to continuously get one at a time timings.
 *  4) when \c irda_encode() returns IrdaStatusDone, it means new message is fully encoded.
 *  5) to encode additional timings, just continue calling \c irda_encode().
 *
 * \param[in]   handler     - handler to IRDA encoder. Should be acquired with \c irda_alloc_encoder().
 * \param[out]  duration    - encoded timing.
 * \param[out]  level       - encoded level.
 *
 * \return      status of encode operation.
 */
IrdaStatus irda_encode(IrdaEncoderHandler* handler, uint32_t* duration, bool* level);

/**
 * Reset IRDA encoder and set new message to encode. If it's not called after receiveing
 * IrdaStatusDone in \c irda_encode(), encoder will encode repeat messages
 * till the end of time.
 *
 * \param[in]   handler     - handler to IRDA encoder. Should be acquired with \c irda_alloc_encoder().
 * \param[in]   message     - message to encode.
 */
void irda_reset_encoder(IrdaEncoderHandler* handler, const IrdaMessage* message);

/**
 * Get PWM frequency value for selected protocol
 *
 * \param[in]   protocol    - protocol to get from PWM frequency
 *
 * \return      frequency
 */
uint32_t irda_get_protocol_frequency(IrdaProtocol protocol);

/**
 * Get PWM duty cycle value for selected protocol
 *
 * \param[in]   protocol    - protocol to get from PWM duty cycle
 *
 * \return      duty cycle
 */
float irda_get_protocol_duty_cycle(IrdaProtocol protocol);

#ifdef __cplusplus
}
#endif