package misc

import (
	"bytes"
	"database/sql/driver"
	"encoding/hex"
	"errors"
	"github.com/NebulousLabs/fastrand"
	"fmt"
)

type UUID [16]byte

func (u UUID) Marshal() ([]byte, error) {
	return u[:], nil
}

func (u UUID) MarshalTo(buf []byte) (n int, err error) {
	if len(u) == 0 {
		return 0, nil
	}
	copy(buf, u[:])
	return len(u), nil
}
func (u *UUID) Unmarshal(buf []byte) error {
	if len(buf) != 16 {
		return fmt.Errorf("invalid UUID (got %d bytes)", len(buf))
	}
	copy(u[:], buf)
	return nil
}

func (u UUID) Compare(other UUID) int {
	return bytes.Compare(u[:], other[:])
}

func (u UUID) Equal(other UUID) bool {
	return u.Compare(other) == 0
}
func (u *UUID) UnmarshalJSON(from []byte) error {
	quote := []byte("\"")
	quoteSize := len(quote)

	if len(from) < quoteSize*2 {
		return errors.New("invalid quote notation")
	}

	if !bytes.HasPrefix(from, quote) || !bytes.HasSuffix(from, quote) {
		return errors.New("invalid quote notation")
	} else if err := u.Unmarshal(from[quoteSize:len(from)-quoteSize]); err != nil {
		return err
	}
	return nil
}

func (u UUID) MarshalJSON() ([]byte, error) {
	var buffer bytes.Buffer
	buffer.WriteRune('"')
	buffer.WriteString(hex.EncodeToString(u[:]))
	buffer.WriteRune('"')
	return buffer.Bytes(), nil
}

func (u *UUID) Size() int {
	if u == nil {
		return 0
	}
	if len(*u) == 0 {
		return 0
	}
	return 16
}

func NewUUID() (u UUID) {
	newObj := UUID{}
	newObj.Random()
	return newObj
}

func (u *UUID) UUIDFromHexString(buf []byte) (error) {
	hexBuf := make([]byte, hex.DecodedLen(len(buf)))
	if _, err := hex.Decode(hexBuf, buf); err != nil {
		return err
	} else if err := u.Unmarshal(hexBuf); err != nil {
		return err
	}
	return nil
}

func (u UUID) ToHexString() (string) {
	return hex.EncodeToString(u[:])
}

// Scan implements the Scanner interface.
func (u *UUID) Scan(src interface{}) error {
	if src == nil {
		return nil
	}

	b, ok := src.([]byte)
	if !ok {
		return errors.New("Scan source was not []bytes")
	}

	u.UUIDFromHexString(b)
	return nil
}

// Value implements the driver Valuer interface.
func (u UUID) Value() (driver.Value, error) {
	return u.ToHexString(), nil
}

func (u UUID) Random() {
	fastrand.Read(u[:])
	u[6] = (u[6] & 0x0f) | 0x40 // Version 4
	u[8] = (u[8] & 0x3f) | 0x80 // Variant is 10
}
