package main

import (
	"fmt"
	"strconv"
	"strings"

	"code.justin.tv/common/twirp/internal/contextkeys"
)

type twirp struct {
	// Map to record whether we've built each package
	pkgs map[string]*FileDescriptor
	gen  *Generator
}

var pkgs map[string]string

func init() {
	RegisterPlugin(new(twirp))
	pkgs = make(map[string]string)
}

func (t *twirp) Name() string { return "twirp" }

func (t *twirp) Init(gen *Generator) {
	t.pkgs = make(map[string]*FileDescriptor)
	t.gen = gen
	registerImportPackageNames()
}

// returns true if this is the first file handled by the generator for a package.
func (t *twirp) firstFileInPackage(file *FileDescriptor) bool {
	if file.Package == nil {
		return false
	}

	have, ok := t.pkgs[*file.Package]
	if !ok {
		t.pkgs[*file.Package] = file
		return true
	}

	return have == file
}

func (t *twirp) Generate(file *FileDescriptor) {
	if len(file.svc) == 0 {
		return
	}

	// For each service, generate client stubs and server
	for i, service := range file.svc {
		t.generateService(file, service, i)
	}

	// Util functions only generated once per package
	if t.firstFileInPackage(file) {
		t.generateUtils()
	}
}

// Make sure import are unique so they don't conflict with the service package name
func registerImportPackageNames() {
	pkgs["bytes"] = RegisterUniquePackageName("bytes", nil)
	pkgs["strings"] = RegisterUniquePackageName("strings", nil)
	pkgs["context"] = RegisterUniquePackageName("context", nil)
	pkgs["ctxhttp"] = RegisterUniquePackageName("ctxhttp", nil)
	pkgs["http"] = RegisterUniquePackageName("http", nil)
	pkgs["io"] = RegisterUniquePackageName("io", nil)
	pkgs["ioutil"] = RegisterUniquePackageName("ioutil", nil)
	pkgs["json"] = RegisterUniquePackageName("json", nil)
	pkgs["jsonpb"] = RegisterUniquePackageName("jsonpb", nil)
	pkgs["log"] = RegisterUniquePackageName("log", nil)
	pkgs["proto"] = RegisterUniquePackageName("proto", nil)
	pkgs["strconv"] = RegisterUniquePackageName("strconv", nil)
	pkgs["twirp"] = RegisterUniquePackageName("twirp", nil)
	pkgs["url"] = RegisterUniquePackageName("url", nil)
	pkgs["chitin"] = RegisterUniquePackageName("chitin", nil)
}

func (t *twirp) GenerateImports(file *FileDescriptor) {
	if len(file.svc) == 0 {
		return
	}
	t.P(`import `, pkgs["bytes"], ` "bytes"`)
	t.P(`import `, pkgs["strconv"], ` "strconv"`)
	t.P(`import `, pkgs["context"], ` "golang.org/x/net/context"`)
	t.P(`import `, pkgs["chitin"], ` "code.justin.tv/common/chitin"`)
	t.P(`import `, pkgs["ctxhttp"], ` "golang.org/x/net/context/ctxhttp"`)
	t.P(`import `, pkgs["http"], ` "net/http"`)
	t.P(`import `, pkgs["ioutil"], ` "io/ioutil"`)
	t.P(`import `, pkgs["jsonpb"], ` "github.com/golang/protobuf/jsonpb"`)
	t.P(`import `, pkgs["log"], ` "log"`)
	t.P(`import `, pkgs["proto"], ` "github.com/golang/protobuf/proto"`)
	t.P(`import `, pkgs["twirp"], ` "code.justin.tv/common/twirp"`)
	t.P()
	if t.firstFileInPackage(file) { // for utils
		t.P("// Imports only used by utility functions:")
		t.P(`import `, pkgs["io"], ` "io"`)
		t.P(`import `, pkgs["strings"], ` "strings"`)
		t.P(`import `, pkgs["json"], ` "encoding/json"`)
		t.P(`import `, pkgs["url"], ` "net/url"`)
	}
}

// Generate utility functions used in Twirp code.
// These should be generated just once per package.
func (t *twirp) generateUtils() {
	t.sectionComment(`Utils`)

	t.P(`// A ContextSource establishes the base context for a Server.`)
	t.P(`type ContextSource func(`, pkgs["http"], `.ResponseWriter, *`, pkgs["http"], `.Request) `, pkgs["context"], `.Context`)
	t.P()
	t.P(`var BackgroundContextSource ContextSource = func(`, pkgs["http"], `.ResponseWriter, *`, pkgs["http"], `.Request) `, pkgs["context"], `.Context {`)
	t.P(`	return `, pkgs["context"], `.Background()`)
	t.P(`}`)
	t.P()
	t.P(`// done returns ctx.Err() if ctx.Done() indicates that the context done`)
	t.P(`func done(ctx `, pkgs["context"], `.Context) error {`)
	t.P(`  select {`)
	t.P(`  case <-ctx.Done():`)
	t.P(`    return ctx.Err()`)
	t.P(`  default:`)
	t.P(`    return nil`)
	t.P(`  }`)
	t.P(`}`)
	t.P()

	t.P(`// urlBase helps ensure that addr specifies a scheme. If it is unparsable`)
	t.P(`// as a URL, it returns addr unchanged.`)
	t.P(`func urlBase(addr string) string {`)
	t.P(`  // If the addr specifies a scheme, use it. If not, default to`)
	t.P(`  // http. If url.Parse fails on it, return it unchanged.`)
	t.P(`  url, err := `, pkgs["url"], `.Parse(addr)`)
	t.P(`  if err != nil {`)
	t.P(`    return addr`)
	t.P(`  }`)
	t.P(`  if url.Scheme == "" {`)
	t.P(`    url.Scheme = "http"`)
	t.P(`  }`)
	t.P(`  return url.String()`)
	t.P(`}`)
	t.P()

	t.P(`// setMethodName, setServerName, and setStatusCode are functions to`)
	t.P(`// expose internal state through contexts. The context keys hardcoded`)
	t.P(`// in here are not to be used on their own. Twirp provides accessor`)
	t.P(`// functions which will retrieve these values out and give them the`)
	t.P(`// correct type for you.`)
	t.P(`func setMethodName(ctx `, pkgs["context"], `.Context, name string) `, pkgs["context"], `.Context {`)
	t.P(`  return `, pkgs["context"], `.WithValue(ctx, "`, contextkeys.MethodNameKey, `", name)`)
	t.P(`}`)
	t.P(`func setServerName(ctx `, pkgs["context"], `.Context, name string) `, pkgs["context"], `.Context {`)
	t.P(`  return `, pkgs["context"], `.WithValue(ctx, "`, contextkeys.ServiceNameKey, `", name)`)
	t.P(`}`)
	t.P(`func setStatusCode(ctx `, pkgs["context"], `.Context, code int) `, pkgs["context"], `.Context {`)
	t.P(`  return `, pkgs["context"], `.WithValue(ctx, "`, contextkeys.StatusCodeKey, `", `, pkgs["strconv"], `.Itoa(code))`)
	t.P(`}`)
	t.P()

	t.P(`type privateContextKey int`)
	t.P()
	t.P(`var versionKey = new(privateContextKey)`)
	t.P()

	t.P(`// setVersionFromPath adds "v1", "v2" or "v3" to the context depending on the path.`)
	t.P(`func setVersionFromPath(ctx `, pkgs["context"], `.Context, path string) `, pkgs["context"], `.Context {`)
	t.P(`  var version string`)
	t.P(`  if `, pkgs["strings"], `.HasPrefix(path, "/v1/") {`)
	t.P(`    version = "v1"`)
	t.P(`  } else if `, pkgs["strings"], `.HasPrefix(path, "/v2/") {`)
	t.P(`    version = "v2"`)
	t.P(`  } else {`)
	t.P(`    version = "v3"`)
	t.P(`  }`)
	t.P(`  return `, pkgs["context"], `.WithValue(ctx, versionKey, version)`)
	t.P(`}`)
	t.P()

	t.P(`// getVersion returns the version number ("v1", "v2", "v3") or empty string if unset.`)
	t.P(`func getVersion(ctx `, pkgs["context"], `.Context) string {`)
	t.P(`  v, _ := ctx.Value(versionKey).(string)`)
	t.P(`  return v`)
	t.P(`}`)
	t.P()

	t.P(`// getCustomHTTPReqHeaders retrieves a copy of any headers that are set in`)
	t.P(`// a context through the twirp.WithHTTPRequestHeaders function.`)
	t.P(`// If there are no headers set, or if they have the wrong type, nil is returned.`)
	t.P(`func getCustomHTTPReqHeaders(ctx `, pkgs["context"], `.Context) `, pkgs["http"], `.Header {`)
	t.P(`  header, ok := ctx.Value("`, contextkeys.RequestHeaderKey, `").(http.Header)`)
	t.P(`  if !ok || header == nil {`)
	t.P(`    return nil`)
	t.P(`  }`)
	t.P(`  copied := make(`, pkgs["http"], `.Header)`)
	t.P(`  for k, vv := range header {`)
	t.P(`    if vv == nil {`)
	t.P(`      copied[k] = nil`)
	t.P(`      continue`)
	t.P(`    }`)
	t.P(`    copied[k] = make([]string, len(vv))`)
	t.P(`    copy(copied[k], vv)`)
	t.P(`  }`)
	t.P(`  return copied`)
	t.P(`}`)
	t.P()

	t.P(`// closebody closes a response or request body and just logs `)
	t.P(`// any error encountered while closing, since errors are `)
	t.P(`// considered very unusual.`)
	t.P(`func closebody(body io.Closer) {`)
	t.P(`  if err := body.Close(); err != nil {`)
	t.P(`    log.Printf("error closing body: %q", err)`)
	t.P(`  }`)
	t.P(`}`)
	t.P()

	t.P(`// newRequest makes an http.Request from a client, adding common headers.`)
	t.P(`func newRequest(ctx `, pkgs["context"], `.Context, url string, reqBody io.Reader, contentType string) (*`, pkgs["http"], `.Request, error) {`)
	t.P(`  req, err := `, pkgs["http"], `.NewRequest("POST", url, reqBody)`)
	t.P(`  if err != nil {`)
	t.P(`    return nil, err`)
	t.P(`  }`)
	t.P(`  req = req.WithContext(ctx)`)
	t.P(`  if customHeader := getCustomHTTPReqHeaders(ctx); customHeader != nil {`)
	t.P(`    req.Header = customHeader`)
	t.P(`  }`)
	t.P(`  req.Header.Set("Content-Type", contentType)`)
	t.P(`  req.Header.Set("Twirp-Version", "`, twirpVersion, `")`)
	t.P(`  return req, nil`)
	t.P(`}`)

	t.P(`// JSON serialization for errors`)
	t.P(`type twerrJSON struct {`)
	t.P("  Code string            `json:\"code\"`")
	t.P("  Msg  string            `json:\"msg\"`")
	t.P("  Meta map[string]string `json:\"meta,omitempty\"`")
	t.P(`}`)
	t.P()
	t.P(`// marshalErrorToJSON returns JSON from a twirp.Error, that can be used as HTTP error response body.`)
	t.P(`// If serialization fails, it will use a descriptive Internal error instead.`)
	t.P(`func marshalErrorToJSON(twerr `, pkgs["twirp"], `.Error) []byte {`)
	t.P(`  // make sure that msg is not too large`)
	t.P(`  msg := twerr.Msg()`)
	t.P(`  if len(msg) > 1e6 {`)
	t.P(`    msg = msg[:1e6]`)
	t.P(`  }`)
	t.P(``)
	t.P(`  tj := twerrJSON{`)
	t.P(`    Code: string(twerr.Code()),`)
	t.P(`    Msg:  msg,`)
	t.P(`    Meta: twerr.MetaMap(),`)
	t.P(`  }`)
	t.P(``)
	t.P(`  buf, err := `, pkgs["json"], `.Marshal(&tj)`)
	t.P(`  if err != nil {`)
	t.P(`    buf = []byte("{\"type\": \"" + `, pkgs["twirp"], `.Internal +"\", \"msg\": \"There was an error but it could not be serialized into JSON\"}") // fallback`)
	t.P(`  }`)
	t.P(``)
	t.P(`  return buf`)
	t.P(`}`)
	t.P()
	t.P(`// unmarshalErrorFromJSONResponse returns a twirp.Error from JSON data.`)
	t.P(`// If de-serialization fails, it returns a descriptive Internal error instead`)
	t.P(`func unmarshalErrorFromJSONResponse(body io.Reader) `, pkgs["twirp"], `.Error {`)
	t.P(`  data, err := `, pkgs["ioutil"], `.ReadAll(body)`)
	t.P(`  if err != nil {`)
	t.P(`    msg := "failed to read server error response body: "+err.Error()`)
	t.P(`    return ` + pkgs["twirp"] + `.InternalError(msg)`)
	t.P(`  }`)
	t.P(`  var tj twerrJSON`)
	t.P(`  err = `, pkgs["json"], `.Unmarshal(data, &tj)`)
	t.P(`  if err != nil {`)
	t.P(`    msg := "failed to unmarshal server error response: "+err.Error()`)
	t.P(`    return ` + pkgs["twirp"] + `.InternalError(msg)`)
	t.P(`  }`)
	t.P(`  code := ` + pkgs["twirp"] + `.ErrorCode(tj.Code)`)
	t.P(`  if !` + pkgs["twirp"] + `.IsValidErrorCode(code) {`)
	t.P(`    msg := "invalid type returned from server error response: "+tj.Code`)
	t.P(`    return ` + pkgs["twirp"] + `.InternalError(msg)`)
	t.P(`  }`)
	t.P(``)
	t.P(`  twerr := ` + pkgs["twirp"] + `.NewError(code, tj.Msg)`)
	t.P(`  for k, v := range(tj.Meta) {`)
	t.P(`    twerr = twerr.WithMeta(k, v)`)
	t.P(`  }`)
	t.P(`  return twerr`)
	t.P(`}`)
	t.P()
	t.P(`// clientError adds consistency to errors generated in the client`)
	t.P(`func clientError(desc string, err error) `, pkgs["twirp"], `.Error {`)
	t.P(`    msg := desc + ": " + err.Error()`)
	t.P(`    return ` + pkgs["twirp"] + `.InternalError(msg)`)
	t.P(`}`)
}

// P forwards to g.gen.P, which prints output.
func (t *twirp) P(args ...string) { t.gen.P(args...) }

// Big header comments to makes it easier to visually parse a generated file.
func (t *twirp) sectionComment(sectionTitle string) {
	t.P()
	t.P(`// `, strings.Repeat("=", len(sectionTitle)))
	t.P(`// `, sectionTitle)
	t.P(`// `, strings.Repeat("=", len(sectionTitle)))
	t.P()
}

func (t *twirp) generateService(file *FileDescriptor, service *ServiceDescriptor, index int) {
	servName := serviceName(service)

	// Service interface
	t.sectionComment(servName + ` Interface`)
	t.generateTwirpInterface(service)
	t.P()

	// Clients
	t.sectionComment(servName + ` Clients`)
	t.generateProtobufClient(file, service)
	t.generateChitinClient(file, service)
	t.generateJSONClient(file, service)

	// Server
	t.sectionComment(servName + ` Server Handler`)
	t.generateServer(file, service)
}

func (t *twirp) generateTwirpInterface(service *ServiceDescriptor) {
	servName := serviceName(service)

	t.gen.PrintComments(service.path)
	t.P(`type `, servName, ` interface {`)
	for _, method := range service.methods {
		t.gen.PrintComments(method.path)
		t.P(t.generateSignature(method))
		t.P()
	}
	t.P(`}`)
}

func (t *twirp) generateSignature(method *MethodDescriptor) string {
	methName := methodName(method)
	inputType := t.typeName(method.GetInputType())
	outputType := t.typeName(method.GetOutputType())
	return fmt.Sprintf(`%s(%s.Context, *%s) (*%s, error)`, methName, pkgs["context"], inputType, outputType)
}

func (t *twirp) generateJSONClient(file *FileDescriptor, service *ServiceDescriptor) {
	servName := serviceName(service)
	pathPrefixConst := servName + "PathPrefix"
	structName := unexported(servName) + "JSONClient"
	newJSONClientFunc := "New" + servName + "JSONClient"

	t.P(`type `, structName, ` struct {`)
	t.P(`  urlBase string`)
	t.P(`  client *`, pkgs["http"], `.Client`)
	t.P(`}`)
	t.P()
	t.P(`// `, newJSONClientFunc, ` creates a JSON client that implements the `, servName, ` interface.`)
	t.P(`// It communicates using JSON requests and responses instead of protobuf messages.`)
	t.P(`func `, newJSONClientFunc, `(addr string, client *`, pkgs["http"], `.Client) `, servName, ` {`)
	t.P(`  return &`, structName, `{`)
	t.P(`    urlBase: urlBase(addr),`)
	t.P(`    client:  client,`)
	t.P(`  }`)
	t.P(`}`)
	t.P()
	for _, method := range service.methods {
		methName := methodName(method)
		inputType := t.typeName(method.GetInputType())
		outputType := t.typeName(method.GetOutputType())

		t.P(`func (c *`, structName, `) `, methName, `(ctx `, pkgs["context"], `.Context, in *`, inputType, `) (*`, outputType, `, error) {`)
		t.P(`  var err error`)
		t.P(`  body := `, pkgs["bytes"], `.NewBuffer(nil)`)
		t.P(`  marshaler := &`, pkgs["jsonpb"], `.Marshaler{OrigName: true}`)
		t.P(`  if err = marshaler.Marshal(body, in); err != nil {`)
		t.P(`    return nil, clientError("failed to marshal json request", err)`)
		t.P(`  }`)
		t.P(`  if err = done(ctx); err != nil {`)
		t.P(`    return nil, clientError("aborted because context was done", err)`)
		t.P(`  }`)
		t.P()
		t.P(`  url := c.urlBase + `, pathPrefixConst, ` + "`, methName, `"`)
		t.P(`  req, err := newRequest(ctx, url, body, "application/json")`)
		t.P(`  if err != nil {`)
		t.P(`    return nil, clientError("could not build request", err)`)
		t.P(`  }`)
		t.P()
		t.P(`  resp, err := `, pkgs["ctxhttp"], `.Do(ctx, c.client, req)`)
		t.P(`  if err != nil {`)
		t.P(`    return nil, clientError("failed to do request", err)`)
		t.P(`  }`)
		t.P(`  defer closebody(resp.Body)`)
		t.P()
		t.P(`  if resp.StatusCode != 200 {`)
		t.P(`    return nil, unmarshalErrorFromJSONResponse(resp.Body)`)
		t.P(`  }`)
		t.P()
		t.P(`  if err = done(ctx); err != nil {`)
		t.P(`    return nil, clientError("aborted because context was done", err)`)
		t.P(`  }`)
		t.P()
		t.P(`  respContent := new(`, outputType, `)`)
		t.P(`  if err = `, pkgs["jsonpb"], `.Unmarshal(resp.Body, respContent); err != nil {`)
		t.P(`    return nil, clientError("failed to unmarshal json response", err)`)
		t.P(`  }`)
		t.P()
		t.P(`  return respContent, nil`)
		t.P(`}`)
		t.P()
	}
}

func (t *twirp) generateProtobufClient(file *FileDescriptor, service *ServiceDescriptor) {
	servName := serviceName(service)
	pathPrefixConst := servName + "PathPrefix"
	structName := unexported(servName) + "ProtobufClient"
	newProtobufClientFunc := "New" + servName + "ProtobufClient"

	t.P(`type `, structName, ` struct {`)
	t.P(`  urlBase string`)
	t.P(`  client *`, pkgs["http"], `.Client`)
	t.P(`}`)
	t.P()
	t.P(`// `, newProtobufClientFunc, ` creates a Protobuf client that implements the `, servName, ` interface.`)
	t.P(`// It communicates using protobuf messages and can be configured with a custom http.Client.`)
	t.P(`func `, newProtobufClientFunc, `(addr string, client *`, pkgs["http"], `.Client) `, servName, ` {`)
	t.P(`  return &`, structName, `{`)
	t.P(`    urlBase: urlBase(addr),`)
	t.P(`    client:  client,`)
	t.P(`  }`)
	t.P(`}`)
	t.P()
	for _, method := range service.methods {
		methName := methodName(method)
		inputType := t.typeName(method.GetInputType())
		outputType := t.typeName(method.GetOutputType())

		t.P(`func (c *`, structName, `) `, methName, `(ctx `, pkgs["context"], `.Context, in *`, inputType, `) (*`, outputType, `, error) {`)
		t.P(`  var err error`)
		t.P(`  bodyBytes, err := `, pkgs["proto"], `.Marshal(in)`)
		t.P(`  if err != nil {`)
		t.P(`    return nil, clientError("failed to marshal proto request", err)`)
		t.P(`  }`)
		t.P(`  body := `, pkgs["bytes"], `.NewBuffer(bodyBytes)`)
		t.P(`  if err = done(ctx); err != nil {`)
		t.P(`    return nil, clientError("aborted because context was done", err)`)
		t.P(`  }`)
		t.P()
		t.P(`  url := c.urlBase + `, pathPrefixConst, ` + "`, methName, `"`)
		t.P(`  req, err := newRequest(ctx, url, body, "application/protobuf")`)
		t.P(`  if err != nil {`)
		t.P(`    return nil, clientError("could not build request", err)`)
		t.P(`  }`)
		t.P()
		t.P(`  resp, err := `, pkgs["ctxhttp"], `.Do(ctx, c.client, req)`)
		t.P(`  if err != nil {`)
		t.P(`    return nil, clientError("failed to do request", err)`)
		t.P(`  }`)
		t.P(`  defer closebody(resp.Body)`)
		t.P()
		t.P(`  if resp.StatusCode != 200 {`)
		t.P(`    return nil, unmarshalErrorFromJSONResponse(resp.Body)`)
		t.P(`  }`)
		t.P()
		t.P(`  respBody, err := `, pkgs["ioutil"], `.ReadAll(resp.Body)`)
		t.P(`  if err != nil {`)
		t.P(`    return nil, clientError("failed to read response body", err)`)
		t.P(`  }`)
		t.P()
		t.P(`  if err = done(ctx); err != nil {`)
		t.P(`    return nil, clientError("failed to read response because context was done", err)`)
		t.P(`  }`)
		t.P()
		t.P(`  respContent := new(`, outputType, `)`)
		t.P(`  if err = `, pkgs["proto"], `.Unmarshal(respBody, respContent); err != nil {`)
		t.P(`    return nil, clientError("failed to unmarshal proto response", err)`)
		t.P(`  }`)
		t.P()
		t.P(`  return respContent, nil`)
		t.P(`}`)
		t.P()
	}
}

func (t *twirp) generateChitinClient(file *FileDescriptor, service *ServiceDescriptor) {
	servName := serviceName(service)
	newChitinClientFunc := "New" + servName + "Client"
	newProtobufClientFunc := "New" + servName + "ProtobufClient"

	t.P(`// `, newChitinClientFunc, ` creates a Protobuf client that implements the `, servName, ` interface.`)
	t.P(`// It is the same as `, newProtobufClientFunc, ` but pre-configured with a Chitin RoundTripper.`)
	t.P(`func `, newChitinClientFunc, `(addr string) `, servName, ` {`)
	t.P(`  chitinRT, err := `, pkgs["chitin"], `.RoundTripper(context.Background(), `, pkgs["http"], `.DefaultTransport)`)
	t.P(`  if err != nil {`)
	t.P(`    panic("You are using a very old version of chitin, please upgrade!")`)
	t.P(`  }`)
	t.P(`  chitinClient := `, pkgs["http"], `.Client{Transport: chitinRT}`)
	t.P(`  return `, newProtobufClientFunc, `(addr, &chitinClient)`)
	t.P(`}`)
}

func (t *twirp) generateServer(file *FileDescriptor, service *ServiceDescriptor) {
	servName := serviceName(service)

	// Server implementation.
	servStruct := serviceStruct(service)
	t.P(`type `, servStruct, ` struct {`)
	t.P(`  `, servName)
	t.P(`  hooks     *`, pkgs["twirp"], `.ServerHooks`)
	t.P(`  ctxSource ContextSource`)
	t.P(`}`)
	t.P()

	// Constructor for server implementation
	t.P(`func New`, servName, `Server(svc `, servName, `, hooks *`, pkgs["twirp"], `.ServerHooks, ctxSrc ContextSource) `, pkgs["http"], `.Handler {`)
	t.P(`  if hooks == nil {`)
	t.P(`    hooks = `, pkgs["twirp"], `.NewServerHooks()`)
	t.P(`  }`)
	t.P(`  if ctxSrc == nil {`)
	t.P(`    ctxSrc = BackgroundContextSource`)
	t.P(`  }`)
	t.P(`  return &`, servStruct, `{svc, hooks, ctxSrc}`)
	t.P(`}`)
	t.P()

	// Routing
	t.generateServerRouting(servStruct, file, service)

	// Serve Errors
	t.P(`// Make HTTP response from an error.`)
	t.P(`// If err is not a twirp.Error, it will get wrapped with twirp.InternalErrorWith(err)`)
	t.P(`func (s *`, servStruct, `) serveError(ctx `, pkgs["context"], `.Context, resp `, pkgs["http"], `.ResponseWriter, req *`, pkgs["http"], `.Request, err error) {`)
	t.P(`  // When handling v2 client requests, serve backwards-compatible error responses`)
	t.P(`  if version := getVersion(ctx); (version == "v1" || version == "v2") {`)
	t.P(`    s.serveErrorV1V2(ctx, resp, req, err)`)
	t.P(`  } else {`)
	t.P(`    s.serveErrorV3Plus(ctx, resp, req, err)`)
	t.P(`  }`)
	t.P(`}`)
	t.P()
	t.P(`// v3+ error responses.`)
	t.P(`func (s *`, servStruct, `) serveErrorV3Plus(ctx `, pkgs["context"], `.Context, resp `, pkgs["http"], `.ResponseWriter, req *`, pkgs["http"], `.Request, err error) {`)
	t.P(`  // Non-twirp errors are wrapped as Internal (default)`)
	t.P(`  twerr, ok := err.(`, pkgs["twirp"], `.Error)`)
	t.P(`  if !ok {`)
	t.P(`    twerr = `, pkgs["twirp"], `.InternalErrorWith(err)`)
	t.P(`  }`)
	t.P(``)
	t.P(`  statusCode := `, pkgs["twirp"], `.ServerHTTPStatusFromErrorCode(twerr.Code())`)
	t.P(`  ctx = setStatusCode(ctx, statusCode)`)
	t.P(`  ctx = s.hooks.Error(ctx, twerr)`)
	t.P(``)
	t.P(`  resp.Header().Set("Content-Type", "application/json") // Error responses are always JSON (instead of protobuf)`)
	t.P(`  resp.WriteHeader(statusCode) // HTTP response status code`)
	t.P(``)
	t.P(`  respBody := marshalErrorToJSON(twerr)`)
	t.P(`  _, err2 := resp.Write(respBody)`)
	t.P(`  if err2 != nil {`)
	t.P(`    log.Printf("unable to send error message %q: %s", twerr, err2)`)
	t.P(`  }`)
	t.P(`  ctx = s.hooks.ResponseSent(ctx)`)
	t.P(`}`)
	t.P()

	t.P(`// v1/v2 backwards-compatible error responses.`)
	t.P(`// To make it easy to upgrade to v3, a v3 server keeps serving v2-style errors to v2 clients.`)
	t.P(`func (s *`, servStruct, `) serveErrorV1V2(ctx `, pkgs["context"], `.Context, resp `, pkgs["http"], `.ResponseWriter, req *`, pkgs["http"], `.Request, err error) {`)
	t.P(`  // Non-twirp errors are wrapped as Unknown 500 (default)`)
	t.P(`  twerr, ok := err.(`, pkgs["twirp"], `.Error)`)
	t.P(`  if !ok {`)
	t.P(`    twerr = `, pkgs["twirp"], `.NewError(`, pkgs["twirp"], `.Unknown, err.Error())`)
	t.P(`  }`)
	t.P(``)
	t.P(`  resp.Header().Set("Content-Type", "text/plain")`)
	t.P(``)
	t.P(`  // v2err.Retryable <= v3err.Meta("retryable")`)
	t.P(`  if twerr.Meta("retryable") != "" {`)
	t.P(`    resp.Header().Set("Twirp-Error-Retryable", "true")`)
	t.P(`  }`)
	t.P(``)
	t.P(`  // v2err.ErrorID <= v3err.Meta("v2_error_id") if present, otherwise v3err.Code()`)
	t.P(`  // or "unroutable" if generated from twirp router (NotFound with Meta("twirp_invalid_route"))`)
	t.P(`  var v2ErrorID string`)
	t.P(`  if twerr.Code() == `, pkgs["twirp"], `.NotFound && twerr.Meta("twirp_invalid_route") != "" {`)
	t.P(`    v2ErrorID = "unroutable" // as was generated by previous router`)
	t.P(`  } else if twerr.Meta("v2_error_id") != "" {`)
	t.P(`    v2ErrorID = twerr.Meta("v2_error_id")`)
	t.P(`  } else {`)
	t.P(`    v2ErrorID = string(twerr.Code()) // the string-format of v3 codes match v2 error ids for the most part: i.e. "not_found", "internal", "unknown", "canceled", etc.`)
	t.P(`  }`)
	t.P(`  resp.Header().Set("Twirp-ErrorID", v2ErrorID)`)
	t.P(``)
	t.P(`  // v2err.StatusCode <= v3err.Meta("v2_status_code") if present, otherwise same as in v3  `)
	t.P(`  statusCode, err := `, pkgs["strconv"], `.Atoi(twerr.Meta("v2_status_code"))`)
	t.P(`  if err != nil {`)
	t.P(`    statusCode = `, pkgs["twirp"], `.ServerHTTPStatusFromErrorCode(twerr.Code()) // v3 it's the same as v2 for the most part: i.e. "not_found" is 404`)
	t.P(`  }`)
	t.P(`  ctx = setStatusCode(ctx, statusCode)`)
	t.P(`  ctx = s.hooks.Error(ctx, twerr)`)
	t.P(`  resp.WriteHeader(statusCode)`)
	t.P(``)
	t.P(`  msg := []byte(twerr.Msg())`)
	t.P(`  if len(msg) > 1e6 {`)
	t.P(`    msg = msg[:1e6]`)
	t.P(`  }`)
	t.P(`  _, err2 := resp.Write(msg)`)
	t.P(`  if err2 != nil {`)
	t.P(`    log.Printf("unable to send error message %q: %s", twerr, err2)`)
	t.P(`  }`)
	t.P(`  ctx = s.hooks.ResponseSent(ctx)`)
	t.P(`}`)
	t.P()

	// Methods.
	for _, method := range service.methods {
		t.generateServerMethod(service, method)
	}
}

// pathPrefix returns the base path for all methods handled by a particular service.
// It includes a trailing slash. (for example "/v2/code.justin.tv.common.twirp.example.Haberdasher/").
func (t *twirp) pathPrefix(version string, file *FileDescriptor, service *ServiceDescriptor) string {
	switch version {
	case "v1":
		// Initially twirp paths included only the service name:
		// i.e. /v1/Haberdasher/
		return fmt.Sprintf("/v1/%s/", service.GetName())
	case "v2":
		// v2 included the full package name as a prefix for the service:
		// i.e. /v2/code.justin.tv.common.twirp.example.Haberdasher/
		return fmt.Sprintf("/v2/%s/", fullServiceName(file, service))
	case "v3", "twirp":
		// v3 changed the version for "twirp", which makes it easier to identify twirp servers
		// i.e. /twirp/code.justin.tv.common.twirp.example.Haberdasher/
		return fmt.Sprintf("/twirp/%s/", fullServiceName(file, service))
	default:
		panic(fmt.Sprintf("unknown version supplied: %s", version))
	}

}

// pathFor returns the complete path for requests to a particular method on a particular service.
// version must be "v1", "v2" or "twirp"
func (t *twirp) pathFor(version string, file *FileDescriptor, service *ServiceDescriptor, method *MethodDescriptor) string {
	return t.pathPrefix(version, file, service) + method.GetName()
}

func (t *twirp) generateServerRouting(servStruct string, file *FileDescriptor, service *ServiceDescriptor) {
	servName := serviceName(service)

	pathPrefixConst := servName + "PathPrefix"
	t.P(`// `, pathPrefixConst, ` is used for all URL paths on a twirp `, servName, ` server.`)
	t.P(`// Requests are always: POST `, pathPrefixConst, `/method`)
	t.P(`// It can be used in an HTTP mux to route twirp requests along with non-twirp requests on other routes.`)
	t.P(`const `, pathPrefixConst, ` = `, strconv.Quote(t.pathPrefix("twirp", file, service)))
	t.P()
	pathPrefixConstOld := servName + "PathPrefixOld"
	t.P(`// `, pathPrefixConstOld, ` is used to handle requests from v2 Clients.`)
	t.P(`// If you are using an HTTP mux, please handle this routes as well to upgrade from v2 to v3.`)
	t.P(`const `, pathPrefixConstOld, ` = `, strconv.Quote(t.pathPrefix("v2", file, service)))
	t.P()

	t.P(`func (s *`, servStruct, `) ServeHTTP(resp `, pkgs["http"], `.ResponseWriter, req *`, pkgs["http"], `.Request) {`)
	t.P(`  ctx := s.ctxSource(resp, req)`)
	t.P(`  ctx = setServerName(ctx, "`, servName, `")`)
	t.P(`  ctx = setVersionFromPath(ctx, req.URL.Path)`)
	t.P(`  ctx = s.hooks.RequestReceived(ctx)`)
	t.P()
	t.P(`  if req.Method != "POST" {`)
	t.P(`    twerr := `, pkgs["twirp"], `.NotFoundError(fmt.Sprintf("unsupported method %q (only POST is allowed)", req.Method))`)
	t.P(`    twerr = twerr.WithMeta("twirp_invalid_route", req.Method+" "+req.URL.Path)`)
	t.P(`    s.serveError(ctx, resp, req, twerr)`)
	t.P(`    return`)
	t.P(`  }`)
	t.P()
	t.P(`  switch req.URL.Path {`)
	for _, method := range service.methods {
		rpcName := CamelCase(method.GetName())
		methName := "serve" + rpcName
		v3Path := pathPrefixConst + `+"` + rpcName + `"`
		v2Path := pathPrefixConstOld + `+"` + rpcName + `"`
		v1Path := t.pathFor("v1", file, service, method)
		t.P(`  case `, v3Path, `, `, v2Path, `, "`, v1Path, `":`)
		t.P(`    s.`, methName, `(ctx, resp, req)`)
		t.P(`    return`)
	}
	t.P(`  default:`)
	t.P(`    twerr := `, pkgs["twirp"], `.NotFoundError(fmt.Sprintf("no handler for path %q", req.URL.Path))`)
	t.P(`    twerr = twerr.WithMeta("twirp_invalid_route", req.Method+" "+req.URL.Path)`)
	t.P(`    s.serveError(ctx, resp, req, twerr)`)
	t.P(`    return`)
	t.P(`  }`)
	t.P(`}`)
	t.P()
}

func (t *twirp) generateServerMethod(service *ServiceDescriptor, method *MethodDescriptor) {
	methName := CamelCase(method.GetName())
	servStruct := serviceStruct(service)
	t.P(`func (s *`, servStruct, `) serve`, methName, `(ctx `, pkgs["context"], `.Context, resp `, pkgs["http"], `.ResponseWriter, req *`, pkgs["http"], `.Request) {`)
	t.P(`  switch req.Header.Get("Content-Type") {`)
	t.P(`  case "application/json":`)
	t.P(`    s.serve`, methName, `JSON(ctx, resp, req)`)
	t.P(`  case "application/protobuf":`)
	t.P(`    s.serve`, methName, `Protobuf(ctx, resp, req)`)
	t.P(`  default:`)
	t.P(`    twerr := `, pkgs["twirp"], `.NotFoundError(fmt.Sprintf("unexpected Content-Type: %q", req.Header.Get("Content-Type")))`)
	t.P(`    twerr = twerr.WithMeta("twirp_invalid_route", req.Method+" "+req.URL.Path+" - Content-Type:"+req.Header.Get("Content-Type"))`)
	t.P(`    s.serveError(ctx, resp, req, twerr)`)
	t.P(`  }`)
	t.P(`}`)
	t.P()
	t.generateServerJSONMethod(service, method)
	t.generateServerProtobufMethod(service, method)
}

func (t *twirp) generateServerJSONMethod(service *ServiceDescriptor, method *MethodDescriptor) {
	servStruct := serviceStruct(service)
	methName := CamelCase(method.GetName())
	t.P(`func (s *`, servStruct, `) serve`, methName, `JSON(ctx `, pkgs["context"], `.Context, resp `, pkgs["http"], `.ResponseWriter, req *`, pkgs["http"], `.Request) {`)
	t.P(`  var err error`)
	t.P(`  ctx = setMethodName(ctx, "`, methName, `")`)
	t.P(`  ctx = s.hooks.RequestRouted(ctx)`)
	t.P()
	t.P(`  defer closebody(req.Body)`)
	t.P(`  reqContent := new(`, t.typeName(method.GetInputType()), `)`)
	t.P(`  if err = `, pkgs["jsonpb"], `.Unmarshal(req.Body, reqContent); err != nil {`)
	t.P(`    msg := "failed to parse request json: "+err.Error()`)
	t.P(`    s.serveError(ctx, resp, req, `, pkgs["twirp"], `.InternalError(msg))`)
	t.P(`    return`)
	t.P(`  }`)
	t.P()
	t.P(`  // Call service method`)
	t.P(`  var respContent *`, t.typeName(method.GetOutputType()))
	t.P(`  func() {`)
	t.P(`    defer func() {`)
	t.P(`      // In case of a panic, serve a 500 error and then panic.`)
	t.P(`      if r := recover(); r != nil {`)
	t.P(`        s.serveError(ctx, resp, req, `, pkgs["twirp"], `.InternalError("Internal service panic"))`)
	t.P(`        panic(r)`)
	t.P(`      }`)
	t.P(`    }()`)
	t.P(`    respContent, err = s.`, methName, `(ctx, reqContent)`)
	t.P(`  }()`)
	t.P()
	t.P(`  if err != nil {`)
	t.P(`    s.serveError(ctx, resp, req, err)`)
	t.P(`    return`)
	t.P(`  }`)
	t.P(`  if respContent == nil {`)
	t.P(`    s.serveError(ctx, resp, req, `, pkgs["twirp"], `.InternalError("received a nil *`, t.typeName(method.GetOutputType()), ` and nil error while calling `, methName, `. nil responses are not supported"))`)
	t.P(`    return`)
	t.P(`  }`)
	t.P()
	t.P(`  ctx = s.hooks.ResponsePrepared(ctx)`)
	t.P()
	t.P(`  var buf `, pkgs["bytes"], `.Buffer`)
	t.P(`  marshaler := &`, pkgs["jsonpb"], `.Marshaler{OrigName: true}`)
	t.P(`  if err = marshaler.Marshal(&buf, respContent); err != nil {`)
	t.P(`    s.serveError(ctx, resp, req, `, pkgs["twirp"], `.InternalError("failed to marshal json response"))`)
	t.P(`    return`)
	t.P(`  }`)
	t.P()
	t.P(`  ctx = setStatusCode(ctx, `, pkgs["http"], `.StatusOK)`)
	t.P(`  resp.Header().Set("Content-Type", "application/json")`)
	t.P(`  resp.WriteHeader(`, pkgs["http"], `.StatusOK)`)
	t.P(`  if _, err = resp.Write(buf.Bytes()); err != nil {`)
	t.P(`    log.Printf("errored while writing response to client, but already sent response status code to 200: %s", err)`)
	t.P(`  }`)
	t.P(`  ctx = s.hooks.ResponseSent(ctx)`)
	t.P(`}`)
	t.P()
}

func (t *twirp) generateServerProtobufMethod(service *ServiceDescriptor, method *MethodDescriptor) {
	servStruct := serviceStruct(service)
	methName := CamelCase(method.GetName())

	t.P(`func (s *`, servStruct, `) serve`, methName, `Protobuf(ctx `, pkgs["context"], `.Context, resp `, pkgs["http"], `.ResponseWriter, req *`, pkgs["http"], `.Request) {`)
	t.P(`  var err error`)
	t.P(`  ctx = setMethodName(ctx, "`, methName, `")`)
	t.P(`  ctx = s.hooks.RequestRouted(ctx)`)
	t.P()
	t.P(`  defer closebody(req.Body)`)
	t.P(`  buf, err := `, pkgs["ioutil"], `.ReadAll(req.Body)`)
	t.P(`  if err != nil {`)
	t.P(`    msg := "unable to read request body: "+err.Error()`)
	t.P(`    s.serveError(ctx, resp, req, `, pkgs["twirp"], `.InternalError(msg))`)
	t.P(`    return`)
	t.P(`  }`)
	t.P(`  reqContent := new(`, t.typeName(method.GetInputType()), `)`)
	t.P(`  if err = `, pkgs["proto"], `.Unmarshal(buf, reqContent); err != nil {`)
	t.P(`    msg := "failed to parse request proto: "+err.Error()`)
	t.P(`    s.serveError(ctx, resp, req, `, pkgs["twirp"], `.InternalError(msg))`)
	t.P(`    return`)
	t.P(`  }`)
	t.P()
	t.P(`  // Call service method`)
	t.P(`  var respContent *`, t.typeName(method.GetOutputType()))
	t.P(`  func() {`)
	t.P(`    defer func() {`)
	t.P(`      // In case of a panic, serve a 500 error and then panic.`)
	t.P(`      if r := recover(); r != nil {`)
	t.P(`        s.serveError(ctx, resp, req, `, pkgs["twirp"], `.InternalError("Internal service panic"))`)
	t.P(`        panic(r)`)
	t.P(`      }`)
	t.P(`    }()`)
	t.P(`    respContent, err = s.`, methName, `(ctx, reqContent)`)
	t.P(`  }()`)
	t.P()
	t.P(`  if err != nil {`)
	t.P(`    s.serveError(ctx, resp, req, err)`)
	t.P(`    return`)
	t.P(`  }`)
	t.P(`  if respContent == nil {`)
	t.P(`    s.serveError(ctx, resp, req, `, pkgs["twirp"], `.InternalError("received a nil *`, t.typeName(method.GetOutputType()), ` and nil error while calling `, methName, `. nil responses are not supported"))`)
	t.P(`    return`)
	t.P(`  }`)
	t.P()
	t.P(`  ctx = s.hooks.ResponsePrepared(ctx)`)
	t.P()
	t.P(`  respBytes, err := `, pkgs["proto"], `.Marshal(respContent)`)
	t.P(`  if err != nil {`)
	t.P(`    s.serveError(ctx, resp, req, `, pkgs["twirp"], `.InternalError("failed to marshal proto response"))`)
	t.P(`    return`)
	t.P(`  }`)
	t.P()
	t.P(`  ctx = setStatusCode(ctx, `, pkgs["http"], `.StatusOK)`)
	t.P(`  resp.Header().Set("Content-Type", "application/protobuf")`)
	t.P(`  resp.WriteHeader(`, pkgs["http"], `.StatusOK)`)
	t.P(`  if _, err = resp.Write(respBytes); err != nil {`)
	t.P(`    log.Printf("errored while writing response to client, but already sent response status code to 200: %s", err)`)
	t.P(`  }`)
	t.P(`  ctx = s.hooks.ResponseSent(ctx)`)
	t.P(`}`)
	t.P()
}

// Given a type name defined in a .proto, return its name as we will print it.
func (t *twirp) typeName(str string) string {
	return t.gen.TypeName(t.objectNamed(str))
}

// Given a type name defined in a .proto, return its object.
// Also record that we're using it, to guarantee the associated import.
func (t *twirp) objectNamed(name string) Object {
	t.gen.RecordTypeUse(name)
	return t.gen.ObjectNamed(name)
}

func unexported(s string) string { return strings.ToLower(s[:1]) + s[1:] }

func fullServiceName(file *FileDescriptor, service *ServiceDescriptor) string {
	name := CamelCase(service.GetName())
	if pkg := file.GetPackage(); pkg != "" {
		name = pkg + "." + name
	}
	return name
}

func serviceName(service *ServiceDescriptor) string {
	return CamelCase(service.GetName())
}

func serviceStruct(service *ServiceDescriptor) string {
	return unexported(serviceName(service)) + "Server"
}

func methodName(method *MethodDescriptor) string {
	return CamelCase(method.GetName())
}
