// Copyright 2016 Luke Shumaker
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package sd_id128 reads, generates, and processes 128-bit ID values.
//
// These 128-bit IDs are a generalization of DCE UUIDs (of which
// Microsoft GUIDs are one implementation), but use a simpler string
// format.  This package imposes no structure on the used IDs, unlike
// UUIDs, but are fully compatible with those types of IDs.
//
// DCE UUID: https://tools.ietf.org/html/rfc4122
//
// Microsoft GUID: https://msdn.microsoft.com/en-us/library/windows/desktop/aa373931(v=vs.85).aspx
//
// The UUID string representation is a (zero-padded) 32-digit
// hexadecimal number with a hyphen after the 8th, 12th, 16th, and
// 20th digits (for a total length of 36 glyphs).  This package
// supports this format, but the primary ("plain") string
// representation is simply a (zero-padded) 32-digit hexadecimal
// number.
//
// For example:
//
//     Plain: 6ba7b8109dad11d180b400c04fd430c8
//     UUID : 6ba7b810-9dad-11d1-80b4-00c04fd430c8
//
// Several restrictions are imposed on the structure of UUIDs.  Most
// notably, the most significant 1-3 bits of the 8th octet specify the
// UUID type ("variant"); unless dealing with legacy UUIDs, the most
// significant 2 bits of the octet will be "10", specifying a RFC 4122
// type UUID.  Assuming RFC 4122 type, the most significant 4 bits of
// the 6th octet then specify the sub-type ("version"), which could
// impose complicated requirements on the rest of the UUID.
//
// This package doesn't really care about all of these UUID structure
// requirements.  Nothing in this package ever inspects an ID's
// structure.  However, because it is anticipated that the IDs
// generated by this package might be used in a context where a UUID
// is required, many of the functions that emit an ID ensure that the
// ID emitted is structured to be a valid UUID.
package sd_id128

import (
	"errors"
)

var (
	ErrInvalid = errors.New("Invalid ID")
	ErrNone    = errors.New("No Invocation ID set")
)

type ID128 [16]byte

// String formats the ID as a plain zero-padded 32-digit hexadecimal
// number.
func (id ID128) String() string {
	var s [32]byte
	for n := range id {
		s[n*2] = hexchar(id[n] >> 4)
		s[n*2+1] = hexchar(id[n] & 0xF)
	}
	return string(s[:])
}

// UUID formats the ID as a UUID string.
func (id ID128) UUID() string {
	var s [36]byte
	k := 0
	for n := range id {
		switch n {
		case 4, 6, 8, 10:
			s[k] = '-'
			k++
		}
		s[k] = hexchar(id[n] >> 4)
		k++
		s[k] = hexchar(id[n] & 0xF)
		k++
	}
	return string(s[:])
}

// Parse parses a 128-bit ID represented either in the plain string
// format, or in the UUID string format.
//
// If you need to validate that the string is in one format or the
// other, then you should use the ParsePlain or ParseUUID functions.
//
// If the input string does not match either of these formats, then
// ErrInvalid is returned.
func Parse(s string) (ID128, error) {
	ret := ID128{}
	isGuid := false

	i := 0
	n := 0
	for n < 16 {
		if s[i] == '-' {
			if i == 8 {
				isGuid = true
			} else if i == 13 || i == 18 || i == 23 {
				if !isGuid {
					return ID128{}, ErrInvalid
				}
			} else {
				return ID128{}, ErrInvalid
			}
			i++
		} else {
			if i+2 >= len(s) {
				return ID128{}, ErrInvalid
			}

			a, err := unhexchar(s[i])
			i++
			if err != nil {
				return ID128{}, err
			}

			b, err := unhexchar(s[i])
			i++
			if err != nil {
				return ID128{}, err
			}

			ret[n] = (a << 4) | b
			n++
		}
	}

	if !((i == 36 && isGuid) || (i == 32 && !isGuid)) {
		return ID128{}, ErrInvalid
	}
	if i != len(s) {
		return ID128{}, ErrInvalid
	}

	return ret, nil
}

// ParsePlain parses a 128-bit ID represented in the plain string
// format.
//
// If you would like more flexibility, and would like to be accept
// either the plain string format or the UUID string format, then you
// should use the Parse function.
//
// If the input string is not a 32-digit hexadecimal number, then
// ErrInvalid is returned.
func ParsePlain(s string) (ID128, error) {
	if len(s) != 32 {
		return ID128{}, ErrInvalid
	}
	return Parse(s)
}

// ParsePlain parses a 128-bit ID represented in the UUID string
// format.
//
// If you would like more flexibility, and would like to be accept
// either the plain string format or the UUID string format, then you
// should use the Parse function.
//
// If the input string is not a 36-character UUID string, then
// ErrInvalid is returned.  This function does not validate the
// variant or version bit fields, nor does it validate any structure
// implied by them.
func ParseUUID(s string) (ID128, error) {
	if len(s) != 36 {
		return ID128{}, ErrInvalid
	}
	return Parse(s)
}