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

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/33cn/chain33/common/crypto"
	"github.com/33cn/chain33/common/log/log15"
	"github.com/33cn/chain33/common/merkle"
	"github.com/33cn/chain33/types"
	tmtypes "github.com/33cn/plugin/plugin/dapp/valnode/types"
	"github.com/golang/protobuf/proto"
)

var (
	blocklog = log15.New("module", "tendermint-block")
	// ConsensusCrypto define
	ConsensusCrypto crypto.Crypto
)

// BlockID struct
type BlockID struct {
	tmtypes.BlockID
}

// IsZero returns true if this is the BlockID for a nil-block
func (blockID BlockID) IsZero() bool {
	return len(blockID.Hash) == 0
}

// Equals returns true if the BlockID matches the given BlockID
func (blockID BlockID) Equals(other BlockID) bool {
	return bytes.Equal(blockID.Hash, other.Hash)
}

// Key returns a machine-readable string representation of the BlockID
func (blockID BlockID) Key() string {
	return string(blockID.Hash)
}

// String returns a human readable string representation of the BlockID
func (blockID BlockID) String() string {
	return Fmt(`%v`, blockID.Hash)
}

//TendermintBlock struct
type TendermintBlock struct {
	*tmtypes.TendermintBlock
}

// MakeBlock returns a new block with an empty header, except what can be computed from itself.
// It populates the same set of fields validated by ValidateBasic
func MakeBlock(height int64, round int64, Txs []*types.Transaction, commit *tmtypes.TendermintCommit) *TendermintBlock {
	block := &TendermintBlock{&tmtypes.TendermintBlock{
		Header: &tmtypes.TendermintBlockHeader{
			Height: height,
			Round:  round,
			Time:   time.Now().UnixNano(),
			NumTxs: int64(len(Txs)),
		},
		Txs:        Txs,
		LastCommit: commit,
		Evidence:   &tmtypes.EvidenceData{Evidence: make([]*tmtypes.EvidenceEnvelope, 0)},
	},
	}
	block.FillHeader()
	return block
}

// AddEvidence appends the given evidence to the block
func (b *TendermintBlock) AddEvidence(evidence []Evidence) {
	for _, item := range evidence {
		ev := item.Child()
		if ev != nil {
			data, err := proto.Marshal(ev)
			if err != nil {
				blocklog.Error("AddEvidence marshal failed", "error", err)
				panic("AddEvidence marshal failed")
			}
			env := &tmtypes.EvidenceEnvelope{
				TypeName: item.TypeName(),
				Data:     data,
			}
			b.Evidence.Evidence = append(b.Evidence.Evidence, env)
		}
	}
}

// ValidateBasic performs basic validation that doesn't involve state data.
// It checks the internal consistency of the block.
func (b *TendermintBlock) ValidateBasic() (int64, error) {
	newTxs := int64(len(b.Txs))

	if b.Header.NumTxs != newTxs {
		return 0, fmt.Errorf("Wrong Block.Header.NumTxs. Expected %v, got %v", newTxs, b.Header.NumTxs)
	}
	lastCommit := Commit{
		TendermintCommit: b.LastCommit,
	}
	if !bytes.Equal(b.Header.LastCommitHash, lastCommit.Hash()) {
		return 0, fmt.Errorf("Wrong Block.Header.LastCommitHash.  Expected %v, got %v", b.Header.LastCommitHash, lastCommit.Hash())
	}
	if b.Header.Height != 1 {
		if err := lastCommit.ValidateBasic(); err != nil {
			return 0, err
		}
	}

	evidence := &EvidenceData{EvidenceData: b.Evidence}
	if !bytes.Equal(b.Header.EvidenceHash, evidence.Hash()) {
		return 0, errors.New(Fmt("Wrong Block.Header.EvidenceHash.  Expected %v, got %v", b.Header.EvidenceHash, evidence.Hash()))
	}
	return newTxs, nil
}

// FillHeader fills in any remaining header fields that are a function of the block data
func (b *TendermintBlock) FillHeader() {
	if b.Header.LastCommitHash == nil {
		lastCommit := &Commit{
			TendermintCommit: b.LastCommit,
		}
		b.Header.LastCommitHash = lastCommit.Hash()
	}
	if b.Header.EvidenceHash == nil {
		evidence := &EvidenceData{EvidenceData: b.Evidence}
		b.Header.EvidenceHash = evidence.Hash()
	}
}

// Hash computes and returns the block hash.
// If the block is incomplete, block hash is nil for safety.
func (b *TendermintBlock) Hash() []byte {
	if b == nil || b.Header == nil || b.LastCommit == nil {
		return nil
	}
	b.FillHeader()
	header := &Header{TendermintBlockHeader: b.Header}
	return header.Hash()
}

// HashesTo is a convenience function that checks if a block hashes to the given argument.
// A nil block never hashes to anything, and nothing hashes to a nil hash.
func (b *TendermintBlock) HashesTo(hash []byte) bool {
	if len(hash) == 0 {
		return false
	}
	if b == nil {
		return false
	}
	return bytes.Equal(b.Hash(), hash)
}

// String returns a string representation of the block
func (b *TendermintBlock) String() string {
	return b.StringIndented("")
}

// StringIndented returns a string representation of the block
func (b *TendermintBlock) StringIndented(indent string) string {
	if b == nil {
		return "nil-Block"
	}
	header := &Header{TendermintBlockHeader: b.Header}
	lastCommit := &Commit{TendermintCommit: b.LastCommit}
	return Fmt(`Block{
%s  %v
%s  %v
%s  %v
%s}#%v`,
		indent, header.StringIndented(indent+"  "),
		//		indent, b.Evidence.StringIndented(indent+"  "),
		indent, lastCommit.StringIndented(indent+"  "),
		indent, b.Hash())
}

// StringShort returns a shortened string representation of the block
func (b *TendermintBlock) StringShort() string {
	if b == nil {
		return "nil-Block"
	}
	return Fmt("Block#%v", b.Hash())
}

// Header defines the structure of a Tendermint block header
// TODO: limit header size
// NOTE: changes to the Header should be duplicated in the abci Header
type Header struct {
	*tmtypes.TendermintBlockHeader
}

// Hash returns the hash of the header.
// Returns nil if ValidatorHash is missing.
func (h *Header) Hash() []byte {
	if len(h.ValidatorsHash) == 0 {
		return nil
	}
	bytes, err := json.Marshal(h)
	if err != nil {
		blocklog.Error("block header Hash() marshal failed", "error", err)
		return nil
	}
	return crypto.Ripemd160(bytes)
}

// StringIndented returns a string representation of the header
func (h *Header) StringIndented(indent string) string {
	if h == nil {
		return "nil-Header"
	}
	return Fmt(`Header{
%s  ChainID:        %v
%s  Height:         %v
%s  Time:           %v
%s  NumTxs:         %v
%s  TotalTxs:       %v
%s  LastBlockID:    %v
%s  LastCommit:     %v
%s  Validators:     %v
%s  App:            %v
%s  Conensus:       %v
%s  Results:        %v
%s  Evidence:       %v
%s}#%v`,
		indent, h.ChainID,
		indent, h.Height,
		indent, time.Unix(0, h.Time),
		indent, h.NumTxs,
		indent, h.TotalTxs,
		indent, h.LastBlockID,
		indent, h.LastCommitHash,
		indent, h.ValidatorsHash,
		indent, h.AppHash,
		indent, h.ConsensusHash,
		indent, h.LastResultsHash,
		indent, h.EvidenceHash,
		indent, h.Hash())
}

// Commit struct
type Commit struct {
	*tmtypes.TendermintCommit

	firstPrecommit *tmtypes.Vote
	hash           []byte
	bitArray       *BitArray
}

// FirstPrecommit returns the first non-nil precommit in the commit
func (commit *Commit) FirstPrecommit() *tmtypes.Vote {
	if len(commit.Precommits) == 0 {
		return nil
	}
	if commit.firstPrecommit != nil {
		return commit.firstPrecommit
	}
	for _, precommit := range commit.Precommits {
		if precommit != nil && len(precommit.Signature) > 0 {
			commit.firstPrecommit = precommit
			return precommit
		}
	}
	return nil
}

// Height returns the height of the commit
func (commit *Commit) Height() int64 {
	if len(commit.Precommits) == 0 {
		return 0
	}
	return commit.FirstPrecommit().Height
}

// Round returns the round of the commit
func (commit *Commit) Round() int {
	if len(commit.Precommits) == 0 {
		return 0
	}
	return int(commit.FirstPrecommit().Round)
}

// Type returns the vote type of the commit, which is always VoteTypePrecommit
func (commit *Commit) Type() byte {
	return VoteTypePrecommit
}

// Size returns the number of votes in the commit
func (commit *Commit) Size() int {
	if commit == nil {
		return 0
	}
	return len(commit.Precommits)
}

// BitArray returns a BitArray of which validators voted in this commit
func (commit *Commit) BitArray() *BitArray {
	if commit.bitArray == nil {
		commit.bitArray = NewBitArray(len(commit.Precommits))
		for i, precommit := range commit.Precommits {
			// TODO: need to check the BlockID otherwise we could be counting conflicts,
			// not just the one with +2/3 !
			commit.bitArray.SetIndex(i, precommit.ValidatorAddress != nil)
		}
	}
	return commit.bitArray
}

// GetByIndex returns the vote corresponding to a given validator index
func (commit *Commit) GetByIndex(index int) *Vote {
	return &Vote{Vote: commit.Precommits[index]}
}

// IsCommit returns true if there is at least one vote
func (commit *Commit) IsCommit() bool {
	return len(commit.Precommits) != 0
}

// ValidateBasic performs basic validation that doesn't involve state data.
func (commit *Commit) ValidateBasic() error {
	blockID := &BlockID{BlockID: *commit.BlockID}
	if blockID.IsZero() {
		return errors.New("Commit cannot be for nil block")
	}
	if len(commit.Precommits) == 0 {
		return errors.New("No precommits in commit")
	}
	height, round := commit.Height(), commit.Round()

	// validate the precommits
	for _, precommit := range commit.Precommits {
		// It's OK for precommits to be missing.
		if precommit == nil {
			continue
		}
		// Ensure that all votes are precommits
		if byte(precommit.Type) != VoteTypePrecommit {
			return fmt.Errorf("Invalid commit vote. Expected precommit, got %v",
				precommit.Type)
		}
		// Ensure that all heights are the same
		if precommit.Height != height {
			return fmt.Errorf("Invalid commit precommit height. Expected %v, got %v",
				height, precommit.Height)
		}
		// Ensure that all rounds are the same
		if int(precommit.Round) != round {
			return fmt.Errorf("Invalid commit precommit round. Expected %v, got %v",
				round, precommit.Round)
		}
	}
	return nil
}

// Hash returns the hash of the commit
func (commit *Commit) Hash() []byte {
	if commit.hash == nil {
		bs := make([][]byte, len(commit.Precommits))
		for i, item := range commit.Precommits {
			precommit := Vote{Vote: item}
			bs[i] = precommit.Hash()
		}
		commit.hash = merkle.GetMerkleRoot(bs)
	}
	return commit.hash
}

// StringIndented returns a string representation of the commit
func (commit *Commit) StringIndented(indent string) string {
	if commit == nil {
		return "nil-Commit"
	}
	precommitStrings := make([]string, len(commit.Precommits))
	for i, precommit := range commit.Precommits {
		precommitStrings[i] = precommit.String()
	}
	return Fmt(`Commit{
%s  BlockID:    %v
%s  Precommits: %v
%s}#%v`,
		indent, commit.BlockID,
		indent, strings.Join(precommitStrings, "\n"+indent+"  "),
		indent, commit.hash)
}

// SignedHeader is a header along with the commits that prove it
type SignedHeader struct {
	Header *Header `json:"header"`
	Commit *Commit `json:"commit"`
}

// EvidenceEnvelope ...
type EvidenceEnvelope struct {
	*tmtypes.EvidenceEnvelope
}

// EvidenceEnvelopeList contains any evidence of malicious wrong-doing by validators
type EvidenceEnvelopeList []EvidenceEnvelope

// Hash ...
func (env EvidenceEnvelope) Hash() []byte {
	penv := env.EvidenceEnvelope
	evidence := EvidenceEnvelope2Evidence(penv)
	if evidence != nil {
		return evidence.Hash()
	}
	return nil
}

func (env EvidenceEnvelope) String() string {
	penv := env.EvidenceEnvelope
	evidence := EvidenceEnvelope2Evidence(penv)
	if evidence != nil {
		return evidence.String()
	}
	return ""
}

// Hash returns the simple merkle root hash of the EvidenceList.
func (evl EvidenceEnvelopeList) Hash() []byte {
	// Recursive impl.
	// Copied from tmlibs/merkle to avoid allocations
	switch len(evl) {
	case 0:
		return nil
	case 1:
		return evl[0].Hash()
	default:
		left := evl[:(len(evl)+1)/2].Hash()
		right := evl[(len(evl)+1)/2:].Hash()
		cache := make([]byte, len(left)+len(right))
		return merkle.GetHashFromTwoHash(cache, left, right)
	}
}

func (evl EvidenceEnvelopeList) String() string {
	s := ""
	for _, e := range evl {
		s += Fmt("%s\t\t", e)
	}
	return s
}

// Has returns true if the evidence is in the EvidenceList.
func (evl EvidenceEnvelopeList) Has(evidence Evidence) bool {
	for _, ev := range evl {
		penv := ev.EvidenceEnvelope
		tmp := EvidenceEnvelope2Evidence(penv)
		if tmp != nil {
			if tmp.Equal(evidence) {
				return true
			}
		}
	}
	return false
}

// EvidenceData ...
type EvidenceData struct {
	*tmtypes.EvidenceData
	hash []byte
}

// Hash returns the hash of the data.
func (data *EvidenceData) Hash() []byte {
	if data.hash == nil {
		if data.EvidenceData == nil {
			return nil
		}
		var evidence EvidenceEnvelopeList
		for _, item := range data.Evidence {
			elem := EvidenceEnvelope{
				EvidenceEnvelope: item,
			}
			evidence = append(evidence, elem)
		}
		data.hash = evidence.Hash()
	}
	return data.hash
}

// StringIndented returns a string representation of the evidence.
func (data *EvidenceData) StringIndented(indent string) string {
	if data == nil {
		return "nil-Evidence"
	}
	evStrings := make([]string, MinInt(len(data.Evidence), 21))
	for i, ev := range data.Evidence {
		if i == 20 {
			evStrings[i] = Fmt("... (%v total)", len(data.Evidence))
			break
		}
		evStrings[i] = Fmt("Evidence:%v", ev)
	}
	return Fmt(`Data{
%s  %v
%s}#%v`,
		indent, strings.Join(evStrings, "\n"+indent+"  "),
		indent, data.hash)
}
