package native

import (
	"crypto/sha1"
	"crypto/sha256"
	"math"
)

// Borrowed from GoMySQL
// SHA1(SHA1(SHA1(password)), scramble) XOR SHA1(password)
func encryptedPasswd(password string, scramble []byte) (out []byte) {
	if len(password) == 0 {
		return
	}
	// stage1_hash = SHA1(password)
	// SHA1 encode
	crypt := sha1.New()
	crypt.Write([]byte(password))
	stg1Hash := crypt.Sum(nil)
	// token = SHA1(SHA1(stage1_hash), scramble) XOR stage1_hash
	// SHA1 encode again
	crypt.Reset()
	crypt.Write(stg1Hash)
	stg2Hash := crypt.Sum(nil)
	// SHA1 2nd hash and scramble
	crypt.Reset()
	crypt.Write(scramble)
	crypt.Write(stg2Hash)
	stg3Hash := crypt.Sum(nil)
	// XOR with first hash
	out = make([]byte, len(scramble))
	for ii := range scramble {
		out[ii] = stg3Hash[ii] ^ stg1Hash[ii]
	}
	return
}

// Hash password using MySQL 8+ method (SHA256)
func encryptedSHA256Passwd(password string, scramble []byte) []byte {
	if len(password) == 0 {
		return nil
	}

	// XOR(SHA256(password), SHA256(SHA256(SHA256(password)), scramble))

	crypt := sha256.New()
	crypt.Write([]byte(password))
	message1 := crypt.Sum(nil)

	crypt.Reset()
	crypt.Write(message1)
	message1Hash := crypt.Sum(nil)

	crypt.Reset()
	crypt.Write(message1Hash)
	crypt.Write(scramble)
	message2 := crypt.Sum(nil)

	for i := range message1 {
		message1[i] ^= message2[i]
	}

	return message1
}

// Old password handling based on translating to Go some functions from
// libmysql

// The main idea is that no password are sent between client & server on
// connection and that no password are saved in mysql in a decodable form.
//
// On connection a random string is generated and sent to the client.
// The client generates a new string with a random generator inited with
// the hash values from the password and the sent string.
// This 'check' string is sent to the server where it is compared with
// a string generated from the stored hash_value of the password and the
// random string.

// libmysql/my_rnd.c
type myRnd struct {
	seed1, seed2 uint32
}

const myRndMaxVal = 0x3FFFFFFF

func newMyRnd(seed1, seed2 uint32) *myRnd {
	r := new(myRnd)
	r.seed1 = seed1 % myRndMaxVal
	r.seed2 = seed2 % myRndMaxVal
	return r
}

func (r *myRnd) Float64() float64 {
	r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal
	r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal
	return float64(r.seed1) / myRndMaxVal
}

// libmysql/password.c
func pwHash(password []byte) (result [2]uint32) {
	var nr, add, nr2, tmp uint32
	nr, add, nr2 = 1345345333, 7, 0x12345671

	for _, c := range password {
		if c == ' ' || c == '\t' {
			continue // skip space in password
		}

		tmp = uint32(c)
		nr ^= (((nr & 63) + add) * tmp) + (nr << 8)
		nr2 += (nr2 << 8) ^ nr
		add += tmp
	}

	result[0] = nr & ((1 << 31) - 1) // Don't use sign bit (str2int)
	result[1] = nr2 & ((1 << 31) - 1)
	return
}

func encryptedOldPassword(password string, scramble []byte) []byte {
	if len(password) == 0 {
		return nil
	}
	scramble = scramble[:8]
	hashPw := pwHash([]byte(password))
	hashSc := pwHash(scramble)
	r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1])
	var out [8]byte
	for i := range out {
		out[i] = byte(math.Floor(r.Float64()*31) + 64)
	}
	extra := byte(math.Floor(r.Float64() * 31))
	for i := range out {
		out[i] ^= extra
	}
	return out[:]
}
