package sequence

import (
	"bytes"
	"code.google.com/p/weed-fs/go/glog"
	"code.google.com/p/weed-fs/go/metastore"
	"encoding/gob"
	"sync"
)

const (
	FileIdSaveInterval = 10000
)

type Sequencer interface {
	NextFileId(count int) (uint64, int)
}
type SequencerImpl struct {
	fileFullPath string

	volumeLock   sync.Mutex
	sequenceLock sync.Mutex

	FileIdSequence uint64
	fileIdCounter  uint64

	metaStore *metastore.MetaStore
}

func NewFileSequencer(filepath string) (m *SequencerImpl) {
	m = &SequencerImpl{fileFullPath: filepath}
	m.metaStore = &metastore.MetaStore{metastore.NewMetaStoreFileBacking()}
	m.initilize()
	return
}

func (m *SequencerImpl) initilize() {
	if !m.metaStore.Has(m.fileFullPath) {
		m.FileIdSequence = FileIdSaveInterval
		glog.V(0).Infoln("Setting file id sequence", m.FileIdSequence)
	} else {
		var err error
		if m.FileIdSequence, err = m.metaStore.GetUint64(m.fileFullPath); err != nil {
			if data, err := m.metaStore.Get(m.fileFullPath); err == nil {
				m.FileIdSequence = decode(data)
				glog.V(0).Infoln("Decoding old version of FileIdSequence", m.FileIdSequence)
			} else {
				glog.V(0).Infof("No existing FileIdSequence: %s", err)
			}
		} else {
			glog.V(0).Infoln("Loading file id sequence", m.FileIdSequence)
		}
		//in case the server stops between intervals
	}
	return
}

//count should be 1 or more
func (m *SequencerImpl) NextFileId(count int) (uint64, int) {
	if count <= 0 {
		return 0, 0
	}
	m.sequenceLock.Lock()
	defer m.sequenceLock.Unlock()
	if m.fileIdCounter < uint64(count) {
		m.fileIdCounter = FileIdSaveInterval
		m.FileIdSequence += FileIdSaveInterval
		m.saveSequence()
	}
	m.fileIdCounter = m.fileIdCounter - uint64(count)
	return m.FileIdSequence - m.fileIdCounter - uint64(count), count
}
func (m *SequencerImpl) saveSequence() {
	glog.V(0).Infoln("Saving file id sequence", m.FileIdSequence, "to", m.fileFullPath)
	if e := m.metaStore.SetUint64(m.fileFullPath, m.FileIdSequence); e != nil {
		glog.Fatalf("Sequence id Save [ERROR] %s", e)
	}
}

//decode are for backward compatible purpose
func decode(input string) uint64 {
	var x uint64
	b := bytes.NewReader([]byte(input))
	decoder := gob.NewDecoder(b)
	if e := decoder.Decode(&x); e == nil {
		return x
	}
	return 0
}