// Copyright (c) Faye Amacker. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

package cbor

import (
	"encoding/binary"
	"errors"
	"io"
	"strconv"
)

// SyntaxError is a description of a CBOR syntax error.
type SyntaxError struct {
	msg string
}

func (e *SyntaxError) Error() string { return e.msg }

// SemanticError is a description of a CBOR semantic error.
type SemanticError struct {
	msg string
}

func (e *SemanticError) Error() string { return e.msg }

// MaxNestedLevelError indicates exceeded max nested level of any combination of CBOR arrays/maps/tags.
type MaxNestedLevelError struct {
	maxNestedLevels int
}

func (e *MaxNestedLevelError) Error() string {
	return "cbor: exceeded max nested level " + strconv.Itoa(e.maxNestedLevels)
}

// MaxArrayElementsError indicates exceeded max number of elements for CBOR arrays.
type MaxArrayElementsError struct {
	maxArrayElements int
}

func (e *MaxArrayElementsError) Error() string {
	return "cbor: exceeded max number of elements " + strconv.Itoa(e.maxArrayElements) + " for CBOR array"
}

// MaxMapPairsError indicates exceeded max number of key-value pairs for CBOR maps.
type MaxMapPairsError struct {
	maxMapPairs int
}

func (e *MaxMapPairsError) Error() string {
	return "cbor: exceeded max number of key-value pairs " + strconv.Itoa(e.maxMapPairs) + " for CBOR map"
}

// IndefiniteLengthError indicates found disallowed indefinite length items.
type IndefiniteLengthError struct {
	t cborType
}

func (e *IndefiniteLengthError) Error() string {
	return "cbor: indefinite-length " + e.t.String() + " isn't allowed"
}

// TagsMdError indicates found disallowed CBOR tags.
type TagsMdError struct {
}

func (e *TagsMdError) Error() string {
	return "cbor: CBOR tag isn't allowed"
}

// valid checks whether CBOR data is complete and well-formed.
func (d *decodeState) valid() error {
	if len(d.data) == d.off {
		return io.EOF
	}
	_, err := d.validInternal(0)
	return err
}

// validInternal checks data's well-formedness and returns max depth and error.
func (d *decodeState) validInternal(depth int) (int, error) {
	t, ai, val, err := d.validHead()
	if err != nil {
		return 0, err
	}

	switch t {
	case cborTypeByteString, cborTypeTextString:
		if ai == 31 {
			if d.dm.indefLength == IndefLengthForbidden {
				return 0, &IndefiniteLengthError{t}
			}
			return d.validIndefiniteString(t, depth)
		}
		valInt := int(val)
		if valInt < 0 {
			// Detect integer overflow
			return 0, errors.New("cbor: " + t.String() + " length " + strconv.FormatUint(val, 10) + " is too large, causing integer overflow")
		}
		if len(d.data)-d.off < valInt { // valInt+off may overflow integer
			return 0, io.ErrUnexpectedEOF
		}
		d.off += valInt
	case cborTypeArray, cborTypeMap:
		depth++
		if depth > d.dm.maxNestedLevels {
			return 0, &MaxNestedLevelError{d.dm.maxNestedLevels}
		}

		if ai == 31 {
			if d.dm.indefLength == IndefLengthForbidden {
				return 0, &IndefiniteLengthError{t}
			}
			return d.validIndefiniteArrayOrMap(t, depth)
		}

		valInt := int(val)
		if valInt < 0 {
			// Detect integer overflow
			return 0, errors.New("cbor: " + t.String() + " length " + strconv.FormatUint(val, 10) + " is too large, it would cause integer overflow")
		}

		if t == cborTypeArray {
			if valInt > d.dm.maxArrayElements {
				return 0, &MaxArrayElementsError{d.dm.maxArrayElements}
			}
		} else {
			if valInt > d.dm.maxMapPairs {
				return 0, &MaxMapPairsError{d.dm.maxMapPairs}
			}
		}

		count := 1
		if t == cborTypeMap {
			count = 2
		}
		maxDepth := depth
		for j := 0; j < count; j++ {
			for i := 0; i < valInt; i++ {
				var dpt int
				if dpt, err = d.validInternal(depth); err != nil {
					return 0, err
				}
				if dpt > maxDepth {
					maxDepth = dpt // Save max depth
				}
			}
		}
		depth = maxDepth
	case cborTypeTag:
		if d.dm.tagsMd == TagsForbidden {
			return 0, &TagsMdError{}
		}

		// Scan nested tag numbers to avoid recursion.
		for {
			if len(d.data) == d.off { // Tag number must be followed by tag content.
				return 0, io.ErrUnexpectedEOF
			}
			if cborType(d.data[d.off]&0xe0) != cborTypeTag {
				break
			}
			if _, _, _, err = d.validHead(); err != nil {
				return 0, err
			}
			depth++
			if depth > d.dm.maxNestedLevels {
				return 0, &MaxNestedLevelError{d.dm.maxNestedLevels}
			}
		}
		// Check tag content.
		return d.validInternal(depth)
	}
	return depth, nil
}

// validIndefiniteString checks indefinite length byte/text string's well-formedness and returns max depth and error.
func (d *decodeState) validIndefiniteString(t cborType, depth int) (int, error) {
	var err error
	for {
		if len(d.data) == d.off {
			return 0, io.ErrUnexpectedEOF
		}
		if d.data[d.off] == 0xff {
			d.off++
			break
		}
		// Peek ahead to get next type and indefinite length status.
		nt := cborType(d.data[d.off] & 0xe0)
		if t != nt {
			return 0, &SyntaxError{"cbor: wrong element type " + nt.String() + " for indefinite-length " + t.String()}
		}
		if (d.data[d.off] & 0x1f) == 31 {
			return 0, &SyntaxError{"cbor: indefinite-length " + t.String() + " chunk is not definite-length"}
		}
		if depth, err = d.validInternal(depth); err != nil {
			return 0, err
		}
	}
	return depth, nil
}

// validIndefiniteArrayOrMap checks indefinite length array/map's well-formedness and returns max depth and error.
func (d *decodeState) validIndefiniteArrayOrMap(t cborType, depth int) (int, error) {
	var err error
	maxDepth := depth
	i := 0
	for {
		if len(d.data) == d.off {
			return 0, io.ErrUnexpectedEOF
		}
		if d.data[d.off] == 0xff {
			d.off++
			break
		}
		var dpt int
		if dpt, err = d.validInternal(depth); err != nil {
			return 0, err
		}
		if dpt > maxDepth {
			maxDepth = dpt
		}
		i++
		if t == cborTypeArray {
			if i > d.dm.maxArrayElements {
				return 0, &MaxArrayElementsError{d.dm.maxArrayElements}
			}
		} else {
			if i%2 == 0 && i/2 > d.dm.maxMapPairs {
				return 0, &MaxMapPairsError{d.dm.maxMapPairs}
			}
		}
	}
	if t == cborTypeMap && i%2 == 1 {
		return 0, &SyntaxError{"cbor: unexpected \"break\" code"}
	}
	return maxDepth, nil
}

func (d *decodeState) validHead() (t cborType, ai byte, val uint64, err error) {
	dataLen := len(d.data) - d.off
	if dataLen == 0 {
		return 0, 0, 0, io.ErrUnexpectedEOF
	}

	t = cborType(d.data[d.off] & 0xe0)
	ai = d.data[d.off] & 0x1f
	val = uint64(ai)
	d.off++

	if ai < 24 {
		return t, ai, val, nil
	}
	if ai == 24 {
		if dataLen < 2 {
			return 0, 0, 0, io.ErrUnexpectedEOF
		}
		val = uint64(d.data[d.off])
		d.off++
		if t == cborTypePrimitives && val < 32 {
			return 0, 0, 0, &SyntaxError{"cbor: invalid simple value " + strconv.Itoa(int(val)) + " for type " + t.String()}
		}
		return t, ai, val, nil
	}
	if ai == 25 {
		if dataLen < 3 {
			return 0, 0, 0, io.ErrUnexpectedEOF
		}
		val = uint64(binary.BigEndian.Uint16(d.data[d.off : d.off+2]))
		d.off += 2
		return t, ai, val, nil
	}
	if ai == 26 {
		if dataLen < 5 {
			return 0, 0, 0, io.ErrUnexpectedEOF
		}
		val = uint64(binary.BigEndian.Uint32(d.data[d.off : d.off+4]))
		d.off += 4
		return t, ai, val, nil
	}
	if ai == 27 {
		if dataLen < 9 {
			return 0, 0, 0, io.ErrUnexpectedEOF
		}
		val = binary.BigEndian.Uint64(d.data[d.off : d.off+8])
		d.off += 8
		return t, ai, val, nil
	}
	if ai == 31 {
		switch t {
		case cborTypePositiveInt, cborTypeNegativeInt, cborTypeTag:
			return 0, 0, 0, &SyntaxError{"cbor: invalid additional information " + strconv.Itoa(int(ai)) + " for type " + t.String()}
		case cborTypePrimitives: // 0xff (break code) should not be outside validIndefinite().
			return 0, 0, 0, &SyntaxError{"cbor: unexpected \"break\" code"}
		}
		return t, ai, val, nil
	}
	// ai == 28, 29, 30
	return 0, 0, 0, &SyntaxError{"cbor: invalid additional information " + strconv.Itoa(int(ai)) + " for type " + t.String()}
}
