package address

import (
	"crypto/rand"
	"fmt"

	"github.com/iotaledger/hive.go/crypto/ed25519"
	"github.com/mr-tron/base58"
	"golang.org/x/crypto/blake2b"

	"github.com/iotaledger/hive.go/marshalutil"
)

type Version = byte

type Digest = []byte

type Address [Length]byte

const (
	// every signature scheme has a version byte associated to it.
	VERSION_ED25519 = byte(1)
	VERSION_BLS     = byte(2)
)

// Random creates a random address, which can for example be used in unit tests.
// first byte (version) is also random

func Random() (address Address) {
	// generate a random sequence of bytes
	if _, err := rand.Read(address[:]); err != nil {
		panic(err)
	}
	return
}

// FromBase58 creates an address from a base58 encoded string.
func FromBase58(base58String string) (address Address, err error) {
	// decode string
	bytes, err := base58.Decode(base58String)
	if err != nil {
		return
	}

	// sanitize input
	if len(bytes) != Length {
		err = fmt.Errorf("base58 encoded string does not match the length of an address")

		return
	}

	// copy bytes to result
	copy(address[:], bytes)

	return
}

// FromED25519PubKey creates an address from an ed25519 public key.
func FromED25519PubKey(key ed25519.PublicKey) (address Address) {
	digest := blake2b.Sum256(key[:])

	address[0] = VERSION_ED25519
	copy(address[1:], digest[:])

	return
}

// FromBLSPubKey creates an address from marshaled BLS public key
// unmarshaled BLS public key conforms to interface kyber.Point

func FromBLSPubKey(pubKey []byte) (address Address) {
	digest := blake2b.Sum256(pubKey)

	address[0] = VERSION_BLS
	copy(address[1:], digest[:])

	return
}

// FromBytes unmarshals an address from a sequence of bytes.
func FromBytes(bytes []byte) (result Address, err error, consumedBytes int) {
	// parse the bytes
	marshalUtil := marshalutil.New(bytes)
	addressBytes, err := marshalUtil.ReadBytes(Length)
	if err != nil {
		return
	}
	copy(result[:], addressBytes)
	consumedBytes = marshalUtil.ReadOffset()

	return
}

// Parse is a wrapper for simplified unmarshaling of a byte stream using the marshalUtil package.
func Parse(marshalUtil *marshalutil.MarshalUtil) (Address, error) {
	if address, err := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return FromBytes(data) }); err != nil {
		return Address{}, err
	} else {
		return address.(Address), nil
	}
}

// Version returns the version of the address, which corresponds to the signature scheme that is used.
func (address *Address) Version() Version {
	return address[0]
}

// Digest returns the digest part of an address (i.e. the hashed version of the ed25519 public key)-
func (address *Address) Digest() Digest {
	return address[1:]
}

// Bytes returns a marshaled version of this address.
func (address Address) Bytes() []byte {
	return address[:]
}

// String returns a human readable (base58 encoded) version of the address.
func (address Address) String() string {
	return base58.Encode(address.Bytes())
}

// Length contains the length of an address (digest length = 32 + version byte length = 1).
const Length = 33