package scheduler

import (
	"errors"
	"reflect"
	"runtime"
	"sort"
	"time"
)

const (
	MAX_TASKS = 10000
)

type Task struct {
	id          string
	intervalSec uint64
	TaskFunc    string
	period      time.Duration
	taskFunc    map[string]interface{}
	funcParams  map[string]([]interface{})
	lastAt      time.Time
	nextAt      time.Time
	running     bool
}

func NewTask(id string, intervel uint64) *Task {
	return &Task{
		id,
		intervel,
		"",
		0,
		make(map[string]interface{}),
		make(map[string]([]interface{})),
		time.Unix(0, 0),
		time.Unix(0, 0),
		true,
	}
}

func (t *Task) runnable() bool {
	return time.Now().After(t.nextAt)
}

func (t *Task) run() {
	if !t.running {
		return
	}
	taskFunc := reflect.ValueOf(t.taskFunc[t.TaskFunc])
	taskParams := t.funcParams[t.TaskFunc]
	in := make([]reflect.Value, len(taskParams))
	for i, param := range taskParams {
		in[i] = reflect.ValueOf(param)
	}
	taskFunc.Call(in)
	t.lastAt = time.Now()
	t.addNextAt()
}

func (t *Task) invoke(TaskFunc interface{}, params ...interface{}) error {
	if reflect.TypeOf(TaskFunc).Kind() != reflect.Func {
		return errors.New("Not a function type.")
	}

	funcName := runtime.FuncForPC(reflect.ValueOf((TaskFunc)).Pointer()).Name()
	t.taskFunc[funcName] = TaskFunc
	t.funcParams[funcName] = params
	t.TaskFunc = funcName
	t.addNextAt()

	return nil
}

func (t *Task) addNextAt() {
	if t.lastAt == time.Unix(0, 0) {
		t.lastAt = time.Now()
	}

	if t.period == 0 {
		t.period = time.Duration(t.intervalSec)
		t.nextAt = t.lastAt.Add(1 * time.Second)
	} else {
		t.nextAt = t.lastAt.Add((t.period * time.Second) - time.Second)
	}

}

type Scheduler struct {
	Tasks [MAX_TASKS]*Task
	size  int
	stop  chan bool
}

func (s *Scheduler) Len() int {
	return s.size
}

func (s *Scheduler) Swap(i, j int) {
	s.Tasks[i], s.Tasks[j] = s.Tasks[j], s.Tasks[i]
}

func (s *Scheduler) Less(i, j int) bool {
	return s.Tasks[j].nextAt.After(s.Tasks[i].nextAt)
}

func (s *Scheduler) RunnableTasks() ([MAX_TASKS]*Task, int) {
	runnableTasks := [MAX_TASKS]*Task{}
	n := 0
	sort.Sort(s)
	for i := 0; i < s.size; i++ {
		if s.Tasks[i].runnable() {
			runnableTasks[n] = s.Tasks[i]
			n++
		} else {
			break
		}
	}
	return runnableTasks, n
}

func (s *Scheduler) nextRun() (*Task, time.Time) {
	if s.size <= 0 {
		return nil, time.Now()
	}
	sort.Sort(s)
	return s.Tasks[0], s.Tasks[0].nextAt
}

func (s *Scheduler) addTask(id string, intervalSec uint64) *Task {
	s.remove(id)

	Task := NewTask(id, intervalSec)
	s.Tasks[s.size] = Task
	s.size++
	return Task
}

func (s *Scheduler) runAll() {
	runnableTasks, n := s.RunnableTasks()

	if n != 0 {
		for i := 0; i < n; i++ {
			go runnableTasks[i].run()
		}
	}
}

func (s *Scheduler) remove(id string) error {
	i := 0
	var ex bool
	for ; i < s.size; i++ {
		if s.Tasks[i].id == id {
			ex = true
			break
		}
	}
	if !ex {
		return nil
	}
	for j := (i + 1); j < s.size; j++ {
		s.Tasks[i] = s.Tasks[j]
		i++
	}
	s.size = s.size - 1

	return nil
}

func (s *Scheduler) Start() {
	s.stop = make(chan bool, 1)
	ticker := time.NewTicker(time.Second * 1)

	go func() {
		for {
			select {
			case <-ticker.C:
				s.runAll()
			case <-s.stop:
				return
			}
		}
	}()
}

func (s *Scheduler) Stop() {
	s.stop <- true
}

func (s *Scheduler) NewSchedule(id string, interval uint64, fn interface{}) error {
	return s.addTask(id, interval).invoke(fn, id)
}

func (s *Scheduler) RemoveSchedule(id string) error {
	i := 0
	var ex bool
	for ; i < s.size; i++ {
		if s.Tasks[i].id == id {
			ex = true
			break
		}
	}
	if !ex {
		return nil
	}
	for j := (i + 1); j < s.size; j++ {
		s.Tasks[i] = s.Tasks[j]
		i++
	}
	s.size = s.size - 1

	return nil
}

func (s *Scheduler) RemoveAllSchedule() error {
	for i := 0; i < s.size; i++ {
		s.Tasks[i] = nil
	}
	s.size = 0

	return nil
}

func (s *Scheduler) UpdateInterval(id string, interval uint64) {
	for i := 0; i < s.Len(); i++ {
		if s.Tasks[i].id == id {
			s.Tasks[i].intervalSec = interval
			s.Tasks[i].period = 0
		}
	}
}

func (s *Scheduler) StartSchedule(id string) error {
	i := 0
	var ex bool
	for ; i < s.size; i++ {
		if s.Tasks[i].id == id {
			ex = true
			break
		}
	}
	if !ex {
		return errors.New("Cannot find the task. ID : " + id)
	}
	if s.Tasks[i].running {
		return errors.New("Already started task. ID : " + id)
	}
	s.Tasks[i].running = true
	return nil
}

func (s *Scheduler) StopSchedule(id string) error {
	i := 0
	var ex bool
	for ; i < s.size; i++ {
		if s.Tasks[i].id == id {
			ex = true
			break
		}
	}
	if s.Tasks[i].running {
		s.Tasks[i].running = false
	}

	if !ex {
		return errors.New("Cannot find the task. ID : " + id)
	}
	if !s.Tasks[i].running {
		return errors.New("Already stopped task. ID : " + id)
	}
	s.Tasks[i].running = false
	return nil
}
