package task

import "sync"

// Pool 带有最大配额的goroutines任务池
type Pool struct {
	// 容量
	capacity int
	// 初始容量
	initialCapacity int

	// 终止信号
	terminateSignal chan error
	// 全部任务完成的信号
	finishSignal chan bool
	// 有空余位置的信号
	freeSignal chan bool

	// 是否已关闭
	closed bool
	// 是否正在等待任务结束
	waiting bool

	// 互斥锁
	lock sync.Mutex
	// 等待队列
	pending []Job
}

// Job 任务
type Job interface {
	// 任务处理方法,如果error不为nil,
	// 任务池会关闭并中止接受新任务
	Do() error
}

// NewGoroutinePool 创建一个容量为capacity的任务池
func NewGoroutinePool(capacity int) *Pool {
	pool := &Pool{
		capacity:        capacity,
		initialCapacity: capacity,
		terminateSignal: make(chan error),
		finishSignal:    make(chan bool),
		freeSignal:      make(chan bool),
	}
	go pool.Schedule()
	return pool
}

// Schedule 为等待队列的任务分配Worker,以及检测错误状态、所有任务完成
func (pool *Pool) Schedule() {
	for {
		select {
		case <-pool.freeSignal:
			// 有新的空余名额
			pool.lock.Lock()
			if len(pool.pending) > 0 {
				// 有待处理的任务,开始处理
				var job Job
				job, pool.pending = pool.pending[0], pool.pending[1:]
				go pool.start(job)
			} else {
				if pool.waiting && pool.capacity == pool.initialCapacity {
					// 所有任务已结束
					pool.lock.Unlock()
					pool.finishSignal <- true
					return
				}
				pool.lock.Unlock()
			}
		case <-pool.terminateSignal:
			// 有任务意外中止,则发送完成信号
			pool.finishSignal <- true
			return
		}
	}
}

// Wait 等待队列中所有任务完成或有Job返回错误中止
func (pool *Pool) Wait() chan bool {
	pool.lock.Lock()
	pool.waiting = true
	pool.lock.Unlock()
	return pool.finishSignal
}

// Submit 提交新任务
func (pool *Pool) Submit(job Job) {
	if pool.closed {
		return
	}

	pool.lock.Lock()
	if pool.capacity < 1 {
		// 容量为空时,加入等待队列
		pool.pending = append(pool.pending, job)
		pool.lock.Unlock()
		return
	}

	// 还有空闲容量时,开始执行任务
	go pool.start(job)
}

// 开始执行任务
func (pool *Pool) start(job Job) {
	pool.capacity--
	pool.lock.Unlock()

	err := job.Do()
	if err != nil {
		pool.closed = true
		select {
		case <-pool.terminateSignal:
		default:
			close(pool.terminateSignal)
		}
	}

	pool.lock.Lock()
	pool.capacity++
	pool.lock.Unlock()
	pool.freeSignal <- true
}