package uuid

/****************
 * Date: 21/06/15
 * Time: 5:48 PM
 ***************/

import (
	"encoding/gob"
	"log"
	"os"
	"time"
)

func init() {
	gob.Register(stateEntity{})
}

func SetupFileSystemStateSaver(pConfig StateSaverConfig) {
	saver := &FileSystemSaver{}
	saver.saveReport = pConfig.SaveReport
	saver.saveSchedule = int64(pConfig.SaveSchedule)
	SetupCustomStateSaver(saver)
}

// A wrapper for default setup of the FileSystemStateSaver
type StateSaverConfig struct {

	// Print save log
	SaveReport bool

	// Save every x nanoseconds
	SaveSchedule time.Duration
}

// ***********************************************  StateEntity

// StateEntity acts as a marshaller struct for the state
type stateEntity struct {
	Past     Timestamp
	Node     []byte
	Sequence uint16
}

// This implements the StateSaver interface for UUIDs
type FileSystemSaver struct {
	cache        *os.File
	saveState    uint64
	saveReport   bool
	saveSchedule int64
}

// Saves the current state of the generator
// If the scheduled file save is reached then the file is synced
func (o *FileSystemSaver) Save(pState *State) {
	if pState.past >= pState.next {
		err := o.open()
		defer o.cache.Close()
		if err != nil {
			log.Println("uuid.State.save:", err)
			return
		}
		// do the save
		o.encode(pState)
		// a tick is 100 nano seconds
		pState.next = pState.past + Timestamp(o.saveSchedule / 100)
		if o.saveReport {
			log.Printf("UUID STATE: SAVED %d", pState.past)
		}
	}
}

func (o *FileSystemSaver) Init(pState *State) {
	pState.saver = o
	err := o.open()
	defer o.cache.Close()
	if err != nil {
		if os.IsNotExist(err) {
			log.Printf("'%s' created\n", "uuid.SaveState")
			var err error
			o.cache, err = os.Create(os.TempDir() + "/state.unique")
			if err != nil {
				log.Println("uuid.State.init: SaveState error:", err)
				goto pastInit
			}
			o.encode(pState)
		} else {
			log.Println("uuid.State.init: SaveState error:", err)
			goto pastInit
		}
	}
	err = o.decode(pState)
	if err != nil {
		goto pastInit
	}
	pState.randomSequence = false
pastInit:
	if timestamp() <= pState.past {
		pState.sequence++
	}
	pState.next = pState.past
}

func (o *FileSystemSaver) reset() {
	o.cache.Seek(0, 0)
}

func (o *FileSystemSaver) open() error {
	var err error
	o.cache, err = os.OpenFile(os.TempDir()+"/state.unique", os.O_RDWR, os.ModeExclusive)
	return err
}

// Encodes State generator data into a saved file
func (o *FileSystemSaver) encode(pState *State) {
	// ensure reader state is ready for use
	o.reset()
	enc := gob.NewEncoder(o.cache)
	// Wrap private State data into the StateEntity
	err := enc.Encode(&stateEntity{pState.past, pState.node, pState.sequence})
	if err != nil {
		log.Panic("UUID.encode error:", err)
	}
}

// Decodes StateEntity data into the main State
func (o *FileSystemSaver) decode(pState *State) error {
	o.reset()
	dec := gob.NewDecoder(o.cache)
	entity := stateEntity{}
	err := dec.Decode(&entity)
	if err != nil {
		log.Println("uuid.decode error:", err)
		return err
	}
	pState.past = entity.Past
	pState.node = entity.Node
	pState.sequence = entity.Sequence
	return nil
}