#include <stdio.h>
#include <stdlib.h>
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "esp_system.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"

// Define pins for the RGB LEDs
#define RED_PIN_1 35
#define GREEN_PIN_1 32
#define BLUE_PIN_1 25

#define RED_PIN_2 26
#define GREEN_PIN_2 27
#define BLUE_PIN_2 14

#define RED_PIN_3 19
#define GREEN_PIN_3 18
#define BLUE_PIN_3 5

#define IR_SENSOR_PIN 12
#define TRIG_PIN 33
#define ECHO_PIN 34

#define LEDC_BASE_FREQ     5000

#define LEDC_CHANNEL_0 0
#define LEDC_CHANNEL_1 1
#define LEDC_CHANNEL_2 2
#define LEDC_CHANNEL_3 3
#define LEDC_CHANNEL_4 4
#define LEDC_CHANNEL_5 5
#define LEDC_CHANNEL_6 6
#define LEDC_CHANNEL_7 7
#define LEDC_CHANNEL_8 8

static const char *TAG = "main";

// LED controller class
class RGBLEDController {
public:
    RGBLEDController() {
        setup();
    }

    void setLEDColor(int channelRed, int channelGreen, int channelBlue, int r, int g, int b) {
        ledc_set_duty(LEDC_LOW_SPEED_MODE, channelRed, r * 32); // Convert 0-255 range to 0-255 (8-bit resolution, 32 levels per color)
        ledc_update_duty(LEDC_LOW_SPEED_MODE, channelRed);

        ledc_set_duty(LEDC_LOW_SPEED_MODE, channelGreen, g * 32);
        ledc_update_duty(LEDC_LOW_SPEED_MODE, channelGreen);

        ledc_set_duty(LEDC_LOW_SPEED_MODE, channelBlue, b * 32);
        ledc_update_duty(LEDC_LOW_SPEED_MODE, channelBlue);
    }

    void setAllLEDs(int r1, int g1, int b1, int r2, int g2, int b2, int r3, int g3, int b3) {
        setLEDColor(LEDC_CHANNEL_0, LEDC_CHANNEL_1, LEDC_CHANNEL_2, r1, g1, b1);
        setLEDColor(LEDC_CHANNEL_3, LEDC_CHANNEL_4, LEDC_CHANNEL_5, r2, g2, b2);
        setLEDColor(LEDC_CHANNEL_6, LEDC_CHANNEL_7, LEDC_CHANNEL_8, r3, g3, b3);
    }

private:
    void setup() {
        ESP_LOGI(TAG, "Setup complete");

        // Configure IR Sensor Pin
        gpio_set_direction(IR_SENSOR_PIN, GPIO_MODE_INPUT);

        // Configure TRIG Pin
        gpio_set_direction(TRIG_PIN, GPIO_MODE_OUTPUT);

        // Configure ECHO Pin
        gpio_set_direction(ECHO_PIN, GPIO_MODE_INPUT);

        // Initialize the LEDC timer
        ledc_timer_config_t ledc_timer = {
            .speed_mode = LEDC_LOW_SPEED_MODE,
            .timer_num = LEDC_TIMER_0,
            .duty_resolution = LEDC_TIMER_8_BIT,
            .freq_hz = LEDC_BASE_FREQ,
            .clk_cfg = LEDC_AUTO_CLK,
        };
        ledc_timer_config(&ledc_timer);

        // Define LEDC channel configurations
        ledc_channel_config_t ledc_channel_configs[] = {
            { .speed_mode = LEDC_LOW_SPEED_MODE, .channel = LEDC_CHANNEL_0, .gpio_num = RED_PIN_1, .duty = 0, .hpoint = 0, .timer_sel = LEDC_TIMER_0 },
            { .speed_mode = LEDC_LOW_SPEED_MODE, .channel = LEDC_CHANNEL_1, .gpio_num = GREEN_PIN_1, .duty = 0, .hpoint = 0, .timer_sel = LEDC_TIMER_0 },
            { .speed_mode = LEDC_LOW_SPEED_MODE, .channel = LEDC_CHANNEL_2, .gpio_num = BLUE_PIN_1, .duty = 0, .hpoint = 0, .timer_sel = LEDC_TIMER_0 },
            { .speed_mode = LEDC_LOW_SPEED_MODE, .channel = LEDC_CHANNEL_3, .gpio_num = RED_PIN_2, .duty = 0, .hpoint = 0, .timer_sel = LEDC_TIMER_0 },
            { .speed_mode = LEDC_LOW_SPEED_MODE, .channel = LEDC_CHANNEL_4, .gpio_num = GREEN_PIN_2, .duty = 0, .hpoint = 0, .timer_sel = LEDC_TIMER_0 },
            { .speed_mode = LEDC_LOW_SPEED_MODE, .channel = LEDC_CHANNEL_5, .gpio_num = BLUE_PIN_2, .duty = 0, .hpoint = 0, .timer_sel = LEDC_TIMER_0 },
            { .speed_mode = LEDC_LOW_SPEED_MODE, .channel = LEDC_CHANNEL_6, .gpio_num = RED_PIN_3, .duty = 0, .hpoint = 0, .timer_sel = LEDC_TIMER_0 },
            { .speed_mode = LEDC_LOW_SPEED_MODE, .channel = LEDC_CHANNEL_7, .gpio_num = GREEN_PIN_3, .duty = 0, .hpoint = 0, .timer_sel = LEDC_TIMER_0 },
            { .speed_mode = LEDC_LOW_SPEED_MODE, .channel = LEDC_CHANNEL_8, .gpio_num = BLUE_PIN_3, .duty = 0, .hpoint = 0, .timer_sel = LEDC_TIMER_0 },
        };

        // Configure LEDC channels
        for (int i = 0; i < sizeof(ledc_channel_configs) / sizeof(ledc_channel_config_t); i++) {
            ledc_channel_config(&ledc_channel_configs[i]);
        }
    }
};

// Function to read the distance from the ultrasonic sensor
long readUltrasonicDistance() {
    gpio_set_level(TRIG_PIN, 0);
    esp_rom_delay_us(2);
    gpio_set_level(TRIG_PIN, 1);
    esp_rom_delay_us(10);
    gpio_set_level(TRIG_PIN, 0);

    int64_t start_time = esp_timer_get_time();
    while (gpio_get_level(ECHO_PIN) == 0) {
        if (esp_timer_get_time() - start_time > 10000) {
            return -1;  // Timeout
        }
    }

    start_time = esp_timer_get_time();
    while (gpio_get_level(ECHO_PIN) == 1) {
        if (esp_timer_get_time() - start_time > 10000) {
            return -1;  // Timeout
        }
    }

    long duration = esp_timer_get_time() - start_time;
    long distance = duration / 58.2;  // Convert to centimeters

    ESP_LOGI(TAG, "Duration: %ld microseconds", duration);
    ESP_LOGI(TAG, "Distance: %ld cm", distance);
    return distance;
}

// Main application function
extern "C" void app_main() {
    RGBLEDController ledController;

    while (true) {
        if (gpio_get_level(IR_SENSOR_PIN) == 1) {
            long distance = readUltrasonicDistance();

            if (distance == -1) {
                ESP_LOGE(TAG, "Error reading distance");
                continue;
            }

            if (distance == 0) {
                ESP_LOGI(TAG, "All white LEDs");
                ledController.setAllLEDs(255, 255, 255, 255, 255, 255, 255, 255, 255);
            } else if (distance >= 1 && distance <= 3) {
                ESP_LOGI(TAG, "First pair Red");
                ledController.setLEDColor(LEDC_CHANNEL_0, LEDC_CHANNEL_1, LEDC_CHANNEL_2, 255, 0, 0); // First pair Red
                ledController.setLEDColor(LEDC_CHANNEL_3, LEDC_CHANNEL_4, LEDC_CHANNEL_5, 255, 0, 0); // Second pair Red
                ledController.setLEDColor(LEDC_CHANNEL_6, LEDC_CHANNEL_7, LEDC_CHANNEL_8, 0, 0, 0);
            } else if (distance >= 4 && distance <= 6) {
                ESP_LOGI(TAG, "Second pair Green");
                ledController.setLEDColor(LEDC_CHANNEL_0, LEDC_CHANNEL_1, LEDC_CHANNEL_2, 0, 0, 0);
                ledController.setLEDColor(LEDC_CHANNEL_3, LEDC_CHANNEL_4, LEDC_CHANNEL_5, 0, 255, 0); // Second pair Green
                ledController.setLEDColor(LEDC_CHANNEL_6, LEDC_CHANNEL_7, LEDC_CHANNEL_8, 0, 0, 0);
            } else if (distance >= 7 && distance <= 9) {
                ESP_LOGI(TAG, "Third pair Blue");
                ledController.setLEDColor(LEDC_CHANNEL_0, LEDC_CHANNEL_1, LEDC_CHANNEL_2, 0, 0, 0);
                ledController.setLEDColor(LEDC_CHANNEL_3, LEDC_CHANNEL_4, LEDC_CHANNEL_5, 0, 0, 0);
                ledController.setLEDColor(LEDC_CHANNEL_6, LEDC_CHANNEL_7, LEDC_CHANNEL_8, 0, 0, 255); // Third pair Blue
            } else if (distance >= 10 && distance <= 12) {
                ESP_LOGI(TAG, "Randomly blinking colors");
                for (int i = 0; i < 3; i++) {
                    ledController.setAllLEDs(rand() % 256, rand() % 256, rand() % 256, rand() % 256, rand() % 256, rand() % 256, rand() % 256, rand() % 256, rand() % 256);
                    vTaskDelay(200 / portTICK_PERIOD_MS);
                }
            } else if (distance >= 13 && distance <= 15) {
                ESP_LOGI(TAG, "Random effects on all three pairs");
                for (int i = 0; i < 3; i++) {
                    ledController.setLEDColor(LEDC_CHANNEL_0, LEDC_CHANNEL_1, LEDC_CHANNEL_2, rand() % 256, rand() % 256, rand() % 256);
                    ledController.setLEDColor(LEDC_CHANNEL_3, LEDC_CHANNEL_4, LEDC_CHANNEL_5, rand() % 256, rand() % 256, rand() % 256);
                    ledController.setLEDColor(LEDC_CHANNEL_6, LEDC_CHANNEL_7, LEDC_CHANNEL_8, rand() % 256, rand() % 256, rand() % 256);
                    vTaskDelay(200 / portTICK_PERIOD_MS);
                }
            }
        } else {
            // Turn off all LED strips if IR sensor is not triggered
            ESP_LOGI(TAG, "All LEDs off");
            ledController.setAllLEDs(0, 0, 0, 0, 0, 0, 0, 0, 0);
        }

        vTaskDelay(100 / portTICK_PERIOD_MS);
    }
}