package expire

import (
	"sync"
	"time"
)

// Item is a something that can expire
type Item interface {
	Expires() time.Time
}

// List of expireable items
type List struct {
	mu      sync.Mutex
	queue   queue
	bgrun   bool
	Expired func(item Item)
}

// Push an item onto the queue
func (list *List) Push(item Item) {
	unix := item.Expires().UnixNano()
	list.mu.Lock()
	if !list.bgrun {
		list.bgrun = true
		go list.bg()
	}
	list.queue.push(unix, item)
	list.mu.Unlock()
}

func (list *List) bg() {
	now := time.Now().UnixNano()
	for {
		list.mu.Lock()
		if list.queue.len == 0 {
			list.bgrun = false
			list.mu.Unlock()
			break
		}
		if now > list.queue.peek().unix { // now.After(list.queue.peek().unix)
			n := list.queue.pop()
			list.mu.Unlock()
			if list.Expired != nil {
				list.Expired(n.item)
			}
		} else {
			list.mu.Unlock()
			time.Sleep(time.Second / 10)
			now = time.Now().UnixNano()
		}
	}
}

type qnode struct {
	unix int64
	item Item
}

type queue struct {
	nodes []qnode
	len   int
	size  int
}

func (q *queue) push(unix int64, item Item) {
	if q.nodes == nil {
		q.nodes = make([]qnode, 2)
	} else {
		q.nodes = append(q.nodes, qnode{})
	}
	i := q.len + 1
	j := i / 2
	for i > 1 && q.nodes[j].unix > unix {
		q.nodes[i] = q.nodes[j]
		i = j
		j = j / 2
	}
	q.nodes[i].unix = unix
	q.nodes[i].item = item
	q.len++
}

func (q *queue) peek() qnode {
	if q.len == 0 {
		return qnode{}
	}
	return q.nodes[1]
}

func (q *queue) pop() qnode {
	if q.len == 0 {
		return qnode{}
	}
	n := q.nodes[1]
	q.nodes[1] = q.nodes[q.len]
	q.len--
	var j, k int
	i := 1
	for i != q.len+1 {
		k = q.len + 1
		j = 2 * i
		if j <= q.len && q.nodes[j].unix < q.nodes[k].unix {
			k = j
		}
		if j+1 <= q.len && q.nodes[j+1].unix < q.nodes[k].unix {
			k = j + 1
		}
		q.nodes[i] = q.nodes[k]
		i = k
	}
	return n
}