// Copyright 2016-2020, Pulumi Corporation.
//
// 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.

package deepcopy

import (
	"reflect"

	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
	"github.com/pulumi/pulumi/sdk/v3/go/internal"
)

// Copy returns a deep copy of the provided value.
//
// If there are multiple references to the same value inside the provided value, the multiply-referenced value will be
// copied multiple times.
//
// NOTE: Unexported members of structs will *not* be copied.
func Copy(i interface{}) interface{} {
	if i == nil {
		return nil
	}
	return deepCopy(reflect.ValueOf(i)).Interface()
}

func deepCopy(v reflect.Value) reflect.Value {
	if !v.IsValid() {
		return v
	}

	if v.Type() == reflect.TypeOf(internal.OutputState{}) {
		contract.Failf("Outputs cannot be deep copied")
	}

	typ := v.Type()
	switch typ.Kind() {
	case reflect.Bool,
		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
		reflect.Float32, reflect.Float64,
		reflect.Complex64, reflect.Complex128,
		reflect.String,
		reflect.Func:
		// These all have value semantics. Return them as-is.
		return v
	case reflect.Chan:
		// Channels have referential semantics, but deep-copying them has no meaning. Return them as-is.
		return v
	case reflect.Interface:
		rv := reflect.New(typ).Elem()
		if !v.IsNil() {
			rv.Set(deepCopy(v.Elem()))
		}
		return rv
	case reflect.Ptr:
		if v.IsNil() {
			return reflect.New(typ).Elem()
		}
		elem := deepCopy(v.Elem())
		if elem.CanAddr() {
			return elem.Addr()
		}
		rv := reflect.New(typ.Elem())
		rv.Set(elem)
		return rv
	case reflect.Array:
		rv := reflect.New(typ).Elem()
		for i := 0; i < v.Len(); i++ {
			rv.Index(i).Set(deepCopy(v.Index(i)))
		}
		return rv
	case reflect.Slice:
		rv := reflect.New(typ).Elem()
		if !v.IsNil() {
			rv.Set(reflect.MakeSlice(typ, v.Len(), v.Cap()))
			for i := 0; i < v.Len(); i++ {
				rv.Index(i).Set(deepCopy(v.Index(i)))
			}
		}
		return rv
	case reflect.Map:
		rv := reflect.New(typ).Elem()
		if !v.IsNil() {
			rv.Set(reflect.MakeMap(typ))
			iter := v.MapRange()
			for iter.Next() {
				rv.SetMapIndex(deepCopy(iter.Key()), deepCopy(iter.Value()))
			}
		}
		return rv
	case reflect.Struct:
		rv := reflect.New(typ).Elem()
		for i := 0; i < typ.NumField(); i++ {
			if f := rv.Field(i); f.CanSet() {
				f.Set(deepCopy(v.Field(i)))
			}
		}
		return rv
	case reflect.Invalid, reflect.UnsafePointer:
		panic("unexpected kind " + typ.Kind().String())
	default:
		panic("unexpected kind " + typ.Kind().String())
	}
}