package sm

import (
	"crypto"
	"hash"
)

// Hash identifies a cryptographic hash function that is implemented in another
// package.
// I try to keep it consistent with the standard library
type Hash struct {
	crypto.Hash
}

// HashFunc simply returns the value of h so that Hash implements SignerOpts.
func (h Hash) HashFunc() crypto.Hash {
	return h.Hash
}

func (h Hash) String() string {
	switch h {
	case SM3:
		return "SM3"
	case SM3WithID:
		return "SM3WithID"
	default:
		// return ""
		return h.Hash.String()
	}
}

var (
	SM3       Hash
	SM3WithID Hash
	maxHash   Hash
)
var SHA1 = Hash{crypto.SHA1}
var SHA256 = Hash{crypto.SHA256}

func init() {
	// A hack to get the maxHash in crypto.
	i := new(crypto.Hash)
	*i = 1
	defer func() {
		_ = recover()
		SM3 = Hash{Hash: *i}
		SM3WithID = Hash{Hash: *i + 1}
		maxHash = Hash{Hash: *i + 2}
	}()
	for {
		(*i).Size()
		*i++
	}
}

// Size returns the length, in bytes, of a digest resulting from the given hash
// function. It doesn't require that the hash function in question be linked
// into the program.
func (h Hash) Size() int {
	switch h {
	case SM3, SM3WithID:
		return 32
	default:
		return h.Hash.Size()
	}
}

var hashes = make(map[Hash]func() hash.Hash)

// New returns a new hash.Hash calculating the given hash function. New panics
// if the hash function is not linked into the binary.
func (h Hash) New() hash.Hash {
	if h.Hash >= SM3.Hash && h.Hash < maxHash.Hash {
		f := hashes[h]
		if f != nil {
			return f()
		}
	}
	return h.Hash.New()
}

// Available reports whether the given hash function is linked into the binary.
func (h Hash) Available() bool {
	if h.Hash >= SM3.Hash && h.Hash < maxHash.Hash {
		return true
	}
	return h.Hash.Available()
}

// RegisterHash registers a function that returns a new instance of the given
// hash function. This is intended to be called from the init function in
// packages that implement hash functions.
func RegisterHash(h Hash, f func() hash.Hash) {
	if h.Hash >= maxHash.Hash {
		panic("crypto: RegisterHash of unknown hash function")
	}
	// if h.Hash < SM3.Hash {
	// crypto.RegisterHash(h.Hash, f)
	// }
	hashes[h] = f
}
