package document

import (
	protocol "wellquite.org/edist/protocol/go"
)

type WordManager struct {
	root         *protocol.Word
	orderedWords []*protocol.Word
	wordsById    map[protocol.WordId]*protocol.Word
}

func NewWordManager() *WordManager {
	root := &protocol.Word{
		WordId: protocol.WordId{
			ClientId: 0,
			Counter:  1,
		},
	}
	return &WordManager{
		root:         root,
		orderedWords: []*protocol.Word{root},
		wordsById:    map[protocol.WordId]*protocol.Word{root.WordId: root},
	}
}

type UpdatePair struct {
	original  *protocol.Word
	updated   *protocol.Word
	isNewWord bool
}

func (self *WordManager) FilterUpdatesForNewer(updates []protocol.Word) []*UpdatePair {
	filteredUpdates := make([]*UpdatePair, 0, len(updates))
	for idx, word := range updates {
		existing, found := self.wordsById[word.WordId]
		if found {
			if word.Version.GreaterThan(existing.Version) {
				filteredUpdates = append(filteredUpdates, &UpdatePair{
					original:  existing,
					updated:   &updates[idx],
					isNewWord: false,
				})
			}
		} else {
			filteredUpdates = append(filteredUpdates, &UpdatePair{
				original:  self.NewWord(word.WordId),
				updated:   &updates[idx],
				isNewWord: true,
			})
		}
	}
	return filteredUpdates
}

func (self *WordManager) FilterUpdatesForKnown(updates []*UpdatePair) []protocol.Word {
	words := make([]protocol.Word, 0, len(updates))
	for _, update := range updates {
		if _, found := self.wordsById[update.original.WordId]; found {
			words = append(words, *update.original)
		}
	}
	return words
}

func (self *WordManager) ApplyUpdates(updates []*UpdatePair) {
	for _, update := range updates {
		original, updated := update.original, update.updated

		if updated.Version.GreaterThan(original.Version) {
			original.Version = updated.Version
		}

		original.Letters = updated.Letters
		original.Links = updated.Links
	}
}

func (self *WordManager) NewWord(wordId protocol.WordId) *protocol.Word {
	word := &protocol.Word{
		WordId: wordId,
	}
	self.wordsById[wordId] = word
	return word
}

func (self *WordManager) GC() {
	if len(self.wordsById) < 2 {
		return
	}

	wordsById := make(map[protocol.WordId]*protocol.Word, len(self.wordsById))
	orderedWords := make([]*protocol.Word, 0, len(self.orderedWords))

	for cur := self.root; cur != nil; {
		orderedWords = append(orderedWords, cur)
		wordsById[cur.WordId] = cur

		if cur.Links.Next == nil {
			break
		}
		cur = self.wordsById[*cur.Links.Next]
	}

	self.wordsById = wordsById
	self.orderedWords = orderedWords
}

func (self *WordManager) CreateCheckpoint() *protocol.Message {
	words := make([]protocol.Word, len(self.orderedWords))
	for idx, word := range self.orderedWords {
		words[idx] = *word
	}
	return &protocol.Message{
		Updates: &words,
	}
}

// How do we get from b to a.
func (a *WordManager) Delta(b *WordManager) *protocol.Message {
	words := make([]protocol.Word, 0, len(a.orderedWords))
	for _, wordA := range a.orderedWords {
		wordB, foundB := b.wordsById[wordA.WordId]
		if !foundB || wordA.Version.GreaterThan(wordB.Version) {
			words = append(words, *wordA)
		}
	}
	return &protocol.Message{
		Updates: &words,
	}
}

func (self *WordManager) String() string {
	str := ""
	for _, word := range self.orderedWords {
		str += " " + word.Letters
	}
	if len(str) > 0 {
		return str[1:]
	}
	return str
}
