// 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 accounts

import (
	"time"

	l "github.com/33cn/chain33/common/log/log15"

	"github.com/33cn/chain33/types"

	//"encoding/json"
	//"io/ioutil"
	"fmt"
	"strconv"

	rpctypes "github.com/33cn/chain33/rpc/types"
)

const secondsPerBlock = 15
const btyPreBlock = 18
const statInterval = 3600
const monitorBtyLowLimit = 3 * 1e7 * types.Coin

var log = l.New("module", "accounts")

//ShowMinerAccount 挖矿账户
type ShowMinerAccount struct {
	DataDir string
	Addrs   []string
}

//Echo 打印
func (*ShowMinerAccount) Echo(in *string, out *interface{}) error {
	if in == nil {
		return types.ErrInvalidParam
	}
	*out = *in
	return nil
}

//TimeAt time
type TimeAt struct {
	// YYYY-mm-dd-HH
	TimeAt string   `json:"timeAt,omitempty"`
	Addrs  []string `json:"addrs,omitempty"`
}

//Get get
func (show *ShowMinerAccount) Get(in *TimeAt, out *interface{}) error {
	if in == nil {
		log.Error("show", "in", "nil")
		return types.ErrInvalidParam
	}
	seconds := time.Now().Unix()
	if len(in.TimeAt) != 0 {
		tm, err := time.Parse("2006-01-02-15", in.TimeAt)
		if err != nil {
			log.Error("show", "in.TimeAt Parse", err)
			return types.ErrInvalidParam
		}
		seconds = tm.Unix()
	}
	log.Info("show", "utc", seconds)

	addrs := show.Addrs
	if in.Addrs != nil && len(in.Addrs) > 0 {
		addrs = in.Addrs
	}
	log.Info("show", "miners", addrs)
	//for i := int64(0); i < 200; i++ {
	header, curAcc, err := cache.getBalance(addrs, "ticket", seconds)
	if err != nil {
		log.Error("show", "getBalance failed", err, "ts", seconds)
		return nil
	}

	totalBty := int64(0)
	for _, acc := range curAcc {
		totalBty += acc.Frozen
	}

	monitorInterval := int64(statInterval)
	if totalBty < monitorBtyLowLimit && totalBty > 0 {
		monitorInterval = int64(float64(statInterval) * float64(monitorBtyLowLimit) / float64(totalBty))
	}
	log.Info("show", "monitor Interval", monitorInterval)
	lastHourHeader, lastAcc, err := cache.getBalance(addrs, "ticket", header.BlockTime-monitorInterval)
	if err != nil {
		log.Error("show", "getBalance failed", err, "ts", header.BlockTime-monitorInterval)
		return nil
	}
	fmt.Print(curAcc, lastAcc)

	miner := &MinerAccounts{}
	miner.Seconds = header.BlockTime - lastHourHeader.BlockTime
	miner.Blocks = header.Height - lastHourHeader.Height
	miner.ExpectBlocks = miner.Seconds / secondsPerBlock

	miner = calcIncrease(miner, curAcc, lastAcc, header)
	*out = &miner

	seconds = seconds - statInterval
	//}

	return nil
}

func calcIncrease(miner *MinerAccounts, acc1, acc2 []*rpctypes.Account, header *rpctypes.Header) *MinerAccounts {
	type minerAt struct {
		addr    string
		curAcc  *rpctypes.Account
		lastAcc *rpctypes.Account
	}
	miners := map[string]*minerAt{}
	for _, a := range acc1 {
		miners[a.Addr] = &minerAt{a.Addr, a, nil}
	}
	for _, a := range acc2 {
		if _, ok := miners[a.Addr]; ok {
			miners[a.Addr].lastAcc = a
		}
	}

	totalIncrease := float64(0)
	expectTotalIncrease := float64(0)
	totalFrozen := float64(0)
	for _, v := range miners {
		if v.lastAcc != nil && v.curAcc != nil {
			totalFrozen += float64(v.curAcc.Frozen) / float64(types.Coin)
		}
	}
	ticketTotal := float64(30000 * 10000)
	_, ticketAcc, err := cache.getBalance([]string{"16htvcBNSEA7fZhAdLJphDwQRQJaHpyHTp"}, "coins", header.BlockTime)
	if err == nil && len(ticketAcc) == 1 {
		ticketTotal = float64(ticketAcc[0].Balance+ticketAcc[0].Frozen) / float64(types.Coin)
	}
	for k, v := range miners {
		if v.lastAcc != nil && v.curAcc != nil {
			total := v.curAcc.Balance + v.curAcc.Frozen
			increase := total - v.lastAcc.Balance - v.lastAcc.Frozen
			expectIncrease := float64(miner.Blocks) * float64(btyPreBlock) * (float64(v.curAcc.Frozen) / float64(types.Coin)) / ticketTotal

			m := &MinerAccount{
				Addr:           k,
				Total:          strconv.FormatFloat(float64(total)/float64(types.Coin), 'f', 4, 64),
				Increase:       strconv.FormatFloat(float64(increase)/float64(types.Coin), 'f', 4, 64),
				Frozen:         strconv.FormatFloat(float64(v.curAcc.Frozen)/float64(types.Coin), 'f', 4, 64),
				ExpectIncrease: strconv.FormatFloat(expectIncrease, 'f', 4, 64),
			}

			//if m.Addr == "1Lw6QLShKVbKM6QvMaCQwTh5Uhmy4644CG" {
			//	log.Info("acount", "Increase", m.Increase, "expectIncrease", m.ExpectIncrease)
			//	fmt.Println(os.Stderr, "Increase", m.Increase, "expectIncrease", m.ExpectIncrease)
			//}

			// 由于取不到挖矿的交易， 通过预期挖矿数， 推断间隔多少个区块能挖到。
			// 由于挖矿分布的波动， 用双倍的预期能挖到区块的时间间隔来预警
			expectBlocks := (expectIncrease / btyPreBlock)     // 一个小时预期挖多少个块
			expectMinerInterval := statInterval / expectBlocks // 预期多少秒可以挖一个块
			moniterInterval := int64(2*expectMinerInterval) + 1

			m.ExpectMinerBlocks = strconv.FormatFloat(expectBlocks, 'f', 4, 64)
			_, acc, err := cache.getBalance([]string{m.Addr}, "ticket", header.BlockTime-moniterInterval)
			if err != nil || len(acc) == 0 {
				m.MinerBtyDuring = "0.0000"
			} else {
				minerDelta := total - acc[0].Balance - acc[0].Frozen
				m.MinerBtyDuring = strconv.FormatFloat(float64(minerDelta)/float64(types.Coin), 'f', 4, 64)
			}

			miner.MinerAccounts = append(miner.MinerAccounts, m)
			totalIncrease += float64(increase) / float64(types.Coin)
			expectTotalIncrease += expectIncrease
		}
	}
	miner.TotalIncrease = strconv.FormatFloat(totalIncrease, 'f', 4, 64)
	miner.ExpectTotalIncrease = strconv.FormatFloat(expectTotalIncrease, 'f', 4, 64)

	return miner

}

/*
func readJson(jsonFile string) (*Accounts, error) {
	d1, err := ioutil.ReadFile(jsonFile)
	if err != nil {
		log.Error("show", "read json", jsonFile, "err", err)
		return nil, err
	}
	var acc Accounts
	err = json.Unmarshal([]byte(d1), &acc)
	if err != nil {
		log.Error("show", "read json", jsonFile, "err", err)
		return nil, err
	}
	return &acc, nil
}

func parseAccounts(acc *Accounts) (*map[string]float64, error) {
	result := map[string]float64{}
	for _, a := range acc.Accounts {
		f1, e1 := strconv.ParseFloat(a.Balance, 64)
		f2, e2 := strconv.ParseFloat(a.Frozen, 64)
		if e1 != nil || e2 != nil {
			log.Error("show", "account2  len", e1, "account  len", e2)
			return nil, types.ErrNotFound
		}
		result[a.Addr] = f1 + f2
	}
	return &result, nil
}

func getAccountDetail(jsonFile string) (*map[string]float64, error) {
	acc, err := readJson(jsonFile)
	if err != nil {
		return nil, err
	}
	return parseAccounts(acc)
}

*/
