/*
 * MIT License
 *
 * Copyright (c) 2020 Nicolas JUHEL
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 *
 */

package curves

import (
	"crypto/tls"
	"math"
	"slices"
	"strings"
)

type Cipher uint16

const (
	Unknown Cipher = Cipher(0)

	// TLS 1.0 - 1.2 cipher suites.
	TLS_RSA_WITH_AES_128_GCM_SHA256               = Cipher(tls.TLS_RSA_WITH_AES_128_GCM_SHA256)
	TLS_RSA_WITH_AES_256_GCM_SHA384               = Cipher(tls.TLS_RSA_WITH_AES_256_GCM_SHA384)
	TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256         = Cipher(tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
	TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256       = Cipher(tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)
	TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384         = Cipher(tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384)
	TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384       = Cipher(tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)
	TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256   = Cipher(tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256)
	TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = Cipher(tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256)

	// TLS 1.3 cipher suites.
	TLS_AES_128_GCM_SHA256       = Cipher(tls.TLS_AES_128_GCM_SHA256)
	TLS_AES_256_GCM_SHA384       = Cipher(tls.TLS_AES_256_GCM_SHA384)
	TLS_CHACHA20_POLY1305_SHA256 = Cipher(tls.TLS_CHACHA20_POLY1305_SHA256)
)
const (
	// TLS 1.0 - 1.2 cipher suites no sha for retro compt
	TLS_RSA_WITH_AES_128_GCM Cipher = iota + 1
	TLS_RSA_WITH_AES_256_GCM
	TLS_ECDHE_RSA_WITH_AES_128_GCM
	TLS_ECDHE_ECDSA_WITH_AES_128_GCM
	TLS_ECDHE_RSA_WITH_AES_256_GCM
	TLS_ECDHE_ECDSA_WITH_AES_256_GCM
	TLS_RSA_WITH_AES128_GCM Cipher = iota + 1
	TLS_RSA_WITH_AES256_GCM
	TLS_ECDHE_RSA_WITH_AES128_GCM
	TLS_ECDHE_ECDSA_WITH_AES128_GCM
	TLS_ECDHE_RSA_WITH_AES256_GCM
	TLS_ECDHE_ECDSA_WITH_AES256_GCM
	TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
	TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305

	// TLS 1.3 cipher suites retro compat
	TLS_AES_128_GCM
	TLS_AES_256_GCM
	TLS_AES128_GCM
	TLS_AES256_GCM
	TLS_CHACHA20_POLY1305
)

func List() []Cipher {
	return []Cipher{
		TLS_RSA_WITH_AES_128_GCM_SHA256,
		TLS_RSA_WITH_AES_256_GCM_SHA384,
		TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
		TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
		TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
		TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
		TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
		TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
		TLS_AES_128_GCM_SHA256,
		TLS_AES_256_GCM_SHA384,
		TLS_CHACHA20_POLY1305_SHA256,
	}
}

func ListString() []string {
	var res = make([]string, 0)
	for _, c := range List() {
		res = append(res, c.String())
	}
	return res
}

func Parse(s string) Cipher {
	s = strings.ToLower(s)
	s = strings.Replace(s, "\"", "", -1)
	s = strings.Replace(s, "'", "", -1)
	s = strings.Replace(s, "tls", "", -1)
	s = strings.Replace(s, ".", "_", -1)
	s = strings.Replace(s, "-", "_", -1)
	s = strings.Replace(s, " ", "_", -1)
	s = strings.TrimSpace(s)

	p := strings.Split(s, "_")

	switch {
	case containString(p, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256.Code()):
		return TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
	case containString(p, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256.Code()):
		return TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
	case containString(p, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384.Code()):
		return TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
	case containString(p, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384.Code()):
		return TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
	case containString(p, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256.Code()):
		return TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
	case containString(p, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256.Code()):
		return TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
	case containString(p, TLS_CHACHA20_POLY1305_SHA256.Code()):
		return TLS_CHACHA20_POLY1305_SHA256
	case containString(p, TLS_RSA_WITH_AES_128_GCM_SHA256.Code()):
		return TLS_RSA_WITH_AES_128_GCM_SHA256
	case containString(p, TLS_RSA_WITH_AES_256_GCM_SHA384.Code()):
		return TLS_RSA_WITH_AES_256_GCM_SHA384
	case containString(p, TLS_AES_128_GCM_SHA256.Code()):
		return TLS_AES_128_GCM_SHA256
	case containString(p, TLS_AES_256_GCM_SHA384.Code()):
		return TLS_AES_256_GCM_SHA384
	// retro compat
	case containString(p, TLS_ECDHE_RSA_WITH_AES_128_GCM.Code()):
		return TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
	case containString(p, TLS_ECDHE_ECDSA_WITH_AES_128_GCM.Code()):
		return TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
	case containString(p, TLS_ECDHE_RSA_WITH_AES_256_GCM.Code()):
		return TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
	case containString(p, TLS_ECDHE_ECDSA_WITH_AES_256_GCM.Code()):
		return TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
	case containString(p, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305.Code()):
		return TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
	case containString(p, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305.Code()):
		return TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
	case containString(p, TLS_CHACHA20_POLY1305.Code()):
		return TLS_CHACHA20_POLY1305_SHA256
	case containString(p, TLS_RSA_WITH_AES_128_GCM.Code()):
		return TLS_RSA_WITH_AES_128_GCM_SHA256
	case containString(p, TLS_RSA_WITH_AES_256_GCM.Code()):
		return TLS_RSA_WITH_AES_256_GCM_SHA384
	case containString(p, TLS_AES_128_GCM.Code()):
		return TLS_AES_128_GCM_SHA256
	case containString(p, TLS_AES_256_GCM.Code()):
		return TLS_AES_256_GCM_SHA384

	case containString(p, TLS_ECDHE_RSA_WITH_AES128_GCM.Code()):
		return TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
	case containString(p, TLS_ECDHE_ECDSA_WITH_AES128_GCM.Code()):
		return TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
	case containString(p, TLS_ECDHE_RSA_WITH_AES256_GCM.Code()):
		return TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
	case containString(p, TLS_ECDHE_ECDSA_WITH_AES256_GCM.Code()):
		return TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
	case containString(p, TLS_RSA_WITH_AES128_GCM.Code()):
		return TLS_RSA_WITH_AES_128_GCM_SHA256
	case containString(p, TLS_RSA_WITH_AES256_GCM.Code()):
		return TLS_RSA_WITH_AES_256_GCM_SHA384
	case containString(p, TLS_AES128_GCM.Code()):
		return TLS_AES_128_GCM_SHA256
	case containString(p, TLS_AES256_GCM.Code()):
		return TLS_AES_256_GCM_SHA384
	// not found
	default:
		return Unknown
	}
}

func containString[S ~[]string](s S, v S) bool {
	keys := []string{
		"chacha20",
		"poly1305",
		"ecdhe",
		"rsa",
		"ecdsa",
		"aes",
		"128",
		"256",
		"sha256",
		"sha384",
		"gcm",
	}

	for _, k := range keys {
		if !keyContainString(s, v, k) {
			return false
		}
	}

	return true
}

func keyContainString[S ~[]string](s S, v S, k string) bool {
	if slices.Contains(s, k) && !slices.Contains(v, k) {
		return false
	} else if !slices.Contains(s, k) && slices.Contains(v, k) {
		return false
	}

	return true
}

func ParseInt(d int) Cipher {
	if d > math.MaxUint16 {
		d = math.MaxUint16
	} else if d < 1 {
		d = 0
	}

	switch uint16(d) {
	case tls.TLS_RSA_WITH_AES_128_GCM_SHA256:
		return TLS_RSA_WITH_AES_128_GCM_SHA256
	case tls.TLS_RSA_WITH_AES_256_GCM_SHA384:
		return TLS_RSA_WITH_AES_256_GCM_SHA384
	case tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
		return TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
	case tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
		return TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
	case tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
		return TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
	case tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
		return TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
	case tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
		return TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
	case tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
		return TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
	case tls.TLS_AES_128_GCM_SHA256:
		return TLS_AES_128_GCM_SHA256
	case tls.TLS_AES_256_GCM_SHA384:
		return TLS_AES_256_GCM_SHA384
	case tls.TLS_CHACHA20_POLY1305_SHA256:
		return TLS_CHACHA20_POLY1305_SHA256
	default:
		return Unknown
	}
}

func Check(cipher uint16) bool {
	if c := ParseInt(int(cipher)); c == Unknown {
		return false
	}
	return true
}

func parseBytes(p []byte) Cipher {
	return Parse(string(p))
}
