/*
 * Step 4: Water Flow Simulation (Memory Optimized)
 * 아두이노 우노 메모리 최적화 버전
 */
#include <LedControl.h>
#include <Wire.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
// 상수
constexpr uint8_t NUM_PARTICLES = 3;
constexpr float GRAVITY_SCALE = 8.0; // 중력 강화 (2.5 -> 8.0)
constexpr float PARTICLE_RADIUS = 0.45;
constexpr float BOUNCE_DAMPING = 0.85;   // 바운스 강화 (0.7 -> 0.85)
constexpr float COLLISION_DAMPING = 0.9; // 충돌 반발 강화 (0.8 -> 0.9)
constexpr float FRICTION = 0.995;        // 마찰 감소 (0.98 -> 0.995)
constexpr float MIN_VELOCITY = 0.02;     // 최소 속도 증가
constexpr float ACCEL_THRESHOLD = 0.15;  // 임계값 감소 (더 민감하게)
// 객체
LedControl matrix = LedControl(11, 13, 10, 1);
Adafruit_MPU6050 mpu;
// 입자 구조체
struct Particle
{
    float x, y;
    float vx, vy;
    float ax, ay;
};
Particle p[NUM_PARTICLES];
// 중력
struct
{
    float x, y;
} gravity;
// 자이로 기반 회전 각도 (라디안)
float rotation_x = 0.0; // X축 회전 (좌우 기울기)
float rotation_y = 0.0; // Y축 회전 (앞뒤 기울기)
// 타이밍
uint32_t prev_update = 0;
uint32_t prev_sensor = 0;
void setup()
{
    Serial.begin(115200);
    Serial.println(F("\n=== Water Flow ==="));
    // LED Matrix
    matrix.shutdown(0, false);
    matrix.setIntensity(0, 8);
    matrix.clearDisplay(0);
    // 테스트 패턴
    for (uint8_t i = 0; i < 8; i++)
        matrix.setLed(0, i, i, true);
    delay(300);
    matrix.clearDisplay(0);
    Serial.println(F("Matrix OK"));
    // MPU6050
    if (!mpu.begin())
    {
        Serial.println(F("No sensor"));
        gravity.x = 0;
        gravity.y = 1;
    }
    else
    {
        mpu.setAccelerometerRange(MPU6050_RANGE_8_G);
        mpu.setGyroRange(MPU6050_RANGE_500_DEG);
        mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);
        Serial.println(F("Sensor OK"));
    }
    // 입자 초기화 (화면 상단 중앙에 배치)
    for (uint8_t i = 0; i < NUM_PARTICLES; i++)
    {
        p[i].x = 3.0 + i * 1.0; // 3, 4, 5 위치
        p[i].y = 1.0 + i * 0.5; // 상단에서 시작
        p[i].vx = p[i].vy = 0;
        p[i].ax = p[i].ay = 0;
    }
    Serial.println(F("Start\n"));
}
void loop()
{
    uint32_t now = millis();
    // 센서 읽기
    if (now - prev_sensor >= 50)
    {
        prev_sensor = now;
        read_sensor();
    }
    // 물리 업데이트
    if (now - prev_update >= 50)
    {
        prev_update = now;
        update_physics();
        render();
    }
}
void read_sensor()
{
    static uint32_t last_time = 0;
    uint32_t now = millis();
    float dt = (now - last_time) / 1000.0;
    if (last_time == 0)
        dt = 0.05;
    last_time = now;
    sensors_event_t a, g, temp;
    if (!mpu.getEvent(&a, &g, &temp))
        return;
    // 각도 드리프트 보정 (가속도계 기반 - 더 강한 보정)
    float ax = a.acceleration.x;
    float ay = a.acceleration.y;
    float az = a.acceleration.z;
    float accel_angle_x = atan2(ay, sqrt(ax * ax + az * az));
    float accel_angle_y = atan2(-ax, sqrt(ay * ay + az * az));
    // 자이로스코프 회전 속도를 적분하여 회전 각도 계산
    rotation_x += g.gyro.x * dt; // rad/s * s = rad
    rotation_y += g.gyro.y * dt;
    // 상보 필터: 자이로 90% + 가속도 10% (드리프트 보정 강화)
    rotation_x = rotation_x * 0.90 + accel_angle_x * 0.10;
    rotation_y = rotation_y * 0.90 + accel_angle_y * 0.10;
    // 각도를 -60° ~ 60° 범위로 제한 (실제 사용 범위)
    constexpr float MAX_ANGLE = 1.05; // 약 60도
    rotation_x = constrain(rotation_x, -MAX_ANGLE, MAX_ANGLE);
    rotation_y = constrain(rotation_y, -MAX_ANGLE, MAX_ANGLE);
    // 회전 각도를 중력 벡터로 변환
    // X축 회전 -> 좌우 중력
    // Y축 회전 -> 앞뒤 중력 (LED 매트릭스에서는 Y축)
    float tilt_x = sin(rotation_y) * GRAVITY_SCALE;
    float tilt_y = sin(rotation_x) * GRAVITY_SCALE;
    // 기본 중력(1.0) + 기울기 성분
    gravity.x = tilt_x;
    gravity.y = cos(rotation_x) * cos(rotation_y) * GRAVITY_SCALE;
    // 중력 크기 제한
    float mag = sqrt(gravity.x * gravity.x + gravity.y * gravity.y);
    if (mag > GRAVITY_SCALE)
    {
        gravity.x = gravity.x / mag * GRAVITY_SCALE;
        gravity.y = gravity.y / mag * GRAVITY_SCALE;
    }
}
void update_physics()
{
    float dt = 0.05;
    // 각 입자
    for (uint8_t i = 0; i < NUM_PARTICLES; i++)
    {
        // 중력
        p[i].ax = gravity.x;
        p[i].ay = gravity.y;
        // 마찰
        p[i].vx *= FRICTION;
        p[i].vy *= FRICTION;
        if (abs(p[i].vx) < MIN_VELOCITY)
            p[i].vx = 0;
        if (abs(p[i].vy) < MIN_VELOCITY)
            p[i].vy = 0;
        // 속도/위치
        p[i].vx += p[i].ax * dt;
        p[i].vy += p[i].ay * dt;
        p[i].x += p[i].vx * dt;
        p[i].y += p[i].vy * dt;
        // 벽 충돌
        if (p[i].x < 0)
        {
            p[i].x = 0;
            p[i].vx = -p[i].vx * BOUNCE_DAMPING;
        }
        if (p[i].x >= 8)
        {
            p[i].x = 7.99;
            p[i].vx = -p[i].vx * BOUNCE_DAMPING;
        }
        if (p[i].y < 0)
        {
            p[i].y = 0;
            p[i].vy = -p[i].vy * BOUNCE_DAMPING;
        }
        if (p[i].y >= 8)
        {
            p[i].y = 7.99;
            p[i].vy = -p[i].vy * BOUNCE_DAMPING;
        }
    }
    // 입자간 충돌
    for (uint8_t i = 0; i < NUM_PARTICLES; i++)
    {
        for (uint8_t j = i + 1; j < NUM_PARTICLES; j++)
        {
            float dx = p[j].x - p[i].x;
            float dy = p[j].y - p[i].y;
            float dist = sqrt(dx * dx + dy * dy);
            if (dist < PARTICLE_RADIUS * 2 && dist > 0)
            {
                // 충돌 해결
                float nx = dx / dist;
                float ny = dy / dist;
                float dvx = p[j].vx - p[i].vx;
                float dvy = p[j].vy - p[i].vy;
                float dvn = dvx * nx + dvy * ny;
                if (dvn < 0)
                {
                    p[i].vx += dvn * nx * COLLISION_DAMPING;
                    p[i].vy += dvn * ny * COLLISION_DAMPING;
                    p[j].vx -= dvn * nx * COLLISION_DAMPING;
                    p[j].vy -= dvn * ny * COLLISION_DAMPING;
                    // 위치 보정
                    float overlap = (PARTICLE_RADIUS * 2) - dist;
                    float sep = overlap / 2 + 0.01;
                    p[i].x -= nx * sep;
                    p[i].y -= ny * sep;
                    p[j].x += nx * sep;
                    p[j].y += ny * sep;
                }
            }
        }
    }
}
void render()
{
    matrix.clearDisplay(0);
    for (uint8_t i = 0; i < NUM_PARTICLES; i++)
    {
        int8_t px = (int8_t)p[i].x;
        int8_t py = (int8_t)p[i].y;
        if (px >= 0 && px < 8 && py >= 0 && py < 8)
        {
            matrix.setLed(0, py, px, true);
        }
    }
    // 간단한 디버그 (30 프레임마다)
    static uint32_t count = 0;
    if (++count % 30 == 0)
    {
        Serial.print(F("Rot:"));
        Serial.print(rotation_x * 57.3, 0); // 라디안 -> 도
        Serial.print(F("°,"));
        Serial.print(rotation_y * 57.3, 0);
        Serial.print(F("° G:"));
        Serial.print(gravity.x, 1);
        Serial.print(F(","));
        Serial.print(gravity.y, 1);
        Serial.print(F(" |"));
        for (uint8_t i = 0; i < NUM_PARTICLES; i++)
        {
            Serial.print(F(" P"));
            Serial.print(i);
            Serial.print(F("("));
            Serial.print((int)p[i].x);
            Serial.print(F(","));
            Serial.print((int)p[i].y);
            Serial.print(F(")"));
        }
        Serial.println();
    }
}