package storage

import (
	"sort"
	"strings"
	"sync"
	"time"
)

type ChannelListItem struct {
	Name      string
	UserCount int
	Topic     string
}

type ChannelListIndex interface {
	Add(item *ChannelListItem)
	Finish()
	Search(q string) []*ChannelListItem
	SearchN(q string, start, n int) []*ChannelListItem
}

type MapChannelListIndex struct {
	channels chanList
	m        map[string][]*ChannelListItem
}

func NewMapChannelListIndex() *MapChannelListIndex {
	return &MapChannelListIndex{
		m: map[string][]*ChannelListItem{},
	}
}

func (idx *MapChannelListIndex) Add(item *ChannelListItem) {
	idx.channels = append(idx.channels, item)
}

func (idx *MapChannelListIndex) Finish() {
	sort.Sort(idx.channels)

	for _, ch := range idx.channels {
		key := strings.TrimLeft(strings.ToLower(ch.Name), "#")

		for i := 1; i <= len(key); i++ {
			k := key[:i]
			if _, ok := idx.m[k]; ok {
				idx.m[k] = append(idx.m[k], ch)
			} else {
				idx.m[k] = chanList{ch}
			}
		}
	}
}

func (idx *MapChannelListIndex) Search(q string) []*ChannelListItem {
	if q == "" {
		return idx.channels
	}
	return idx.m[q]
}

func (idx *MapChannelListIndex) SearchN(q string, start, n int) []*ChannelListItem {
	if q == "" {
		if start >= len(idx.channels) {
			return nil
		}
		return idx.channels[start:min(start+n, len(idx.channels))]
	}

	res := idx.m[q]
	if start >= len(res) {
		return nil
	}
	return res[start:min(start+n, len(res))]
}

func min(x, y int) int {
	if x < y {
		return x
	}
	return y
}

type chanList []*ChannelListItem

func (c chanList) Len() int {
	return len(c)
}

func (c chanList) Less(i, j int) bool {
	return c[i].UserCount > c[j].UserCount ||
		(c[i].UserCount == c[j].UserCount &&
			strings.ToLower(c[i].Name) < strings.ToLower(c[j].Name))
}

func (c chanList) Swap(i, j int) {
	ch := c[i]
	c[i] = c[j]
	c[j] = ch
}

const ChannelListUpdateInterval = time.Hour * 24
const ChannelListUpdateTimeout = time.Minute * 5

type ChannelIndexManager struct {
	indexes map[string]*managedChannelIndex
	lock    sync.Mutex
}

func NewChannelIndexManager() *ChannelIndexManager {
	return &ChannelIndexManager{
		indexes: map[string]*managedChannelIndex{},
	}
}

type managedChannelIndex struct {
	index     ChannelListIndex
	updatedAt time.Time
	updating  bool
}

func (m *ChannelIndexManager) Get(server string) (ChannelListIndex, bool) {
	m.lock.Lock()
	defer m.lock.Unlock()

	idx, ok := m.indexes[server]
	if !ok {
		m.indexes[server] = &managedChannelIndex{
			updating: true,
		}
		go m.timeoutUpdate(server)
		return nil, true
	}

	if !idx.updating && time.Since(idx.updatedAt) > ChannelListUpdateInterval {
		idx.updating = true
		go m.timeoutUpdate(server)
		return idx.index, true
	}

	return idx.index, false
}

func (m *ChannelIndexManager) Set(server string, index ChannelListIndex) {
	m.lock.Lock()
	m.indexes[server] = &managedChannelIndex{
		index:     index,
		updatedAt: time.Now(),
	}
	m.lock.Unlock()
}

func (m *ChannelIndexManager) timeoutUpdate(server string) {
	time.Sleep(ChannelListUpdateTimeout)

	m.lock.Lock()
	if m.indexes[server].updating {
		m.indexes[server].updating = false
	}
	m.lock.Unlock()
}
