package uuid

/****************
 * Date: 14/02/14
 * Time: 7:43 PM
 ***************/

import (
	"bytes"
	"log"
	seed "math/rand"
	"net"
	"sync"
)


// **************************************************** State

func SetupCustomStateSaver(pSaver StateSaver) {
	state.Lock()
	pSaver.Init(&state)
	state.init()
	state.Unlock()
}

// Holds package information about the current
// state of the UUID generator
type State struct {

	// A flag which informs whether to
	// randomly create a node id
	randomNode bool

	// A flag which informs whether to
	// randomly create the sequence
	randomSequence bool

	// the last time UUID was saved
	past Timestamp

	// the next time the state will be saved
	next Timestamp

	// the last node which saved a UUID
	node []byte

	// An iterated value to help ensure different
	// values across the same domain
	sequence uint16

	sync.Mutex

	// save state interface
	saver StateSaver
}

// Changes the state with current data
// Compares the current found node to the last node stored,
// If they are the same or randomSequence is already set due
// to an earlier read issue then the sequence is randomly generated
// else if there is an issue with the time the sequence is incremented
func (o *State) read(pNow Timestamp, pNode net.HardwareAddr) {
	if bytes.Equal([]byte(pNode), o.node) || o.randomSequence {
		o.sequence = uint16(seed.Int()) & 0x3FFF
	} else if pNow < o.past {
		o.sequence++
	}
	o.past = pNow
	o.node = pNode
}

func (o *State) persist() {
	if o.saver != nil {
		o.saver.Save(o)
	}
}

// Initialises the UUID state when the package is first loaded
// it first attempts to decode the file state into State
// if this file does not exist it will create the file and do a flush
// of the random state which gets loaded at package runtime
// second it will attempt to resolve the current hardware address nodeId
// thirdly it will check the state of the clock
func (o *State) init() {
	if o.saver != nil {
		intfcs, err := net.Interfaces()
		if err != nil {
			log.Println("uuid.State.init: address error: will generate random node id instead", err)
			return
		}
		a := getHardwareAddress(intfcs)
		if a == nil {
			log.Println("uuid.State.init: address error: will generate random node id instead", err)
			return
		}
		// Don't use random as we have a real address
		o.randomSequence = false
		if bytes.Equal([]byte(a), state.node) {
			state.sequence++
		}
		state.node = a
		state.randomNode = false
	}
}

func getHardwareAddress(pInterfaces []net.Interface) net.HardwareAddr {
	for _, inter := range pInterfaces {
		// Initially I could multicast out the Flags to get
		// whether the interface was up but started failing
		if (inter.Flags & (1 << net.FlagUp)) != 0 {
			//if inter.Flags.String() != "0" {
			if addrs, err := inter.Addrs(); err == nil {
				for _, addr := range addrs {
					if addr.String() != "0.0.0.0" && !bytes.Equal([]byte(inter.HardwareAddr), make([]byte, len(inter.HardwareAddr))) {
						return inter.HardwareAddr
					}
				}
			}
		}
	}
	return nil
}

// *********************************************** StateSaver interface

// Use this interface to setup a custom state saver if you wish to have
// v1 UUIDs based on your node id and constant time.
type StateSaver interface {
	// Init is run if Setup() is false
	// Init should setup the system to save the state
	Init(*State)

	// Save saves the state and is called only if const V1Save and
	// Setup() is true
	Save(*State)
}