/*
 * Copyright 2018 The openwallet Authors
 * This file is part of the openwallet library.
 *
 * The openwallet library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * The openwallet library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 */

package openw

import (
	"fmt"
	"io/ioutil"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/asdine/storm"
	"github.com/astaxie/beego/config"
	"github.com/blocktree/openwallet/v2/common/file"
	"github.com/blocktree/openwallet/v2/log"
	"github.com/blocktree/openwallet/v2/openwallet"
	"github.com/blocktree/openwallet/v2/timer"
	bolt "go.etcd.io/bbolt"
)

var (
	PeriodOfTask = 5 * time.Second
	//配置文件路径
	configFilePath = filepath.Join("conf")
)

type NotificationObject interface {

	//BlockScanNotify 新区块扫描完成通知
	BlockScanNotify(header *openwallet.BlockHeader) error

	//BlockTxExtractDataNotify 区块提取结果通知
	BlockTxExtractDataNotify(account *openwallet.AssetsAccount, data *openwallet.TxExtractData) error
}

//WalletManager OpenWallet钱包管理器
type WalletManager struct {
	appDB             map[string]*StormDB
	cfg               *Config
	initialized       bool
	mu                sync.RWMutex
	observers         map[NotificationObject]bool //观察者
	importAddressTask *timer.TaskTimer
	AddressInScanning map[string]string //加入扫描的地址
}

// NewWalletManager
func NewWalletManager(config *Config) *WalletManager {
	wm := WalletManager{}
	wm.cfg = config
	wm.Init()
	return &wm
}

//Init 初始化
func (wm *WalletManager) Init() {
	wm.mu.Lock()

	if wm.initialized {
		wm.mu.Unlock()
		return
	}

	log.Info("openwallet Manager is initializing ...")

	//新建文件目录
	file.MkdirAll(wm.cfg.DBPath)
	file.MkdirAll(wm.cfg.KeyDir)

	wm.observers = make(map[NotificationObject]bool)
	wm.appDB = make(map[string]*StormDB)
	wm.AddressInScanning = make(map[string]string)

	wm.initialized = true

	wm.mu.Unlock()

	wm.initSupportAssetsAdapter()

	//启动定时导入地址到核心钱包
	//task := timer.NewTask(PeriodOfTask, wm.importNewAddressToCoreWallet)
	//wm.importAddressTask = task
	//wm.importAddressTask.Start()

	log.Info("openwallet Manager has been initialized!")
}

//AddObserver 添加观测者
func (wm *WalletManager) AddObserver(obj NotificationObject) {
	wm.mu.Lock()

	defer wm.mu.Unlock()

	if obj == nil {
		return
	}
	if _, exist := wm.observers[obj]; exist {
		//已存在，不重复订阅
		return
	}

	wm.observers[obj] = true
}

//RemoveObserver 移除观测者
func (wm *WalletManager) RemoveObserver(obj NotificationObject) {
	wm.mu.Lock()
	defer wm.mu.Unlock()

	delete(wm.observers, obj)
}

//DBFile 应用数据库文件
func (wm *WalletManager) DBFile(appID string) string {
	return filepath.Join(wm.cfg.DBPath, appID+".db")
}

//OpenDB 打开应用数据库文件
func (wm *WalletManager) OpenDB(appID string) (*StormDB, error) {

	var (
		db  *StormDB
		err error
		ok  bool
	)

	//数据库文件
	db, ok = wm.appDB[appID]

	if ok && db.Opened {
		return db, nil
	}

	//保证数据库文件并发下不被同时打开
	wm.mu.Lock()
	defer wm.mu.Unlock()

	//解锁进入后，再次确认是否已经存在
	db, ok = wm.appDB[appID]
	if ok && db.Opened {
		return db, nil

	}

	db, err = OpenStormDB(
		wm.DBFile(appID),
		storm.Batch(),
		storm.BoltOptions(0600, &bolt.Options{Timeout: 3 * time.Second}),
	)
	log.Debug("open storm db appID:", appID)
	if err != nil {
		return nil, err
	}

	//return opendb, nil
	wm.appDB[appID] = db

	return db, nil
}

//CloseDB 关闭应用数据库文件
func (wm *WalletManager) CloseDB(appID string) error {

	//数据库文件
	db, ok := wm.appDB[appID]
	if ok {
		db.Close()
	}

	return nil
}

//loadAllAppIDs 加载全部应用ID
func (wm *WalletManager) loadAllAppIDs() ([]string, error) {

	var (
		apps = make([]string, 0)
		dir  = wm.cfg.DBPath
	)

	//扫描key目录的所有钱包
	files, err := ioutil.ReadDir(dir)
	if err != nil {
		return nil, err
	}

	for _, fi := range files {
		// Skip any non-key files from the folder
		if !file.IsUserFile(fi) {
			continue
		}
		if fi.IsDir() {
			continue
		}

		appID := strings.TrimSuffix(fi.Name(), ".db")
		apps = append(apps, appID)

	}

	return apps, nil
}

// initBlockScanner 初始化区块链扫描器
func (wm *WalletManager) initSupportAssetsAdapter() error {

	//加载已存在所有app
	appIDs, err := wm.loadAllAppIDs()
	if err != nil {
		return err
	}

	wm.ClearAddressForBlockScan()

	for _, appID := range appIDs {

		wrapper, err := wm.NewWalletWrapper(appID, "")
		if err != nil {
			log.Error("wallet manager init unexpected error:", err)
			continue
		}

		addrs, err := wrapper.GetAddressList(0, -1)

		for _, address := range addrs {
			key := wm.encodeSourceKey(appID, address.AccountID)
			wm.AddAddressForBlockScan(address.Address, key)
		}

	}

	for _, symbol := range wm.cfg.SupportAssets {
		assetsMgr, err := GetAssetsAdapter(symbol)
		if err != nil {
			log.Error(symbol, "is not support")
			continue
		}
		//读取配置
		absFile := filepath.Join(wm.cfg.ConfigDir, symbol+".ini")
		//log.Debug("absFile:", absFile)
		c, err := config.NewConfig("ini", absFile)
		if err != nil {
			continue
		}
		assetsMgr.LoadAssetsConfig(c)
		//log.Debug("c:", c)
		if !wm.cfg.EnableBlockScan {
			//不加载区块扫描
			continue
		}

		assetsLogger := assetsMgr.GetAssetsLogger()
		if assetsLogger != nil {
			assetsLogger.SetLogFuncCall(true)
		}

		scanner := assetsMgr.GetBlockScanner()

		if scanner == nil {
			log.Error(symbol, "is not support block scan")
			continue
		}

		//加载地址时，暂停区块扫描
		scanner.Pause()

		//添加观测者到区块扫描器
		scanner.AddObserver(wm)

		//设置查找地址算法
		scanner.SetBlockScanAddressFunc(wm.GetSourceKeyByAddressForBlockScan)

		scanner.Run()
	}

	return nil
}

//NewWalletWrapper 创建App专用的包装器
func (wm *WalletManager) NewWalletWrapper(appID, walletID string) (*WalletWrapper, error) {

	var walletWrapper *WalletWrapper

	//打开数据库
	db, err := wm.OpenDB(appID)
	if err != nil {
		return nil, err
	}

	dbFile := WalletDBFile(wm.DBFile(appID))
	wrapperAppID := WalletDBFile(appID)
	wrapper := NewAppWrapper(wrapperAppID, dbFile, db)

	if len(walletID) > 0 {

		wallet, err := wrapper.GetWalletInfo(walletID)
		if err != nil {
			return nil, fmt.Errorf("wallet not exist")
		}

		keyFile := WalletKeyFile(wallet.KeyFile)
		walletWrapper = NewWalletWrapper(wallet, keyFile, wrapper)

	} else {
		walletWrapper = NewWalletWrapper(wrapper)
	}

	return walletWrapper, nil
}

// encodeSourceKey 编码sourceKey
func (wm *WalletManager) encodeSourceKey(appID, accountID string) string {
	key := appID + ":" + accountID
	return key
}

// decodeSourceKey 解码sourceKey
func (wm *WalletManager) decodeSourceKey(key string) (appID string, accountID string) {
	sources := strings.Split(key, ":")
	if len(sources) == 2 {
		return sources[0], sources[1]
	} else {
		return "", ""
	}
}
