// Copyright 2023 The bsc Authors
// This file is part of bsc.
package main

import (
	"bytes"
	"encoding/hex"
	"errors"
	"flag"
	"fmt"
	"os"
	"sort"
	"strings"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/rlp"
	"github.com/willf/bitset"
)

// follow define in parlia
const (
	AddressLength      = 20
	BLSPublicKeyLength = 48

	// follow order in extra field
	// |---Extra Vanity---|---Validators Number and Validators Bytes (or Empty)---|---Turn Length (or Empty)---|---Vote Attestation (or Empty)---|---Extra Seal---|
	extraVanityLength    = 32 // Fixed number of extra-data prefix bytes reserved for signer vanity
	validatorNumberSize  = 1  // Fixed number of extra prefix bytes reserved for validator number after Luban
	validatorBytesLength = common.AddressLength + types.BLSPublicKeyLength
	extraSealLength      = 65 // Fixed number of extra-data suffix bytes reserved for signer seal
)

type Extra struct {
	ExtraVanity   string
	ValidatorSize uint8
	Validators    validatorsAscending
	TurnLength    *uint8
	*types.VoteAttestation
	ExtraSeal []byte
}

type ValidatorInfo struct {
	common.Address
	types.BLSPublicKey
	VoteIncluded bool
}

// validatorsAscending implements the sort interface to allow sorting a list of ValidatorInfo
type validatorsAscending []ValidatorInfo

func (s validatorsAscending) Len() int { return len(s) }
func (s validatorsAscending) Less(i, j int) bool {
	return bytes.Compare(s[i].Address[:], s[j].Address[:]) < 0
}
func (s validatorsAscending) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

func init() {
	flag.Usage = func() {
		fmt.Fprintln(os.Stderr, "Usage:", os.Args[0], "[extraHexData]")
		flag.PrintDefaults()
		fmt.Fprintln(os.Stderr, `
Dumps extra info from the given hex data, only support extra after luban upgrade.`)
	}
}

func main() {
	flag.Parse()
	extraHexData := os.Args[1]
	if extra, err := parseExtra(extraHexData); err == nil {
		fmt.Println("extra parsed successly")
		prettyExtra(*extra)
	} else {
		fmt.Println("extra parsed failed", "err", err)
	}
}

// parseExtra parse hex data into type Extra
func parseExtra(hexData string) (*Extra, error) {
	// decode hex into bytes
	data, err := hex.DecodeString(strings.TrimPrefix(hexData, "0x"))
	if err != nil {
		return nil, errors.New("invalid hex data")
	}

	// parse ExtraVanity and ExtraSeal
	dataLength := len(data)
	var extra Extra
	if dataLength < extraVanityLength+extraSealLength {
		fmt.Println("length less than min required")
	}
	extra.ExtraVanity = string(data[:extraVanityLength])
	extra.ExtraSeal = data[dataLength-extraSealLength:]
	data = data[extraVanityLength : dataLength-extraSealLength]
	dataLength = len(data)

	// parse Validators and Vote Attestation
	if dataLength > 0 {
		// parse Validators
		if data[0] != '\xf8' { // rlp format of attestation begin with 'f8'
			validatorNum := int(data[0])
			validatorBytesTotalLength := validatorNumberSize + validatorNum*validatorBytesLength
			if dataLength < validatorBytesTotalLength {
				return nil, errors.New("parse validators failed")
			}
			extra.ValidatorSize = uint8(validatorNum)
			data = data[validatorNumberSize:]
			for i := 0; i < validatorNum; i++ {
				var validatorInfo ValidatorInfo
				validatorInfo.Address = common.BytesToAddress(data[i*validatorBytesLength : i*validatorBytesLength+common.AddressLength])
				copy(validatorInfo.BLSPublicKey[:], data[i*validatorBytesLength+common.AddressLength:(i+1)*validatorBytesLength])
				extra.Validators = append(extra.Validators, validatorInfo)
			}
			sort.Sort(extra.Validators)
			data = data[validatorBytesTotalLength-validatorNumberSize:]
			dataLength = len(data)

			// parse TurnLength
			if dataLength > 0 {
				if data[0] != '\xf8' {
					extra.TurnLength = &data[0]
					data = data[1:]
					dataLength = len(data)
				}
			}
		}

		// parse Vote Attestation
		if dataLength > 0 {
			if err := rlp.Decode(bytes.NewReader(data), &extra.VoteAttestation); err != nil {
				return nil, errors.New("parse voteAttestation failed")
			}
			if extra.ValidatorSize > 0 {
				validatorsBitSet := bitset.From([]uint64{uint64(extra.VoteAddressSet)})
				for i := 0; i < int(extra.ValidatorSize); i++ {
					if validatorsBitSet.Test(uint(i)) {
						extra.Validators[i].VoteIncluded = true
					}
				}
			}
		}
	}

	return &extra, nil
}

// prettyExtra print Extra with a pretty format
func prettyExtra(extra Extra) {
	fmt.Printf("ExtraVanity	:	%s\n", extra.ExtraVanity)

	if extra.ValidatorSize > 0 {
		fmt.Printf("ValidatorSize	:	%d\n", extra.ValidatorSize)
		for i := 0; i < int(extra.ValidatorSize); i++ {
			fmt.Printf("Validator	%d\n", i+1)
			fmt.Printf("\tAddress	:	%s\n", common.Bytes2Hex(extra.Validators[i].Address[:]))
			fmt.Printf("\tVoteKey	:	%s\n", common.Bytes2Hex(extra.Validators[i].BLSPublicKey[:]))
			fmt.Printf("\tVoteIncluded	:	%t\n", extra.Validators[i].VoteIncluded)
		}
	}

	if extra.TurnLength != nil {
		fmt.Printf("TurnLength	:	%d\n", *extra.TurnLength)
	}

	if extra.VoteAttestation != nil {
		fmt.Printf("Attestation	:\n")
		fmt.Printf("\tVoteAddressSet	:	%b,	%d\n", extra.VoteAddressSet, bitset.From([]uint64{uint64(extra.VoteAddressSet)}).Count())
		fmt.Printf("\tAggSignature	:	%s\n", common.Bytes2Hex(extra.AggSignature[:]))
		fmt.Printf("\tVoteData	:\n")
		fmt.Printf("\t\tSourceNumber	:	%d\n", extra.Data.SourceNumber)
		fmt.Printf("\t\tSourceHash	:	%s\n", common.Bytes2Hex(extra.Data.SourceHash[:]))
		fmt.Printf("\t\tTargetNumber	:	%d\n", extra.Data.TargetNumber)
		fmt.Printf("\t\tTargetHash	:	%s\n", common.Bytes2Hex(extra.Data.TargetHash[:]))
	}

	fmt.Printf("ExtraSeal	:	%s\n", common.Bytes2Hex(extra.ExtraSeal))
}
