////////////////////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 xx network SEZC                                                       //
//                                                                                        //
// Use of this source code is governed by a license that can be found in the LICENSE file //
////////////////////////////////////////////////////////////////////////////////////////////

// Contains the generic ID type, which is a byte array that represents an entity
// ID. The first bytes in the array contain the actual ID data while the last
// byte contains the ID type, which is either generic, gateway, node, or user.
// IDs can be hard coded or generated using a cryptographic function found in
// crypto.
package id

import (
	"encoding/base64"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"github.com/pkg/errors"
	jww "github.com/spf13/jwalterweatherman"
	"io"
	"regexp"
	"testing"
)

const (
	// Length of the ID data
	dataLen = 32

	// Length of the full ID array
	ArrIDLen = dataLen + 1

	// Alphanumeric contains the regular expression to search for an alphanumeric string.
	Alphanumeric string = "^[a-zA-Z0-9]+$"
)

// regexAlphanumeric is the regex for searching for an alphanumeric string.
var regexAlphanumeric = regexp.MustCompile(Alphanumeric)

// ID is a fixed-length array containing data that services as an identifier for
// entities. The first 32 bytes hold the ID data while the last byte holds the
// type, which describes the type of entity the ID belongs to.
type ID [ArrIDLen]byte

// Marshal returns the ID bytes in wire format.
func (id *ID) Marshal() []byte {
	return id.Bytes()
}

// Unmarshal unmarshalls the ID wire format into an ID object.
func Unmarshal(data []byte) (*ID, error) {
	// Return an error if the provided data is not the correct length
	if len(data) != ArrIDLen {
		return nil, errors.Errorf("Failed to unmarshal ID: length of data "+
			"must be %d, length received is %d", ArrIDLen, len(data))
	}

	return copyID(data), nil
}

// Bytes returns a copy of an ID as a byte slice. Note that Bytes() is used by
// Marshal() and any changes made here will affect how Marshal() functions.
func (id *ID) Bytes() []byte {
	if id == nil {
		jww.FATAL.Panicf("%+v", errors.Errorf("Failed to get bytes of ID: ID is nil."))
	}

	newBytes := make([]byte, ArrIDLen)
	copy(newBytes, id[:])

	return newBytes
}

// Cmp determines whether two IDs are equal. Returns true if they are equal and
// false otherwise.
func (id *ID) Cmp(y *ID) bool {
	if id == nil || y == nil {
		jww.FATAL.Panicf("%+v", errors.Errorf("Failed to compare IDs: one or both IDs are nil."))
	}

	return *id == *y
}

// DeepCopy creates a copy of an ID.
func (id *ID) DeepCopy() *ID {
	if id == nil {
		jww.FATAL.Panicf("%+v", errors.Errorf("Failed to create a copy of ID: ID is nil."))
	}

	return copyID(id.Bytes())
}

// String converts an ID to a string via base64 encoding.
func (id *ID) String() string {
	return base64.StdEncoding.EncodeToString(id.Bytes())
}

// GetType returns the ID's type. It is the last byte of the array.
func (id *ID) GetType() Type {
	if id == nil {
		jww.FATAL.Panicf("%+v", errors.Errorf("Failed to get ID type: ID is nil."))
	}

	return Type(id[ArrIDLen-1])
}

// SetType changes the ID type by setting the last byte to the specified type.
func (id *ID) SetType(idType Type) {
	if id == nil {
		jww.FATAL.Panicf("%+v", errors.Errorf("Failed to set ID type: ID is nil."))
	}

	id[ArrIDLen-1] = byte(idType)
}

// UnmarshalJSON is part of the json.Unmarshaler interface and allows IDs to be
// marshaled into JSON.
func (id *ID) UnmarshalJSON(b []byte) error {
	var buff []byte
	if err := json.Unmarshal(b, &buff); err != nil {
		return err
	}

	newID, err := Unmarshal(buff)
	if err != nil {
		return err
	}

	*id = *newID

	return nil
}

// MarshalJSON is part of the json.Marshaler interface and allows IDs to be
// marshaled into JSON.
func (id ID) MarshalJSON() ([]byte, error) {
	return json.Marshal(id.Marshal())
}

// NewRandomID generates a random ID using the passed in io.Reader r
// and sets the ID to Type t. If the base64 string of the generated
// ID does not begin with an alphanumeric character, then another ID
// is generated until the encoding begins with an alphanumeric character.
func NewRandomID(r io.Reader, t Type) (*ID, error) {
	for {
		// Generate random bytes
		idBytes := make([]byte, ArrIDLen)
		if _, err := r.Read(idBytes); err != nil {
			return nil, errors.Errorf("failed to generate random bytes for new "+
				"ID: %+v", err)
		}

		// Create ID from bytes
		id := copyID(idBytes)

		// Set new ID type
		id.SetType(t)

		// Avoid the first character being a special character
		base64Id := id.String()
		if regexAlphanumeric.MatchString(string(base64Id[0])) {
			return id, nil
		}
	}

}

// NewIdFromBytes creates a new ID from the supplied byte slice. It is similar
// to Unmarshal() but does not do any error checking. If the data is longer than
// ArrIDLen, then it is truncated. If it is shorter, then the remaining bytes
// are filled with zeroes. This function is for testing purposes only.
func NewIdFromBytes(data []byte, x interface{}) *ID {
	// Ensure that this function is only run in testing environments
	switch x.(type) {
	case *testing.T, *testing.M, *testing.B:
		break
	default:
		panic("NewIdFromBytes() can only be used for testing.")
	}

	return copyID(data)
}

// NewIdFromString creates a new ID from the given string and type. If the
// string is longer than dataLen, then it is truncated. If it is shorter, then
// the remaining bytes are filled with zeroes. This function is for testing
// purposes only.
func NewIdFromString(idString string, idType Type, x interface{}) *ID {
	// Ensure that this function is only run in testing environments
	switch x.(type) {
	case *testing.T, *testing.M, *testing.B:
		break
	default:
		panic("NewIdFromString() can only be used for testing.")
	}

	// Convert the string to bytes and create new ID from it
	newID := NewIdFromBytes([]byte(idString), x)

	// Set the ID type
	newID.SetType(idType)

	return newID
}

// NewIdFromUInt converts the specified uint64 into bytes and returns a new ID
// based off it with the specified ID type. The remaining space of the array is
// filled with zeros. This function is for testing purposes only.
func NewIdFromUInt(idUInt uint64, idType Type, x interface{}) *ID {
	// Ensure that this function is only run in testing environments
	switch x.(type) {
	case *testing.T, *testing.M, *testing.B:
		break
	default:
		panic("NewIdFromUInt() can only be used for testing.")
	}

	// Create the new ID
	newID := new(ID)

	// Convert the uints to bytes
	binary.BigEndian.PutUint64(newID[:], idUInt)

	// Set the ID's type
	newID.SetType(idType)

	return newID
}

// HexEncode encodes the Id without 33rd type byte
func (id *ID) HexEncode() string {
	return "0x" + hex.EncodeToString(id.Bytes()[:32])
}

// NewIdFromUInts converts the specified uint64 array into bytes and returns a
// new ID based off it with the specified ID type. Unlike NewIdFromUInt(), the
// four uint64s provided fill the entire ID array. This function is for testing
// purposes only.
func NewIdFromUInts(idUInts [4]uint64, idType Type, x interface{}) *ID {
	// Ensure that this function is only run in testing environments
	switch x.(type) {
	case *testing.T, *testing.M, *testing.B:
		break
	default:
		panic("NewIdFromUInts() can only be used for testing.")
	}

	// Create the new ID
	newID := new(ID)

	// Convert the uints to bytes
	for i, idUint := range idUInts {
		binary.BigEndian.PutUint64(newID[i*8:], idUint)
	}

	// Set the ID's type
	newID.SetType(idType)

	return newID
}

// copyID copies the bytes into a new ID.
func copyID(buff []byte) *ID {
	newID := new(ID)
	copy(newID[:], buff)
	return newID
}