// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 (
	"strings"

	surface "github.com/googleapis/gnostic/surface"
)

// ParameterList returns a string representation of a method's parameters
func ParameterList(parametersType *surface.Type) string {
	result := ""
	if parametersType != nil {
		for _, field := range parametersType.Fields {
			result += field.ParameterName + " " + field.NativeType + "," + "\n"
		}
	}
	return result
}

func (renderer *Renderer) RenderClient() ([]byte, error) {
	f := NewLineWriter()

	f.WriteLine("// GENERATED FILE: DO NOT EDIT!")
	f.WriteLine(``)
	f.WriteLine("package " + renderer.Package)

	// imports will be automatically added by goimports

	f.WriteLine(`// Client represents an API client.`)
	f.WriteLine(`type Client struct {`)
	f.WriteLine(`  service string`)
	f.WriteLine(`  APIKey string`)
	f.WriteLine(`  client *http.Client`)
	f.WriteLine(`}`)

	f.WriteLine(`// NewClient creates an API client.`)
	f.WriteLine(`func NewClient(service string, c *http.Client) *Client {`)
	f.WriteLine(`	client := &Client{}`)
	f.WriteLine(`	client.service = service`)
	f.WriteLine(`  if c != nil {`)
	f.WriteLine(`    client.client = c`)
	f.WriteLine(`  } else {`)
	f.WriteLine(`    client.client = http.DefaultClient`)
	f.WriteLine(`  }`)
	f.WriteLine(`	return client`)
	f.WriteLine(`}`)

	for _, method := range renderer.Model.Methods {
		parametersType := renderer.Model.TypeWithTypeName(method.ParametersTypeName)
		responsesType := renderer.Model.TypeWithTypeName(method.ResponsesTypeName)

		f.WriteLine(commentForText(method.Description))
		f.WriteLine(`func (client *Client) ` + method.ClientName + `(`)
		f.WriteLine(ParameterList(parametersType) + `) (`)
		if method.ResponsesTypeName == "" {
			f.WriteLine(`err error,`)
		} else {
			f.WriteLine(`response *` + method.ResponsesTypeName + `,`)
			f.WriteLine(`err error,`)
		}
		f.WriteLine(` ) {`)

		path := method.Path
		path = strings.Replace(path, "{+", "{", -1)
		f.WriteLine(`path := client.service + "` + path + `"`)

		if parametersType != nil {
			if parametersType.HasFieldWithPosition(surface.Position_PATH) {
				for _, field := range parametersType.Fields {
					if field.Position == surface.Position_PATH {
						f.WriteLine(`path = strings.Replace(path, "{` + field.Name + `}", fmt.Sprintf("%v", ` +
							field.ParameterName + `), 1)`)
					}
				}
			}
			if parametersType.HasFieldWithPosition(surface.Position_QUERY) {
				f.WriteLine(`v := url.Values{}`)
				for _, field := range parametersType.Fields {
					if field.Position == surface.Position_QUERY {
						if field.NativeType == "string" {
							f.WriteLine(`if (` + field.ParameterName + ` != "") {`)
							f.WriteLine(`  v.Set("` + field.Name + `", ` + field.ParameterName + `)`)
							f.WriteLine(`}`)
						}
					}
				}
				f.WriteLine(`if client.APIKey != "" {`)
				f.WriteLine(`  v.Set("key", client.APIKey)`)
				f.WriteLine(`}`)
				f.WriteLine(`if len(v) > 0 {`)
				f.WriteLine(`  path = path + "?" + v.Encode()`)
				f.WriteLine(`}`)
			}
		}

		if method.Method == "POST" {
			f.WriteLine(`body := new(bytes.Buffer)`)
			f.WriteLine(`json.NewEncoder(body).Encode(` + parametersType.FieldWithPosition(surface.Position_BODY).Name + `)`)
			f.WriteLine(`req, err := http.NewRequest("` + method.Method + `", path, body)`)
			f.WriteLine(`reqHeaders := make(http.Header)`)
			f.WriteLine(`reqHeaders.Set("Content-Type", "application/json")`)
			f.WriteLine(`req.Header = reqHeaders`)
		} else {
			f.WriteLine(`req, err := http.NewRequest("` + method.Method + `", path, nil)`)
		}
		f.WriteLine(`if err != nil {return}`)
		f.WriteLine(`resp, err := client.client.Do(req)`)
		f.WriteLine(`if err != nil {return}`)
		f.WriteLine(`defer resp.Body.Close()`)
		f.WriteLine(`if resp.StatusCode != 200 {`)

		if responsesType != nil {
			f.WriteLine(`	return nil, errors.New(resp.Status)`)
		} else {
			f.WriteLine(`	return errors.New(resp.Status)`)
		}
		f.WriteLine(`}`)

		if responsesType != nil {
			f.WriteLine(`response = &` + responsesType.Name + `{}`)

			f.WriteLine(`switch {`)
			// first handle everything that isn't "default"
			for _, responseField := range responsesType.Fields {
				if responseField.Name != "default" {
					f.WriteLine(`case resp.StatusCode == ` + responseField.Name + `:`)
					f.WriteLine(`  body, err := ioutil.ReadAll(resp.Body)`)
					f.WriteLine(`  if err != nil {return nil, err}`)
					f.WriteLine(`  result := &` + responseField.NativeType + `{}`)
					f.WriteLine(`  err = json.Unmarshal(body, result)`)
					f.WriteLine(`  if err != nil {return nil, err}`)
					f.WriteLine(`  response.` + responseField.FieldName + ` = result`)
				}
			}

			// then handle "default"
			hasDefault := false
			for _, responseField := range responsesType.Fields {
				if responseField.Name == "default" {
					hasDefault = true
					f.WriteLine(`default:`)
					f.WriteLine(`  defer resp.Body.Close()`)
					f.WriteLine(`  body, err := ioutil.ReadAll(resp.Body)`)
					f.WriteLine(`  if err != nil {return nil, err}`)
					f.WriteLine(`  result := &` + responseField.NativeType + `{}`)
					f.WriteLine(`  err = json.Unmarshal(body, result)`)
					f.WriteLine(`  if err != nil {return nil, err}`)
					f.WriteLine(`  response.` + responseField.FieldName + ` = result`)
				}
			}
			if !hasDefault {
				f.WriteLine(`default:`)
				f.WriteLine(`  break`)
			}
			f.WriteLine(`}`) // close switch statement
		}
		f.WriteLine("return")
		f.WriteLine("}")
	}

	return f.Bytes(), nil
}
