package tools

import (
	"context"
	"errors"
	"math/big"
	"math/rand"
	"time"
)

var (
	rnd       = rand.New(rand.NewSource(time.Now().UnixNano())) // Генератор случайных чисел
	intZero   = big.NewInt(0)
	intMax, _ = new(big.Int).SetString("9223372036854775807", 10)
)

func intCopy(val *big.Int) (res *big.Int) {
	if val != nil {
		res = new(big.Int).Set(val)
	}
	return
}

// Возвращает число или 0 при пустом указателе
func mustBigInt(val *big.Int) (res *big.Int) {
	res = val
	if res == nil {
		res = big.NewInt(0)
	}
	return res
}

func IsPrime(val *big.Int) bool {
	return val.ProbablyPrime(100)
}

func SearchP(min *big.Int, ctx context.Context) (p *big.Int, g *big.Int, err error) {
	ch := make(chan struct{})
	go func() {
		defer func() {
			close(ch)
		}()
		p = intCopy(min)
		var check bool
		for {
			select {
			case <-ctx.Done():
				err = errors.New("Поиск поля и первообразного корня - время вышло")
				return
			default:
				if g, check = CheckP(p); check {
					ch <- struct{}{}
					return
				}
				p = Add64(p, 1)
			}
		}
	}()
	<-ch
	return
}

func CheckP(val *big.Int) (res *big.Int, check bool) {
	if IsPrime(val) {
		//log.Println("PRIME", val, Div64(Sub64(val, 1), 2))
		res = Div64(Sub64(val, 1), 2)
		check = IsPrime(res)
	}
	return
}

func SearchPrime(val *big.Int) *big.Int {
	rVal := new(big.Int).Set(val)
	//log.Println(rVal)
	for {
		if rVal.ProbablyPrime(100) {
			break
		}
		rVal.Add(rVal, big.NewInt(1))
		//log.Println(rVal)
	}
	return rVal
}

func Random(min, max *big.Int) *big.Int {
	if min == nil {
		min = intZero
	}
	if max == nil {
		max = intMax
	}
	res := new(big.Int).Rand(rnd, max)
	for res.Cmp(min) == -1 {
		res = new(big.Int).Rand(rnd, max)
	}
	return res
}

// Проверка кривой на сингулярность (4a³ + 27b² = 0)
func IsCurveSingular(a, b *big.Int) bool {
	l := Mul64(Exp64(a, 3), 4)
	r := Mul64(Exp64(b, 2), 27)
	return l.Add(l, r).Cmp(intZero) == 0
}

func CurveRandomParams(max *big.Int) (a, b, p *big.Int) {
	for {
		a, b = Random(intZero, max), Random(intZero, max)
		if !IsCurveSingular(a, b) {
			break
		}
	}
	p = SearchPrime(Random(intZero, max))
	return
}