// Package ledis provide session Provider
package ledis

import (
	"net/http"
	"strconv"
	"strings"
	"sync"

	"github.com/astaxie/beego/session"
	"github.com/siddontang/ledisdb/config"
	"github.com/siddontang/ledisdb/ledis"
)

var (
	ledispder = &Provider{}
	c         *ledis.DB
)

// SessionStore ledis session store
type SessionStore struct {
	sid         string
	lock        sync.RWMutex
	values      map[interface{}]interface{}
	maxlifetime int64
}

// Set value in ledis session
func (ls *SessionStore) Set(key, value interface{}) error {
	ls.lock.Lock()
	defer ls.lock.Unlock()
	ls.values[key] = value
	return nil
}

// Get value in ledis session
func (ls *SessionStore) Get(key interface{}) interface{} {
	ls.lock.RLock()
	defer ls.lock.RUnlock()
	if v, ok := ls.values[key]; ok {
		return v
	}
	return nil
}

// Delete value in ledis session
func (ls *SessionStore) Delete(key interface{}) error {
	ls.lock.Lock()
	defer ls.lock.Unlock()
	delete(ls.values, key)
	return nil
}

// Flush clear all values in ledis session
func (ls *SessionStore) Flush() error {
	ls.lock.Lock()
	defer ls.lock.Unlock()
	ls.values = make(map[interface{}]interface{})
	return nil
}

// SessionID get ledis session id
func (ls *SessionStore) SessionID() string {
	return ls.sid
}

// SessionRelease save session values to ledis
func (ls *SessionStore) SessionRelease(w http.ResponseWriter) {
	b, err := session.EncodeGob(ls.values)
	if err != nil {
		return
	}
	c.Set([]byte(ls.sid), b)
	c.Expire([]byte(ls.sid), ls.maxlifetime)
}

// Provider ledis session provider
type Provider struct {
	maxlifetime int64
	savePath    string
	db          int
}

// SessionInit init ledis session
// savepath like ledis server saveDataPath,pool size
// e.g. 127.0.0.1:6379,100,astaxie
func (lp *Provider) SessionInit(maxlifetime int64, savePath string) error {
	var err error
	lp.maxlifetime = maxlifetime
	configs := strings.Split(savePath, ",")
	if len(configs) == 1 {
		lp.savePath = configs[0]
	} else if len(configs) == 2 {
		lp.savePath = configs[0]
		lp.db, err = strconv.Atoi(configs[1])
		if err != nil {
			return err
		}
	}
	cfg := new(config.Config)
	cfg.DataDir = lp.savePath

	var ledisInstance *ledis.Ledis
	ledisInstance, err = ledis.Open(cfg)
	if err != nil {
		return err
	}
	c, err = ledisInstance.Select(lp.db)
	return err
}

// SessionRead read ledis session by sid
func (lp *Provider) SessionRead(sid string) (session.Store, error) {
	var (
		kv  map[interface{}]interface{}
		err error
	)

	kvs, _ := c.Get([]byte(sid))

	if len(kvs) == 0 {
		kv = make(map[interface{}]interface{})
	} else {
		if kv, err = session.DecodeGob(kvs); err != nil {
			return nil, err
		}
	}

	ls := &SessionStore{sid: sid, values: kv, maxlifetime: lp.maxlifetime}
	return ls, nil
}

// SessionExist check ledis session exist by sid
func (lp *Provider) SessionExist(sid string) bool {
	count, _ := c.Exists([]byte(sid))
	return !(count == 0)
}

// SessionRegenerate generate new sid for ledis session
func (lp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) {
	count, _ := c.Exists([]byte(sid))
	if count == 0 {
		// oldsid doesn't exists, set the new sid directly
		// ignore error here, since if it return error
		// the existed value will be 0
		c.Set([]byte(sid), []byte(""))
		c.Expire([]byte(sid), lp.maxlifetime)
	} else {
		data, _ := c.Get([]byte(oldsid))
		c.Set([]byte(sid), data)
		c.Expire([]byte(sid), lp.maxlifetime)
	}
	return lp.SessionRead(sid)
}

// SessionDestroy delete ledis session by id
func (lp *Provider) SessionDestroy(sid string) error {
	c.Del([]byte(sid))
	return nil
}

// SessionGC Impelment method, no used.
func (lp *Provider) SessionGC() {
}

// SessionAll return all active session
func (lp *Provider) SessionAll() int {
	return 0
}
func init() {
	session.Register("ledis", ledispder)
}
