/*
 * PCG Random 알고리즘을 사용한 로또 번호 생성기
 * 1~45 사이의 숫자에서 중복 없이 6개의 번호를 100세트 생성하고
 * 각 세트 간의 중복 여부를 검사합니다.
 *
 * 아날로그 핀을 사용하여 무작위성을 높입니다.
 */
// PCG 난수 생성기 구현
class PCGRandom
{
private:
    uint64_t state;
    uint64_t inc;
public:
    PCGRandom(uint64_t initstate = 0, uint64_t initseq = 0)
    {
        setState(initstate);
        setIncrement(initseq);
    }
    void setState(uint64_t initstate)
    {
        state = 0;
        next();
        state += initstate;
        next();
    }
    void setIncrement(uint64_t initseq)
    {
        inc = (initseq << 1) | 1;
    }
    uint32_t next()
    {
        uint64_t oldstate = state;
        state = oldstate * 6364136223846793005ULL + inc;
        uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
        uint32_t rot = oldstate >> 59u;
        return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
    }
    uint32_t bounded(uint32_t bound)
    {
        uint32_t threshold = -bound % bound;
        while (true)
        {
            uint32_t r = next();
            if (r >= threshold)
                return r % bound;
        }
    }
};
// 로또 번호 세트 (6개 숫자)를 표현하는 클래스
class LottoSet
{
public:
    byte numbers[6];
    void sort()
    {
        // 버블 정렬로 번호 정렬
        for (int i = 0; i < 5; i++)
        {
            for (int j = 0; j < 5 - i; j++)
            {
                if (numbers[j] > numbers[j + 1])
                {
                    byte temp = numbers[j];
                    numbers[j] = numbers[j + 1];
                    numbers[j + 1] = temp;
                }
            }
        }
    }
    // 두 로또 세트가 동일한지 비교
    bool equals(const LottoSet &other) const
    {
        for (int i = 0; i < 6; i++)
        {
            if (numbers[i] != other.numbers[i])
            {
                return false;
            }
        }
        return true;
    }
    // 출력용 문자열 변환
    void toString(char *buffer)
    {
        sprintf(buffer, "%02d %02d %02d %02d %02d %02d",
                numbers[0], numbers[1], numbers[2], numbers[3], numbers[4], numbers[5]);
    }
};
// 로또 세트 저장소
const int MAX_SETS = 100;
LottoSet lottoSets[MAX_SETS];
int setsGenerated = 0;
// PCG 난수 생성기 인스턴스
PCGRandom pcg;
// 첫 번째 실행인지 확인하는 플래그
bool firstRun = true;
void setup()
{
    Serial.begin(115200);
    while (!Serial)
    {
    }
    Serial.println(F("PCG Random 알고리즘을 이용한 로또 번호 생성기"));
    Serial.println(F("1~45 사이의 숫자에서 중복 없이 6개의 번호를 100세트 생성합니다."));
    Serial.println();
    // 아날로그 핀에서 시드 값 읽기
    randomSeed(analogRead(A0));
    // PCG 초기화 - 아날로그 핀과 내부 타이머 값을 시드로 사용
    uint64_t seed1 = (uint64_t)analogRead(A0) << 32 | analogRead(A1) << 16 | analogRead(A2);
    uint64_t seed2 = (uint64_t)analogRead(A3) << 32 | analogRead(A4) << 16 | micros();
    pcg = PCGRandom(seed1, seed2);
    // 로또 번호 100세트 생성
    generateLottoSets();
    // 중복 검사 수행
    checkDuplicates();
}
void loop()
{
    // 루프에서는 추가 작업 없음
    if (firstRun)
    {
        firstRun = false;
        Serial.println(F("완료! 다시 실행하려면 리셋 버튼을 누르세요."));
    }
}
// 로또 번호 세트 생성
void generateLottoSets()
{
    Serial.println(F("로또 번호 생성 중..."));
    for (int set = 0; set < MAX_SETS; set++)
    {
        generateLottoSet(&lottoSets[set]);
        setsGenerated++;
        // 10개 세트마다 진행 상황 출력
        if ((set + 1) % 10 == 0 || set == 0)
        {
            Serial.print(F("생성된 세트: "));
            Serial.println(set + 1);
        }
    }
    Serial.println(F("로또 번호 생성 완료!"));
    Serial.println();
}
// 단일 로또 세트 생성 (중복 숫자 없이)
void generateLottoSet(LottoSet *set)
{
    bool used[46] = {false}; // 1-45 범위의 숫자 사용 여부 (0은 사용하지 않음)
    for (int i = 0; i < 6; i++)
    {
        int num;
        do
        {
            // 1-45 범위의 난수 생성
            num = pcg.bounded(45) + 1;
        } while (used[num]); // 이미 사용된 숫자라면 다시 생성
        set->numbers[i] = num;
        used[num] = true;
    }
    // 오름차순 정렬
    set->sort();
}
// 모든 생성된 세트 간의 중복 검사
void checkDuplicates()
{
    Serial.println(F("생성된 모든 로또 세트:"));
    // 모든 세트 출력
    char buffer[50];
    for (int i = 0; i < setsGenerated; i++)
    {
        lottoSets[i].toString(buffer);
        Serial.print(i + 1);
        Serial.print(F(": "));
        Serial.println(buffer);
    }
    Serial.println();
    Serial.println(F("세트 간 중복 검사 중..."));
    bool foundDuplicate = false;
    int totalComparisons = 0;
    // 모든 가능한 세트 쌍 비교
    for (int i = 0; i < setsGenerated - 1; i++)
    {
        for (int j = i + 1; j < setsGenerated; j++)
        {
            totalComparisons++;
            if (lottoSets[i].equals(lottoSets[j]))
            {
                foundDuplicate = true;
                Serial.print(F("중복 발견! 세트 #"));
                Serial.print(i + 1);
                Serial.print(F(" 와 세트 #"));
                Serial.println(j + 1);
                char buffer1[50], buffer2[50];
                lottoSets[i].toString(buffer1);
                lottoSets[j].toString(buffer2);
                Serial.print(F("  세트 #"));
                Serial.print(i + 1);
                Serial.print(F(": "));
                Serial.println(buffer1);
                Serial.print(F("  세트 #"));
                Serial.print(j + 1);
                Serial.print(F(": "));
                Serial.println(buffer2);
            }
        }
        // 진행 상황 표시 (10% 단위)
        if ((i + 1) % (setsGenerated / 10) == 0)
        {
            int percent = ((i + 1) * 100) / (setsGenerated - 1);
            Serial.print(F("검사 진행: "));
            Serial.print(percent);
            Serial.println(F("%"));
        }
    }
    Serial.println();
    Serial.print(F("총 비교 횟수: "));
    Serial.println(totalComparisons);
    if (foundDuplicate)
    {
        Serial.println(F("중복된 세트가 발견되었습니다!"));
    }
    else
    {
        Serial.println(F("중복된 세트가 없습니다! 모든 100개 세트는 고유합니다."));
    }
    // 통계 검사 추가 - 세 가지 체크 로직
    checkStatistics();
}
// 생성된 로또 번호의 통계 검사 (세 가지 체크 로직)
void checkStatistics()
{
    Serial.println();
    Serial.println(F("===== 로또 번호 통계 검사 ====="));
    // 1. 체크 로직 1: 각 숫자의 출현 빈도
    Serial.println(F("1. 각 숫자의 출현 빈도:"));
    int numberFrequency[46] = {0}; // 1-45의 숫자 빈도
    for (int i = 0; i < setsGenerated; i++)
    {
        for (int j = 0; j < 6; j++)
        {
            numberFrequency[lottoSets[i].numbers[j]]++;
        }
    }
    // 출현 빈도 출력
    for (int i = 1; i <= 45; i++)
    {
        Serial.print(F("숫자 "));
        Serial.print(i);
        Serial.print(F(": "));
        Serial.print(numberFrequency[i]);
        Serial.print(F("회"));
        if (i % 5 == 0)
        {
            Serial.println();
        }
        else
        {
            Serial.print(F("\t"));
        }
    }
    Serial.println();
    // 2. 체크 로직 2: 홀수/짝수 분포
    Serial.println(F("2. 홀수/짝수 분포:"));
    int oddEvenDistribution[7] = {0}; // 홀수 0개~6개 분포
    for (int i = 0; i < setsGenerated; i++)
    {
        int oddCount = 0;
        for (int j = 0; j < 6; j++)
        {
            if (lottoSets[i].numbers[j] % 2 == 1)
            { // 홀수 카운트
                oddCount++;
            }
        }
        oddEvenDistribution[oddCount]++;
    }
    Serial.println(F("홀수 개수별 세트 수:"));
    for (int i = 0; i <= 6; i++)
    {
        Serial.print(F("홀수 "));
        Serial.print(i);
        Serial.print(F("개 (짝수 "));
        Serial.print(6 - i);
        Serial.print(F("개): "));
        Serial.print(oddEvenDistribution[i]);
        Serial.println(F("세트"));
    }
    // 3. 체크 로직 3: 총합 분포
    Serial.println(F("3. 번호 총합 분포:"));
    int sumDistribution[300] = {0}; // 가능한 총합 범위
    int minSum = 255, maxSum = 0;
    for (int i = 0; i < setsGenerated; i++)
    {
        int sum = 0;
        for (int j = 0; j < 6; j++)
        {
            sum += lottoSets[i].numbers[j];
        }
        sumDistribution[sum]++;
        if (sum < minSum)
            minSum = sum;
        if (sum > maxSum)
            maxSum = sum;
    }
    Serial.print(F("최소 총합: "));
    Serial.println(minSum);
    Serial.print(F("최대 총합: "));
    Serial.println(maxSum);
    Serial.println(F("총합 분포:"));
    for (int i = minSum; i <= maxSum; i++)
    {
        if (sumDistribution[i] > 0)
        {
            Serial.print(F("총합 "));
            Serial.print(i);
            Serial.print(F(": "));
            Serial.print(sumDistribution[i]);
            Serial.println(F("세트"));
        }
    }
}