package memory

import (
	"container/list"
	"sync"
	"time"

	"git.markbailey.dev/cervers/ptpp/lib/session"
)

var prov = &Provider{list: list.New()}

type MemSessionStore struct {
	timeAccessed time.Time
	value        map[interface{}]interface{}
	sid          string
}

func (m *MemSessionStore) Set(key interface{}, value interface{}) error {
	m.value[key] = value
	err := prov.SessionUpdate(m.sid)
	if err != nil {
		return err
	}

	return nil
}

func (m *MemSessionStore) Get(key interface{}) interface{} {
	err := prov.SessionUpdate(m.sid)
	if err != nil {
		return nil
	}
	if v, ok := m.value[key]; ok {
		return v
	}

	return nil
}

func (m *MemSessionStore) Delete(key interface{}) error {
	delete(m.value, key)
	err := prov.SessionUpdate(m.sid)
	if err != nil {
		return err
	}

	return nil
}

func (m *MemSessionStore) SessionID() string {
	return m.sid
}

type Provider struct {
	sessions map[string]*list.Element
	list     *list.List
	lock     sync.Mutex
}

func (p *Provider) SessionInit(sid string) (session.ISession, error) {
	prov.lock.Lock()
	defer prov.lock.Unlock()
	v := make(map[interface{}]interface{}, 0)
	newSess := &MemSessionStore{sid: sid, timeAccessed: time.Now(), value: v}
	elem := prov.list.PushBack(newSess)
	prov.sessions[sid] = elem
	return newSess, nil
}

func (p *Provider) SessionRead(sid string) (session.ISession, error) {
	if element, ok := prov.sessions[sid]; ok {
		return element.Value.(*MemSessionStore), nil
	}

	sess, err := prov.SessionInit(sid)
	return sess, err
}

func (p *Provider) SessionDestroy(sid string) error {
	if element, ok := prov.sessions[sid]; ok {
		delete(prov.sessions, sid)
		prov.list.Remove(element)
	}

	return nil
}

func (p *Provider) SessionGC(maxLifeTime int64) {
	prov.lock.Lock()
	defer prov.lock.Unlock()

	for {
		element := prov.list.Back()
		if element == nil {
			break
		}

		if (element.Value.(*MemSessionStore).timeAccessed.Unix() + maxLifeTime) < time.Now().Unix() {
			prov.list.Remove(element)
			delete(prov.sessions, element.Value.(*MemSessionStore).sid)
		} else {
			break
		}
	}
}

func (p *Provider) SessionUpdate(sid string) error {
	p.lock.Lock()
	defer p.lock.Unlock()
	if elem, ok := p.sessions[sid]; ok {
		elem.Value.(*MemSessionStore).timeAccessed = time.Now()
		p.list.MoveToFront(elem)
		return nil
	}

	return nil
}

func init() {
	prov.sessions = make(map[string]*list.Element, 0)
	session.Register("memory", prov)
}
