package secio

import (
	"errors"
	"fmt"
	"strings"

	"crypto/aes"
	"crypto/cipher"
	"crypto/hmac"
	"crypto/sha512"
	"hash"

	ci "github.com/libp2p/go-libp2p-core/crypto"
	sha256 "github.com/minio/sha256-simd"
)

// SupportedExchanges is the list of supported ECDH curves
var SupportedExchanges = DefaultSupportedExchanges

const DefaultSupportedExchanges = "P-256,P-384,P-521"

// SupportedCiphers is the list of supported Ciphers
var SupportedCiphers = DefaultSupportedCiphers

const DefaultSupportedCiphers = "AES-256,AES-128"

// SupportedHashes is the list of supported Hashes
var SupportedHashes = DefaultSupportedHashes

const DefaultSupportedHashes = "SHA256,SHA512"

// HMAC carries a hash and its size
type HMAC struct {
	hash.Hash
	size int
}

// encParams represent encryption parameters
type encParams struct {
	// keys
	permanentPubKey ci.PubKey
	ephemeralPubKey []byte
	keys            ci.StretchedKeys

	// selections
	curveT  string
	cipherT string
	hashT   string

	// cipher + mac
	cipher cipher.Stream
	mac    HMAC
}

func (e *encParams) makeMacAndCipher() error {
	m, err := newMac(e.hashT, e.keys.MacKey)
	if err != nil {
		return err
	}

	bc, err := newBlockCipher(e.cipherT, e.keys.CipherKey)
	if err != nil {
		return err
	}

	e.cipher = cipher.NewCTR(bc, e.keys.IV)
	e.mac = m
	return nil
}

func newMac(hashType string, key []byte) (HMAC, error) {
	switch hashType {
	case "SHA512":
		return HMAC{hmac.New(sha512.New, key), sha512.Size}, nil
	case "SHA256":
		return HMAC{hmac.New(sha256.New, key), sha256.Size}, nil
	default:
		return HMAC{}, fmt.Errorf("Unrecognized hash type: %s", hashType)
	}
}

func newBlockCipher(cipherT string, key []byte) (cipher.Block, error) {
	switch cipherT {
	case "AES-128", "AES-256":
		return aes.NewCipher(key)
	default:
		return nil, fmt.Errorf("Unrecognized cipher type: %s", cipherT)
	}
}

// Determines which algorithm to use.  Note:  f(a, b) = f(b, a)
func selectBest(order int, p1, p2 string) (string, error) {
	var f, s []string
	switch {
	case order < 0:
		f = strings.Split(p2, ",")
		s = strings.Split(p1, ",")
	case order > 0:
		f = strings.Split(p1, ",")
		s = strings.Split(p2, ",")
	default: // Exact same preferences.
		p := strings.Split(p1, ",")
		return p[0], nil
	}

	for _, fc := range f {
		for _, sc := range s {
			if fc == sc {
				return fc, nil
			}
		}
	}

	return "", errors.New("No algorithms in common!")
}
