package generator

import (
	"strings"
	"text/template"

	"golang.org/x/text/cases"
	"golang.org/x/text/language"
)

var title = cases.Title(language.Und, cases.NoLower)

var hasConstraintInterface = func(f *Fake) bool { return f.HasConstraintInterface() }

var interfaceFuncs = template.FuncMap{
	"ToLower":                strings.ToLower,
	"UnExport":               unexport,
	"Replace":                strings.Replace,
	"IsExported":             isExported,
	"Title":                  title.String,
	"HasConstraintInterface": hasConstraintInterface,
}

const interfaceTemplate string = `{{.Header}}// Code generated by counterfeiter. DO NOT EDIT.
package {{.DestinationPackage}}

import (
	{{- range $index, $import := .Imports.ByAlias}}
	{{$import}}
	{{- end}}
)

type {{.Name}}{{.GenericTypeParametersAndConstraints}} struct {
	{{- range .Methods}}
	{{.Name}}Stub func({{.Params.AsArgs}}) {{.Returns.AsReturnSignature}}
	{{UnExport .Name}}Mutex sync.RWMutex
	{{UnExport .Name}}ArgsForCall []struct{
		{{- range .Params}}
		{{.Name}} {{if .IsVariadic}}{{Replace .Type "..." "[]" -1}}{{else}}{{.Type}}{{end}}
		{{- end}}
	}
	{{- if .Returns.HasLength}}
	{{UnExport .Name}}Returns struct{
		{{- range .Returns}}
		{{UnExport .Name}} {{.Type}}
		{{- end}}
	}
	{{UnExport .Name}}ReturnsOnCall map[int]struct{
		{{- range .Returns}}
		{{UnExport .Name}} {{.Type}}
		{{- end}}
	}
	{{- end}}
	{{- end}}
	invocations      map[string][][]interface{}
	invocationsMutex sync.RWMutex
}

{{range .Methods -}}
func (fake *{{$.Name}}{{$.GenericTypeParameters}}) {{.Name}}({{.Params.AsNamedArgsWithTypes}}) {{.Returns.AsReturnSignature}} {
	{{- range .Params.Slices}}
	var {{UnExport .Name}}Copy {{.Type}}
	if {{UnExport .Name}} != nil {
		{{UnExport .Name}}Copy = make({{.Type}}, len({{UnExport .Name}}))
		copy({{UnExport .Name}}Copy, {{UnExport .Name}})
	}
	{{- end}}
	fake.{{UnExport .Name}}Mutex.Lock()
	{{- if .Returns.HasLength}}
	ret, specificReturn := fake.{{UnExport .Name}}ReturnsOnCall[len(fake.{{UnExport .Name}}ArgsForCall)]
	{{- end}}
	fake.{{UnExport .Name}}ArgsForCall = append(fake.{{UnExport .Name}}ArgsForCall, struct{
		{{- range .Params}}
		{{.Name}} {{if .IsVariadic}}{{Replace .Type "..." "[]" -1}}{{else}}{{.Type}}{{end}}
		{{- end}}
	}{ {{- .Params.AsNamedArgs -}} })
	stub := fake.{{.Name}}Stub
	{{- if .Returns.HasLength}}
	fakeReturns := fake.{{UnExport .Name}}Returns
	{{- end}}
	fake.recordInvocation("{{.Name}}", []interface{}{ {{- if .Params.HasLength}}{{.Params.AsNamedArgs}}{{end -}} })
	fake.{{UnExport .Name}}Mutex.Unlock()
	if stub != nil {
		{{- if .Returns.HasLength}}
		return stub({{.Params.AsNamedArgsForInvocation}}){{else}}fake.{{.Name}}Stub({{.Params.AsNamedArgsForInvocation}})
		{{- end}}
	}
	{{- if .Returns.HasLength}}
	if specificReturn {
		return {{.Returns.WithPrefix "ret."}}
	}
	return {{.Returns.WithPrefix "fakeReturns."}}
	{{- end}}
}

func (fake *{{$.Name}}{{$.GenericTypeParameters}}) {{Title .Name}}CallCount() int {
	fake.{{UnExport .Name}}Mutex.RLock()
	defer fake.{{UnExport .Name}}Mutex.RUnlock()
	return len(fake.{{UnExport .Name}}ArgsForCall)
}

func (fake *{{$.Name}}{{$.GenericTypeParameters}}) {{Title .Name}}Calls(stub func({{.Params.AsArgs}}) {{.Returns.AsReturnSignature}}) {
	fake.{{UnExport .Name}}Mutex.Lock()
	defer fake.{{UnExport .Name}}Mutex.Unlock()
	fake.{{.Name}}Stub = stub
}

{{if .Params.HasLength -}}
func (fake *{{$.Name}}{{$.GenericTypeParameters}}) {{Title .Name}}ArgsForCall(i int) {{.Params.AsReturnSignature}} {
	fake.{{UnExport .Name}}Mutex.RLock()
	defer fake.{{UnExport .Name}}Mutex.RUnlock()
	argsForCall := fake.{{UnExport .Name}}ArgsForCall[i]
	return {{.Params.WithPrefix "argsForCall."}}
}
{{- end}}

{{if .Returns.HasLength -}}
func (fake *{{$.Name}}{{$.GenericTypeParameters}}) {{Title .Name}}Returns({{.Returns.AsNamedArgsWithTypes}}) {
	fake.{{UnExport .Name}}Mutex.Lock()
	defer fake.{{UnExport .Name}}Mutex.Unlock()
	fake.{{.Name}}Stub = nil
	fake.{{UnExport .Name}}Returns = struct {
		{{- range .Returns}}
		{{UnExport .Name}} {{.Type}}
		{{- end}}
	}{ {{- .Returns.AsNamedArgs -}} }
}

func (fake *{{$.Name}}{{$.GenericTypeParameters}}) {{Title .Name}}ReturnsOnCall(i int, {{.Returns.AsNamedArgsWithTypes}}) {
	fake.{{UnExport .Name}}Mutex.Lock()
	defer fake.{{UnExport .Name}}Mutex.Unlock()
	fake.{{.Name}}Stub = nil
	if fake.{{UnExport .Name}}ReturnsOnCall == nil {
		fake.{{UnExport .Name}}ReturnsOnCall = make(map[int]struct {
			{{- range .Returns}}
			{{UnExport .Name}} {{.Type}}
			{{- end}}
		})
	}
	fake.{{UnExport .Name}}ReturnsOnCall[i] = struct {
		{{- range .Returns}}
		{{UnExport .Name}} {{.Type}}
		{{- end}}
	}{ {{- .Returns.AsNamedArgs -}} }
}

{{end -}}
{{end}}

func (fake *{{.Name}}{{$.GenericTypeParameters}}) Invocations() map[string][][]interface{} {
	fake.invocationsMutex.RLock()
	defer fake.invocationsMutex.RUnlock()
	copiedInvocations := map[string][][]interface{}{}
	for key, value := range fake.invocations {
		copiedInvocations[key] = value
	}
	return copiedInvocations
}

func (fake *{{.Name}}{{$.GenericTypeParameters}}) recordInvocation(key string, args []interface{}) {
	fake.invocationsMutex.Lock()
	defer fake.invocationsMutex.Unlock()
	if fake.invocations == nil {
		fake.invocations = map[string][][]interface{}{}
	}
	if fake.invocations[key] == nil {
		fake.invocations[key] = [][]interface{}{}
	}
	fake.invocations[key] = append(fake.invocations[key], args)
}

{{if IsExported .TargetName -}}
{{if not (HasConstraintInterface .) -}}
var _ {{.TargetAlias}}.{{.TargetName}}{{.GenericTypeConstraints}} = new({{.Name}}{{.GenericTypeConstraints}})
{{- end}}
{{- end}}
`
