// Copyright 2021 The Erigon Authors
// This file is part of Erigon.
//
// Erigon 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.
//
// Erigon 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 Erigon. If not, see <http://www.gnu.org/licenses/>.

package rlp

import (
	"errors"
	"fmt"

	"github.com/holiman/uint256"
)

var (
	ErrBase   = errors.New("rlp")
	ErrParse  = fmt.Errorf("%w parse", ErrBase)
	ErrDecode = fmt.Errorf("%w decode", ErrBase)
)

// BeInt parses Big Endian representation of an integer from given payload at given position
func BeInt(payload []byte, pos, length int) (int, error) {
	var r int
	if pos+length > len(payload) {
		return 0, fmt.Errorf("%w: unexpected end of payload", ErrParse)
	}
	if length > 0 && payload[pos] == 0 {
		return 0, fmt.Errorf("%w: integer encoding for RLP must not have leading zeros: %x", ErrParse, payload[pos:pos+length])
	}
	for _, b := range payload[pos : pos+length] {
		r = (r << 8) | int(b)
	}
	return r, nil
}

// Prefix parses RLP Prefix from given payload at given position. It returns the offset and length of the RLP element
// as well as the indication of whether it is a list of string
func Prefix(payload []byte, pos int) (dataPos int, dataLen int, isList bool, err error) {
	if pos < 0 {
		return 0, 0, false, fmt.Errorf("%w: negative position not allowed", ErrParse)
	}
	if pos >= len(payload) {
		return 0, 0, false, fmt.Errorf("%w: unexpected end of payload", ErrParse)
	}
	switch first := payload[pos]; {
	case first < 128:
		dataPos = pos
		dataLen = 1
		isList = false
	case first < 184:
		// Otherwise, if a string is 0-55 bytes long,
		// the RLP encoding consists of a single byte with value 0x80 plus the
		// length of the string followed by the string. The range of the first
		// byte is thus [0x80, 0xB7].
		dataPos = pos + 1
		dataLen = int(first) - 128
		isList = false
		if dataLen == 1 && dataPos < len(payload) && payload[dataPos] < 128 {
			err = fmt.Errorf("%w: non-canonical size information", ErrParse)
		}
	case first < 192:
		// If a string is more than 55 bytes long, the
		// RLP encoding consists of a single byte with value 0xB7 plus the length
		// of the length of the string in binary form, followed by the length of
		// the string, followed by the string. For example, a length-1024 string
		// would be encoded as 0xB90400 followed by the string. The range of
		// the first byte is thus [0xB8, 0xBF].
		beLen := int(first) - 183
		dataPos = pos + 1 + beLen
		dataLen, err = BeInt(payload, pos+1, beLen)
		isList = false
		if dataLen < 56 {
			err = fmt.Errorf("%w: non-canonical size information", ErrParse)
		}
	case first < 248:
		// isList of len < 56
		// If the total payload of a list
		// (i.e. the combined length of all its items) is 0-55 bytes long, the
		// RLP encoding consists of a single byte with value 0xC0 plus the length
		// of the list followed by the concatenation of the RLP encodings of the
		// items. The range of the first byte is thus [0xC0, 0xF7].
		dataPos = pos + 1
		dataLen = int(first) - 192
		isList = true
	default:
		// If the total payload of a list is more than 55 bytes long,
		// the RLP encoding consists of a single byte with value 0xF7
		// plus the length of the length of the payload in binary
		// form, followed by the length of the payload, followed by
		// the concatenation of the RLP encodings of the items. The
		// range of the first byte is thus [0xF8, 0xFF].
		beLen := int(first) - 247
		dataPos = pos + 1 + beLen
		dataLen, err = BeInt(payload, pos+1, beLen)
		isList = true
		if dataLen < 56 {
			err = fmt.Errorf("%w: : non-canonical size information", ErrParse)
		}
	}
	if err == nil {
		if dataPos+dataLen > len(payload) {
			err = fmt.Errorf("%w: unexpected end of payload", ErrParse)
		} else if dataPos+dataLen < 0 {
			err = fmt.Errorf("%w: found too big len", ErrParse)
		}
	}
	return
}

func ParseList(payload []byte, pos int) (dataPos, dataLen int, err error) {
	dataPos, dataLen, isList, err := Prefix(payload, pos)
	if err != nil {
		return 0, 0, err
	}
	if !isList {
		return 0, 0, fmt.Errorf("%w: must be a list", ErrParse)
	}
	return
}

func ParseString(payload []byte, pos int) (dataPos, dataLen int, err error) {
	dataPos, dataLen, isList, err := Prefix(payload, pos)
	if err != nil {
		return 0, 0, err
	}
	if isList {
		return 0, 0, fmt.Errorf("%w: must be a string, instead of a list", ErrParse)
	}
	return
}
func StringOfLen(payload []byte, pos, expectedLen int) (dataPos int, err error) {
	dataPos, dataLen, err := ParseString(payload, pos)
	if err != nil {
		return 0, err
	}
	if dataLen != expectedLen {
		return 0, fmt.Errorf("%w: expected string of len %d, got %d", ErrParse, expectedLen, dataLen)
	}
	return
}

// U64 parses uint64 number from given payload at given position
func ParseU64(payload []byte, pos int) (int, uint64, error) {
	dataPos, dataLen, isList, err := Prefix(payload, pos)
	if err != nil {
		return 0, 0, err
	}
	if isList {
		return 0, 0, fmt.Errorf("%w: uint64 must be a string, not isList", ErrParse)
	}
	if dataLen > 8 {
		return 0, 0, fmt.Errorf("%w: uint64 must not be more than 8 bytes long, got %d", ErrParse, dataLen)
	}
	if dataLen > 0 && payload[dataPos] == 0 {
		return 0, 0, fmt.Errorf("%w: integer encoding for RLP must not have leading zeros: %x", ErrParse, payload[dataPos:dataPos+dataLen])
	}
	var r uint64
	for _, b := range payload[dataPos : dataPos+dataLen] {
		r = (r << 8) | uint64(b)
	}
	return dataPos + dataLen, r, nil
}

// U32 parses uint64 number from given payload at given position
func ParseU32(payload []byte, pos int) (int, uint32, error) {
	dataPos, dataLen, isList, err := Prefix(payload, pos)
	if err != nil {
		return 0, 0, err
	}
	if isList {
		return 0, 0, fmt.Errorf("%w: uint32 must be a string, not isList", ErrParse)
	}
	if dataLen > 4 {
		return 0, 0, fmt.Errorf("%w: uint32 must not be more than 4 bytes long, got %d", ErrParse, dataLen)
	}
	if dataLen > 0 && payload[dataPos] == 0 {
		return 0, 0, fmt.Errorf("%w: integer encoding for RLP must not have leading zeros: %x", ErrParse, payload[dataPos:dataPos+dataLen])
	}
	var r uint32
	for _, b := range payload[dataPos : dataPos+dataLen] {
		r = (r << 8) | uint32(b)
	}
	return dataPos + dataLen, r, nil
}

// U256 parses uint256 number from given payload at given position
func ParseU256(payload []byte, pos int, x *uint256.Int) (int, error) {
	dataPos, dataLen, err := ParseString(payload, pos)
	if err != nil {
		return 0, err
	}
	if dataLen > 32 {
		return 0, fmt.Errorf("%w: uint256 must not be more than 32 bytes long, got %d", ErrParse, dataLen)
	}
	if dataLen > 0 && payload[dataPos] == 0 {
		return 0, fmt.Errorf("%w: integer encoding for RLP must not have leading zeros: %x", ErrParse, payload[dataPos:dataPos+dataLen])
	}
	x.SetBytes(payload[dataPos : dataPos+dataLen])
	return dataPos + dataLen, nil
}

func ParseHash(payload []byte, pos int, hashbuf []byte) (int, error) {
	pos, err := StringOfLen(payload, pos, 32)
	if err != nil {
		return 0, fmt.Errorf("%s: hash len: %w", ParseHashErrorPrefix, err)
	}
	copy(hashbuf, payload[pos:pos+32])
	return pos + 32, nil
}

const ParseHashErrorPrefix = "parse hash payload"

const ParseAnnouncementsErrorPrefix = "parse announcement payload"

func ParseAnnouncements(payload []byte, pos int) ([]byte, []uint32, []byte, int, error) {
	pos, totalLen, err := ParseList(payload, pos)
	if err != nil {
		return nil, nil, nil, pos, err
	}
	if pos+totalLen > len(payload) {
		return nil, nil, nil, pos, fmt.Errorf("%s: totalLen %d is beyond the end of payload", ParseAnnouncementsErrorPrefix, totalLen)
	}
	pos, typesLen, err := ParseString(payload, pos)
	if err != nil {
		return nil, nil, nil, pos, err
	}
	if pos+typesLen > len(payload) {
		return nil, nil, nil, pos, fmt.Errorf("%s: typesLen %d is beyond the end of payload", ParseAnnouncementsErrorPrefix, typesLen)
	}
	types := payload[pos : pos+typesLen]
	pos += typesLen
	pos, sizesLen, err := ParseList(payload, pos)
	if err != nil {
		return nil, nil, nil, pos, err
	}
	if pos+sizesLen > len(payload) {
		return nil, nil, nil, pos, fmt.Errorf("%s: sizesLen %d is beyond the end of payload", ParseAnnouncementsErrorPrefix, sizesLen)
	}
	sizes := make([]uint32, typesLen)
	for i := 0; i < len(sizes); i++ {
		if pos, sizes[i], err = ParseU32(payload, pos); err != nil {
			return nil, nil, nil, pos, err
		}
	}
	pos, hashesLen, err := ParseList(payload, pos)
	if err != nil {
		return nil, nil, nil, pos, err
	}
	if pos+hashesLen > len(payload) {
		return nil, nil, nil, pos, fmt.Errorf("%s: hashesLen %d is beyond the end of payload", ParseAnnouncementsErrorPrefix, hashesLen)
	}
	hashes := make([]byte, 32*(hashesLen/33))
	for i := 0; i < len(hashes); i += 32 {
		if pos, err = ParseHash(payload, pos, hashes[i:]); err != nil {
			return nil, nil, nil, pos, err
		}
	}
	return types, sizes, hashes, pos, nil
}
