// Copyright 2011 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"go/ast"
	"go/token"
)

var httpserverFix = fix{
	"httpserver",
	httpserver,
	`Adapt http server methods and functions to changes
made to the http ResponseWriter interface.

http://codereview.appspot.com/4245064  Hijacker
http://codereview.appspot.com/4239076  Header
http://codereview.appspot.com/4239077  Flusher
http://codereview.appspot.com/4248075  RemoteAddr, UsingTLS
`,
}

func init() {
	register(httpserverFix)
}

func httpserver(f *ast.File) bool {
	if !imports(f, "http") {
		return false
	}

	fixed := false
	for _, decl := range f.Decls {
		fn, ok := decl.(*ast.FuncDecl)
		if !ok {
			continue
		}
		w, req, ok := isServeHTTP(fn)
		if !ok {
			continue
		}
		walk(fn.Body, func(n interface{}) {
			// Want to replace expression sometimes,
			// so record pointer to it for updating below.
			ptr, ok := n.(*ast.Expr)
			if ok {
				n = *ptr
			}

			// Look for w.UsingTLS() and w.Remoteaddr().
			call, ok := n.(*ast.CallExpr)
			if !ok || (len(call.Args) != 0 && len(call.Args) != 2) {
				return
			}
			sel, ok := call.Fun.(*ast.SelectorExpr)
			if !ok {
				return
			}
			if !refersTo(sel.X, w) {
				return
			}
			switch sel.Sel.String() {
			case "Hijack":
				// replace w with w.(http.Hijacker)
				sel.X = &ast.TypeAssertExpr{
					X:    sel.X,
					Type: ast.NewIdent("http.Hijacker"),
				}
				fixed = true
			case "Flush":
				// replace w with w.(http.Flusher)
				sel.X = &ast.TypeAssertExpr{
					X:    sel.X,
					Type: ast.NewIdent("http.Flusher"),
				}
				fixed = true
			case "UsingTLS":
				if ptr == nil {
					// can only replace expression if we have pointer to it
					break
				}
				// replace with req.TLS != nil
				*ptr = &ast.BinaryExpr{
					X: &ast.SelectorExpr{
						X:   ast.NewIdent(req.String()),
						Sel: ast.NewIdent("TLS"),
					},
					Op: token.NEQ,
					Y:  ast.NewIdent("nil"),
				}
				fixed = true
			case "RemoteAddr":
				if ptr == nil {
					// can only replace expression if we have pointer to it
					break
				}
				// replace with req.RemoteAddr
				*ptr = &ast.SelectorExpr{
					X:   ast.NewIdent(req.String()),
					Sel: ast.NewIdent("RemoteAddr"),
				}
				fixed = true
			case "SetHeader":
				// replace w.SetHeader with w.Header().Set
				// or w.Header().Del if second argument is ""
				sel.X = &ast.CallExpr{
					Fun: &ast.SelectorExpr{
						X:   ast.NewIdent(w.String()),
						Sel: ast.NewIdent("Header"),
					},
				}
				sel.Sel = ast.NewIdent("Set")
				if len(call.Args) == 2 && isEmptyString(call.Args[1]) {
					sel.Sel = ast.NewIdent("Del")
					call.Args = call.Args[:1]
				}
				fixed = true
			}
		})
	}
	return fixed
}

func isServeHTTP(fn *ast.FuncDecl) (w, req *ast.Ident, ok bool) {
	for _, field := range fn.Type.Params.List {
		if isPkgDot(field.Type, "http", "ResponseWriter") {
			w = field.Names[0]
			continue
		}
		if isPtrPkgDot(field.Type, "http", "Request") {
			req = field.Names[0]
			continue
		}
	}

	ok = w != nil && req != nil
	return
}
