/*
 * Copyright 2018-present Open Networking Foundation

 * 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 main

import (
{{if .HasFuncs}}
	"fmt"
	"time"
	"errors"
	"context"
	"encoding/json"
	//"golang.org/x/net/context"
	"google.golang.org/grpc/metadata"
	"github.com/opencord/voltha-go/common/log"
{{end}}
	{{range .Imports}}
	{{if .Used}}
	"{{.Package}}"
	{{end}}
	{{end}}
	{{range .ProtoImports}}
	{{if .Used}}
	{{.Short}} "{{.Package}}"
	{{end}}
	{{end}}
)


{{if .FileNum}}
{{else}}
var glCtx context.Context

func resetChannels() {
	// Drain all channels of data
	for _,v := range servers {
		done := false
		for {
			select {
			case _ = <-v.incmg:
			case _ = <-v.replyData:
			default:
				done = true
			}
			if done == true {
				break
			}
		}
	}
}

type testData struct {
	function func(int, string, string, []string,[]string,[]string,
				  map[string][]string,interface{}, interface{}) error
	testNum int
	testName string
	sendClient string
	srvrs []string
	sendMeta []string
	expectMeta []string // Send Meta
	srvMeta map[string][]string
	parm interface{}
	ret interface{}
}

func addTestSlot(stats * TestRun) {
	tsIdx := len(stats.TestSuites) - 1
	stats.TestSuites[tsIdx].TestCases =
			append(stats.TestSuites[tsIdx].TestCases, TestCase{Info:[]string{}})
}
{{end}}

{{if .FileNum}}
func runTests{{.FileNum}}() {
{{else}}
func runTests() {
{{end}}
	tsIdx := len(stats.TestSuites) - 1
	tests := []testData {
	{{$ofs := .Offset}}
	{{range $k,$v := .Tests}}
	testData {
		{{$v.Send.Method}}_test,
		{{$k}} + {{$ofs}},
		"{{$v.Name}}",
		"{{$v.Send.Client}}",
		[]string{ {{range $sk,$sv := $v.Srvr}} "{{$sv.Name}}",{{end}} },
		[]string{ {{range $mk,$mv := $v.Send.MetaData}}"{{$mv.Key}}","{{$mv.Val}}",{{end}} },
		[]string{ {{range $mk,$mv := $v.Send.ExpectMeta}}"{{$mv.Key}}","{{$mv.Val}}",{{end}} },
		map[string][]string {
		{{range $sk,$sv := $v.Srvr}}
			"{{$sv.Name}}":[]string {
			{{range $mk, $mv := $sv.Meta}}
				 "{{$mv.Key}}","{{$mv.Val}}",
			{{end}}
			},
		{{end}}
		},
		&{{$v.Send.ParamType}}{{$v.Send.Param}},
		&{{$v.Send.ExpectType}}{{$v.Send.Expect}},
	},
	{{end}}
	}

	for _,v := range tests {
		addTestSlot(stats)
		stats.TestSuites[tsIdx].TestCases[v.testNum].Title = v.testName
		if err := v.function(
			v.testNum,
			v.testName,
			v.sendClient,
			v.srvrs,
			v.sendMeta,
			v.expectMeta,
			v.srvMeta,
			v.parm,
			v.ret); err != nil {
			stats.TestSuites[tsIdx].TestCases[v.testNum].Result = false
		} else {
			stats.TestSuites[tsIdx].TestCases[v.testNum].Result = true
		}
	}
	{{if .FileNum}}
	{{else}}
	{{range $k,$v := .RunTestsCallList}}
	{{$v}}()
	{{end}}
	{{end}}
	return
	//resetChannels()
}

{{range $k,$v := .Funcs }}
{{if $v.CodeGenerated}}
{{else}}
func {{$k}}_test(testNum int, testName string, sendClient string, srvrs []string,
				 sendMeta []string, expectMeta []string, srvrMeta map[string][]string,
				 parm interface{}, ret interface{}) error {

	var rtrn error = nil
	var cancel context.CancelFunc
	var repl *reply

	log.Debug("Running Test %d",testNum)
	glCtx, cancel = context.WithTimeout(context.Background(), 900*time.Millisecond)
	defer cancel()

	cl := clients[sendClient]
	// Create the server's reply data structure
	switch r := ret.(type) {
	case *{{$v.ReturnType}}:
		repl = &reply{repl:r}
	default:
		log.Errorf("Invalid type in call to {{$k}}_test expecting {{$v.ReturnType}} got %T", ret)
	}
	// Send the reply data structure to each of the servers
	for _,v := range srvrs {
		if servers[v] == nil {
			err := errors.New(fmt.Sprintf("Server %s is nil", v))
			log.Error(err)
			return err
		}
		// Start a go routine to send the the reply data to the
		// server. The go routine blocks until the server picks
		// up the data or the timeout is exceeded.
		go func (ctx context.Context, srv string) {
			select {
			case servers[srv].replyData <- repl:
			case <-ctx.Done():
				rtrn := errors.New(fmt.Sprintf("Could not provide server %s with reply data",srv))
				log.Error(rtrn)
				stats.testLog("%s\n", rtrn.Error())
			}
		}(glCtx,v)
	}

	// Now call the RPC with the data provided
	if expct,err := json.Marshal(repl.repl); err != nil {
		log.Errorf("Marshaling the reply for test %s: %v",testName, err)
	} else {
		// Create the context for the call
		ctx := context.Background()
		for i:=0; i<len(sendMeta); i += 2 {
			ctx = metadata.AppendToOutgoingContext(ctx, sendMeta[i], sendMeta[i+1])
		}
		var md map[string]string = make(map[string]string)
		for i:=0; i<len(expectMeta); i+=2 {
			md[expectMeta[i]] = expectMeta[i+1]
		}
		expectMd := metadata.New(md)
		if err := cl.send("{{$k}}", ctx, parm, string(expct), expectMd); err != nil {
			log.Errorf("Test case %s failed!: %v", testName, err)
			rtrn = err
		}
	}

	// Now read the servers' information to validate it
	var s *serverCtl
	var payload string
	var i *incoming
	if pld, err := json.Marshal(parm); err != nil {
		log.Errorf("Marshaling paramter for test %s: %v", testName, err)
	} else {
		payload = string(pld)
	}
	for _,v := range srvrs {
		s = servers[v]
		// Oddly sometimes the data isn't in the channel yet when we come to read it.
		select {
		case i = <-s.incmg:
			if i.payload != payload {
				rtrn = errors.New(fmt.Sprintf("Mismatched payload expected '%s', got '%s'", payload, i.payload))
				log.Error(rtrn.Error())
				stats.testLog("%s\n", rtrn.Error())
			}
			for j:=0; j<len(srvrMeta[v]); j+=2 {
				if mv,ok := i.meta[srvrMeta[v][j]]; ok == true {
					if srvrMeta[v][j+1] != mv[0] {
						rtrn=errors.New(fmt.Sprintf("Mismatched metadata on server '%s' expected '%s', got '%s'", srvrMeta[v][j], srvrMeta[v][j+1], mv[0]))
						log.Error(rtrn.Error())
						stats.testLog("%s\n", rtrn.Error())
					}
				}
			}
		case <-glCtx.Done():
			rtrn = errors.New(fmt.Sprintf("Timeout: no response data available for server %s", testName))
			stats.testLog("%s\n", rtrn.Error())
			log.Error(rtrn)
		}
	}

	return rtrn
}
{{end}}
{{end}}
