/*
 * Xoshiro256++ 알고리즘을 사용한 로또 번호 생성기
 * 1~45 사이의 숫자에서 중복 없이 6개의 번호를 100세트 생성하고
 * 각 세트 간의 중복 여부를 검사합니다.
 *
 * 아날로그 핀을 사용하여 무작위성을 높입니다.
 * https://wokwi.com/projects/428584279970392065
 */
// Xoshiro256++ 난수 생성기 구현
class Xoshiro256PlusPlus
{
private:
    uint64_t s[4];
    // 64비트 회전 연산
    static inline uint64_t rotl(const uint64_t x, int k)
    {
        return (x << k) | (x >> (64 - k));
    }
public:
    // 초기화 - 4개의 64비트 시드 값 필요
    Xoshiro256PlusPlus(uint64_t seed1, uint64_t seed2, uint64_t seed3, uint64_t seed4)
    {
        s[0] = seed1;
        s[1] = seed2;
        s[2] = seed3;
        s[3] = seed4;
    }
    // 시드 초기화
    void setSeed(uint64_t seed1, uint64_t seed2, uint64_t seed3, uint64_t seed4)
    {
        s[0] = seed1;
        s[1] = seed2;
        s[2] = seed3;
        s[3] = seed4;
    }
    // SplitMix64 알고리즘으로 시드 생성
    // 단일 시드값으로부터 4개의 독립적인 시드 생성
    void seedFromSingle(uint64_t seed)
    {
        for (int i = 0; i < 4; i++)
        {
            seed += 0x9e3779b97f4a7c15;
            uint64_t z = seed;
            z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
            z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
            s[i] = z ^ (z >> 31);
        }
    }
    // 다음 난수 생성 (xoshiro256++ 알고리즘)
    uint64_t next()
    {
        const uint64_t result = rotl(s[0] + s[3], 23) + s[0];
        const uint64_t t = s[1] << 17;
        s[2] ^= s[0];
        s[3] ^= s[1];
        s[1] ^= s[2];
        s[0] ^= s[3];
        s[2] ^= t;
        s[3] = rotl(s[3], 45);
        return result;
    }
    // 범위 내 난수 생성 (0 ~ bound-1)
    uint32_t bounded(uint32_t bound)
    {
        // 범위 왜곡 방지를 위한 임계값 계산
        uint32_t threshold = -bound % bound;
        while (true)
        {
            uint32_t r = (uint32_t)(next() & 0xFFFFFFFF); // 64비트를 32비트로 축소
            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;
// Xoshiro256++ 난수 생성기 인스턴스
Xoshiro256PlusPlus rng(0, 0, 0, 0);
// 첫 번째 실행인지 확인하는 플래그
bool firstRun = true;
void setup()
{
    Serial.begin(115200);
    while (!Serial)
    {
    }
    Serial.println(F("Xoshiro256++ 알고리즘을 이용한 로또 번호 생성기"));
    Serial.println(F("1~45 사이의 숫자에서 중복 없이 6개의 번호를 100세트 생성합니다."));
    Serial.println();
    // 아날로그 핀 및 타이머를 이용한 시드 생성
    initializeRNG();
    // 로또 번호 100세트 생성
    generateLottoSets();
    // 중복 검사 수행
    checkDuplicates();
}
void loop()
{
    // 루프에서는 추가 작업 없음
    if (firstRun)
    {
        firstRun = false;
        Serial.println(F("완료! 다시 실행하려면 리셋 버튼을 누르세요."));
    }
}
// RNG 초기화 - 아날로그 핀과 타이머를 활용한 시드 생성
void initializeRNG()
{
    // 시스템 시작 이후 경과한 마이크로초
    uint64_t microTime = micros();
    // 아날로그 핀에서 노이즈 값 읽기
    uint64_t analogNoise1 = (uint64_t)analogRead(A0) << 48 | (uint64_t)analogRead(A1) << 32 |
                            (uint64_t)analogRead(A2) << 16 | analogRead(A3);
    uint64_t analogNoise2 = (uint64_t)analogRead(A4) << 48 | (uint64_t)analogRead(A5) << 32 |
                            (uint64_t)analogRead(A0) << 16 | analogRead(A1);
    // 추가 노이즈 수집 (마이크로초 값도 계속 변화)
    delay(1);
    uint64_t analogNoise3 = (uint64_t)analogRead(A2) << 48 | (uint64_t)analogRead(A3) << 32 |
                            (uint64_t)analogRead(A4) << 16 | analogRead(A5);
    uint64_t analogNoise4 = (uint64_t)analogRead(A0) << 48 | (uint64_t)analogRead(A3) << 32 |
                            (uint64_t)micros() << 16 | (analogRead(A2) * analogRead(A4));
    // 초기 시드 설정
    rng.setSeed(
        analogNoise1 ^ microTime,
        analogNoise2 ^ (microTime << 11),
        analogNoise3 ^ (microTime >> 13),
        analogNoise4 ^ (microTime * 741));
    // 초기 상태 혼합을 위해 몇 번 건너뛰기
    for (int i = 0; i < 20; i++)
    {
        rng.next();
    }
    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 = rng.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("세트"));
        }
    }
}