// Copyright 2016-2018, 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.

// nolint: lll, interfacer
package pulumi

import (
	"context"
	"reflect"
)

{{with $builtins := .Builtins}}
{{range $builtins}}
var {{.Name | Unexported}}Type = reflect.TypeOf((*{{.ElementType}})(nil)).Elem()

// {{.Name}}Input is an input type that accepts {{.Name}} and {{.Name}}Output values.
type {{.Name}}Input interface {
	Input

	To{{.Name}}Output() {{.Name}}Output
	To{{.Name}}OutputWithContext(ctx context.Context) {{.Name}}Output
	{{if .ImplementsPtrType}}
	To{{.Name}}PtrOutput() {{.Name}}PtrOutput
	To{{.Name}}PtrOutputWithContext(ctx context.Context) {{.Name}}PtrOutput
	{{end}}
}
{{if .DefineInputType}}
// {{.Name}} is an input type for {{.Type}} values.
type {{.Name}} {{.Type}}
{{else if .DefinePtrType}}
type {{.PtrType}} {{.ElemElementType}}

// {{.Name}} is an input type for {{.Type}} values.
func {{.Name}}(v {{.ElemElementType}}) {{.Name}}Input {
	return ({{.InputType}})(&v)
}
{{end}}
{{if .DefineInputMethods}}
// ElementType returns the element type of this Input ({{.ElementType}}).
func ({{.InputType}}) ElementType() reflect.Type {
	return {{.Name | Unexported}}Type
}

func (in {{.InputType}}) To{{.Name}}Output() {{.Name}}Output {
	return ToOutput(in).({{.Name}}Output)
}

func (in {{.InputType}}) To{{.Name}}OutputWithContext(ctx context.Context) {{.Name}}Output {
	return ToOutputWithContext(ctx, in).({{.Name}}Output)
}

{{with $builtin := .}}
{{range $t := .Implements}}
func (in {{$builtin.InputType}}) To{{$t.Name}}Output() {{$t.Name}}Output {
	return in.To{{$t.Name}}OutputWithContext(context.Background())
}

func (in {{$builtin.InputType}}) To{{$t.Name}}OutputWithContext(ctx context.Context) {{$t.Name}}Output {
	return in.To{{$builtin.Name}}OutputWithContext(ctx).To{{$t.Name}}OutputWithContext(ctx)
}
{{end}}
{{end}}
{{if .ImplementsPtrType}}
func (in {{.InputType}}) To{{.Name}}PtrOutput() {{.Name}}PtrOutput {
	return in.To{{.Name}}PtrOutputWithContext(context.Background())
}

func (in {{.InputType}}) To{{.Name}}PtrOutputWithContext(ctx context.Context) {{.Name}}PtrOutput {
	return in.To{{.Name}}OutputWithContext(ctx).To{{.Name}}PtrOutputWithContext(ctx)
}
{{end}}
{{end}}
// {{.Name}}Output is an Output that returns {{.ElementType}} values.
type {{.Name}}Output struct { *OutputState }

// ElementType returns the element type of this Output ({{.ElementType}}).
func ({{.Name}}Output) ElementType() reflect.Type {
	return {{.Name | Unexported}}Type
}

func (o {{.Name}}Output) To{{.Name}}Output() {{.Name}}Output {
	return o
}

func (o {{.Name}}Output) To{{.Name}}OutputWithContext(ctx context.Context) {{.Name}}Output {
	return o
}
{{with $builtin := .}}
{{range $t := .Implements}}
func (o {{$builtin.Name}}Output) To{{$t.Name}}Output() {{$t.Name}}Output {
	return o.To{{$t.Name}}OutputWithContext(context.Background())
}

func (o {{$builtin.Name}}Output) To{{$t.Name}}OutputWithContext(ctx context.Context) {{$t.Name}}Output {
	return o.ApplyTWithContext(ctx,	func(_ context.Context, v {{$builtin.ElementType}}) {{$t.ElementType}} {
		return ({{$t.ElementType}})(v)
	}).({{$t.Name}}Output)
}
{{end}}
{{if .ImplementsPtrType}}
func (o {{.Name}}Output) To{{.Name}}PtrOutput() {{.Name}}PtrOutput {
	return o.To{{.Name}}PtrOutputWithContext(context.Background())
}

func (o {{.Name}}Output) To{{.Name}}PtrOutputWithContext(ctx context.Context) {{.Name}}PtrOutput {
	return o.ApplyTWithContext(ctx,	func(_ context.Context, v {{.ElementType}}) *{{.ElementType}} {
		return &v
	}).({{.Name}}PtrOutput)
}
{{end}}
{{end}}
{{if .DefineElem}}
// Elem dereferences the pointer value or returns the zero value of the approporiate type if the pointer is nil.
func (o {{.Name}}Output) Elem() {{.ElemReturnType}}Output {
	return o.ApplyT(func (v {{.ElementType}}) {{.ElemElementType}} {
		var ret {{.ElemElementType}}
		if v != nil {
			ret = *v
		}
		return ret
	}).({{.ElemReturnType}}Output)
}
{{end}}
{{if .DefineIndex}}
// Index looks up the i'th element of the array if it is in bounds or returns the zero value of the appropriate
// type if the index is out of bounds.
func (o {{.Name}}Output) Index(i IntInput) {{.IndexReturnType}}Output {
	return All(o, i).ApplyT(func(vs []interface{}) {{.IndexElementType}} {
		arr := vs[0].({{.ElementType}})
		idx := vs[1].(int)
		var ret {{.IndexElementType}}
		if idx >= 0 && idx < len(arr) {
			ret = arr[idx]
		}
		return ret
	}).({{.IndexReturnType}}Output)
}

{{if .DefineToFunction}}
func To{{.Name}}(in {{.ElementType}}) {{.Name}} {
	a := make({{.Name}}, len(in))
	for i, v := range in {
		a[i] = {{.IndexConversion}}(v)
	}
	return a
}

func To{{.Name}}Output(in []{{.IndexReturnType}}Output) {{.Name}}Output {
	a := make({{.Name}}, len(in))
	for i, v := range in {
		a[i] = v
	}
	return a.To{{.Name}}Output()
}
{{end}}
{{end}}
{{if .DefineMapIndex}}
// MapIndex looks up the key k in the map.
func (o {{.Name}}Output) MapIndex(k StringInput) {{.MapIndexReturnType}}Output {
	return All(o, k).ApplyT(func(vs []interface{}) {{.MapIndexElementType}} {
		return vs[0].({{.ElementType}})[vs[1].(string)]
	}).({{.MapIndexReturnType}}Output)
}

{{if .DefineToFunction}}
func To{{.Name}}(in {{.ElementType}}) {{.Name}} {
 	m := make({{.Name}})
 	for k, v := range in {
 		m[k] = {{.IndexConversion}}(v)
 	}
 	return m
}

func To{{.Name}}Output(in map[string]{{.MapIndexReturnType}}Output) {{.Name}}Output {
	m := make({{.Name}})
 	for k, v := range in {
 		m[k] = v
 	}
 	return m.To{{.Name}}Output()
}
{{end}}
{{end}}
{{end}}
{{end}}

func getResolvedValue(input Input) (reflect.Value, bool) {
	switch input := input.(type) {
	case *asset, *archive:
		return reflect.ValueOf(input), true
	default:
		return reflect.Value{}, false
	}
}

func init() {
{{- range .Builtins}}
	RegisterOutputType({{.Name}}Output{})
{{- end}}
}
