/*
Festival Stick is a dense corkscrew of LEDs that is wrapped around one end of
a wooden walking stick commonly found on amazon.A0

The UI screenmap projects this cork screw into polar coordinates, so that the LEDs are
mapped to a sprial, with the inner portion of the spiral being the top, the outer
most portion being the bottom.

*/



#include "fl/stl/assert.h"
#include "fl/math/screenmap.h"
#include "fl/log/log.h"
#include "noise.h"
#include <FastLED.h>
// #include "fl::vec3.h"


// Power management settings
#define VOLTS 5
#define MAX_AMPS 1


#define PIN_DATA 9
#define PIN_CLOCK 7

// Pin could have been tied to ground, instead it's tied to another pin.
#define PIN_BUTTON 1
#define PIN_GRND 2

#define NUM_LEDS 288
// #define CM_BETWEEN_LEDS 1.0 // 1cm between LEDs
// #define CM_LED_DIAMETER 0.5 // 0.5cm LED diameter

fl::UITitle festivalStickTitle("Festival Stick - Classic Version");
fl::UIDescription festivalStickDescription(
    "Take a wooden walking stick, wrap dense LEDs around it like a corkscrew. Super simple but very awesome looking. "
    "This classic version uses 3D Perlin noise to create organic, flowing patterns around the cylindrical surface. "
    "Assumes dense 144 LEDs/meter (288 total LEDs).");

// UIHelp festivalStickHelp("Festival Stick - Classic Guide");

// UIHelp technicalHelp("Technical Details - Classic Festival Stick");
// UIHelp usageHelp("Usage Guide - Classic Festival Stick");  
// UIHelp physicalBuildHelp("Building Your Festival Stick");


fl::UISlider ledsScale("Leds scale", 0.1f, 0.1f, 1.0f, 0.01f);
fl::UIButton button("Button");

// Adding a brightness slider
fl::UISlider brightness("Brightness", 16, 0, 255, 1); // Brightness from 0 to 255

fl::CRGB leds[NUM_LEDS];


// fl::vector<vec3f>
struct corkscrew_args {
    int num_leds = NUM_LEDS;
    float leds_per_turn = 15.5;
    float width_cm = 1.0;
};

fl::vector<vec3f> makeCorkScrew(corkscrew_args args = corkscrew_args()) {
    // int num_leds, float leds_per_turn, float width_cm
    int num_leds = args.num_leds;
    float leds_per_turn = args.leds_per_turn;
    float width_cm = args.width_cm;

    const float circumference = leds_per_turn;
    const float radius = circumference / (2.0 * FL_PI);      // radius in mm
    const float angle_per_led = 2.0 * FL_PI / leds_per_turn; // degrees per LED
    const float total_angle_radians = angle_per_led * num_leds;
    const float total_turns = total_angle_radians / (2.0 * FL_PI); // total turns
    const float height_per_turn_cm = width_cm; // 10cm height per turn
    const float height_per_led =
        height_per_turn_cm /
        leds_per_turn; // this is the changing height per led.
    const float total_height =
        height_per_turn_cm * total_turns; // total height of the corkscrew
    fl::vector<vec3f> out;
    for (int i = 0; i < num_leds; i++) {
        float angle = i * angle_per_led; // angle in radians
        float height = (i / leds_per_turn) * height_per_turn_cm; // height in cm

        // Calculate the x, y, z coordinates for the corkscrew
        float x = radius * fl::cos(angle); // x coordinate
        float z = radius * fl::sin(angle); // y coordinate
        float y = height;              // z coordinate

        // Store the 3D coordinates in the fl::vector
        vec3f led_position(x, y, z);
        // screenMap.set(i, led_position);
        out.push_back(led_position);
    }
    return out;
}


fl::ScreenMap makeScreenMap(corkscrew_args args = corkscrew_args()) {
    // Create a fl::ScreenMap for the corkscrew
    fl::vector<vec2f> points(args.num_leds);

    int num_leds = args.num_leds;
    float leds_per_turn = args.leds_per_turn;
    float width_cm = args.width_cm;


    const float circumference = leds_per_turn;
    const float radius = circumference / (2.0 * FL_PI);      // radius in mm
    const float angle_per_led = 2.0 * FL_PI / leds_per_turn; // degrees per LED
    const float height_per_turn_cm = width_cm; // 10cm height per turn
    const float height_per_led =
        height_per_turn_cm /
        leds_per_turn * 1.3; // this is the changing height per led.



    for (int i = 0; i < num_leds; i++) {
        float angle = i * angle_per_led; // angle in radians
        float r = radius + 10 + i * height_per_led; // height in cm

        // Calculate the x, y coordinates for the corkscrew
        float x = r * fl::cos(angle); // x coordinate
        float y = r * fl::sin(angle); // y coordinate

        // Store the 2D coordinates in the fl::vector
        points[i] = vec2f(x, y);
    }

    FL_WARN("Creating fl::ScreenMap with:\n" << points);

    // Create a fl::ScreenMap from the points
    fl::ScreenMap screenMap(points.data(), num_leds, .5);
    return screenMap;
}


// Create a corkscrew with:
// - 30cm total length (300mm)
// - 5cm width (50mm)
// - 2mm LED inner diameter
// - 24 LEDs per turn
// fl::ScreenMap screenMap = makeCorkScrew(NUM_LEDS,
// 300.0f, 50.0f, 2.0f, 24.0f);

corkscrew_args args = corkscrew_args();
fl::vector<vec3f> mapCorkScrew = makeCorkScrew(args);
fl::ScreenMap screenMap;


CLEDController* addController() {
    CLEDController* controller = &FastLED.addLeds<APA102HD, PIN_DATA, PIN_CLOCK, BGR>(leds, NUM_LEDS);
    return controller;
}

void setup() {
    pinMode(PIN_GRND, OUTPUT);
    digitalWrite(PIN_GRND, LOW); // Set ground pin to low
    button.addRealButton(Button(PIN_BUTTON));
    screenMap = makeScreenMap(args);
    //screenMap = fl::ScreenMap::Circle(NUM_LEDS, 1.5f, 0.5f, 1.0f);
    auto controller = addController();
    // Set the screen map for the controller
    controller->setScreenMap(screenMap);
    
    // Set power management. This allows this festival stick to conformatable
    // run on any USB battery that can output at least 1A at 5V.
    // Keep in mind that this sketch is designed to use APA102HD mode, which will
    // result in even lowwer run power consumption, since the power mode does not take
    // into account the APA102HD gamma correction. However it is still a correct upper bound
    // that will match the ledset exactly when the display tries to go full white.
    FastLED.setMaxPowerInVoltsAndMilliamps(VOLTS, MAX_AMPS * 1000);
    // set brightness 8
    FastLED.setBrightness(brightness.as_int());
    button.onChanged([](fl::UIButton& but) {
        // This function is called when the button is pressed
        // If the button is pressed, show the generative pattern
        if (but.isPressed()) {
            FL_WARN("Button pressed");
        } else {
            FL_WARN("NOT Button pressed");
        }
    });

}


void showGenerative(uint32_t now) {
    // This function is called to show the generative pattern
    for (int i = 0; i < NUM_LEDS; i++) {
        // Get the 2D position of this LED from the screen map
        fl::vec3f pos = mapCorkScrew[i];
        float x = pos.x;
        float y = pos.y;
        float z = pos.z;

        x*= 20.0f * ledsScale.value();
        y*= 20.0f * ledsScale.value();
        z*= 20.0f * ledsScale.value();

        uint16_t noise_value = inoise16(x,y,z, now / 100);
        // Normalize the noise value to 0-255
        uint8_t brightness = map(noise_value, 0, 65535, 0, 255);
        // Create a hue that changes with position and time
        uint8_t sat = int32_t((x * 10 + y * 5 + now / 5)) % 256;
        // Set the color
        leds[i] = fl::CHSV(170, sat, fl::clamp(255- sat, 64, 255));
    }
}

void loop() {
    uint32_t now = millis();
    fl::clear(leds);
    showGenerative(now);
    FastLED.show();
}
