#include <Arduino.h>
#include <ezButton.h>
#include <U8g2lib.h>
// 핀 설정
#define ENCODER_PIN_A 2      // DT
#define ENCODER_PIN_B 3      // CLK
#define ENCODER_BUTTON_PIN 1 // Button
#define BUZZER_PIN 0         // D1에 부저 연결
// 2옥타브 도~솔~도(3옥타브) 주파수 정의
#define NOTE_C2 65
#define NOTE_G2 98
#define NOTE_C3 131
// 메뉴 이동 효과음 (2옥타브 도)
void playMoveSound()
{
    tone(BUZZER_PIN, NOTE_C2, 30);
    delay(30);
    noTone(BUZZER_PIN);
}
// 선택 효과음 (띠리릭: 도-솔-도)
void playSelectSound()
{
    tone(BUZZER_PIN, NOTE_C2, 60);
    delay(60);
    tone(BUZZER_PIN, NOTE_G2, 60);
    delay(60);
    tone(BUZZER_PIN, NOTE_C3, 120);
    delay(120);
    noTone(BUZZER_PIN);
}
// 라이브러리 객체
volatile long encoderPosition = 0;
volatile int lastEncoded = 0;
void handleEncoder(); // 함수 선언 (ISR에서 사용되므로)
ezButton button(ENCODER_BUTTON_PIN);
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE);
// 메뉴 상태
uint8_t currentIndex = 0;
uint8_t selectedIndex = 255; // 255는 선택되지 않음을 의미 (uint8_t의 최대값)
// 엔코더 단계 저장용 (encoderPosition/4)
long prevStep = 0;
void drawMenu(uint8_t sel)
{
    u8g2.clearBuffer();
    uint8_t w = u8g2.getDisplayWidth();  // 128
    uint8_t h = u8g2.getDisplayHeight(); // 64
    uint8_t cellW = w / 3;
    uint8_t cellH = h / 3;
    // 메뉴 항목 그릴 때는 한글 폰트 사용
    u8g2.setFont(u8g2_font_unifont_t_korean2);
    for (uint8_t i = 0; i < 9; i++)
    {
        uint8_t x = (i % 3) * cellW;
        uint8_t y = (i / 3) * cellH;
        // 폰트 렌더링 모드 설정 (중요: 투명 배경, 기본값)
        u8g2.setFontMode(0);
        u8g2.setDrawColor(1); // 기본 그리기 색상 (흰색)
        if (i == sel)
        {
            // 선택된 항목: 반전 배경
            u8g2.drawBox(x, y, cellW, cellH); // 배경 채우기
            u8g2.setDrawColor(0);             // 글자 색상 (검정색)
            char buf[2] = {(char)('1' + i), 0};
            uint8_t tw = u8g2.getStrWidth(buf);
            // u8g2.getAscent() 사용 권장 (폰트 높이 기준)
            uint8_t th = u8g2.getAscent() - u8g2.getDescent();
            u8g2.drawStr(x + (cellW - tw) / 2, y + (cellH + th) / 2, buf);
            u8g2.setDrawColor(1); // 다음 루프를 위해 그리기 색상 복원
        }
        else
        {
            // 선택되지 않은 항목: 테두리만
            u8g2.drawFrame(x, y, cellW, cellH);
            // 글자 색상 (흰색) - 위에서 setDrawColor(1) 했으므로 필요 없음
            char buf[2] = {(char)('1' + i), 0};
            uint8_t tw = u8g2.getStrWidth(buf);
            uint8_t th = u8g2.getAscent() - u8g2.getDescent();
            u8g2.drawStr(x + (cellW - tw) / 2, y + (cellH + th) / 2, buf);
        }
    }
    u8g2.sendBuffer();
}
// 선택 확정 메시지 출력
void drawSelection(uint8_t idx)
{
    playSelectSound(); // 선택 효과음
    u8g2.clearBuffer();
    // *** 수정된 부분: 깨짐 현상이 보고된 폰트 대신 다른 기본 폰트 사용 ***
    // u8g2.setFont(u8g2_font_5x7_tr); // 기존 폰트 (숫자 9에서 문제 발생 가능성)
    u8g2.setFont(u8g2_font_ncenB08_tr); // 다른 안정적인 기본 ASCII 폰트 사용 (추천)
    // 또는 u8g2_font_6x10_tf 등 다른 폰트도 가능
    // 폰트 렌더링 모드 설정 (투명 배경)
    u8g2.setFontMode(0);
    u8g2.setDrawColor(1); // 글자 색상 (흰색)
    char buf[16];
    // idx는 0부터 시작하므로 사용자에게 보여줄 때는 +1
    snprintf(buf, sizeof(buf), "Selected: %d", idx + 1);
    // 디버깅: 시리얼 출력은 유지
    Serial.print("drawSelection: idx=");
    Serial.print(idx); // 실제 인덱스(0~8)
    Serial.print(", string=");
    Serial.println(buf); // 화면에 표시될 문자열
    // 문자열 폭과 높이 계산
    uint8_t tw = u8g2.getStrWidth(buf);
    uint8_t th = u8g2.getAscent() - u8g2.getDescent(); // 폰트의 실제 높이
    // 화면 중앙에 출력
    u8g2.drawStr((u8g2.getDisplayWidth() - tw) / 2, (u8g2.getDisplayHeight() + th) / 2, buf);
    u8g2.sendBuffer();
    delay(1000); // 1초 동안 선택 메시지 표시
}
void setup()
{
    // 시리얼
    Serial.begin(9600);
    Serial.println("Setup Start");
    // I2C 속도 400kHz로 올리기 (FPS 향상)
    u8g2.begin();
#if defined(TWBR)
    Wire.setClock(400000);
#endif
    // 엔코더 핀
    pinMode(ENCODER_PIN_A, INPUT_PULLUP);
    pinMode(ENCODER_PIN_B, INPUT_PULLUP);
    // 버튼 핀 (ezButton 라이브러리가 내부적으로 처리하므로 pinMode 불필요)
    // pinMode(ENCODER_BUTTON_PIN, INPUT_PULLUP); // ezButton 사용 시 불필요
    // 버튼 설정
    button.setDebounceTime(50); // 디바운스 시간 설정
    // 초기 엔코더 값 읽기 (인터럽트 설정 전)
    int MSB = digitalRead(ENCODER_PIN_A);
    int LSB = digitalRead(ENCODER_PIN_B);
    lastEncoded = (MSB << 1) | LSB;
    // 인터럽트 설정
    attachInterrupt(digitalPinToInterrupt(ENCODER_PIN_A), handleEncoder, CHANGE);
    attachInterrupt(digitalPinToInterrupt(ENCODER_PIN_B), handleEncoder, CHANGE);
    // OLED 초기화
    if (!u8g2.begin())
    {
        Serial.println("U8g2 initialization failed!");
        while (1)
            ; // 실패 시 무한 루프
    }
    Serial.println("U8g2 Initialized");
    // 초기 화면 그리기
    drawMenu(currentIndex);
    // prevStep 초기화 (초기 encoderPosition 값 기준)
    prevStep = encoderPosition / 4;
    Serial.println("Setup Complete");
}
void loop()
{
    // 버튼 상태 갱신 (매 루프마다 호출 필수)
    button.loop();
    // 버튼이 눌렸을 때
    if (button.isPressed())
    {
        selectedIndex = currentIndex; // 현재 인덱스를 선택된 인덱스로 저장
        Serial.print("Button Pressed. Selected Index: ");
        Serial.println(selectedIndex); // 디버깅: 선택된 인덱스(0~8) 출력
        // 선택된 번호(1~9)를 화면에 표시
        drawSelection(selectedIndex);
        // 선택 후 엔코더 값과 prevStep 동기화 (선택 화면 후 메뉴 복귀 시 현재 위치 유지)
        // 중요: delay(1000) 동안 엔코더가 돌아갈 수 있으므로, drawSelection 후에 동기화
        noInterrupts(); // 인터럽트 잠시 비활성화 (encoderPosition 안전하게 읽기)
        prevStep = encoderPosition / 4;
        interrupts(); // 인터럽트 다시 활성화
        // selectedIndex를 바로 초기화하지 않고, released 상태에서 메뉴를 다시 그림
    }
    // 버튼이 떼어졌고, 이전에 선택이 완료되었을 때 (drawSelection이 호출된 후)
    else if (button.isReleased() && selectedIndex != 255)
    {
        Serial.println("Button Released after selection. Returning to menu.");
        selectedIndex = 255; // 선택 상태 초기화 (다시 메뉴 조작 가능하게)
        // 현재 인덱스 기준으로 메뉴 다시 그리기
        drawMenu(currentIndex);
    }
    // 엔코더 값 변화 감지 (선택되지 않은 상태일 때만 메뉴 이동)
    if (selectedIndex == 255) // 선택 메시지가 표시 중이지 않을 때만 엔코더 처리
    {
        // 인터럽트 비활성화하고 volatile 변수 읽기 (더 안전)
        long currentEncoderPos;
        noInterrupts();
        currentEncoderPos = encoderPosition;
        interrupts();
        long step = currentEncoderPos / 4; // 4틱 당 1단계
        if (step != prevStep)
        {
            prevStep = step;
            // 인덱스 계산 (0~8 범위, 음수 처리 포함)
            currentIndex = ((step % 9) + 9) % 9;
            Serial.print("Encoder Changed. Current Index: ");
            Serial.println(currentIndex); // 디버깅: 변경된 인덱스 출력
            playMoveSound(); // 메뉴 이동시 효과음
            // 메뉴 다시 그리기
            drawMenu(currentIndex);
        }
    }
}
// 엔코더 인터럽트 핸들러
void handleEncoder()
{
    int MSB = digitalRead(ENCODER_PIN_A); // digitalRead는 ISR에서 안전함
    int LSB = digitalRead(ENCODER_PIN_B);
    int encoded = (MSB << 1) | LSB;         // 현재 상태 (2비트)
    int sum = (lastEncoded << 2) | encoded; // 이전 상태와 현재 상태 결합 (4비트)
    // 표준 쿼드러처 엔코더 상태 전이 테이블 기반
    // 시계 방향 (CW)
    if (sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011)
    {
        encoderPosition++;
    }
    // 반시계 방향 (CCW)
    if (sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000)
    {
        encoderPosition--;
    }
    lastEncoded = encoded; // 다음 비교를 위해 현재 상태 저장
}