package mongo

import (
	"fmt"
	"mongo.games.com/goserver/core/logger"
	"sync"
	"sync/atomic"
	"time"

	"github.com/globalsign/mgo"
	"mongo.games.com/goserver/core"
	"mongo.games.com/goserver/core/container"
)

var Config = Configuration{
	Dbs: make(map[string]DbConfig),
}

var autoPingInterval time.Duration = 30 * time.Second
var mgoSessions = container.NewSynchronizedMap()
var databases = container.NewSynchronizedMap()
var cLock sync.RWMutex
var collections = make(map[string]*Collection)
var resetingSess int32

type Configuration struct {
	Dbs map[string]DbConfig
}
type DbConfig struct {
	Host     string
	Database string
	User     string
	Password string
	Safe     mgo.Safe
}

func (c *Configuration) Name() string {
	return "mongo"
}

func (c *Configuration) Init() error {
	//auto ping, ensure net is connected
	go func() {
		for {
			select {
			case <-time.After(autoPingInterval):
				Ping()
			}
		}
	}()
	return nil
}

func (c *Configuration) Close() error {
	sessions := mgoSessions.Items()
	for k, s := range sessions {
		if session, ok := s.(*mgo.Session); ok && session != nil {
			logger.Logger.Warnf("mongo.Close!!! (%v)", k)
			session.Close()
		}
	}
	return nil
}

func init() {
	core.RegistePackage(&Config)
}

type Collection struct {
	*mgo.Collection
	Ref   int32
	valid bool
}

func (c *Collection) Hold() {
	if atomic.AddInt32(&c.Ref, 1) == 1 {
		key := c.Database.Name + c.FullName
		cLock.Lock()
		if old, exist := collections[key]; exist {
			old.valid = false
		}
		collections[key] = c
		cLock.Unlock()
	}
}

func (c *Collection) Unhold() {
	if atomic.AddInt32(&c.Ref, -1) == 0 {
		key := c.Database.Name + c.FullName
		cLock.Lock()
		delete(collections, key)
		cLock.Unlock()
	}
}

func (c *Collection) IsValid() bool {
	return c.valid
}

func newDBSession(dbc *DbConfig) (s *mgo.Session, err error) {
	login := ""
	if dbc.User != "" {
		login = dbc.User + ":" + dbc.Password + "@"
	}
	host := "localhost"
	if dbc.Host != "" {
		host = dbc.Host
	}

	// http://goneat.org/pkg/labix.org/v2/mgo/#Session.Mongo
	// [mongodb://][user:pass@]host1[:port1][,host2[:port2],...][/database][?options]
	url := fmt.Sprintf("mongodb://%s%s/admin", login, host)
	//fmt.Println(url)
	session, err := mgo.Dial(url)
	if err != nil {
		return
	}
	session.SetSafe(&dbc.Safe)
	s = session
	return
}

func Ping() {
	if atomic.LoadInt32(&resetingSess) == 1 {
		return
	}
	var err error
	sessions := mgoSessions.Items()
	for k, s := range sessions {
		if session, ok := s.(*mgo.Session); ok && session != nil {
			if atomic.LoadInt32(&resetingSess) == 1 {
				return
			}
			err = session.Ping()
			if err != nil {
				logger.Logger.Errorf("mongo.Ping (%v) err:%v", k, err)
				if atomic.LoadInt32(&resetingSess) == 1 {
					return
				}
				session.Refresh()
			} else {
				logger.Logger.Tracef("mongo.Ping (%v) suc", k)
			}
		}
	}
}

func SetAutoPing(interv time.Duration) {
	autoPingInterval = interv
}

func Database(dbName string) *mgo.Database {
	if atomic.LoadInt32(&resetingSess) == 1 {
		return nil
	}
	var dbc DbConfig
	var exist bool
	if dbc, exist = Config.Dbs[dbName]; !exist {
		return nil
	}
	d := databases.Get(dbName)
	if d == nil {
		s, err := newDBSession(&dbc)
		if err != nil {
			fmt.Println("Database:", dbName, " error:", err)
			return nil
		}
		mgoSessions.Set(dbName, s)
		db := s.DB(dbc.Database)
		if db == nil {
			return nil
		}
		databases.Set(dbName, db)
		return db
	} else {
		if db, ok := d.(*mgo.Database); ok {
			return db
		}
	}
	return nil
}

func DatabaseWithSubName(dbName, subName string) *mgo.Database {
	if atomic.LoadInt32(&resetingSess) == 1 {
		return nil
	}
	var dbc DbConfig
	var exist bool
	if dbc, exist = Config.Dbs[dbName]; !exist {
		return nil
	}
	fullName := fmt.Sprintf("%s_%s", dbName, subName)
	d := databases.Get(fullName)
	if d == nil {
		s, err := newDBSession(&dbc)
		if err != nil {
			fmt.Println("Database:", fullName, " error:", err)
			return nil
		}
		mgoSessions.Set(dbName, s)
		db := s.DB(dbc.Database + subName)
		if db == nil {
			return nil
		}
		databases.Set(fullName, db)
		return db
	} else {
		if db, ok := d.(*mgo.Database); ok {
			return db
		}
	}
	return nil
}

func DatabaseC(dbName, collectionName string) *Collection {
	if atomic.LoadInt32(&resetingSess) == 1 {
		return nil
	}
	//一个库共享一个连接池
	db := Database(dbName)
	if db != nil {
		c := db.C(collectionName)
		if c != nil {
			return &Collection{Collection: c, valid: true}
		}
	}
	return nil
}

func DatabaseWithSubNameC(dbName, subName, collectionName string) *Collection {
	if atomic.LoadInt32(&resetingSess) == 1 {
		return nil
	}
	//一个库共享一个连接池
	db := DatabaseWithSubName(dbName, subName)
	if db != nil {
		c := db.C(collectionName)
		if c != nil {
			return &Collection{Collection: c, valid: true}
		}
	}
	return nil
}

// 不严格的多线程保护
func ResetAllSession() {
	atomic.StoreInt32(&resetingSess, 1)
	defer atomic.StoreInt32(&resetingSess, 0)
	tstart := time.Now()
	logger.Logger.Warnf("ResetAllSession!!! start.")
	sessions := mgoSessions.Items()
	mgoSessions = container.NewSynchronizedMap()
	databases = container.NewSynchronizedMap()

	//使缓存无效
	cLock.Lock()
	for k, c := range collections {
		c.valid = false
		logger.Logger.Warnf("%s collections reset.", k)
	}
	collections = make(map[string]*Collection)
	cLock.Unlock()

	//关闭旧的session
	for k, s := range sessions {
		if session, ok := s.(*mgo.Session); ok && session != nil {
			logger.Logger.Warnf("mongo.Close!!! (%v)", k)
			session.Close()
		}
	}
	logger.Logger.Warnf("ResetAllSession!!! end. take:%v", time.Now().Sub(tstart))
}
