package awsutil

import (
	"io"
	"reflect"
	"time"
)

// Copy deeply copies a src structure to dst.  Useful for copying request and
// response structures. 
//
// Can copy between structs of different type, but will only copy fields which
// are assignable, and exist in both structs.  Fields which are not assignable,
// or do not exist in both structs are ignored. 
func Copy(dst, src interface{}) {
	dstval := reflect. ValueOf(dst)
	if ! dstval. IsValid() {
		panic("Copy dst cannot be nil")
	}

	rcopy(dstval, reflect. ValueOf(src), true)
}

// CopyOf returns a copy of src while also allocating the memory for dst. 
// src must be a pointer type or this operation will fail. 
func CopyOf(src interface{}) (dst interface{}) {
	dsti := reflect. New(reflect. TypeOf(src).Elem())
	dst = dsti. Interface()
	rcopy(dsti, reflect. ValueOf(src), true)
	return
}

// rcopy performs a recursive copy of values from the source to destination. 
//
// root is used to skip certain aspects of the copy which are not valid
// for the root node of a object. 
func rcopy(dst, src reflect. Value, root bool) {
	if ! src. IsValid() {
		return
	}

	switch src. Kind() {
	case reflect. Ptr:
		if _, ok := src. Interface().(io. Reader); ok {
			if dst. Kind() == reflect. Ptr && dst. Elem().CanSet() {
				dst. Elem().Set(src)
			} else if dst. CanSet() {
				dst. Set(src)
			}
		} else {
			e := src. Type().Elem()
			if dst. CanSet() && ! src. IsNil() {
				if _, ok := src. Interface().(*time. Time); ! ok {
					dst. Set(reflect. New(e))
				} else {
					tempValue := reflect. New(e)
					tempValue. Elem().Set(src. Elem())
					// Sets time. Time's unexported values
					dst. Set(tempValue)
				}
			}
			if src. Elem().IsValid() {
				// Keep the current root state since the depth hasn't changed
				rcopy(dst. Elem(), src. Elem(), root)
			}
		}
	case reflect. Struct:
		t := dst. Type()
		for i := 0; i < t.NumField(); i++ {
			name := t.Field(i).Name
			srcVal := src. FieldByName(name)
			dstVal := dst. FieldByName(name)
			if srcVal. IsValid() && dstVal. CanSet() {
				rcopy(dstVal, srcVal, false)
			}
		}
	case reflect. Slice:
		if src. IsNil() {
			break
		}

		s := reflect. MakeSlice(src. Type(), src. Len(), src. Cap())
		dst. Set(s)
		for i := 0; i < src. Len(); i++ {
			rcopy(dst. Index(i), src. Index(i), false)
		}
	case reflect. Map:
		if src. IsNil() {
			break
		}

		s := reflect. MakeMap(src. Type())
		dst. Set(s)
		for _, k := range src. MapKeys() {
			v := src. MapIndex(k)
			v2 := reflect. New(v. Type()).Elem()
			rcopy(v2, v, false)
			dst. SetMapIndex(k, v2)
		}
	default:
		// Assign the value if possible.  If its not assignable, the value would
		// need to be converted and the impact of that may be unexpected, or is
		// not compatible with the dst type. 
		if src. Type().AssignableTo(dst. Type()) {
			dst. Set(src)
		}
	}
}
