/*
Licensed to the Apache Software Foundation (ASF) under one
oor more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you 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.
*/

package amqp

// #include <proton/codec.h>
import "C"

import (
	"bytes"
	"fmt"
	"io"
	"reflect"
	"time"
	"unsafe"
)

const minDecode = 1024

// Error returned if AMQP data cannot be unmarshaled as the desired Go type.
type UnmarshalError struct {
	// The name of the AMQP type.
	AMQPType string
	// The Go type.
	GoType reflect.Type

	s string
}

func (e UnmarshalError) Error() string { return e.s }

// Error returned if there are not enough bytes to decode a complete AMQP value.
var EndOfData = &UnmarshalError{s: "Not enough data for AMQP value"}

var badData = &UnmarshalError{s: "Unexpected error in data"}

func newUnmarshalError(pnType C.pn_type_t, v interface{}) *UnmarshalError {
	e := &UnmarshalError{
		AMQPType: C.pn_type_t(pnType).String(),
		GoType:   reflect.TypeOf(v),
	}
	if e.GoType == nil || e.GoType.Kind() != reflect.Ptr {
		e.s = fmt.Sprintf("cannot unmarshal to Go type %v, not a pointer", e.GoType)
	} else {
		e.s = fmt.Sprintf("cannot unmarshal AMQP %v to Go %v", e.AMQPType, e.GoType.Elem())
	}
	return e
}

func doPanic(data *C.pn_data_t, v interface{}) {
	e := newUnmarshalError(C.pn_data_type(data), v)
	panic(e)
}

func doPanicMsg(data *C.pn_data_t, v interface{}, msg string) {
	e := newUnmarshalError(C.pn_data_type(data), v)
	e.s = e.s + ": " + msg
	panic(e)
}

func panicIfBadData(data *C.pn_data_t, v interface{}) {
	if C.pn_data_errno(data) != 0 {
		doPanicMsg(data, v, PnError(C.pn_data_error(data)).Error())
	}
}

func panicUnless(ok bool, data *C.pn_data_t, v interface{}) {
	if !ok {
		doPanic(data, v)
	}
}

func checkOp(ok bool, v interface{}) {
	if !ok {
		panic(&badData)
	}
}

//
// Decoding from a pn_data_t
//
// NOTE: we use panic() to signal a decoding error, simplifies decoding logic.
// We recover() at the highest possible level - i.e. in the exported Unmarshal or Decode.
//

// Decoder decodes AMQP values from an io.Reader.
//
type Decoder struct {
	reader io.Reader
	buffer bytes.Buffer
}

// NewDecoder returns a new decoder that reads from r.
//
// The decoder has it's own buffer and may read more data than required for the
// AMQP values requested.  Use Buffered to see if there is data left in the
// buffer.
//
func NewDecoder(r io.Reader) *Decoder {
	return &Decoder{r, bytes.Buffer{}}
}

// Buffered returns a reader of the data remaining in the Decoder's buffer. The
// reader is valid until the next call to Decode.
//
func (d *Decoder) Buffered() io.Reader {
	return bytes.NewReader(d.buffer.Bytes())
}

// Decode reads the next AMQP value from the Reader and stores it in the value pointed to by v.
//
// See the documentation for Unmarshal for details about the conversion of AMQP into a Go value.
//
func (d *Decoder) Decode(v interface{}) (err error) {
	data := C.pn_data(0)
	defer C.pn_data_free(data)
	var n int
	for n, err = decode(data, d.buffer.Bytes()); err == EndOfData; {
		err = d.more()
		if err == nil {
			n, err = decode(data, d.buffer.Bytes())
		}
	}
	if err == nil {
		if err = recoverUnmarshal(v, data); err == nil {
			d.buffer.Next(n)
		}
	}
	return
}

/*

Unmarshal decodes AMQP-encoded bytes and stores the result in the Go value
pointed to by v. Legal conversions from the source AMQP type to the target Go
type as follows:

 +----------------------------+-------------------------------------------------+
 |Target Go type              | Allowed AMQP types
 +============================+==================================================+
 |bool                        |bool                                              |
 +----------------------------+--------------------------------------------------+
 |int, int8, int16, int32,    |Equivalent or smaller signed integer type:        |
 |int64                       |byte, short, int, long or char                    |
 +----------------------------+--------------------------------------------------+
 |uint, uint8, uint16, uint32,|Equivalent or smaller unsigned integer type:      |
 |uint64                      |ubyte, ushort, uint, ulong                        |
 +----------------------------+--------------------------------------------------+
 |float32, float64            |Equivalent or smaller float or double             |
 +----------------------------+--------------------------------------------------+
 |string, []byte              |string, symbol or binary                          |
 +----------------------------+--------------------------------------------------+
 |Symbol                      |symbol                                            |
 +----------------------------+--------------------------------------------------+
 |Char                        |char                                              |
 +----------------------------+--------------------------------------------------+
 |Described                   |AMQP described type [1]                           |
 +----------------------------+--------------------------------------------------+
 |Time                        |timestamp                                         |
 +----------------------------+--------------------------------------------------+
 |UUID                        |uuid                                              |
 +----------------------------+--------------------------------------------------+
 |map[interface{}]interface{} |Any AMQP map                                      |
 +----------------------------+--------------------------------------------------+
 |map[K]T                     |map, provided all keys and values can unmarshal   |
 |                            |to types K,T                                      |
 +----------------------------+--------------------------------------------------+
 |[]interface{}               |AMQP list or array                                |
 +----------------------------+--------------------------------------------------+
 |[]T                         |list or array if elements can unmarshal as T      |
 +----------------------------+------------------n-------------------------------+
 |interface{}                 |any AMQP type[2]                                  |
 +----------------------------+--------------------------------------------------+

[1] An AMQP described value can also unmarshal to a plain value, discarding the
descriptor. Unmarshalling into the special amqp.Described type preserves the
descriptor.

[2] Any AMQP value can be unmarshalled to an interface{}. The Go type is
determined by the AMQP type as follows:

 +----------------------------+--------------------------------------------------+
 |Source AMQP Type            |Go Type in target interface{}                     |
 +============================+==================================================+
 |bool                        |bool                                              |
 +----------------------------+--------------------------------------------------+
 |byte,short,int,long         |int8,int16,int32,int64                            |
 +----------------------------+--------------------------------------------------+
 |ubyte,ushort,uint,ulong     |uint8,uint16,uint32,uint64                        |
 +----------------------------+--------------------------------------------------+
 |float, double               |float32, float64                                  |
 +----------------------------+--------------------------------------------------+
 |string                      |string                                            |
 +----------------------------+--------------------------------------------------+
 |symbol                      |Symbol                                            |
 +----------------------------+--------------------------------------------------+
 |char                        |Char                                              |
 +----------------------------+--------------------------------------------------+
 |binary                      |Binary                                            |
 +----------------------------+--------------------------------------------------+
 |null                        |nil                                               |
 +----------------------------+--------------------------------------------------+
 |described type              |Described                                         |
 +----------------------------+--------------------------------------------------+
 |timestamp                   |time.Time                                         |
 +----------------------------+--------------------------------------------------+
 |uuid                        |UUID                                              |
 +----------------------------+--------------------------------------------------+
 |map                         |Map or AnyMap[4]                                  |
 +----------------------------+--------------------------------------------------+
 |list                        |List                                              |
 +----------------------------+--------------------------------------------------+
 |array                       |[]T for simple types, T is chosen as above [3]    |
 +----------------------------+--------------------------------------------------+

[3] An AMQP array of simple types unmarshalls as a slice of the corresponding Go type.
An AMQP array containing complex types (lists, maps or nested arrays) unmarshals
to the generic array type amqp.Array

[4] An AMQP map unmarshals as the generic `type Map map[interface{}]interface{}`
unless it contains key values that are illegal as Go map types, in which case
it unmarshals as type AnyMap.

The following Go types cannot be unmarshaled: uintptr, function, interface,
channel, array (use slice), struct

AMQP types not yet supported: decimal32/64/128
*/
func Unmarshal(bytes []byte, v interface{}) (n int, err error) {
	data := C.pn_data(0)
	defer C.pn_data_free(data)
	n, err = decode(data, bytes)
	if err == nil {
		err = recoverUnmarshal(v, data)
	}
	return
}

// Internal
func UnmarshalUnsafe(pnData unsafe.Pointer, v interface{}) (err error) {
	return recoverUnmarshal(v, (*C.pn_data_t)(pnData))
}

// more reads more data when we can't parse a complete AMQP type
func (d *Decoder) more() error {
	var readSize int64 = minDecode
	if int64(d.buffer.Len()) > readSize { // Grow by doubling
		readSize = int64(d.buffer.Len())
	}
	var n int64
	n, err := d.buffer.ReadFrom(io.LimitReader(d.reader, readSize))
	if n == 0 && err == nil { // ReadFrom won't report io.EOF, just returns 0
		err = io.EOF
	}
	return err
}

// Call unmarshal(), convert panic to error value
func recoverUnmarshal(v interface{}, data *C.pn_data_t) (err error) {
	defer func() {
		if r := recover(); r != nil {
			if uerr, ok := r.(*UnmarshalError); ok {
				err = uerr
			} else {
				panic(r)
			}
		}
	}()
	unmarshal(v, data)
	return nil
}

// Unmarshal from data into value pointed at by v. Returns v.
// NOTE: If you update this you also need to update getInterface()
func unmarshal(v interface{}, data *C.pn_data_t) {
	rt := reflect.TypeOf(v)
	rv := reflect.ValueOf(v)
	panicUnless(v != nil && rt.Kind() == reflect.Ptr && !rv.IsNil(), data, v)

	// Check for PN_DESCRIBED first, as described types can unmarshal into any of the Go types.
	// An interface{} target is handled in the switch below, even for described types.
	if _, isInterface := v.(*interface{}); !isInterface && bool(C.pn_data_is_described(data)) {
		getDescribed(data, v)
		return
	}

	// Unmarshal based on the target type
	pnType := C.pn_data_type(data)
	switch v := v.(type) {

	case *bool:
		panicUnless(pnType == C.PN_BOOL, data, v)
		*v = bool(C.pn_data_get_bool(data))

	case *int8:
		panicUnless(pnType == C.PN_BYTE, data, v)
		*v = int8(C.pn_data_get_byte(data))

	case *uint8:
		panicUnless(pnType == C.PN_UBYTE, data, v)
		*v = uint8(C.pn_data_get_ubyte(data))

	case *int16:
		switch C.pn_data_type(data) {
		case C.PN_BYTE:
			*v = int16(C.pn_data_get_byte(data))
		case C.PN_SHORT:
			*v = int16(C.pn_data_get_short(data))
		default:
			doPanic(data, v)
		}

	case *uint16:
		switch pnType {
		case C.PN_UBYTE:
			*v = uint16(C.pn_data_get_ubyte(data))
		case C.PN_USHORT:
			*v = uint16(C.pn_data_get_ushort(data))
		default:
			doPanic(data, v)
		}

	case *int32:
		switch pnType {
		case C.PN_CHAR:
			*v = int32(C.pn_data_get_char(data))
		case C.PN_BYTE:
			*v = int32(C.pn_data_get_byte(data))
		case C.PN_SHORT:
			*v = int32(C.pn_data_get_short(data))
		case C.PN_INT:
			*v = int32(C.pn_data_get_int(data))
		default:
			doPanic(data, v)
		}

	case *uint32:
		switch pnType {
		case C.PN_CHAR:
			*v = uint32(C.pn_data_get_char(data))
		case C.PN_UBYTE:
			*v = uint32(C.pn_data_get_ubyte(data))
		case C.PN_USHORT:
			*v = uint32(C.pn_data_get_ushort(data))
		case C.PN_UINT:
			*v = uint32(C.pn_data_get_uint(data))
		default:
			doPanic(data, v)
		}

	case *int64:
		switch pnType {
		case C.PN_CHAR:
			*v = int64(C.pn_data_get_char(data))
		case C.PN_BYTE:
			*v = int64(C.pn_data_get_byte(data))
		case C.PN_SHORT:
			*v = int64(C.pn_data_get_short(data))
		case C.PN_INT:
			*v = int64(C.pn_data_get_int(data))
		case C.PN_LONG:
			*v = int64(C.pn_data_get_long(data))
		default:
			doPanic(data, v)
		}

	case *uint64:
		switch pnType {
		case C.PN_CHAR:
			*v = uint64(C.pn_data_get_char(data))
		case C.PN_UBYTE:
			*v = uint64(C.pn_data_get_ubyte(data))
		case C.PN_USHORT:
			*v = uint64(C.pn_data_get_ushort(data))
		case C.PN_ULONG:
			*v = uint64(C.pn_data_get_ulong(data))
		default:
			doPanic(data, v)
		}

	case *int:
		switch pnType {
		case C.PN_CHAR:
			*v = int(C.pn_data_get_char(data))
		case C.PN_BYTE:
			*v = int(C.pn_data_get_byte(data))
		case C.PN_SHORT:
			*v = int(C.pn_data_get_short(data))
		case C.PN_INT:
			*v = int(C.pn_data_get_int(data))
		case C.PN_LONG:
			if intIs64 {
				*v = int(C.pn_data_get_long(data))
			} else {
				doPanic(data, v)
			}
		default:
			doPanic(data, v)
		}

	case *uint:
		switch pnType {
		case C.PN_CHAR:
			*v = uint(C.pn_data_get_char(data))
		case C.PN_UBYTE:
			*v = uint(C.pn_data_get_ubyte(data))
		case C.PN_USHORT:
			*v = uint(C.pn_data_get_ushort(data))
		case C.PN_UINT:
			*v = uint(C.pn_data_get_uint(data))
		case C.PN_ULONG:
			if intIs64 {
				*v = uint(C.pn_data_get_ulong(data))
			} else {
				doPanic(data, v)
			}
		default:
			doPanic(data, v)
		}

	case *float32:
		panicUnless(pnType == C.PN_FLOAT, data, v)
		*v = float32(C.pn_data_get_float(data))

	case *float64:
		switch pnType {
		case C.PN_FLOAT:
			*v = float64(C.pn_data_get_float(data))
		case C.PN_DOUBLE:
			*v = float64(C.pn_data_get_double(data))
		default:
			doPanic(data, v)
		}

	case *string:
		switch pnType {
		case C.PN_STRING:
			*v = goString(C.pn_data_get_string(data))
		case C.PN_SYMBOL:
			*v = goString(C.pn_data_get_symbol(data))
		case C.PN_BINARY:
			*v = goString(C.pn_data_get_binary(data))
		default:
			doPanic(data, v)
		}

	case *[]byte:
		switch pnType {
		case C.PN_STRING:
			*v = goBytes(C.pn_data_get_string(data))
		case C.PN_SYMBOL:
			*v = goBytes(C.pn_data_get_symbol(data))
		case C.PN_BINARY:
			*v = goBytes(C.pn_data_get_binary(data))
		default:
			doPanic(data, v)
		}
		return

	case *Char:
		panicUnless(pnType == C.PN_CHAR, data, v)
		*v = Char(C.pn_data_get_char(data))

	case *Binary:
		panicUnless(pnType == C.PN_BINARY, data, v)
		*v = Binary(goBytes(C.pn_data_get_binary(data)))

	case *Symbol:
		panicUnless(pnType == C.PN_SYMBOL, data, v)
		*v = Symbol(goBytes(C.pn_data_get_symbol(data)))

	case *time.Time:
		panicUnless(pnType == C.PN_TIMESTAMP, data, v)
		*v = time.Unix(0, int64(C.pn_data_get_timestamp(data))*1000)

	case *UUID:
		panicUnless(pnType == C.PN_UUID, data, v)
		pn := C.pn_data_get_uuid(data)
		copy((*v)[:], C.GoBytes(unsafe.Pointer(&pn.bytes), 16))

	case *AnnotationKey:
		panicUnless(pnType == C.PN_ULONG || pnType == C.PN_SYMBOL || pnType == C.PN_STRING, data, v)
		unmarshal(&v.value, data)

	case *AnyMap:
		panicUnless(C.pn_data_type(data) == C.PN_MAP, data, v)
		n := int(C.pn_data_get_map(data)) / 2
		if cap(*v) < n {
			*v = make(AnyMap, n)
		}
		*v = (*v)[:n]
		data.enter(*v)
		defer data.exit(*v)
		for i := 0; i < n; i++ {
			data.next(*v)
			unmarshal(&(*v)[i].Key, data)
			data.next(*v)
			unmarshal(&(*v)[i].Value, data)
		}

	case *interface{}:
		getInterface(data, v)

	default: // This is not one of the fixed well-known types, reflect for map and slice types

		switch rt.Elem().Kind() {
		case reflect.Map:
			getMap(data, v)
		case reflect.Slice:
			getSequence(data, v)
		default:
			doPanic(data, v)
		}
	}
}

// Unmarshalling into an interface{} the type is determined by the AMQP source type,
// since the interface{} target can hold any Go type.
func getInterface(data *C.pn_data_t, vp *interface{}) {
	pnType := C.pn_data_type(data)
	switch pnType {
	case C.PN_BOOL:
		*vp = bool(C.pn_data_get_bool(data))
	case C.PN_UBYTE:
		*vp = uint8(C.pn_data_get_ubyte(data))
	case C.PN_BYTE:
		*vp = int8(C.pn_data_get_byte(data))
	case C.PN_USHORT:
		*vp = uint16(C.pn_data_get_ushort(data))
	case C.PN_SHORT:
		*vp = int16(C.pn_data_get_short(data))
	case C.PN_UINT:
		*vp = uint32(C.pn_data_get_uint(data))
	case C.PN_INT:
		*vp = int32(C.pn_data_get_int(data))
	case C.PN_CHAR:
		*vp = Char(C.pn_data_get_char(data))
	case C.PN_ULONG:
		*vp = uint64(C.pn_data_get_ulong(data))
	case C.PN_LONG:
		*vp = int64(C.pn_data_get_long(data))
	case C.PN_FLOAT:
		*vp = float32(C.pn_data_get_float(data))
	case C.PN_DOUBLE:
		*vp = float64(C.pn_data_get_double(data))
	case C.PN_BINARY:
		*vp = Binary(goBytes(C.pn_data_get_binary(data)))
	case C.PN_STRING:
		*vp = goString(C.pn_data_get_string(data))
	case C.PN_SYMBOL:
		*vp = Symbol(goString(C.pn_data_get_symbol(data)))
	case C.PN_TIMESTAMP:
		*vp = time.Unix(0, int64(C.pn_data_get_timestamp(data))*1000)
	case C.PN_UUID:
		var u UUID
		unmarshal(&u, data)
		*vp = u
	case C.PN_MAP:
		// We will try to unmarshal as a Map first, if that fails try AnyMap
		m := make(Map, int(C.pn_data_get_map(data))/2)
		if err := recoverUnmarshal(&m, data); err == nil {
			*vp = m
		} else {
			am := make(AnyMap, int(C.pn_data_get_map(data))/2)
			unmarshal(&am, data)
			*vp = am
		}
	case C.PN_LIST:
		l := List{}
		unmarshal(&l, data)
		*vp = l
	case C.PN_ARRAY:
		sp := getArrayStore(data) // interface{} containing T* for suitable T
		unmarshal(sp, data)
		*vp = reflect.ValueOf(sp).Elem().Interface()
	case C.PN_DESCRIBED:
		d := Described{}
		unmarshal(&d, data)
		*vp = d
	case C.PN_NULL:
		*vp = nil
	case C.PN_INVALID:
		// Allow decoding from an empty data object to an interface, treat it like NULL.
		// This happens when optional values or properties are omitted from a message.
		*vp = nil
	default: // Don't know how to handle this
		panic(newUnmarshalError(pnType, vp))
	}
}

// Return an interface{} containing a pointer to an appropriate slice or Array
func getArrayStore(data *C.pn_data_t) interface{} {
	// TODO aconway 2017-11-10: described arrays.
	switch C.pn_data_get_array_type(data) {
	case C.PN_BOOL:
		return new([]bool)
	case C.PN_UBYTE:
		return new([]uint8)
	case C.PN_BYTE:
		return new([]int8)
	case C.PN_USHORT:
		return new([]uint16)
	case C.PN_SHORT:
		return new([]int16)
	case C.PN_UINT:
		return new([]uint32)
	case C.PN_INT:
		return new([]int32)
	case C.PN_CHAR:
		return new([]Char)
	case C.PN_ULONG:
		return new([]uint64)
	case C.PN_LONG:
		return new([]int64)
	case C.PN_FLOAT:
		return new([]float32)
	case C.PN_DOUBLE:
		return new([]float64)
	case C.PN_BINARY:
		return new([]Binary)
	case C.PN_STRING:
		return new([]string)
	case C.PN_SYMBOL:
		return new([]Symbol)
	case C.PN_TIMESTAMP:
		return new([]time.Time)
	case C.PN_UUID:
		return new([]UUID)
	}
	return new(Array) // Not a simple type, use generic Array
}

var typeOfInterface = reflect.TypeOf(interface{}(nil))

// get into map pointed at by v
func getMap(data *C.pn_data_t, v interface{}) {
	panicUnless(C.pn_data_type(data) == C.PN_MAP, data, v)
	n := int(C.pn_data_get_map(data)) / 2
	mapValue := reflect.ValueOf(v).Elem()
	mapValue.Set(reflect.MakeMap(mapValue.Type())) // Clear the map
	data.enter(v)
	defer data.exit(v)
	// Allocate re-usable key/val values
	keyType := mapValue.Type().Key()
	keyPtr := reflect.New(keyType)
	valPtr := reflect.New(mapValue.Type().Elem())
	for i := 0; i < n; i++ {
		data.next(v)
		unmarshal(keyPtr.Interface(), data)
		if keyType.Kind() == reflect.Interface && !keyPtr.Elem().Elem().Type().Comparable() {
			doPanicMsg(data, v, fmt.Sprintf("key %#v is not comparable", keyPtr.Elem().Interface()))
		}
		data.next(v)
		unmarshal(valPtr.Interface(), data)
		mapValue.SetMapIndex(keyPtr.Elem(), valPtr.Elem())
	}
}

func getSequence(data *C.pn_data_t, vp interface{}) {
	var count int
	pnType := C.pn_data_type(data)
	switch pnType {
	case C.PN_LIST:
		count = int(C.pn_data_get_list(data))
	case C.PN_ARRAY:
		count = int(C.pn_data_get_array(data))
	default:
		doPanic(data, vp)
	}
	listValue := reflect.MakeSlice(reflect.TypeOf(vp).Elem(), count, count)
	data.enter(vp)
	defer data.exit(vp)
	for i := 0; i < count; i++ {
		data.next(vp)
		val := reflect.New(listValue.Type().Elem())
		unmarshal(val.Interface(), data)
		listValue.Index(i).Set(val.Elem())
	}
	reflect.ValueOf(vp).Elem().Set(listValue)
}

func getDescribed(data *C.pn_data_t, vp interface{}) {
	d, isDescribed := vp.(*Described)
	data.enter(vp)
	defer data.exit(vp)
	data.next(vp)
	if isDescribed {
		unmarshal(&d.Descriptor, data)
		data.next(vp)
		unmarshal(&d.Value, data)
	} else {
		data.next(vp)       // Skip descriptor
		unmarshal(vp, data) // Unmarshal plain value
	}
}

// decode from bytes.
// Return bytes decoded or 0 if we could not decode a complete object.
//
func decode(data *C.pn_data_t, bytes []byte) (int, error) {
	n := C.pn_data_decode(data, cPtr(bytes), cLen(bytes))
	if n == C.PN_UNDERFLOW {
		C.pn_error_clear(C.pn_data_error(data))
		return 0, EndOfData
	} else if n <= 0 {
		return 0, &UnmarshalError{s: fmt.Sprintf("unmarshal %v", PnErrorCode(n))}
	}
	return int(n), nil
}

// Checked versions of pn_data functions

func (data *C.pn_data_t) enter(v interface{}) { checkOp(bool(C.pn_data_enter(data)), v) }
func (data *C.pn_data_t) exit(v interface{})  { checkOp(bool(C.pn_data_exit(data)), v) }
func (data *C.pn_data_t) next(v interface{})  { checkOp(bool(C.pn_data_next(data)), v) }
