package api

import (
	"fmt"
	"time"
)

type pollingHandler func() (exit bool, state interface{}, err error)

func poll(handler pollingHandler, timeout time.Duration) (chan (interface{}), chan (interface{}), chan (error)) {

	compChan := make(chan interface{})
	progChan := make(chan interface{})
	errChan := make(chan error)

	tick := time.Tick(5 * time.Second)
	bomb := time.After(timeout)

	go func() {
		for {
			select {
			case <-tick:
				exit, state, err := handler()
				if state != nil {
					progChan <- state
				}
				if err != nil {
					errChan <- fmt.Errorf("Failed: poll: %s", err)
					return
				}
				if exit {
					compChan <- state
					return
				}
			case <-bomb:
				errChan <- fmt.Errorf("Timeout")
				return
			}
		}
	}()
	return compChan, progChan, errChan
}

func blockingPoll(handler pollingHandler, timeout time.Duration) error {
	c, p, e := poll(handler, timeout)
	for {
		select {
		case <-c:
			return nil
		case <-p:
			// noop
		case err := <-e:
			return err
		}
	}
}

type hasAvailable interface {
	IsAvailable() bool
}
type hasFailed interface {
	IsFailed() bool
}

func waitingForAvailableFunc(readFunc func() (hasAvailable, error), maxRetry int) func() (bool, interface{}, error) {
	counter := 0
	return func() (bool, interface{}, error) {
		counter++
		v, err := readFunc()
		if err != nil {
			if maxRetry > 0 && counter < maxRetry {
				return false, nil, nil
			}
			return false, nil, err
		}
		if v == nil {
			return false, nil, fmt.Errorf("readFunc returns nil")
		}

		if v.IsAvailable() {
			return true, v, nil
		}
		if f, ok := v.(hasFailed); ok && f.IsFailed() {
			return false, v, fmt.Errorf("InstanceState is failed: %#v", v)
		}

		return false, v, nil
	}
}

type hasUpDown interface {
	IsUp() bool
	IsDown() bool
}

func waitingForUpFunc(readFunc func() (hasUpDown, error), maxRetry int) func() (bool, interface{}, error) {
	counter := 0
	return func() (bool, interface{}, error) {
		counter++
		v, err := readFunc()
		if err != nil {
			if maxRetry > 0 && counter < maxRetry {
				return false, nil, nil
			}
			return false, nil, err
		}
		if v == nil {
			return false, nil, fmt.Errorf("readFunc returns nil")
		}

		if v.IsUp() {
			return true, v, nil
		}
		return false, v, nil
	}
}

func waitingForDownFunc(readFunc func() (hasUpDown, error), maxRetry int) func() (bool, interface{}, error) {
	counter := 0
	return func() (bool, interface{}, error) {
		counter++
		v, err := readFunc()
		if err != nil {
			if maxRetry > 0 && counter < maxRetry {
				return false, nil, nil
			}
			return false, nil, err
		}
		if v == nil {
			return false, nil, fmt.Errorf("readFunc returns nil")
		}

		if v.IsDown() {
			return true, v, nil
		}
		return false, v, nil
	}
}

func waitingForReadFunc(readFunc func() (interface{}, error), maxRetry int) func() (bool, interface{}, error) {
	counter := 0
	return func() (bool, interface{}, error) {
		counter++
		v, err := readFunc()
		if err != nil {
			if maxRetry > 0 && counter < maxRetry {
				return false, nil, nil
			}
			return false, nil, err
		}
		if v != nil {
			return true, v, nil
		}
		return false, v, nil
	}
}
