package fzf

import (
	"fmt"
	"os"
	"runtime"
	"time"
)

const COORDINATOR_DELAY time.Duration = 100 * time.Millisecond

func initProcs() {
	runtime.GOMAXPROCS(runtime.NumCPU())
}

/*
Reader   -> EVT_READ_FIN
Reader   -> EVT_READ_NEW        -> Matcher  (restart)
Terminal -> EVT_SEARCH_NEW      -> Matcher  (restart)
Matcher  -> EVT_SEARCH_PROGRESS -> Terminal (update info)
Matcher  -> EVT_SEARCH_FIN      -> Terminal (update list)
*/

func Run(options *Options) {
	initProcs()

	opts := ParseOptions()

	if opts.Version {
		fmt.Println(VERSION)
		os.Exit(0)
	}

	// Event channel
	eventBox := NewEventBox()

	// Chunk list
	var chunkList *ChunkList
	if len(opts.WithNth) == 0 {
		chunkList = NewChunkList(func(data *string, index int) *Item {
			return &Item{text: data, index: index}
		})
	} else {
		chunkList = NewChunkList(func(data *string, index int) *Item {
			item := Item{text: data, index: index}
			tokens := Tokenize(item.text, opts.Delimiter)
			item.origText = item.text
			item.text = Transform(tokens, opts.WithNth).whole
			return &item
		})
	}

	// Reader
	reader := Reader{func(str string) { chunkList.Push(str) }, eventBox}
	go reader.ReadSource()

	// Matcher
	patternBuilder := func(runes []rune) *Pattern {
		return BuildPattern(
			opts.Mode, opts.Case, opts.Nth, opts.Delimiter, runes)
	}
	matcher := NewMatcher(patternBuilder, opts.Sort > 0, eventBox)

	// Defered-interactive / Non-interactive
	//   --select-1 | --exit-0 | --filter
	if filtering := opts.Filter != nil; filtering || opts.Select1 || opts.Exit0 {
		limit := 0
		var patternString string
		if filtering {
			patternString = *opts.Filter
		} else {
			if opts.Select1 || opts.Exit0 {
				limit = 1
			}
			patternString = opts.Query
		}
		pattern := patternBuilder([]rune(patternString))

		looping := true
		eventBox.Unwatch(EVT_READ_NEW)
		for looping {
			eventBox.Wait(func(events *Events) {
				for evt, _ := range *events {
					switch evt {
					case EVT_READ_FIN:
						looping = false
						return
					}
				}
			})
		}

		snapshot, _ := chunkList.Snapshot()
		matches, cancelled := matcher.scan(MatchRequest{
			chunks:  snapshot,
			pattern: pattern}, limit)

		if !cancelled && (filtering ||
			opts.Exit0 && len(matches) == 0 || opts.Select1 && len(matches) == 1) {
			if opts.PrintQuery {
				fmt.Println(patternString)
			}
			for _, item := range matches {
				item.Print()
			}
			os.Exit(0)
		}
	}

	// Go interactive
	go matcher.Loop()

	// Terminal I/O
	terminal := NewTerminal(opts, eventBox)
	go terminal.Loop()

	// Event coordination
	reading := true
	ticks := 0
	eventBox.Watch(EVT_READ_NEW)
	for {
		delay := true
		ticks += 1
		eventBox.Wait(func(events *Events) {
			defer events.Clear()
			for evt, value := range *events {
				switch evt {

				case EVT_READ_NEW, EVT_READ_FIN:
					reading = reading && evt == EVT_READ_NEW
					snapshot, count := chunkList.Snapshot()
					terminal.UpdateCount(count, !reading)
					matcher.Reset(snapshot, terminal.Input(), false)

				case EVT_SEARCH_NEW:
					snapshot, _ := chunkList.Snapshot()
					matcher.Reset(snapshot, terminal.Input(), true)
					delay = false

				case EVT_SEARCH_PROGRESS:
					switch val := value.(type) {
					case float32:
						terminal.UpdateProgress(val)
					}

				case EVT_SEARCH_FIN:
					switch val := value.(type) {
					case []*Item:
						terminal.UpdateList(val)
					}
				}
			}
		})
		if ticks > 3 && delay && reading {
			time.Sleep(COORDINATOR_DELAY)
		}
	}
}
