#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>

#include "driver/mcpwm_etm.h"
#include "driver/mcpwm_types.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/mcpwm_prelude.h"
#include "driver/gpio.h"

//#include "driver/mcpwm.h"
#include "hal/mcpwm_types.h"
#include "soc/mcpwm_reg.h"
#include "soc/mcpwm_struct.h"

// #define CONFIG_EXAMPLE_SYNC_FROM_GPIO 1
// #define GPIO_CARRIER_OUT 36

#define EXAMPLE_TIMER_RESOLUTION_HZ 1000000  // 1MHz, 1us per tick
#define EXAMPLE_TIMER_PERIOD        700      // 1000 ticks, 1ms
#define EXAMPLE_GEN_GPIO0           0
#define EXAMPLE_GEN_GPIO1           2
#define EXAMPLE_GEN_GPIO2           4
#define EXAMPLE_SYNC_GPIO           5

static const char *TAG = "MCPWM"; 

static void example_setup_sync_strategy(mcpwm_timer_handle_t timers[])
{
    //    +----GPIO----+
    //    |     |      |
    //    |     |      |
    //    v     v      v
    // timer0 timer1 timer2
    gpio_config_t sync_gpio_conf = {
        .mode = GPIO_MODE_INPUT,
        .pin_bit_mask = BIT(EXAMPLE_SYNC_GPIO),
    };
    ESP_ERROR_CHECK(gpio_config(&sync_gpio_conf));

    ESP_LOGI(TAG, "Create GPIO sync source");
    mcpwm_sync_handle_t gpio_sync_source = NULL;
    mcpwm_gpio_sync_src_config_t gpio_sync_config = {
        .group_id = 0,  // GPIO fault should be in the same group of the above timers
        .gpio_num = EXAMPLE_SYNC_GPIO,
        .flags.pull_up = true,
        .flags.active_neg = true,  // by default, a posedge pulse can trigger a sync event
    };
    ESP_ERROR_CHECK(mcpwm_new_gpio_sync_src(&gpio_sync_config, &gpio_sync_source));

    ESP_LOGI(TAG, "Set timers to sync on the GPIO");
    mcpwm_timer_sync_phase_config_t sync_phase_config = {
        .count_value = 0,
        .direction = MCPWM_TIMER_DIRECTION_UP,
        .sync_src = gpio_sync_source,
    };
    for (int i = 0; i < 3; i++) {
        ESP_ERROR_CHECK(mcpwm_timer_set_phase_on_sync(timers[i], &sync_phase_config));
    }
}

void app_main(void)
{
    ESP_LOGI(TAG, "Create timers");
    mcpwm_timer_handle_t timers[3];
    mcpwm_timer_config_t timer_config = {
        .clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
        .group_id = 0,
        .resolution_hz = EXAMPLE_TIMER_RESOLUTION_HZ,
        .period_ticks = EXAMPLE_TIMER_PERIOD,
        .count_mode = MCPWM_TIMER_COUNT_MODE_UP,
    };
    for (int i = 0; i < 3; i++) {
        ESP_ERROR_CHECK(mcpwm_new_timer(&timer_config, &timers[i]));
    }

    ESP_LOGI(TAG, "Create operators");
    mcpwm_oper_handle_t operators[3];
    mcpwm_operator_config_t operator_config = {
        .group_id = 0, // operator should be in the same group of the above timers
    };
    for (int i = 0; i < 3; ++i) {
        ESP_ERROR_CHECK(mcpwm_new_operator(&operator_config, &operators[i]));
    }

    ESP_LOGI(TAG, "Connect timers and operators with each other");
    for (int i = 0; i < 3; i++) {
        ESP_ERROR_CHECK(mcpwm_operator_connect_timer(operators[i], timers[i]));
    }

    ESP_LOGI(TAG, "Create comparators");
    mcpwm_cmpr_handle_t comparators[3];
    mcpwm_comparator_config_t compare_config = {
        .flags.update_cmp_on_tez = true,
    };
    for (int i = 0; i < 3; i++) {
        ESP_ERROR_CHECK(mcpwm_new_comparator(operators[i], &compare_config, &comparators[i]));
        // init compare for each comparator
        ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparators[i], 400));
//        mcpwm_comparator_new_etm_event(mcpwm_cmpr_handle_t cmpr, const mcpwm_cmpr_etm_event_config_t *config, esp_etm_event_handle_t *out_event) ?
    }

    ESP_LOGI(TAG, "Create generators");
    mcpwm_gen_handle_t generators[3];
    const int gen_gpios[3] = {EXAMPLE_GEN_GPIO0, EXAMPLE_GEN_GPIO1, EXAMPLE_GEN_GPIO2};
    mcpwm_generator_config_t gen_config = {};
    for (int i = 0; i < 3; i++) {
        gen_config.gen_gpio_num = gen_gpios[i];
        ESP_ERROR_CHECK(mcpwm_new_generator(operators[i], &gen_config, &generators[i]));
    }

    ESP_LOGI(TAG, "Set generator actions on timer and compare event");
    for (int i = 0; i < 3; i++) {
		// MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_DIRECTION_DOWN
		// MCPWM_TIMER_EVENT_EMPTY, MCPWM_TIMER_EVENT_FULL
		// MCPWM_GEN_ACTION_HIGH, MCPWM_GEN_ACTION_LOW, MCPWM_GEN_ACTION_KEEP, MCPWM_GEN_ACTION_TOGGLE
        ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(generators[i],
                                                                  // when the timer value is zero, and is counting up, set output to high
                                                                  MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH)));
        // MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_DIRECTION_DOWN
        // OPERATOR ID
        // MCPWM_GEN_ACTION_HIGH, MCPWM_GEN_ACTION_LOW, MCPWM_GEN_ACTION_KEEP, MCPWM_GEN_ACTION_TOGGLE
        ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(generators[i],
                                                                    // when compare event happens, and timer is counting up, set output to low
                                                                    MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, comparators[i], MCPWM_GEN_ACTION_LOW)));                                                              
    }

//    ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(generators[0],
//                    MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, comparators[0], MCPWM_GEN_ACTION_HIGH),
//                    MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, comparators[1], MCPWM_GEN_ACTION_LOW),
//                    MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
//    ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_timer_event(generators[1],
//                    MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_TOGGLE),
//                    MCPWM_GEN_TIMER_EVENT_ACTION_END()));
                    
    // Sync event?
    // mcpwm_generator_set_action_on_sync_event() !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!


// OLD SOLUTION - BEGIN
//    // 3. Nastavení nosné vlny (carrier)
//    mcpwm_carrier_config_t carrier_config;
//    carrier_config.carrier_period = 2;           // Frekvence nosné vlny: 20 kHz
//    carrier_config.carrier_duty = 50;             // Střída nosné vlny: 50%
//    // carrier_config. = MCPWM_SELECT_CLK_APB;
//    carrier_config.carrier_ivt_mode = MCPWM_CARRIER_OUT_IVT_DIS;
//    mcpwm_carrier_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &carrier_config);
//
//    // 4. Povolení nosné vlny
//    mcpwm_carrier_enable(MCPWM_UNIT_0, MCPWM_TIMER_0);
// OLD SOLUTION - END
    
    //* * * * * * * * * * * * * * * * * * * * * ACTIVATE CARRIER BLOCK * * * * * * * * * * * * * * * * * * * *//
    /////////////////////////////// Add Carrier Filling for first generator ////////////////////////////////////
    ESP_LOGI(TAG, "Apply carrier filling for GPIO_CARRIER_OUT");
    mcpwm_carrier_config_t carrier_config = {
        .clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
        .first_pulse_duration_us = 1,  					// First pulse width 1 us
        .frequency_hz = EXAMPLE_TIMER_RESOLUTION_HZ,
        .duty_cycle = 0.5,               				// 50% duty cycle
        .flags.invert_before_modulate = true
    };
    ESP_ERROR_CHECK(mcpwm_operator_apply_carrier(operators[0], &carrier_config));
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

    ESP_LOGI(TAG, "Setup sync strategy");
    example_setup_sync_strategy(timers);

    ESP_LOGI(TAG, "Start timers");
    for (int i = 0; i < 3; i++) {
        ESP_ERROR_CHECK(mcpwm_timer_enable(timers[i]));
        ESP_ERROR_CHECK(mcpwm_timer_start_stop(timers[i], MCPWM_TIMER_START_NO_STOP));
        vTaskDelay(pdMS_TO_TICKS(1));
    }

    while (1) {
        ESP_LOGI(TAG, "MCPWM running with carrier filling...");
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}