#ifndef _PLATFORM_H_
#define _PLATFORM_H_

#include "driver/uart.h"
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include "sdkconfig.h"
#include "cpu_esp32.h"

#include "esp_task.h"


#define PLATFORM_ALIGNMENT __attribute__((aligned(4)))
#define PLATFORM_ALIGNMENT_PACKED __attribute__((aligned(4),packed))

// Error / status codes
enum
{
  PLATFORM_ERR,
  PLATFORM_OK,
  PLATFORM_UNDERFLOW = -1
};


#if CONFIG_NODE_DEBUG
# define NODE_DBG printf
#else
# define NODE_DBG(...) do{}while(0)
#endif

#if CONFIG_NODE_ERR
# define NODE_ERR printf
#else
# define NODE_ERR(...) do{}while(0)
#endif


int platform_init (void);

// *****************************************************************************
// GPIO subsection

int platform_gpio_exists( unsigned gpio );
int platform_gpio_output_exists( unsigned gpio );


// *****************************************************************************
// UART subsection

#define UART_BUFFER_SIZE    512

// Parity
enum
{
  PLATFORM_UART_PARITY_NONE  = 0,
  PLATFORM_UART_PARITY_EVEN  = 1,
  PLATFORM_UART_PARITY_ODD   = 2,
  PLATFORM_UART_PARITY_MARK  = 3,
  PLATFORM_UART_PARITY_SPACE = 4
};


// Stop bits
enum
{
  PLATFORM_UART_STOPBITS_1   = 1,
  PLATFORM_UART_STOPBITS_2   = 2,
  PLATFORM_UART_STOPBITS_1_5 = 3
};

typedef struct {
  int tx_pin;
  int rx_pin;
  int rts_pin;
  int cts_pin;
  bool tx_inverse;
  bool rx_inverse;
  bool rts_inverse;
  bool cts_inverse;
  int flow_control;
} uart_pins_t;

typedef struct {
  QueueHandle_t queue;
  xTaskHandle taskHandle;
  int receive_rf;
  int error_rf;
  char *line_buffer;
  size_t line_position;
  uint16_t need_len;
  int16_t end_char;
} uart_status_t;

typedef struct {
  unsigned id;
  int type;
  size_t size;
  char* data;
} uart_event_post_t;

// Flow control types (this is a bit mask, one can specify PLATFORM_UART_FLOW_RTS | PLATFORM_UART_FLOW_CTS )
#define PLATFORM_UART_FLOW_NONE               0
#define PLATFORM_UART_FLOW_RTS                1
#define PLATFORM_UART_FLOW_CTS                2

// The platform UART functions
static inline int platform_uart_exists( unsigned id ) { return id < NUM_UART; }
uint32_t platform_uart_setup( unsigned id, uint32_t baud, int databits, int parity, int stopbits, uart_pins_t* pins );
void platform_uart_send_multi( unsigned id, const char *data, size_t len );
void platform_uart_send( unsigned id, uint8_t data );
void platform_uart_flush( unsigned id );
int platform_uart_start( unsigned id );
void platform_uart_stop( unsigned id );


// *****************************************************************************
// Sigma-Delta subsection
int platform_sigma_delta_exists( unsigned channel );

uint8_t platform_sigma_delta_setup( uint8_t channel, uint8_t gpio_num );
uint8_t platform_sigma_delta_close( uint8_t channel );
// PWM emulation not possible, code kept for future reference
//uint8_t platform_sigma_delta_set_pwmduty( uint8_t channel, uint8_t duty );
uint8_t platform_sigma_delta_set_prescale( uint8_t channel, uint8_t prescale );
uint8_t platform_sigma_delta_set_duty( uint8_t channel, int8_t duty );

// *****************************************************************************
// ADC
int platform_adc_exists( uint8_t adc );
int platform_adc_channel_exists( uint8_t adc, uint8_t channel );
uint8_t platform_adc_set_width( uint8_t adc, int bits );
uint8_t platform_adc_setup( uint8_t adc, uint8_t channel, uint8_t attn );
int platform_adc_read( uint8_t adc, uint8_t channel );
int platform_adc_read_hall_sensor( );
enum {
    PLATFORM_ADC_ATTEN_0db   = 0,
    PLATFORM_ADC_ATTEN_2_5db = 1,
    PLATFORM_ADC_ATTEN_6db   = 2,
    PLATFORM_ADC_ATTEN_11db  = 3,
};

// *****************************************************************************
// I2C platform interface

// I2C speed
enum
{
  PLATFORM_I2C_SPEED_SLOW = 100000,
  PLATFORM_I2C_SPEED_FAST = 400000,
  PLATFORM_I2C_SPEED_FASTPLUS = 1000000
};

// I2C direction
enum
{
  PLATFORM_I2C_DIRECTION_TRANSMITTER,
  PLATFORM_I2C_DIRECTION_RECEIVER
};

int platform_i2c_exists( unsigned id );
int platform_i2c_setup( unsigned id, uint8_t sda, uint8_t scl, uint32_t speed );
int platform_i2c_send_start( unsigned id );
int platform_i2c_send_stop( unsigned id );
int platform_i2c_send_address( unsigned id, uint16_t address, int direction, int ack_check_en );
int platform_i2c_send_byte( unsigned id, uint8_t data, int ack_check_en );
// ack_val: 1 = send ACK, 0 = send NACK
int platform_i2c_recv_byte( unsigned id, int ack_val );


// *****************************************************************************
// RMT platform interface

/**
 * @brief Allocate an RMT channel.
 *
 * @param num_mem Number of memory blocks.
 *
 * @return
 *     - Channel number when successful
 *     - -1 if no channel available
 *
 */
int platform_rmt_allocate( uint8_t num_mem );

/**
 * @brief Release a previously allocated RMT channel.
 *
 * @param channel Channel number.
 *
 */
void platform_rmt_release( uint8_t channel );


// *****************************************************************************
// Onewire platform interface

typedef struct {
  unsigned char ROM_NO[8];
  uint8_t LastDiscrepancy;
  uint8_t LastFamilyDiscrepancy;
  uint8_t LastDeviceFlag;
  uint8_t power;
} platform_onewire_bus_t;

int platform_onewire_init( uint8_t gpio_num );
int platform_onewire_reset( uint8_t gpio_num, uint8_t *presence );
int platform_onewire_write_bytes( uint8_t gpio_num, const uint8_t *buf, uint16_t count, bool power );
int platform_onewire_depower( uint8_t gpio_num );
int platform_onewire_read_bytes( uint8_t gpio_num, uint8_t *buf, uint16_t count );
int platform_onewire_depower( uint8_t gpio_num );
void platform_onewire_reset_search( platform_onewire_bus_t *bus );
void platform_onewire_target_search( uint8_t family_code, platform_onewire_bus_t *bus );
uint8_t platform_onewire_search( uint8_t pin, uint8_t *newAddr, platform_onewire_bus_t *bus );
uint8_t platform_onewire_crc8( const uint8_t *addr, uint8_t len );
uint8_t platform_onewire_crc8( const uint8_t *addr, uint8_t len );
bool platform_onewire_check_crc16( const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc );
uint16_t platform_onewire_crc16( const uint8_t* input, uint16_t len, uint16_t crc );

// *****************************************************************************
// DHT platform interface
#define PLATFORM_DHT11_WAKEUP_MS 20
#define PLATFORM_DHT2X_WAKEUP_MS 1

int platform_dht_read( uint8_t gpio_num, uint8_t wakeup_ms, uint8_t *data );


// *****************************************************************************
// WS2812 platform interface

int platform_ws2812_setup( uint8_t gpio_num, uint8_t num_mem, const uint8_t *data, size_t len );
int platform_ws2812_release( void );
int platform_ws2812_send( void );


// Internal flash erase/write functions

uint32_t platform_flash_get_sector_of_address( uint32_t addr );
uint32_t platform_flash_write( const void *from, uint32_t toaddr, uint32_t size );
uint32_t platform_flash_read( void *to, uint32_t fromaddr, uint32_t size );
uint32_t platform_s_flash_write( const void *from, uint32_t toaddr, uint32_t size );
uint32_t platform_s_flash_read( void *to, uint32_t fromaddr, uint32_t size );
uint32_t platform_flash_get_num_sectors(void);
int platform_flash_erase_sector( uint32_t sector_id );


// Internal flash partitions
#define PLATFORM_PARTITION_TYPE_APP     0x00
#define PLATFORM_PARTITION_TYPE_DATA    0x01
#define PLATFORM_PARTITION_TYPE_NODEMCU 0xC2

#define PLATFORM_PARTITION_SUBTYPE_APP_FACTORY 0x00
#define PLATFORM_PARTITION_SUBTYPE_APP_OTA(n)  (0x10+n)
#define PLATFORM_PARTITION_SUBTYPE_APP_TEST    0x20

#define PLATFORM_PARTITION_SUBTYPE_DATA_OTA    0x00
#define PLATFORM_PARTITION_SUBTYPE_DATA_RF     0x01
#define PLATFORM_PARTITION_SUBTYPE_DATA_WIFI   0x02

#define PLATFORM_PARTITION_SUBTYPE_NODEMCU_SPIFFS 0x00

typedef struct {
  uint8_t  label[16];
  uint32_t offs;
  uint32_t size;
  uint8_t  type;
  uint8_t  subtype;
} platform_partition_t;

/**
 * Obtain partition information for the internal flash.
 * @param idx Which partition index to load info for.
 * @param info Buffer to store the info in.
 * @returns True if the partition info was loaded, false if not (e.g. no such
 *   partition idx).
 */
bool platform_partition_info (uint8_t idx, platform_partition_t *info);

/**
 * Appends a partition entry to the partition table, if possible.
 * Intended for auto-creation of a SPIFFS partition.
 * @param info The partition definition to append.
 * @returns True if the partition could be added, false if not.
 */
bool platform_partition_add (const platform_partition_t *info);


// *****************************************************************************
// Helper macros
#define MOD_CHECK_ID( mod, id )\
  if( !platform_ ## mod ## _exists( id ) )\
    return luaL_error( L, #mod" %d does not exist", ( unsigned )id )

#define MOD_CHECK_TIMER( id )\
  if( id == PLATFORM_TIMER_SYS_ID && !platform_timer_sys_available() )\
    return luaL_error( L, "the system timer is not available on this platform" );\
  if( !platform_tmr_exists( id ) )\
    return luaL_error( L, "timer %d does not exist", ( unsigned )id )\

#define MOD_CHECK_RES_ID( mod, id, resmod, resid )\
  if( !platform_ ## mod ## _check_ ## resmod ## _id( id, resid ) )\
    return luaL_error( L, #resmod" %d not valid with " #mod " %d", ( unsigned )resid, ( unsigned )id )



#endif
