package lease

import (
	"errors"
	"sync"

	"github.com/tblyler/sheepmq/shepard"
)

var (
	// ErrLeased denotes the item is already leased
	ErrLeased = errors.New("Item is already leased")

	// ErrNoLeaser denotes no leaser was provided to add a lease
	ErrNoLeaser = errors.New("No leaser provided")
)

// Manager contains many leases and their validity
type Manager struct {
	leases map[uint64]Leaser
	locker sync.RWMutex
}

// NewManager creates a new Manager instance
func NewManager() *Manager {
	return &Manager{
		leases: make(map[uint64]Leaser),
	}
}

// AddLease to the manager for the given item
func (m *Manager) AddLease(id uint64, info *shepard.GetInfo) error {
	if m.CheckLease(id) {
		return ErrLeased
	}

	var leaser Leaser
	if info.TimeoutLease != nil {
		leaser = NewTimeout(info.TimeoutLease.Ttl)
	} else if info.PidLease != nil {
		leaser = NewPID(int(info.PidLease.Pid))
	} else if info.HeartLease != nil {
		leaser = NewHeart(info.HeartLease.Ttl)
	} else {
		return ErrNoLeaser
	}

	m.locker.Lock()
	m.leases[id] = leaser
	m.locker.Unlock()

	return nil
}

// CheckLease for validity
func (m *Manager) CheckLease(id uint64) bool {
	m.locker.RLock()
	lease, exists := m.leases[id]
	if !exists {
		m.locker.RUnlock()
		return false
	}

	ret := lease.Valid()
	m.locker.RUnlock()
	if !ret {
		// delete the lease since it is no longer valid
		m.locker.Lock()
		delete(m.leases, id)
		m.locker.Unlock()
	}

	return ret
}

// PruneLeases that fail their checks
func (m *Manager) PruneLeases() {
	deleteKeys := []uint64{}
	m.locker.RLock()

	for key, lease := range m.leases {
		if !lease.Check() {
			deleteKeys = append(deleteKeys, key)
		}
	}

	m.locker.RUnlock()
	m.locker.Lock()
	for _, key := range deleteKeys {
		delete(m.leases, key)
	}

	m.locker.Unlock()
}