/**
 * 日志存储器
 * 1）以通道控制接收日志，ID自动递增作为键有序保存
 * 2）优先响应保存日志，闲时创建关键词反向索引
 * 3）获取存储对象线程安全，带缓存无则创建有则直取，空闲超时自动关闭leveldb，再次获取时自动打开
 */
package logdata

import (
	"errors"
	"glc/cmn"
	"glc/conf"
	"glc/ldb/status"
	"glc/ldb/storage/indexword"
	"glc/ldb/sysmnt"
	"glc/ldb/tokenizer"
	"glc/onexit"
	"log"
	"sync"
	"time"

	"github.com/syndtr/goleveldb/leveldb"
)

// 存储结构体
type LogDataStorage struct {
	storeName         string             // 存储目录
	subPath           string             // 存储目录下的相对路径（存放数据）
	storeChan         chan *LogDataModel // 存储通道
	leveldb           *leveldb.DB        // leveldb
	currentCount      uint32             // 当前件数
	savedCurrentCount uint32             // 已保存的当前件数
	indexedCount      uint32             // 已创建的索引件数
	savedIndexedCount uint32             // 已保存的索引件数(定时保存indexedCount，存起来以便下次启动继续建索引)
	lastTime          int64              // 最后一次访问时间
	closing           bool               // 是否关闭中状态
	mu                sync.Mutex         // 锁
	wg                sync.WaitGroup     // 计数
}

var zeroUint32Bytes []byte = cmn.Uint32ToBytes(0)
var ldbMu sync.Mutex
var mapStorage map[string](*LogDataStorage)

func init() {
	mapStorage = make(map[string](*LogDataStorage))
	onexit.RegisterExitHandle(onExit) // 优雅退出
}

func getCacheStore(cacheName string) *LogDataStorage {
	cacheStore := mapStorage[cacheName]
	if cacheStore != nil && !cacheStore.IsClose() {
		return cacheStore // 缓存中未关闭的存储对象
	}
	return nil
}

// 获取存储对象，线程安全（带缓存无则创建有则直取）
func NewLogDataStorage(storeName string) *LogDataStorage { // 存储器，文档，自定义对象

	subPath := "data"
	// 缓存有则取用
	cacheName := storeName + cmn.PathSeparator() + subPath
	cacheStore := getCacheStore(cacheName)
	if cacheStore != nil && !cacheStore.IsClose() {
		return cacheStore
	}

	// 缓存无则锁后创建返回并存缓存
	ldbMu.Lock()                          // 上锁
	defer ldbMu.Unlock()                  // 解锁
	cacheStore = getCacheStore(cacheName) // 再次尝试取用缓存中存储器
	if cacheStore != nil && !cacheStore.IsClose() {
		return cacheStore
	}

	store := new(LogDataStorage)
	store.storeName = storeName
	store.subPath = subPath
	store.closing = false
	store.lastTime = time.Now().Unix()
	store.storeChan = make(chan *LogDataModel, conf.GetStoreChanLength()) // 初始化管道，设定缓冲

	dbPath := conf.GetStorageRoot() + cmn.PathSeparator() + cacheName
	db, err := leveldb.OpenFile(dbPath, nil) // 打开（在指定子目录中存放数据）
	if err != nil {
		log.Println("打开LogDataStorage失败：", dbPath)
		panic(err)
	}
	store.leveldb = db
	store.loadMetaData()                        // 初始化件数等信息
	status.UpdateStorageStatus(storeName, true) // 更新状态：当前日志仓打开
	mapStorage[cacheName] = store               // 缓存起来

	// 消费就绪
	go store.readyGo()

	// 定时判断保存总件数，避免每次保存以提高性能
	go store.readySaveMetaDate()

	// 逐秒判断，若闲置超时则自动关闭
	go store.autoCloseWhenMaxIdle()

	log.Println("打开LogDataStorage：", cacheName)
	return store
}

// 定时调用保存件数信息，避免每次都存levledb
func (s *LogDataStorage) readySaveMetaDate() {
	ticker := time.NewTicker(time.Second * 5)
	for {
		<-ticker.C
		if s.IsClose() {
			ticker.Stop()
			break
		}
		s.saveMetaData()
	}
}

// 等待接收日志，优先响应保存日志，空时再生成索引
func (s *LogDataStorage) readyGo() {
	for {
		select {
		case data := <-s.storeChan:
			s.wg.Done()
			// 优先响应保存日志
			if data == nil {
				if !s.IsClose() {
					close(s.storeChan) // 关闭通道
				}
				break
			}
			s.saveLogData(data) // 保存日志数据
		default:
			// 空时再生成索引，一次一条日志，有空则生成直到全部完成
			n := s.createInvertedIndex() // 生成反向索引

			// 索引生成完成后，等待接收保存日志
			if n < 1 {
				log.Println("空闲等待接收日志")
				data := <-s.storeChan // 没有索引可生成时，等待storeChan
				s.wg.Done()
				if data == nil {
					if !s.IsClose() {
						close(s.storeChan) // 关闭通道
					}
					break
				}
				s.saveLogData(data) // 保存日志数据
			}
		}
	}
}

func (s *LogDataStorage) saveLogData(model *LogDataModel) {
	//store.wg.Done()
	s.currentCount++                              // ID递增
	doc := new(LogDataDocument)                   // 文档
	doc.Id = s.currentCount                       // 已递增好的值
	model.Id = cmn.Uint32ToString(s.currentCount) // 模型数据要转Json存，也得更新ID,ID用36进制字符串形式表示
	doc.Content = model.ToJson()                  // 转json作为内容(含Id)

	// 保存
	s.put(cmn.Uint32ToBytes(doc.Id), doc.ToBytes()) // 日志数据
	//log.Println("保存日志数据 ", doc.Id)
}

// 创建日志索引（一次建一条日志的索引）,没有可建索引时返回false
func (s *LogDataStorage) createInvertedIndex() int {

	// 索引信息和日志数量相互比较，判断是否继续创建索引
	if s.TotalCount() == 0 || s.indexedCount >= s.TotalCount() {
		return 0 // 没有新的日志需要建索引
	}

	s.indexedCount++                               // 下一条要建索引的日志id
	docm, err := s.GetLogDataModel(s.indexedCount) // 取出日志模型数据
	if err != nil {
		log.Println("取日志模型数据失败：", s.indexedCount, err)
		return 2
	}

	// 整理关键词
	adds := docm.Keywords
	adds = append(adds, docm.Tags...)
	adds = append(adds, docm.Client, docm.Server, docm.System, docm.User)
	kws := tokenizer.CutForSearchEx(docm.Text, adds, docm.Sensitives) // 两数组参数的元素可以重复或空白，会被判断整理
	//	log.Println("GetLogDataModel=", docm.ToJson(), kws)

	// 每个关键词都创建反向索引
	for _, word := range kws {
		idxw := indexword.NewWordIndexStorage(s.StoreName())
		idxw.Add(word, cmn.StringToUint32(docm.Id, 0)) // 日志ID加入索引
	}
	//log.Println("创建日志索引：", cmn.StringToUint32(docm.Id, 0))

	return 1
}

func (s *LogDataStorage) autoCloseWhenMaxIdle() {
	if conf.GetMaxIdleTime() > 0 {
		ticker := time.NewTicker(time.Second)
		for {
			<-ticker.C
			if time.Now().Unix()-s.lastTime > int64(conf.GetMaxIdleTime()) {
				s.Close()
				ticker.Stop()
				break
			}
		}
	}
}

// 直接存入数据到leveldb
func (s *LogDataStorage) put(key []byte, value []byte) error {
	if s.closing {
		return errors.New("current storage is closed") // 关闭中或已关闭时拒绝服务，调用方需自行重取连接等处理
	}
	s.lastTime = time.Now().Unix()
	return s.leveldb.Put(key, value, nil)
}

// 直接从leveldb取数据
func (s *LogDataStorage) Get(key []byte) ([]byte, error) {
	if s.closing {
		return nil, errors.New("current storage is closed") // 关闭中或已关闭时拒绝服务
	}
	s.lastTime = time.Now().Unix()
	return s.leveldb.Get(key, nil)
}

// 直接从leveldb取数据并转换为LogDataModel
func (s *LogDataStorage) GetLogDataModel(id uint32) (*LogDataModel, error) {
	if s.closing {
		return nil, errors.New("current storage is closed") // 关闭中或已关闭时拒绝服务
	}
	s.lastTime = time.Now().Unix()
	bts, err := s.leveldb.Get(cmn.Uint32ToBytes(id), nil)
	if err != nil {
		return nil, err
	}

	doc := new(LogDataDocument)
	doc.LoadBytes(bts)
	return doc.ToLogDataModel(), nil
}

// 添加数据，经通道后由自定义的保存函数处理保存
func (s *LogDataStorage) Add(model *LogDataModel) error {
	if s.closing {
		return errors.New("current storage is closed") // 关闭中或已关闭时拒绝服务
	}
	if model == nil {
		return errors.New("ingore data nil") // 拒绝接收nil（nil作通道的关闭信号用，避免冲突）
	}
	s.lastTime = time.Now().Unix()
	s.wg.Add(1)
	s.storeChan <- model // 把文本发送到消息通道
	return nil
}

// 关闭Storage
func (s *LogDataStorage) Close() {
	if s == nil || s.closing { // 优雅退出时可能会正好nil，判断一下优雅点
		return
	}

	s.mu.Lock()         // 锁
	defer s.mu.Unlock() // 解锁
	if s.closing {
		return
	}

	s.closing = true
	s.wg.Wait()                                    // 等待通道清空
	s.saveMetaData()                               // 保存件数等元信息
	s.wg.Add(1)                                    // 通道消息计数
	s.storeChan <- nil                             // 通道正在在阻塞等待接收，给个nil让它接收后关闭
	s.leveldb.Close()                              // 走到这里时没有db操作了，可以关闭
	mapStorage[s.storeName] = nil                  // 设空，下回GetStorage时自动再创建
	status.UpdateStorageStatus(s.storeName, false) // 更新状态：当前日志仓关闭

	log.Println("关闭LogDataStorage：", s.storeName+cmn.PathSeparator()+s.subPath)
}

func (s *LogDataStorage) loadMetaData() {

	// 初始化：当前日志件数
	bytes, err := s.leveldb.Get(zeroUint32Bytes, nil)
	if err != nil || bytes == nil {
		s.currentCount = 0
	} else {
		s.currentCount = cmn.BytesToUint32(bytes)
		s.savedCurrentCount = s.currentCount
	}

	// 初始化：已建索引件数
	idxw := indexword.NewWordIndexStorage(s.StoreName())
	s.indexedCount = idxw.GetIndexedCount()
	s.savedIndexedCount = s.indexedCount
}

func (s *LogDataStorage) saveMetaData() {

	if s.savedCurrentCount < s.currentCount {
		s.savedCurrentCount = s.currentCount
		s.leveldb.Put(zeroUint32Bytes, cmn.Uint32ToBytes(s.savedCurrentCount), nil) // 保存日志总件数
		sysmntStore := sysmnt.NewSysmntStorage()                                    // 系统管理存储器
		sysmntStore.SetStorageDataCount(s.storeName, s.savedCurrentCount)           // 保存日志总件数
		log.Println("保存LogDataStorage件数:", s.savedCurrentCount)
	}

	if s.savedIndexedCount < s.indexedCount {
		s.savedIndexedCount = s.indexedCount
		idxw := indexword.NewWordIndexStorage(s.StoreName())
		idxw.SaveIndexedCount(s.savedIndexedCount)                         // 保存索引总件数
		sysmntStore := sysmnt.NewSysmntStorage()                           // 系统管理存储器
		sysmntStore.SetStorageIndexCount(s.storeName, s.savedCurrentCount) // 保存索引总件数
		log.Println("保存LogDataStorage已建索引件数:", s.savedIndexedCount)
	}
}

// 总件数
func (s *LogDataStorage) TotalCount() uint32 {
	return s.currentCount
}

// 存储目录名
func (s *LogDataStorage) StoreName() string {
	return s.storeName
}

// 是否关闭中状态
func (s *LogDataStorage) IsClose() bool {
	return s.closing
}

func onExit() {
	for k := range mapStorage {
		mapStorage[k].Close()
	}
	log.Println("退出LogDataStorage")
}
