// Случайные числа: PRNG vs CSPRNG
// Запуск: go run random_demo.go

package main

import (
	"crypto/rand"
	"encoding/binary"
	"fmt"
	"math"
	mrand "math/rand/v2"
)

func main() {
	fmt.Println("=== Go: случайные числа ===")
	fmt.Println()

	// 1. math/rand/v2 — PRNG (Go 1.22+)
	fmt.Println("--- 1. math/rand/v2 (ChaCha8) ---")
	fmt.Print("10 случайных чисел [0, 100): ")
	for i := 0; i < 10; i++ {
		fmt.Printf("%d ", mrand.IntN(100))
	}
	fmt.Println()
	fmt.Println("С Go 1.22: ChaCha8 по умолчанию, автосев от ОС.")
	fmt.Println()

	// 2. Детерминированный PRNG с фиксированным seed
	fmt.Println("--- 2. Фиксированный seed ---")
	src1 := mrand.NewChaCha8([32]byte{42})
	rng1 := mrand.New(src1)
	src2 := mrand.NewChaCha8([32]byte{42})
	rng2 := mrand.New(src2)

	same := true
	for i := 0; i < 100; i++ {
		if rng1.IntN(1000) != rng2.IntN(1000) {
			same = false
			break
		}
	}
	result := "идентичны"
	if !same {
		result = "различны"
	}
	fmt.Printf("Два ChaCha8 с одинаковым seed: последовательности %s\n", result)
	fmt.Println("PRNG детерминирован — воспроизводимость для тестов.")
	fmt.Println()

	// 3. crypto/rand — CSPRNG
	fmt.Println("--- 3. crypto/rand (CSPRNG) ---")
	var buf [8]byte
	_, err := rand.Read(buf[:])
	if err != nil {
		fmt.Printf("Ошибка: %v\n", err)
		return
	}
	fmt.Printf("8 байт из crypto/rand: %x\n", buf)
	n := binary.BigEndian.Uint64(buf[:])
	fmt.Printf("Как uint64: %d\n", n)
	fmt.Println("crypto/rand: /dev/urandom (Linux), CryptGenRandom (Windows).")
	fmt.Println()

	// 4. Распределение
	fmt.Println("--- 4. Проверка равномерности ---")
	var buckets [10]int
	for i := 0; i < 100_000; i++ {
		buckets[mrand.IntN(10)]++
	}
	for i, count := range buckets {
		bar := ""
		for j := 0; j < count/200; j++ {
			bar += "#"
		}
		fmt.Printf("  %d: %5d %s\n", i, count, bar)
	}
	fmt.Println()

	// 5. Модульное смещение
	fmt.Println("--- 5. Модульное смещение (modulo bias) ---")
	fmt.Println("  rand() % N неравномерно, если RAND_MAX % N != 0.")
	fmt.Println("  Пример: rand() возвращает [0, 7], N=5:")
	var biased [5]int
	for v := 0; v <= 7; v++ {
		biased[v%5]++
	}
	for i, c := range biased {
		fmt.Printf("    %d: %d раз\n", i, c)
	}
	fmt.Println("  0, 1, 2 выпадают чаще! IntN() избегает этой проблемы.")
	fmt.Println()

	// 6. Энтропия
	fmt.Println("--- 6. Оценка энтропии ---")
	prngBuckets := [256]int{}
	src := mrand.NewChaCha8([32]byte{99})
	rng := mrand.New(src)
	for i := 0; i < 100_000; i++ {
		prngBuckets[rng.IntN(256)]++
	}
	h := 0.0
	for _, c := range prngBuckets {
		if c > 0 {
			p := float64(c) / 100_000.0
			h -= p * math.Log2(p)
		}
	}
	fmt.Printf("  Энтропия ChaCha8 (100K выборок, 256 значений): %.4f бит\n", h)
	fmt.Printf("  Идеал: %.4f бит\n", math.Log2(256))

	fmt.Println("\n--- Итог ---")
	fmt.Println("Go 1.22+: math/rand/v2 с ChaCha8, автосев от ОС")
	fmt.Println("crypto/rand — для криптографии, токенов, ключей")
	fmt.Println("Правило: если значение влияет на безопасность → crypto/rand")
}
