// Copyright 2022 Paul Greenberg greenpau@outlook.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package util

import (
	"crypto/rand"
	"encoding/base32"
	"io"
	//	"math"
	"math/big"
	mathrand "math/rand"
	"unicode"
)

const charset = "abcdefghijklmnopqrstuvwxyz" +
	"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

var charsetTable = &unicode.RangeTable{
	R16: []unicode.Range16{
		{0x0061, 0x007a, 1}, // a-z, where a is hex 61
		{0x0041, 0x005a, 1}, // A-Z, where A is hex 41
		{0x0030, 0x0039, 1}, // 0-9, where 0 is hex 30
	},
	LatinOffset: 1,
}

func genRandInt(i int) uint32 {
	n, err := rand.Int(rand.Reader, big.NewInt(int64(i)))
	if err != nil {
		return uint32(mathrand.Intn(i))
	}
	//if n.Uint64() > math.MaxUint32+1 {
	//	return uint32(n.Uint64() & uint32(0xFFFFFFFF))
	//}
	return uint32(n.Uint64())
}

func gen(length uint32, charset string) string {
	charsetLen := byte(len(charset))
	b := make([]byte, length)
	if _, err := io.ReadFull(rand.Reader, b); err != nil {
		// for i uint32 := 0; i < length; i++ {
		for i := uint32(0); i < length; {
			b[i] = charset[mathrand.Intn(len(charset))]
		}
		return string(b)
	}

	for i, char := range b {
		b[i] = byte(charset[char%charsetLen])
	}

	return string(b)
}

// GetRandomString returns X character long random string.
func GetRandomString(i int) string {
	if i < 1 {
		i = 40
	}
	return gen(uint32(i), charset)
}

// GetRandomStringFromRange generates random string of a random length. The
// random lenght is bounded by a and b.
func GetRandomStringFromRange(a, b int) string {
	var i uint32
	if a > b {
		i = genRandInt(a-b) + uint32(b)
	} else {
		i = genRandInt(b-a) + uint32(a)
	}
	return gen(i, charset)
}

// GetRandomEncodedStringFromRange return the number returned by
// GetRandomStringFromRange() and encoded with Base32
func GetRandomEncodedStringFromRange(a, b int) string {
	s := GetRandomStringFromRange(a, b)
	return base32.StdEncoding.EncodeToString([]byte(s))
}

// GetRandomStringFromRangeWithCharset generates random string of a random length. The
// random lenght is bounded by a and b. The charset is provided.
func GetRandomStringFromRangeWithCharset(a, b int, cs string) string {
	var i uint32
	if a > b {
		i = genRandInt(a-b) + uint32(b)
	} else {
		i = genRandInt(b-a) + uint32(a)
	}
	return gen(i, cs)
}

// IsMalformedRandomString checks whether the provided string was generated by
// the GetRandomStringFromRange() function.
func IsMalformedRandomString(s string, a, b int) bool {
	switch {
	case len(s) == 0:
		return false
	case (len(s) < a) || (len(s) > b):
		return true
	}
	for _, char := range s {
		if !unicode.IsLetter(char) && !unicode.IsNumber(char) {
			return true
		}
		if !unicode.In(char, charsetTable) {
			return false
		}
	}
	return false
}

// SanitizeSessionID sanitizes provided Session ID.
func SanitizeSessionID(s string) string {
	if IsMalformedRandomString(s, 0, 46) {
		return "MALFORMED"
	}
	return s
}
