// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package blockchain

import (
	"strings"

	"fmt"

	"github.com/33cn/chain33/common/version"
	"github.com/33cn/chain33/types"
)

// Upgrade 升级localDB和storeDB
func (chain *BlockChain) Upgrade() {
	chain.UpgradeChain()
	chain.UpgradeStore()
}

//UpgradeStore 升级storedb
func (chain *BlockChain) UpgradeStore() {
	meta, err := chain.blockStore.GetStoreUpgradeMeta()
	if err != nil {
		panic(err)
	}
	curheight := chain.GetBlockHeight()
	if curheight == -1 {
		meta = &types.UpgradeMeta{
			Version: version.GetStoreDBVersion(),
		}
		err = chain.blockStore.SetStoreUpgradeMeta(meta)
		if err != nil {
			panic(err)
		}
	}
	if chain.needReExec(meta) {
		start := meta.Height
		//reExecBlock 的过程中，会每个高度都去更新meta
		chain.ReExecBlock(start, curheight)
		meta := &types.UpgradeMeta{
			Starting: false,
			Version:  version.GetStoreDBVersion(),
			Height:   0,
		}
		err = chain.blockStore.SetStoreUpgradeMeta(meta)
		if err != nil {
			panic(err)
		}
	}
}

// ReExecBlock 从对应高度本地重新执行区块
func (chain *BlockChain) ReExecBlock(startHeight, curHeight int64) {
	var prevStateHash []byte
	if startHeight > 0 {
		blockdetail, err := chain.GetBlock(startHeight - 1)
		if err != nil {
			panic(fmt.Sprintf("get height=%d err, this not allow fail", startHeight-1))
		}
		prevStateHash = blockdetail.Block.StateHash
	}

	for i := startHeight; i <= curHeight; i++ {
		blockdetail, err := chain.GetBlock(i)
		if err != nil {
			panic(fmt.Sprintf("get height=%d err, this not allow fail", i))
		}
		block := blockdetail.Block
		err = execBlockUpgrade(chain.client, prevStateHash, block, false)
		if err != nil {
			panic(fmt.Sprintf("execBlockEx height=%d err=%s, this not allow fail", i, err.Error()))
		}

		if chain.cfg.EnableReExecLocal {
			// 保存tx信息到db中
			newbatch := chain.blockStore.NewBatch(false)
			err = chain.blockStore.AddTxs(newbatch, blockdetail)
			if err != nil {
				panic(fmt.Sprintf("execBlockEx connectBlock readd Txs fail height=%d err=%s, this not allow fail", i, err.Error()))
			}
			err = newbatch.Write()
			if err != nil {
				panic(err)
			}
		}

		prevStateHash = block.StateHash
		//更新高度
		err = chain.upgradeMeta(i)
		if err != nil {
			panic(err)
		}
	}
}

func (chain *BlockChain) needReExec(meta *types.UpgradeMeta) bool {
	if meta.Starting { //正在
		return true
	}
	v1 := meta.Version
	v2 := version.GetStoreDBVersion()
	v1arr := strings.Split(v1, ".")
	v2arr := strings.Split(v2, ".")
	if len(v1arr) != 3 || len(v2arr) != 3 {
		panic("upgrade store meta version error")
	}
	return v1arr[0] != v2arr[0]
}

func (chain *BlockChain) upgradeMeta(height int64) error {
	meta := &types.UpgradeMeta{
		Starting: true,
		Version:  version.GetStoreDBVersion(),
		Height:   height + 1,
	}
	return chain.blockStore.SetStoreUpgradeMeta(meta)
}
