// Copyright 2024 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Inspired by https://github.com/protocolbuffers/protobuf-go/blob/master/encoding/protowire/wire.go

package server

import (
	"errors"
	"fmt"
	"math"
)

var errProtoInsufficient = errors.New("insufficient data to read a value")
var errProtoOverflow = errors.New("too much data for a value")
var errProtoInvalidFieldNumber = errors.New("invalid field number")

func protoScanField(b []byte) (num, typ, size int, err error) {
	num, typ, sizeTag, err := protoScanTag(b)
	if err != nil {
		return 0, 0, 0, err
	}
	b = b[sizeTag:]

	sizeValue, err := protoScanFieldValue(typ, b)
	if err != nil {
		return 0, 0, 0, err
	}
	return num, typ, sizeTag + sizeValue, nil
}

func protoScanTag(b []byte) (num, typ, size int, err error) {
	tagint, size, err := protoScanVarint(b)
	if err != nil {
		return 0, 0, 0, err
	}

	// NOTE: MessageSet allows for larger field numbers than normal.
	if (tagint >> 3) > uint64(math.MaxInt32) {
		return 0, 0, 0, errProtoInvalidFieldNumber
	}
	num = int(tagint >> 3)
	if num < 1 {
		return 0, 0, 0, errProtoInvalidFieldNumber
	}
	typ = int(tagint & 7)

	return num, typ, size, nil
}

func protoScanFieldValue(typ int, b []byte) (size int, err error) {
	switch typ {
	case 0:
		_, size, err = protoScanVarint(b)
	case 5: // fixed32
		if len(b) < 4 {
			return 0, errProtoInsufficient
		}
		size = 4
	case 1: // fixed64
		if len(b) < 8 {
			return 0, errProtoInsufficient
		}
		size = 8
	case 2: // length-delimited
		size, err = protoScanBytes(b)
	default:
		return 0, fmt.Errorf("unsupported type: %d", typ)
	}
	return size, err
}

func protoScanVarint(b []byte) (v uint64, size int, err error) {
	var y uint64
	if len(b) <= 0 {
		return 0, 0, errProtoInsufficient
	}
	v = uint64(b[0])
	if v < 0x80 {
		return v, 1, nil
	}
	v -= 0x80

	if len(b) <= 1 {
		return 0, 0, errProtoInsufficient
	}
	y = uint64(b[1])
	v += y << 7
	if y < 0x80 {
		return v, 2, nil
	}
	v -= 0x80 << 7

	if len(b) <= 2 {
		return 0, 0, errProtoInsufficient
	}
	y = uint64(b[2])
	v += y << 14
	if y < 0x80 {
		return v, 3, nil
	}
	v -= 0x80 << 14

	if len(b) <= 3 {
		return 0, 0, errProtoInsufficient
	}
	y = uint64(b[3])
	v += y << 21
	if y < 0x80 {
		return v, 4, nil
	}
	v -= 0x80 << 21

	if len(b) <= 4 {
		return 0, 0, errProtoInsufficient
	}
	y = uint64(b[4])
	v += y << 28
	if y < 0x80 {
		return v, 5, nil
	}
	v -= 0x80 << 28

	if len(b) <= 5 {
		return 0, 0, errProtoInsufficient
	}
	y = uint64(b[5])
	v += y << 35
	if y < 0x80 {
		return v, 6, nil
	}
	v -= 0x80 << 35

	if len(b) <= 6 {
		return 0, 0, errProtoInsufficient
	}
	y = uint64(b[6])
	v += y << 42
	if y < 0x80 {
		return v, 7, nil
	}
	v -= 0x80 << 42

	if len(b) <= 7 {
		return 0, 0, errProtoInsufficient
	}
	y = uint64(b[7])
	v += y << 49
	if y < 0x80 {
		return v, 8, nil
	}
	v -= 0x80 << 49

	if len(b) <= 8 {
		return 0, 0, errProtoInsufficient
	}
	y = uint64(b[8])
	v += y << 56
	if y < 0x80 {
		return v, 9, nil
	}
	v -= 0x80 << 56

	if len(b) <= 9 {
		return 0, 0, errProtoInsufficient
	}
	y = uint64(b[9])
	v += y << 63
	if y < 2 {
		return v, 10, nil
	}
	return 0, 0, errProtoOverflow
}

func protoScanBytes(b []byte) (size int, err error) {
	l, lenSize, err := protoScanVarint(b)
	if err != nil {
		return 0, err
	}
	if l > uint64(len(b[lenSize:])) {
		return 0, errProtoInsufficient
	}
	return lenSize + int(l), nil
}

func protoEncodeVarint(v uint64) []byte {
	b := make([]byte, 0, 10)
	switch {
	case v < 1<<7:
		b = append(b, byte(v))
	case v < 1<<14:
		b = append(b,
			byte((v>>0)&0x7f|0x80),
			byte(v>>7))
	case v < 1<<21:
		b = append(b,
			byte((v>>0)&0x7f|0x80),
			byte((v>>7)&0x7f|0x80),
			byte(v>>14))
	case v < 1<<28:
		b = append(b,
			byte((v>>0)&0x7f|0x80),
			byte((v>>7)&0x7f|0x80),
			byte((v>>14)&0x7f|0x80),
			byte(v>>21))
	case v < 1<<35:
		b = append(b,
			byte((v>>0)&0x7f|0x80),
			byte((v>>7)&0x7f|0x80),
			byte((v>>14)&0x7f|0x80),
			byte((v>>21)&0x7f|0x80),
			byte(v>>28))
	case v < 1<<42:
		b = append(b,
			byte((v>>0)&0x7f|0x80),
			byte((v>>7)&0x7f|0x80),
			byte((v>>14)&0x7f|0x80),
			byte((v>>21)&0x7f|0x80),
			byte((v>>28)&0x7f|0x80),
			byte(v>>35))
	case v < 1<<49:
		b = append(b,
			byte((v>>0)&0x7f|0x80),
			byte((v>>7)&0x7f|0x80),
			byte((v>>14)&0x7f|0x80),
			byte((v>>21)&0x7f|0x80),
			byte((v>>28)&0x7f|0x80),
			byte((v>>35)&0x7f|0x80),
			byte(v>>42))
	case v < 1<<56:
		b = append(b,
			byte((v>>0)&0x7f|0x80),
			byte((v>>7)&0x7f|0x80),
			byte((v>>14)&0x7f|0x80),
			byte((v>>21)&0x7f|0x80),
			byte((v>>28)&0x7f|0x80),
			byte((v>>35)&0x7f|0x80),
			byte((v>>42)&0x7f|0x80),
			byte(v>>49))
	case v < 1<<63:
		b = append(b,
			byte((v>>0)&0x7f|0x80),
			byte((v>>7)&0x7f|0x80),
			byte((v>>14)&0x7f|0x80),
			byte((v>>21)&0x7f|0x80),
			byte((v>>28)&0x7f|0x80),
			byte((v>>35)&0x7f|0x80),
			byte((v>>42)&0x7f|0x80),
			byte((v>>49)&0x7f|0x80),
			byte(v>>56))
	default:
		b = append(b,
			byte((v>>0)&0x7f|0x80),
			byte((v>>7)&0x7f|0x80),
			byte((v>>14)&0x7f|0x80),
			byte((v>>21)&0x7f|0x80),
			byte((v>>28)&0x7f|0x80),
			byte((v>>35)&0x7f|0x80),
			byte((v>>42)&0x7f|0x80),
			byte((v>>49)&0x7f|0x80),
			byte((v>>56)&0x7f|0x80),
			1)
	}
	return b
}
