// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum 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 go-ethereum 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.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package dex

import (
	"crypto/ecdsa"
	"fmt"
	"io"
	"math/big"

	"github.com/dexon-foundation/dexon/common"
	"github.com/dexon-foundation/dexon/core"
	"github.com/dexon-foundation/dexon/core/types"
	"github.com/dexon-foundation/dexon/event"
	"github.com/dexon-foundation/dexon/p2p/enode"
	"github.com/dexon-foundation/dexon/rlp"
	"golang.org/x/crypto/sha3"
)

// Constants to match up protocol versions and messages
const (
	dex64 = 64
)

// ProtocolName is the official short name of the protocol used during capability negotiation.
var ProtocolName = "dex"

// ProtocolVersions are the upported versions of the eth protocol (first is primary).
var ProtocolVersions = []uint{dex64}

// ProtocolLengths are the number of implemented message corresponding to different protocol versions.
var ProtocolLengths = []uint64{38}

const ProtocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message

// eth protocol message codes
const (
	// Protocol messages belonging to eth/62
	StatusMsg          = 0x00
	NewBlockHashesMsg  = 0x01
	TxMsg              = 0x02
	GetBlockHeadersMsg = 0x03
	BlockHeadersMsg    = 0x04
	GetBlockBodiesMsg  = 0x05
	BlockBodiesMsg     = 0x06
	NewBlockMsg        = 0x07

	// Protocol messages belonging to eth/63
	GetNodeDataMsg = 0x0d
	NodeDataMsg    = 0x0e
	GetReceiptsMsg = 0x0f
	ReceiptsMsg    = 0x10

	// Protocol messages belonging to dex/64
	MetaMsg = 0x11

	LatticeBlockMsg        = 0x20
	VoteMsg                = 0x21
	AgreementMsg           = 0x22
	RandomnessMsg          = 0x23
	DKGPrivateShareMsg     = 0x24
	DKGPartialSignatureMsg = 0x25
)

type errCode int

const (
	ErrMsgTooLarge = iota
	ErrDecode
	ErrInvalidMsgCode
	ErrProtocolVersionMismatch
	ErrNetworkIdMismatch
	ErrGenesisBlockMismatch
	ErrNoStatusMsg
	ErrExtraStatusMsg
	ErrSuspendedPeer
)

func (e errCode) String() string {
	return errorToString[int(e)]
}

// XXX change once legacy code is out
var errorToString = map[int]string{
	ErrMsgTooLarge:             "Message too long",
	ErrDecode:                  "Invalid message",
	ErrInvalidMsgCode:          "Invalid message code",
	ErrProtocolVersionMismatch: "Protocol version mismatch",
	ErrNetworkIdMismatch:       "NetworkId mismatch",
	ErrGenesisBlockMismatch:    "Genesis block mismatch",
	ErrNoStatusMsg:             "No status message",
	ErrExtraStatusMsg:          "Extra status message",
	ErrSuspendedPeer:           "Suspended peer",
}

type txPool interface {
	// AddRemotes should add the given transactions to the pool.
	AddRemotes([]*types.Transaction) []error

	// Pending should return pending transactions.
	// The slice should be modifiable by the caller.
	Pending() (map[common.Address]types.Transactions, error)

	// SubscribeNewTxsEvent should return an event subscription of
	// NewTxsEvent and send events to the given channel.
	SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
}

type governance interface {
	GetNumChains(uint64) uint32

	LenCRS() uint64

	NotarySet(uint64, uint32) (map[string]struct{}, error)

	DKGSet(uint64) (map[string]struct{}, error)
}

type p2pServer interface {
	Self() *enode.Node

	GetPrivateKey() *ecdsa.PrivateKey

	AddDirectPeer(*enode.Node)

	RemoveDirectPeer(*enode.Node)

	AddGroup(string, []*enode.Node, uint64)

	RemoveGroup(string)
}

// statusData is the network packet for the status message.
type statusData struct {
	ProtocolVersion uint32
	NetworkId       uint64
	TD              *big.Int
	CurrentBlock    common.Hash
	GenesisBlock    common.Hash
}

// newBlockHashesData is the network packet for the block announcements.
type newBlockHashesData []struct {
	Hash   common.Hash // Hash of one particular block being announced
	Number uint64      // Number of one particular block being announced
}

// getBlockHeadersData represents a block header query.
type getBlockHeadersData struct {
	Origin  hashOrNumber // Block from which to retrieve headers
	Amount  uint64       // Maximum number of headers to retrieve
	Skip    uint64       // Blocks to skip between consecutive headers
	Reverse bool         // Query direction (false = rising towards latest, true = falling towards genesis)
}

// hashOrNumber is a combined field for specifying an origin block.
type hashOrNumber struct {
	Hash   common.Hash // Block hash from which to retrieve headers (excludes Number)
	Number uint64      // Block hash from which to retrieve headers (excludes Hash)
}

// EncodeRLP is a specialized encoder for hashOrNumber to encode only one of the
// two contained union fields.
func (hn *hashOrNumber) EncodeRLP(w io.Writer) error {
	if hn.Hash == (common.Hash{}) {
		return rlp.Encode(w, hn.Number)
	}
	if hn.Number != 0 {
		return fmt.Errorf("both origin hash (%x) and number (%d) provided", hn.Hash, hn.Number)
	}
	return rlp.Encode(w, hn.Hash)
}

// DecodeRLP is a specialized decoder for hashOrNumber to decode the contents
// into either a block hash or a block number.
func (hn *hashOrNumber) DecodeRLP(s *rlp.Stream) error {
	_, size, _ := s.Kind()
	origin, err := s.Raw()
	if err == nil {
		switch {
		case size == 32:
			err = rlp.DecodeBytes(origin, &hn.Hash)
		case size <= 8:
			err = rlp.DecodeBytes(origin, &hn.Number)
		default:
			err = fmt.Errorf("invalid input size %d for origin", size)
		}
	}
	return err
}

// newBlockData is the network packet for the block propagation message.
type newBlockData struct {
	Block *types.Block
	TD    *big.Int
}

// blockBody represents the data content of a single block.
type blockBody struct {
	Transactions []*types.Transaction // Transactions contained within a block
	Uncles       []*types.Header      // Uncles contained within a block
}

// blockBodiesData is the network packet for block content distribution.
type blockBodiesData []*blockBody

func rlpHash(x interface{}) (h common.Hash) {
	hw := sha3.NewLegacyKeccak256()
	rlp.Encode(hw, x)
	hw.Sum(h[:0])
	return h
}

/*
type rlpDKGPrivateShare struct {
	ProposerID   coreTypes.NodeID
	ReceiverID   coreTypes.NodeID
	Round        uint64
	PrivateShare []byte
	Signature    crypto.Signature
}

func toRLPDKGPrivateShare(ps *coreTypes.DKGPrivateShare) *rlpDKGPrivateShare {
	return &rlpDKGPrivateShare{
		ProposerID:   ps.ProposerID,
		ReceiverID:   ps.ReceiverID,
		Round:        ps.Round,
		PrivateShare: ps.PrivateShare.Bytes(),
		Signature:    ps.Signature,
	}
}

func fromRLPDKGPrivateShare(rps *rlpDKGPrivateShare) *coreTypes.DKGPrivateShare {
	ps := &coreTypes.DKGPrivateShare{
		ProposerID: rps.ProposerID,
		ReceiverID: rps.ReceiverID,
		Round:      rps.Round,
		Signature:  rps.Signature,
	}
	ps.PrivateShare.SetBytes(rps.PrivateShare)
	return ps
}

type rlpWitness struct {
	Timestamp uint64
	Height    uint64
	Data      []byte
}

type rlpFinalizeResult struct {
	Randomness []byte
	Timestamp  uint64
	Height     uint64
}

type rlpLatticeBlock struct {
	ProposerID   coreTypes.NodeID        `json:"proposer_id"`
	ParentHash   coreCommon.Hash         `json:"parent_hash"`
	Hash         coreCommon.Hash         `json:"hash"`
	Position     coreTypes.Position      `json:"position"`
	Timestamp    uint64                  `json:"timestamps"`
	Acks         coreCommon.SortedHashes `json:"acks"`
	Payload      []byte                  `json:"payload"`
	Witness      rlpWitness
	Finalization rlpFinalizeResult
	Signature    crypto.Signature `json:"signature"`
	CRSSignature crypto.Signature `json:"crs_signature"`
}

func toRLPLatticeBlock(b *coreTypes.Block) *rlpLatticeBlock {
	return &rlpLatticeBlock{
		ProposerID: b.ProposerID,
		ParentHash: b.ParentHash,
		Hash:       b.Hash,
		Position:   b.Position,
		Timestamp:  toMillisecond(b.Timestamp),
		Acks:       b.Acks,
		Payload:    b.Payload,
		Witness: rlpWitness{
			Timestamp: toMillisecond(b.Witness.Timestamp),
			Height:    b.Witness.Height,
			Data:      b.Witness.Data,
		},
		Finalization: rlpFinalizeResult{
			Randomness: b.Finalization.Randomness,
			Timestamp:  toMillisecond(b.Finalization.Timestamp),
			Height:     b.Finalization.Height,
		},
		Signature:    b.Signature,
		CRSSignature: b.CRSSignature,
	}
}

func fromRLPLatticeBlock(rb *rlpLatticeBlock) *coreTypes.Block {
	return &coreTypes.Block{
		ProposerID: rb.ProposerID,
		ParentHash: rb.ParentHash,
		Hash:       rb.Hash,
		Position:   rb.Position,
		Timestamp:  fromMillisecond(rb.Timestamp),
		Acks:       rb.Acks,
		Payload:    rb.Payload,
		Witness: coreTypes.Witness{
			Timestamp: fromMillisecond(rb.Witness.Timestamp),
			Height:    rb.Witness.Height,
			Data:      rb.Witness.Data,
		},
		Finalization: coreTypes.FinalizationResult{
			Randomness: rb.Finalization.Randomness,
			Timestamp:  fromMillisecond(rb.Finalization.Timestamp),
			Height:     rb.Finalization.Height,
		},
		Signature:    rb.Signature,
		CRSSignature: rb.CRSSignature,
	}
}

func fromMillisecond(s uint64) time.Time {
	sec := int64(s / 1000)
	nsec := int64((s % 1000) * 1000000)
	return time.Unix(sec, nsec)
}

func toMillisecond(t time.Time) uint64 {
	return uint64(t.UnixNano() / 1000000)
}
*/