// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package uuid

import (
	"crypto/rand"
	"errors"
	"fmt"
	"strconv"
)

// The UUID reserved variants.
const (
	reservedRFC4122 byte = 0x40
)

// A UUID representation compliant with specification in RFC4122 document.
type UUID [16]byte

// New returns a new UUID using the RFC4122 algorithm.
func New() (UUID, error) {
	u := UUID{}
	// Set all bits to pseudo-random values.
	// NOTE: this takes a process-wide lock
	_, err := rand.Read(u[:])
	if err != nil {
		return u, err
	}
	u[8] = (u[8] | reservedRFC4122) & 0x7F // u.setVariant(ReservedRFC4122)

	var version byte = 4
	u[6] = (u[6] & 0xF) | (version << 4) // u.setVersion(4)
	return u, nil
}

// String returns the UUID in "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" format.
func (u UUID) String() string {
	return fmt.Sprintf("%x-%x-%x-%x-%x", u[0:4], u[4:6], u[6:8], u[8:10], u[10:])
}

// Parse parses a string formatted as "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
// or "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}" into a UUID.
func Parse(s string) (UUID, error) {
	var uuid UUID
	// ensure format
	switch len(s) {
	case 36:
		// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
	case 38:
		// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
		s = s[1:37]
	default:
		return uuid, errors.New("invalid UUID format")
	}
	if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
		return uuid, errors.New("invalid UUID format")
	}
	// parse chunks
	for i, x := range [16]int{
		0, 2, 4, 6,
		9, 11,
		14, 16,
		19, 21,
		24, 26, 28, 30, 32, 34} {
		b, err := strconv.ParseUint(s[x:x+2], 16, 8)
		if err != nil {
			return uuid, fmt.Errorf("invalid UUID format: %s", err)
		}
		uuid[i] = byte(b)
	}
	return uuid, nil
}
