//!  LiquidCrystal_PCF8574 library by Matthias Hertel
//// https://wokwi.com/projects/418239231786728449

////https://wokwi.com/projects/418343691430217729

// # https://wokwi.com/projects/418376297093665793
// # https://wokwi.com/projects/418376297093665793

// # 와이파이 연결은 추후에 하니 나중에 코드 만지기로 할것
// # 링버퍼 방식 공부하기
// # 평균값 없애도 될 것 같고 MIN 값도 없애고
// # 현재값 , 최대값 , 이전의 500넘는겂 , 현재 500넘는 값 표시로 바꾸는게?? -> 구현됨
// # 테스트 할것
// # 피크캡 시간 단위 조절 하는 코드 넣기 (ms , s , m , h)

// This example shows various featues of the library for LCD with 16 chars and 2 lines.

#include <Arduino.h>
#include <Wire.h>
#include <LiquidCrystal_PCF8574.h>

const int LCD_I2C_ADDR = 0x27;
const int LCD_COLUNMS = 20;
const int LCD_ROWS = 4;
LiquidCrystal_PCF8574 lcd(LCD_I2C_ADDR); // set the LCD address to 0x27 for a 16(20) chars and 2(4) line display

int analogCurrentValue = 0;
int analogMaxValue = 0;
long analogPeakGapTime = 0;
int currentAnalogValue = 0;
int wifiSignal = 0;

const int limitArrayCount = 500;
static int rawData[limitArrayCount];
static unsigned long rawTimes[limitArrayCount];
static int readIndex = 0;

const int highValueLimit = 10; // 500이상 값을 저장할 배열 크기를 10개로 제한
static int highData[highValueLimit];
static unsigned long highTimes[highValueLimit];
static int highIndex = 0;

static unsigned long previousMillis = 0;

// LCD 갱신 관련 변수 추가 (전역 변수 선언부에 추가)
const unsigned long LCD_UPDATE_INTERVAL = 500; // LCD 갱신 주기 (500ms)
unsigned long lastLCDUpdate = 0;

//! 모의 데이타 시작
// 모의 데이터 관련 변수 추가
const int MOCK_DATA_SIZE = 200;
int mockData[MOCK_DATA_SIZE];
int mockDataIndex = 0;

void generateMockData()
{
    // 기본값은 0~100 사이의 낮은 값
    for (int i = 0; i < MOCK_DATA_SIZE; i++)
    {
        mockData[i] = random(0, 450);
    }

    // 10개의 높은 값(500~1000)을 랜덤한 위치에 삽입
    for (int i = 0; i < 10; i++)
    {
        int randomPos = random(0, MOCK_DATA_SIZE);
        mockData[randomPos] = random(500, 1024);
    }
}

int getMockAnalogValue()
{
    int value = mockData[mockDataIndex];
    mockDataIndex = (mockDataIndex + 1) % MOCK_DATA_SIZE;
    return value;
}

// # 모의 데이타 끝

void setup_20x4_LCD()
{
    int error;
    Serial.println("LCD...");

    Serial.println("Probing for PCF8574 on address 0x27...");

    // See http://playground.arduino.cc/Main/I2cScanner how to test for a I2C device.
    Wire.begin();
    Wire.beginTransmission(LCD_I2C_ADDR);
    error = Wire.endTransmission();
    Serial.print("Error: ");
    Serial.print(error);

    if (error == 0)
    {
        Serial.println(": LCD found.");
        lcd.begin(LCD_COLUNMS, LCD_ROWS); // initialize the lcd
    }
    else
    {
        Serial.println(": LCD not found.");
    }
}

void loop_mainPage_LCD()
{
    unsigned long currentMillis = millis();
    if (currentMillis - lastLCDUpdate < LCD_UPDATE_INTERVAL)
    {
        return;
    }
    lastLCDUpdate = currentMillis;

    int cValue = currentAnalogValue;
    int maxValue = analogMaxValue;
    long peakGapTime = analogPeakGapTime;
    // 피크캡 시간 단위 조절 함수
    peakGapTimeUnit(peakGapTime);

    // 500 이상 값들의 최근 두 값 가져오기
    int prevHighValue = (highIndex >= 2) ? highData[highIndex - 2] : 0;
    int currentHighValue = (highIndex >= 1) ? highData[highIndex - 1] : 0;

    const int backLightness = 150;
    lcd.setBacklight(backLightness);

    // 현재값 출력 (0,0)
    lcd.setCursor(0, 0);
    lcd.print("C.V: ");
    lcd.print("    ");
    lcd.setCursor(5, 0);
    lcd.print(cValue);

    // 최대값 출력 (11,0)
    lcd.setCursor(11, 0);
    lcd.print("MAX: ");
    lcd.print("    ");
    lcd.setCursor(16, 0);
    lcd.print(maxValue);

    // 이전 500+ 값 출력 (0,1)
    lcd.setCursor(0, 1);
    lcd.print("PRV: ");
    lcd.print("    ");
    lcd.setCursor(5, 1);
    lcd.print(prevHighValue);

    // 현재 500+ 값 출력 (11,1)
    lcd.setCursor(11, 1);
    lcd.print("NOW: ");
    lcd.print("    ");
    lcd.setCursor(16, 1);
    lcd.print(currentHighValue);

    // 피크 갭 타임 출력 (0,2)
    lcd.setCursor(0, 2);
    lcd.print("PeakGap: ");
    lcd.print("         "); // 이전 값을 지우기 위해 더 많은 공백 추가
    lcd.setCursor(9, 2);
    peakGapTimeUnit(peakGapTime); // 직접 값을 출력하지 않고 함수를 통해 변환하여 출력

    // WIFI 상태 출력 (13,3)
    lcd.setCursor(13, 3);
    lcd.print("WIFI:");
    lcd.setCursor(19, 3);
    lcd.print(wifiSignal);
}

void peakGapTimeUnit(long &peakGapTime)
{
    if (peakGapTime < 1000) {
        // 1초 미만: ms 단위로 표시
        lcd.print(peakGapTime);
        lcd.print("ms");
    }
    else if (peakGapTime < 60000) {
        // 1분 미만: 초.밀리초 단위로 표시
        float seconds = peakGapTime / 1000.0;
        int ms = peakGapTime % 1000;
        lcd.print(seconds, 1); // 소수점 1자리까지 표시
        lcd.print("s");
    }
    else if (peakGapTime < 3600000) {
        // 1시간 미만: 분:초 단위로 표시
        int minutes = peakGapTime / 60000;
        int seconds = (peakGapTime % 60000) / 1000;
        lcd.print(minutes);
        lcd.print("m");
        lcd.print(seconds);
        lcd.print("s");
    }
    else {
        // 1시간 이상: 시:분 단위로 표시
        int hours = peakGapTime / 3600000;
        int minutes = (peakGapTime % 3600000) / 60000;
        lcd.print(hours);
        lcd.print("h");
        lcd.print(minutes);
        lcd.print("m");
    }
}

void readRawAnalogValue()
{
    int pos = readIndex % limitArrayCount;
    int val = getMockAnalogValue();
    rawData[pos] = val;
    rawTimes[pos] = millis();

    // 500 이상이면 별도 배열에 저장
    if (val >= 500)
    {
        if (highIndex < highValueLimit)
        {
            // 배열에 여유 공간이 있을 때
            highData[highIndex] = val;
            highTimes[highIndex] = rawTimes[pos];
            highIndex++;
        }
        else
        {
            // 배열이 꽉 찼을 때 왼쪽으로 시프트
            for (int i = 0; i < highValueLimit - 1; i++)
            {
                highData[i] = highData[i + 1];
                highTimes[i] = highTimes[i + 1];
            }
            // 새로운 값을 마지막 위치에 저장
            highData[highValueLimit - 1] = val;
            highTimes[highValueLimit - 1] = rawTimes[pos];
        }
    }
    readIndex++;
    currentAnalogValue = val;
}

void readHighAnalogValue()
{
    int maxVal = 0;
    // 실제 기록된 데이터 개수를 구함
    int count = (readIndex < limitArrayCount) ? readIndex : limitArrayCount;
    for (int i = 0; i < count; i++)
    {
        int pos = i % limitArrayCount;
        if (rawData[pos] > maxVal)
        {
            maxVal = rawData[pos];
        }
    }
    analogMaxValue = maxVal;
}

void printIntervals()
{
    // 배열이 가득 찼을 때도 계산할 수 있도록 수정
    if (highIndex >= 2) // 최소 2개의 값이 있어야 간격 계산 가능
    {
        // 마지막 두 값의 시간 간격 계산
        int lastIdx = (highIndex >= highValueLimit) ? highValueLimit - 1 : highIndex - 1;
        int prevIdx = lastIdx - 1;

        if (highTimes[lastIdx] > 0 && highTimes[prevIdx] > 0)
        {
            unsigned long interval = highTimes[lastIdx] - highTimes[prevIdx];

            // 비정상적으로 큰 간격은 무시 (1분 이내)
            if (interval < 60000)
            {
                Serial.print("High Interval: ");
                Serial.print(interval);
                Serial.println("ms");
                analogPeakGapTime = interval;
            }
        }
    }
}

// Serial 디버그용 함수 수정
void debugPrintAnalogValus()
{
    // 기존 디버그 정보 출력
    Serial.print("Cur: ");
    Serial.print(currentAnalogValue);
    Serial.print("   Max: ");
    Serial.print(analogMaxValue);

    // 고값 배열 전체 내용 출력 (디버그용)
    // Serial.print("High Values: ");
    // for (int i = 0; i < min(highIndex, highValueLimit); i++)
    // {
    //     Serial.print(highData[i]);
    //     Serial.print("(");
    //     Serial.print(highTimes[i]);
    //     Serial.print(") ");
    // }
    // Serial.println();

    // 고값 배열 정보 출력
    if (highIndex > 1) // 최소 2개 이상의 값이 있을 때
    {
        Serial.print("High Values Time Gaps -> ");
        for (int i = 1; i < highIndex; i++)
        {
            unsigned long gap = highTimes[i] - highTimes[i - 1];
            Serial.print(gap);
            Serial.print("ms ");
        }
        Serial.println();
    }
}

// void readWifiRSSI() // 와이파이 신호세기 측정 , 테스트 완료
// {
//     if (WiFi.status() == WL_CONNECTED)
//     {
//         wifiSignal = WiFi.RSSI();
//     }
//     else
//     {
//         wifiSignal = 0;
//     }
// }

// # setup
void setup()
{

    Serial.begin(9600);
    randomSeed(analogRead(A0) + millis());

    // wait on Serial to be available on Leonardo , R4 MINIMA , WIFI
    while (!Serial)
        ;

    randomSeed(analogRead(A0)); // 실제 아날로그 값으로 난수 시드 설정
    generateMockData();         // 모의 데이터 생성
    setup_20x4_LCD();

} // end of setup()

// # loop
void loop()
{
    loop_mainPage_LCD();
    readRawAnalogValue();
    readHighAnalogValue();
    printIntervals();
    debugPrintAnalogValus();
    // readWifiRSSI();
} // end of loop()

/*

if (show == 0)
    {
        lcd.setBacklight(255);
        lcd.home();
        lcd.clear();
        lcd.print("Hello LCD");
        delay(1000);

        lcd.setBacklight(0);
        delay(400);
        lcd.setBacklight(255);
    }
    else if (show == 1)
    {
        lcd.clear();
        lcd.print("Cursor On");
        lcd.cursor();
    }
    else if (show == 2)
    {
        lcd.clear();
        lcd.print("Cursor Blink");
        lcd.blink();
    }
    else if (show == 3)
    {
        lcd.clear();
        lcd.print("Cursor OFF");
        lcd.noBlink();
        lcd.noCursor();
    }
    else if (show == 4)
    {
        lcd.clear();
        lcd.print("Display Off");
        lcd.noDisplay();
    }
    else if (show == 5)
    {
        lcd.clear();
        lcd.print("Display On");
        lcd.display();
    }
    else if (show == 7)
    {
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("*** first line.");
        lcd.setCursor(0, 1);
        lcd.print("*** second line.");
    }
    else if (show == 8)
    {
        lcd.scrollDisplayLeft();
    }
    else if (show == 9)
    {
        lcd.scrollDisplayLeft();
    }
    else if (show == 10)
    {
        lcd.scrollDisplayLeft();
    }
    else if (show == 11)
    {
        lcd.scrollDisplayRight();
    }
    else if (show == 12)
    {
        lcd.clear();
        lcd.print("write-");
    }
    else if (show == 13)
    {
        lcd.clear();
        lcd.print("custom 1:<\01>");
        lcd.setCursor(0, 1);
        lcd.print("custom 2:<\02>");
    }
    else
    {
        lcd.print(show - 13);
    } // if

    delay(1400);
    show = (show + 1) % 16;
    */