/*
Licensed to the Apache Software Foundation (ASF) under one
or 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"
	"time"
	"unsafe"
)

func (t C.pn_type_t) String() string {
	switch C.pn_type_t(t) {
	case C.PN_NULL:
		return "null"
	case C.PN_BOOL:
		return "bool"
	case C.PN_UBYTE:
		return "ubyte"
	case C.PN_BYTE:
		return "byte"
	case C.PN_USHORT:
		return "ushort"
	case C.PN_SHORT:
		return "short"
	case C.PN_CHAR:
		return "char"
	case C.PN_UINT:
		return "uint"
	case C.PN_INT:
		return "int"
	case C.PN_ULONG:
		return "ulong"
	case C.PN_LONG:
		return "long"
	case C.PN_TIMESTAMP:
		return "timestamp"
	case C.PN_FLOAT:
		return "float"
	case C.PN_DOUBLE:
		return "double"
	case C.PN_DECIMAL32:
		return "decimal32"
	case C.PN_DECIMAL64:
		return "decimal64"
	case C.PN_DECIMAL128:
		return "decimal128"
	case C.PN_UUID:
		return "uuid"
	case C.PN_BINARY:
		return "binary"
	case C.PN_STRING:
		return "string"
	case C.PN_SYMBOL:
		return "symbol"
	case C.PN_DESCRIBED:
		return "described"
	case C.PN_ARRAY:
		return "array"
	case C.PN_LIST:
		return "list"
	case C.PN_MAP:
		return "map"
	default:
		return fmt.Sprintf("<bad-type %v>", int(t))
	}
}

// The AMQP map type. A generic map that can have mixed-type keys and values.
type Map map[interface{}]interface{}

// The most general AMQP map type, for unusual interoperability cases.
//
// This is not a Go Map but a sequence of {key, value} pairs.
//
// An AnyMap lets you control or examine the encoded ordering of key,value pairs
// and use key values that are not legal as Go map keys.
//
// The amqp.Map, or plain Go map types are easier to use for most cases.
type AnyMap []KeyValue

// Return a Map constructed from an AnyMap.
// Panic if the AnyMap has key values that are not valid Go map keys (e.g. maps, slices)
func (a AnyMap) Map() (m Map) {
	for _, kv := range a {
		m[kv.Key] = kv.Value
	}
	return
}

// KeyValue pair, used by AnyMap
type KeyValue struct{ Key, Value interface{} }

// The AMQP list type. A generic list that can hold mixed-type values.
type List []interface{}

// The generic AMQP array type, used to unmarshal an array with nested array,
// map or list elements. Arrays of simple type T unmarshal to []T
type Array []interface{}

// Symbol is a string that is encoded as an AMQP symbol
type Symbol string

func (s Symbol) String() string   { return string(s) }
func (s Symbol) GoString() string { return fmt.Sprintf("s\"%s\"", s) }

// Binary is a string that is encoded as an AMQP binary.
// It is a string rather than a byte[] because byte[] is not hashable and can't be used as
// a map key, AMQP frequently uses binary types as map keys. It can convert to and from []byte
type Binary string

func (b Binary) String() string   { return string(b) }
func (b Binary) GoString() string { return fmt.Sprintf("b\"%s\"", b) }

// GoString for Map prints values with their types, useful for debugging.
func (m Map) GoString() string {
	out := &bytes.Buffer{}
	fmt.Fprintf(out, "%T{", m)
	i := len(m)
	for k, v := range m {
		fmt.Fprintf(out, "%T(%#v): %T(%#v)", k, k, v, v)
		i--
		if i > 0 {
			fmt.Fprint(out, ", ")
		}
	}
	fmt.Fprint(out, "}")
	return out.String()
}

// GoString for List prints values with their types, useful for debugging.
func (l List) GoString() string {
	out := &bytes.Buffer{}
	fmt.Fprintf(out, "%T{", l)
	for i := 0; i < len(l); i++ {
		fmt.Fprintf(out, "%T(%#v)", l[i], l[i])
		if i == len(l)-1 {
			fmt.Fprint(out, ", ")
		}
	}
	fmt.Fprint(out, "}")
	return out.String()
}

// pnTime converts Go time.Time to Proton millisecond Unix time.
func pnTime(t time.Time) C.pn_timestamp_t {
	secs := t.Unix()
	// Note: sub-second accuracy is not guaranteed if the Unix time in
	// nanoseconds cannot be represented by an int64 (sometime around year 2260)
	msecs := (t.UnixNano() % int64(time.Second)) / int64(time.Millisecond)
	return C.pn_timestamp_t(secs*1000 + msecs)
}

// goTime converts a pn_timestamp_t to a Go time.Time.
func goTime(t C.pn_timestamp_t) time.Time {
	secs := int64(t) / 1000
	nsecs := (int64(t) % 1000) * int64(time.Millisecond)
	return time.Unix(secs, nsecs)
}

func goBytes(cBytes C.pn_bytes_t) (bytes []byte) {
	if cBytes.start != nil {
		bytes = C.GoBytes(unsafe.Pointer(cBytes.start), C.int(cBytes.size))
	}
	return
}

func goString(cBytes C.pn_bytes_t) (str string) {
	if cBytes.start != nil {
		str = C.GoStringN(cBytes.start, C.int(cBytes.size))
	}
	return
}

func pnBytes(b []byte) C.pn_bytes_t {
	if len(b) == 0 {
		return C.pn_bytes_t{0, nil}
	} else {
		return C.pn_bytes_t{C.size_t(len(b)), (*C.char)(unsafe.Pointer(&b[0]))}
	}
}

func cPtr(b []byte) *C.char {
	if len(b) == 0 {
		return nil
	}
	return (*C.char)(unsafe.Pointer(&b[0]))
}

func cLen(b []byte) C.size_t {
	return C.size_t(len(b))
}

// AnnotationKey is used as a map key for AMQP annotation maps which are
// allowed to have keys that are either symbol or ulong but no other type.
//
type AnnotationKey struct {
	value interface{}
}

func AnnotationKeySymbol(v Symbol) AnnotationKey { return AnnotationKey{v} }
func AnnotationKeyUint64(v uint64) AnnotationKey { return AnnotationKey{v} }
func AnnotationKeyString(v string) AnnotationKey { return AnnotationKey{Symbol(v)} }

// Returns the value which must be Symbol, uint64 or nil
func (k AnnotationKey) Get() interface{} { return k.value }

func (k AnnotationKey) String() string { return fmt.Sprintf("%v", k.Get()) }

// Described represents an AMQP described type, which is really
// just a pair of AMQP values - the first is treated as a "descriptor",
// and is normally a string or ulong providing information about the type.
// The second is the "value" and can be any AMQP value.
type Described struct {
	Descriptor interface{}
	Value      interface{}
}

// UUID is an AMQP 128-bit Universally Unique Identifier, as defined by RFC-4122 section 4.1.2
type UUID [16]byte

func (u UUID) String() string {
	return fmt.Sprintf("UUID(%x-%x-%x-%x-%x)", u[0:4], u[4:6], u[6:8], u[8:10], u[10:])
}

// Char is an AMQP unicode character, equivalent to a Go rune.
// It is defined as a distinct type so it can be distinguished from an AMQP int
type Char rune

const intIs64 = unsafe.Sizeof(int(0)) == 8
