package tasks

import (
	"fmt"
)

type Status int

const (
	StatusPending Status = 0
	StatusRunning        = 1
	StatusFailed         = 2
	StatusDone           = 3
)

func StatusString(s Status) string {
	switch s {
	case StatusPending:
		return "pending"
	case StatusRunning:
		return "running"
	case StatusFailed:
		return "failed"
	case StatusDone:
		return "done"
	}
	panic("MUST NOT REACH")
}

type TaskDoneListener func(err error)

type Subtasks interface {
	Tasks() []Task
}

type ResourceId struct {
	Type string
	Name string
}

type Task interface {
	Resource() *ResourceId
	Title() string
	Start()
	Status() Status
	Err() error
	Subtasks() []Task
	OnDone(l TaskDoneListener)
}

type basicTask struct {
	title       string
	rid         *ResourceId
	status      Status
	err         error
	listeners   []TaskDoneListener
	beforeStart func()
	afterDone   func()
}

func newBasicTask(title string, rid *ResourceId) basicTask {
	return basicTask{
		rid:       rid,
		title:     title,
		status:    StatusPending,
		err:       nil,
		listeners: make([]TaskDoneListener, 0),
	}
}

func (b *basicTask) Resource() *ResourceId {
	return b.rid
}

func (b *basicTask) Title() string {
	return b.title
}

func (b *basicTask) Status() Status {
	return b.status
}

func (b *basicTask) Err() error {
	return b.err
}

func (b *basicTask) OnDone(l TaskDoneListener) {
	b.listeners = append(b.listeners, l)
}

func (b *basicTask) callDoneListeners(err error) {
	if err != nil {
		fmt.Printf("%s %s\n", b.title, err.Error())
	}
	for _, l := range b.listeners {
		go l(err)
	}
	if err == nil {
		b.status = StatusDone
	} else {
		b.status = StatusFailed
		b.err = err
	}
}

type leafTask struct {
	basicTask
	start func() error
}

func newLeafTask(title string, start func() error) leafTask {
	return leafTask{
		basicTask: newBasicTask(title, nil),
		start:     start,
	}
}

func newResourceLeafTask(title string, rid *ResourceId, start func() error) leafTask {
	return leafTask{
		basicTask: newBasicTask(title, rid),
		start:     start,
	}
}

func (b *leafTask) Subtasks() []Task {
	return make([]Task, 0)
}

func (b *leafTask) Start() {
	b.status = StatusRunning
	if b.beforeStart != nil {
		b.beforeStart()
	}
	err := b.start()
	defer b.callDoneListeners(err)
	if b.afterDone != nil {
		b.afterDone()
	}
}

type parentTask struct {
	leafTask
	subtasks     Subtasks
	showChildren bool
}

type TaskSlice []Task

func (s TaskSlice) Tasks() []Task {
	return s
}

func newParentTask(title string, showChildren bool, start func() error, subtasks Subtasks) parentTask {
	return parentTask{
		leafTask:     newLeafTask(title, start),
		subtasks:     subtasks,
		showChildren: showChildren,
	}
}

func (t *parentTask) Subtasks() []Task {
	if t.showChildren {
		return t.subtasks.Tasks()
	} else {
		return make([]Task, 0)
	}
}

func newSequentialParentTask(title string, showChildren bool, subtasks ...Task) *parentTask {
	start := func() error {
		errCh := make(chan error)
		for i := range subtasks[:len(subtasks)-1] {
			next := i + 1
			subtasks[i].OnDone(func(err error) {
				if err == nil {
					go subtasks[next].Start()
				} else {
					errCh <- err
				}
			})
		}
		subtasks[len(subtasks)-1].OnDone(func(err error) {
			errCh <- err
		})
		go subtasks[0].Start()
		return <-errCh
	}
	t := newParentTask(title, showChildren, start, TaskSlice(subtasks))
	return &t
}

func newConcurrentParentTask(title string, showChildren bool, subtasks ...Task) *parentTask {
	start := func() error {
		errCh := make(chan error)
		for i := range subtasks {
			subtasks[i].OnDone(func(err error) {
				errCh <- err
			})
			go subtasks[i].Start()
		}
		cnt := 0
		for _ = range subtasks {
			err := <-errCh
			if err != nil {
				return err
			}
			cnt++
			if cnt == len(subtasks) {
				break
			}
		}
		return nil
	}
	t := newParentTask(title, showChildren, start, TaskSlice(subtasks))
	return &t
}
