package gen

const rootTemplate = `
{{/* The method to create an instance of interface_{{.Name}} */}}
{{template "constructor" $}}

{{/* The actual struct */}}
{{template "struct" $}}

{{/* Each method in the interface will have a stub built */}}
{{- $intfcName := .Name -}}
{{- $backendTypeName := .BackendTypeName -}}
{{- $prefix := .TypePrefix -}}
{{range .Methods -}}
    {{$method := .}}

    {{/* Stub method signature */ -}}
    {{template "methodComment" method $intfcName $backendTypeName $method}}
    func (self *{{$prefix}}{{$intfcName}}) {{.Name}}(
    {{- range $index, $param := .Params -}}
        {{- $param.Name}} {{$param.Type}},
    {{end -}}
    )
    
    {{- $resultCount := len .Results -}}
    {{- if gt $resultCount 1}} ( {{else}} {{end -}}
    {{- range $index, $result := .Results -}}
        {{- if $index}}, {{end -}}
        {{- $result.Type -}}
    {{- end -}}
    {{if gt $resultCount 1}} ) {{else}} {{end -}}
    {
        {{- /* Method body*/ -}}
        {{- if not .Ignore -}}
            {{- template "preCall" $method}}
            
            begin := time.Now()
        {{- end}}
        
        //Call method on inner backend
        {{range $index, $result := .Results -}}
            {{- if $index}}, {{end -}}
            retVal{{add $index 1 -}}
        {{- end -}}
        {{- if len .Results}} := {{end -}}
        self.inner.{{.Name}}(
            {{- range $index, $param := .Params -}}
                {{- $param.Name}},
            {{end -}}
        )
        
        {{if not .Ignore -}}
            //Escape if the inner backend returned an error- don't write to kinesis
            {{range $index, $result := .Results -}}
                {{- if $result.IsError -}}
                    if retVal{{add $index 1}} != nil && self.errCheck.SeriousError(retVal{{add $index 1}}) {
                        return {{range $innerIndex, $innerResult := $method.Results -}}
                            {{- if $innerIndex}}, {{end -}}
                            retVal{{add $innerIndex 1 -}}
                        {{- end}}
                    }
                {{- end}}
            {{- end}}
            
            duration := time.Now().Sub(begin)

            //When this method returns, the response will be sent & the root context will be cancelled-
            //to make sure that these goroutines have a chance of completing, we need a fresh context to execute
            //them on.
            newCtx, _ := context.WithTimeout(context.Background(), 500 * time.Millisecond)
            
            {{template "postCall" method $intfcName $backendTypeName $method -}}
        {{- end}}
        
        {{/* Return values from inner method */ -}}
        return {{range $index, $result := .Results -}}
            {{- if $index}}, {{end -}}
            retVal{{add $index 1 -}}
        {{- end}}
    }
{{end}}
`

const producerMethodComment = `
    //{{.Method.Name}} {{if .Method.Ignore -}}
        is a decorator method that passes directly through to the decorated backend
    {{- else -}}
        is a producer method that calls the decorated backend, then writes the results & timings to 
        //kinesis.
    {{- end -}}`

const producerConstructor = `
//Create{{.Name}}Producer is a method that builds a decorator for a {{.Name}} object which writes backend method calls to
//kinesis.
func Create{{.Name}}Producer(inner {{.Name}}, statsdHostPort string, environment string, repo string, 
partitionKeyBuilder migration.PartitionKeyBuilder, awsProfile string, region string, 
streamName string,
    {{- if .AcceptErrorCheck -}}
        errCheck migration.ErrorCheck,
    {{- end -}}
    ) {{.Name}} {
        
    return &producer_{{.Name}}{
        inner: inner,
        eventWriter: migration.CreateKinesisWriter(statsdHostPort, environment, repo, "{{.Name}}", 
        partitionKeyBuilder, awsProfile, region, streamName),
        errCheck: 
            {{- if .AcceptErrorCheck -}}
                errCheck
            {{- else -}}
                &migration.DefaultErrorCheck{}
            {{- end -}}
        ,
    }
}
`
const producerStruct = `
type producer_{{.Name}} struct {
    inner {{.Name}}
    eventWriter migration.EventWriter
    errCheck migration.ErrorCheck
}
`
const producerPreCall = ``
const producerPostCall = `go func(ctx context.Context) {
    {{- with .Method}}
        //Write to kinesis - write the method name, duration of the call, parameters used, and return value(s)
        self.eventWriter.Write("{{.Name}}", 
            map[string]interface{}{
                {{- range $index, $param := .Params -}}
                    {{- if not .IsContext}}
                        "{{$param.Name}}": {{$param.Name}},
                    {{- end -}}
                {{- end}}
            },  //Parameter map
            []interface{}{
                {{- range $index, $result := .Results -}}
                    {{- if $index}}, {{end -}}
                    retVal{{add $index 1}}
                {{- end -}}
            }, //Return value array
            duration)
    {{end -}}
}(newCtx)`

const secondaryMethodComment = `
    //{{.Method.Name}} {{if .Method.Ignore -}}
        is a decorator method that passes directly through to the decorated backend
    {{- else -}}
        is a secondary mirroring method that calls the decorated backend, then the listed secondary backends
        //and compares their results and timings.
    {{- end -}}`

const secondaryConstructor = `
//Create{{.Name}}Secondary is a method that builds a decorator for a {{.Name}} object which mirrors calls
//to a map of secondary {{.Name}} objects and compares results and timings.
func Create{{.Name}}Secondary(inner {{.Name}}, statsdHostPort string, logLevel comparison.LogLevel, preprocessor comparison.ComparePreprocessor, environment string, repo string,
    {{if .AcceptErrorCheck -}}
        errCheck migration.ErrorCheck,
    {{- end -}}
    secondaries map[string]{{.BackendTypeName -}}
    ) {{.Name}} {
        
        return &secondary_{{.Name}}{
            inner: inner,
            callComparer: migration.CreateCallComparer(statsdHostPort, logLevel, preprocessor, environment, repo, "{{.Name}}"),
            secondaries: secondaries,
            errCheck: 
                {{- if .AcceptErrorCheck -}}
                    errCheck
                {{- else -}}
                    &migration.DefaultErrorCheck{}
                {{- end -}}
            ,
        }
    }
`
const secondaryStruct = `
type secondary_{{.Name}} struct {
    inner {{.Name}}
    callComparer migration.CallComparer
    secondaries map[string]{{.BackendTypeName}}
    errCheck migration.ErrorCheck
}
`
const secondaryPreCall = ``
const secondaryPostCall = `//Write the timing to graphite so we can compare to secondary call duration
self.callComparer.RecordPrimary("{{.Method.Name}}", duration)

//Mirror to each secondary & compare
for key, secondary := range self.secondaries {
    go func(ctx context.Context, secName string, sec {{.BackendTypeName}}) {
        //callComparer.SecondaryCall takes care of wrapping the passed func with timings and
        //comparing results with the primary
        self.callComparer.SecondaryCall("{{.Method.Name}}", secName, duration, 
        []interface{}{
            {{- range $index, $param := .Method.Params -}}
                {{- if not $param.IsContext -}}
                    {{- $param.Name -}},
                {{- end -}}
            {{- end -}}
        }, []interface{}{
            {{- range $index, $result := .Method.Results -}}
                {{- if $index}}, {{end -}}
                retVal{{add $index 1 -}}
            {{- end -}}
        }, func() ([]interface{}, error) {
            
            //Call secondary
            {{range $index, $result := .Method.Results -}}
                {{- if $index}}, {{end -}}
                secondaryRetVal{{add $index 1 -}}
            {{- end -}}
            {{- if len .Method.Results}} := {{end -}}
            sec.{{.Method.Name}}(
                {{- range $index, $param := .Method.Params -}}
                    {{- if $param.IsContext -}}
                        ctx,
                    {{- else -}}
                        {{- $param.Name -}},
                    {{- end}}
                {{end -}}
            )
            
            //Bail if the secondary backend had a serious error
            {{- range $index, $result := .Method.Results -}}
                {{- if $result.IsError}}
                    if secondaryRetVal{{add $index 1}} != nil && self.errCheck.SeriousError(secondaryRetVal{{add $index 1}}) {
                        return []interface{}{}, secondaryRetVal{{add $index 1}}
                    }
                {{- end -}}
            {{end}}

            //Return secondary results
            return []interface{}{
                {{- range $index, $result := .Method.Results -}}
                    {{- if $index}}, {{end -}}
                    secondaryRetVal{{add $index 1 -}}
                {{- end -}}
            }, nil
        })
    }(newCtx, key, secondary)
}`
