// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
// Revel Framework source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.

package revel

import (
	"errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"strings"
	"time"

	"github.com/revel/revel/logger"
	"github.com/revel/revel/session"
	"github.com/revel/revel/utils"
)

// Controller Revel's controller structure that gets embedded in user defined
// controllers
type Controller struct {
	Name          string                 // The controller name, e.g. "Application"
	Type          *ControllerType        // A description of the controller type.
	MethodName    string                 // The method name, e.g. "Index"
	MethodType    *MethodType            // A description of the invoked action type.
	AppController interface{}            // The controller that was instantiated. embeds revel.Controller
	Action        string                 // The fully qualified action name, e.g. "App.Index"
	ClientIP      string                 // holds IP address of request came from

	Request       *Request
	Response      *Response
	Result        Result

	Flash         Flash                  // User cookie, cleared after 1 request.
	Session       session.Session        // Session, stored using the session engine specified
	Params        *Params                // Parameters from URL and form (including multipart).
	Args          map[string]interface{} // Per-request scratch space.
	ViewArgs      map[string]interface{} // Variables passed to the template.
	Validation    *Validation            // Data validation helpers
	Log           logger.MultiLogger     // Context Logger
}

// The map of controllers, controllers are mapped by using the namespace|controller_name as the key
var controllers = make(map[string]*ControllerType)
var controllerLog = RevelLog.New("section", "controller")

// NewController returns new controller instance for Request and Response
func NewControllerEmpty() *Controller {
	return &Controller{Request: NewRequest(nil), Response: NewResponse(nil)}
}

// New controller, creates a new instance wrapping the request and response in it
func NewController(context ServerContext) *Controller {
	c := NewControllerEmpty()
	c.SetController(context)
	return c
}

// Sets the request and the response for the controller
func (c *Controller) SetController(context ServerContext) {

	c.Request.SetRequest(context.GetRequest())
	c.Response.SetResponse(context.GetResponse())
	c.Request.controller = c
	c.Params = new(Params)
	c.Args = map[string]interface{}{}
	c.ViewArgs = map[string]interface{}{
		"RunMode": RunMode,
		"DevMode": DevMode,
	}

}
func (c *Controller) Destroy() {
	// When the instantiated controller gets injected
	// It inherits this method, so we need to
	// check to see if the controller is nil before performing
	// any actions
	if c == nil {
		return
	}
	if c.AppController != nil {
		c.resetAppControllerFields()
		// Return this instance to the pool
		appController := c.AppController
		c.AppController = nil
		if RevelConfig.Controller.Reuse {
			RevelConfig.Controller.CachedMap[c.Name].Push(appController)
		}
	}

	c.Request.Destroy()
	c.Response.Destroy()
	c.Params = nil
	c.Args = nil
	c.ViewArgs = nil
	c.Name = ""
	c.Type = nil
	c.MethodName = ""
	c.MethodType = nil
	c.Action = ""
	c.ClientIP = ""
	c.Result = nil
	c.Flash = Flash{}
	c.Session = session.NewSession()
	c.Params = nil
	c.Validation = nil
	c.Log = nil
}

// FlashParams serializes the contents of Controller.Params to the Flash
// cookie.
func (c *Controller) FlashParams() {
	for key, vals := range c.Params.Values {
		c.Flash.Out[key] = strings.Join(vals, ",")
	}
}

func (c *Controller) SetCookie(cookie *http.Cookie) {
	c.Response.Out.internalHeader.SetCookie(cookie.String())
}

type ErrorCoder interface {
	HTTPCode() int
}

func (c *Controller) RenderError(err error) Result {
	if coder, ok := err.(ErrorCoder); ok {
		c.setStatusIfNil(coder.HTTPCode())
	} else {
		c.setStatusIfNil(http.StatusInternalServerError)
	}

	return ErrorResult{c.ViewArgs, err}
}

func (c *Controller) setStatusIfNil(status int) {
	if c.Response.Status == 0 {
		c.Response.Status = status
	}
}

// Render a template corresponding to the calling Controller method.
// Arguments will be added to c.ViewArgs prior to rendering the template.
// They are keyed on their local identifier.
//
// For example:
//
//     func (c Users) ShowUser(id int) revel.Result {
//     	 user := loadUser(id)
//     	 return c.Render(user)
//     }
//
// This action will render views/Users/ShowUser.html, passing in an extra
// key-value "user": (User).
//
// This is the slower magical version which uses the runtime
// to determine
// 1) Set c.ViewArgs to the arguments passed into this function
// 2) How to call the RenderTemplate by building the following line
// c.RenderTemplate(c.Name + "/" + c.MethodType.Name + "." + c.Request.Format)
//
// If you want your code to run faster it is recommended you add the template values directly
// to the c.ViewArgs and call c.RenderTemplate directly
func (c *Controller) Render(extraViewArgs ...interface{}) Result {
	c.setStatusIfNil(http.StatusOK)

	// Get the calling function line number.
	_, _, line, ok := runtime.Caller(1)
	if !ok {
		controllerLog.Error("Render: Failed to get Caller information")
	}

	// Get the extra ViewArgs passed in.
	if renderArgNames, ok := c.MethodType.RenderArgNames[line]; ok {
		if len(renderArgNames) == len(extraViewArgs) {
			for i, extraRenderArg := range extraViewArgs {
				c.ViewArgs[renderArgNames[i]] = extraRenderArg
			}
		} else {
			controllerLog.Error(fmt.Sprint(len(renderArgNames), "RenderArg names found for",
				len(extraViewArgs), "extra ViewArgs"))
		}
	} else {
		controllerLog.Error(fmt.Sprint("No RenderArg names found for Render call on line", line,
			"(Action", c.Action, ")"), "stack", logger.NewCallStack())
	}

	return c.RenderTemplate(c.Name + "/" + c.MethodType.Name + "." + c.Request.Format)
}

// RenderTemplate method does less magical way to render a template.
// Renders the given template, using the current ViewArgs.
func (c *Controller) RenderTemplate(templatePath string) Result {
	c.setStatusIfNil(http.StatusOK)

	// Get the Template.
	lang, _ := c.ViewArgs[CurrentLocaleViewArg].(string)
	template, err := MainTemplateLoader.TemplateLang(templatePath, lang)
	if err != nil {
		return c.RenderError(err)
	}

	return &RenderTemplateResult{
		Template: template,
		ViewArgs: c.ViewArgs,
	}
}

// TemplateOutput returns the result of the template rendered using the controllers ViewArgs.
func (c *Controller) TemplateOutput(templatePath string) (data []byte, err error) {
	return TemplateOutputArgs(templatePath, c.ViewArgs)
}

// RenderJSON uses encoding/json.Marshal to return JSON to the client.
func (c *Controller) RenderJSON(o interface{}) Result {
	c.setStatusIfNil(http.StatusOK)

	return RenderJSONResult{o, ""}
}

// RenderJSONP renders JSONP result using encoding/json.Marshal
func (c *Controller) RenderJSONP(callback string, o interface{}) Result {
	c.setStatusIfNil(http.StatusOK)

	return RenderJSONResult{o, callback}
}

// RenderXML uses encoding/xml.Marshal to return XML to the client.
func (c *Controller) RenderXML(o interface{}) Result {
	c.setStatusIfNil(http.StatusOK)

	return RenderXMLResult{o}
}

// RenderText renders plaintext in response, printf style.
func (c *Controller) RenderText(text string, objs ...interface{}) Result {
	c.setStatusIfNil(http.StatusOK)

	finalText := text
	if len(objs) > 0 {
		finalText = fmt.Sprintf(text, objs...)
	}
	return &RenderTextResult{finalText}
}

// RenderHTML renders html in response
func (c *Controller) RenderHTML(html string) Result {
	c.setStatusIfNil(http.StatusOK)

	return &RenderHTMLResult{html}
}

// Todo returns an HTTP 501 Not Implemented "todo" indicating that the
// action isn't done yet.
func (c *Controller) Todo() Result {
	c.Response.Status = http.StatusNotImplemented
	controllerLog.Debug("Todo: Not implemented function", "action", c.Action)
	return c.RenderError(&Error{
		Title:       "TODO",
		Description: "This action is not implemented",
	})
}

// NotFound returns an HTTP 404 Not Found response whose body is the
// formatted string of msg and objs.
func (c *Controller) NotFound(msg string, objs ...interface{}) Result {
	finalText := msg
	if len(objs) > 0 {
		finalText = fmt.Sprintf(msg, objs...)
	}
	c.Response.Status = http.StatusNotFound
	return c.RenderError(&Error{
		Title:       "Not Found",
		Description: finalText,
	})
}

// Forbidden returns an HTTP 403 Forbidden response whose body is the
// formatted string of msg and objs.
func (c *Controller) Forbidden(msg string, objs ...interface{}) Result {
	finalText := msg
	if len(objs) > 0 {
		finalText = fmt.Sprintf(msg, objs...)
	}
	c.Response.Status = http.StatusForbidden
	return c.RenderError(&Error{
		Title:       "Forbidden",
		Description: finalText,
	})
}

// RenderFileName returns a file indicated by the path as provided via the filename.
// It can be either displayed inline or downloaded as an attachment.
// The name and size are taken from the file info.
func (c *Controller) RenderFileName(filename string, delivery ContentDisposition) Result {
	f, err := os.Open(filename)
	if err != nil {
		c.Log.Errorf("Cant open file: %v", err)
		return c.RenderError(err)
	}
	return c.RenderFile(f, delivery)
}

// RenderFile returns a file, either displayed inline or downloaded
// as an attachment. The name and size are taken from the file info.
func (c *Controller) RenderFile(file *os.File, delivery ContentDisposition) Result {
	c.setStatusIfNil(http.StatusOK)

	var (
		modtime = time.Now()
		fileInfo, err = file.Stat()
	)
	if err != nil {
		controllerLog.Error("RenderFile: error", "error", err)
	}
	if fileInfo != nil {
		modtime = fileInfo.ModTime()
	}
	return c.RenderBinary(file, filepath.Base(file.Name()), delivery, modtime)
}

// RenderBinary is like RenderFile() except that it instead of a file on disk,
// it renders data from memory (which could be a file that has not been written,
// the output from some function, or bytes streamed from somewhere else, as long
// it implements io.Reader).  When called directly on something generated or
// streamed, modtime should mostly likely be time.Now().
func (c *Controller) RenderBinary(memfile io.Reader, filename string, delivery ContentDisposition, modtime time.Time) Result {
	c.setStatusIfNil(http.StatusOK)

	return &BinaryResult{
		Reader:   memfile,
		Name:     filename,
		Delivery: delivery,
		Length:   -1, // http.ServeContent gets the length itself unless memfile is a stream.
		ModTime:  modtime,
	}
}

// Redirect to an action or to a URL.
//   c.Redirect(Controller.Action)
//   c.Redirect("/controller/action")
//   c.Redirect("/controller/%d/action", id)
func (c *Controller) Redirect(val interface{}, args ...interface{}) Result {
	c.setStatusIfNil(http.StatusFound)

	if url, ok := val.(string); ok {
		if len(args) == 0 {
			return &RedirectToURLResult{url}
		}
		return &RedirectToURLResult{fmt.Sprintf(url, args...)}
	}
	return &RedirectToActionResult{val, args}
}

// This stats returns some interesting stats based on what is cached in memory
// and what is available directly
func (c *Controller) Stats() map[string]interface{} {
	result := CurrentEngine.Stats()
	if RevelConfig.Controller.Reuse {
		result["revel-controllers"] = RevelConfig.Controller.Stack.String()
		for key, appStack := range RevelConfig.Controller.CachedMap {
			result["app-" + key] = appStack.String()
		}
	}
	return result
}

// Message performs a lookup for the given message name using the given
// arguments using the current language defined for this controller.
//
// The current language is set by the i18n plugin.
func (c *Controller) Message(message string, args ...interface{}) string {
	return MessageFunc(c.Request.Locale, message, args...)
}

// SetAction sets the action that is being invoked in the current request.
// It sets the following properties: Name, Action, Type, MethodType
func (c *Controller) SetAction(controllerName, methodName string) error {

	return c.SetTypeAction(controllerName, methodName, nil)
}

// SetAction sets the assigns the Controller type, sets the action and initializes the controller
func (c *Controller) SetTypeAction(controllerName, methodName string, typeOfController *ControllerType) error {

	// Look up the controller and method types.
	if typeOfController == nil {
		if c.Type = ControllerTypeByName(controllerName, anyModule); c.Type == nil {
			return errors.New("revel/controller: failed to find controller " + controllerName)
		}
	} else {
		c.Type = typeOfController
	}

	// Note method name is case insensitive search
	if c.MethodType = c.Type.Method(methodName); c.MethodType == nil {
		return errors.New("revel/controller: failed to find action " + controllerName + "." + methodName)
	}

	c.Name, c.MethodName = c.Type.Type.Name(), c.MethodType.Name
	c.Action = c.Name + "." + c.MethodName

	// Update Logger with controller and namespace
	if c.Log != nil {
		c.Log = c.Log.New("action", c.Action, "namespace", c.Type.Namespace)
	}

	if RevelConfig.Controller.Reuse {
		if _, ok := RevelConfig.Controller.CachedMap[c.Name]; !ok {
			// Create a new stack for this controller
			localType := c.Type.Type
			RevelConfig.Controller.CachedMap[c.Name] = utils.NewStackLock(
				RevelConfig.Controller.CachedStackSize,
				RevelConfig.Controller.CachedStackMaxSize,
				func() interface{} {
					return reflect.New(localType).Interface()
				})
		}
		// Instantiate the controller.
		c.AppController = RevelConfig.Controller.CachedMap[c.Name].Pop()
	} else {
		c.AppController = reflect.New(c.Type.Type).Interface()
	}
	c.setAppControllerFields()

	return nil
}

func ControllerTypeByName(controllerName string, moduleSource *Module) (c *ControllerType) {
	var found bool
	if c, found = controllers[controllerName]; !found {
		// Backup, passed in controllerName should be in lower case, but may not be
		if c, found = controllers[strings.ToLower(controllerName)]; !found {
			controllerLog.Debug("ControllerTypeByName: Cannot find controller in controllers map ", "controller", controllerName)
			// Search for the controller by name
			for _, cType := range controllers {
				testControllerName := strings.ToLower(cType.Type.Name())
				if testControllerName == strings.ToLower(controllerName) && (cType.ModuleSource == moduleSource || moduleSource == anyModule) {
					controllerLog.Warn("ControllerTypeByName: Matched empty namespace controller ", "controller", controllerName, "namespace", cType.ModuleSource.Name)
					c = cType
					found = true
					break
				}
			}
		}
	}
	return
}

// Injects this instance (c) into the AppController instance
func (c *Controller) setAppControllerFields() {
	appController := reflect.ValueOf(c.AppController).Elem()
	cValue := reflect.ValueOf(c)
	for _, index := range c.Type.ControllerIndexes {
		appController.FieldByIndex(index).Set(cValue)
	}
}

// Removes this instance (c) from the AppController instance
func (c *Controller) resetAppControllerFields() {
	appController := reflect.ValueOf(c.AppController).Elem()
	// Zero out controller
	for _, index := range c.Type.ControllerIndexes {
		appController.FieldByIndex(index).Set(reflect.Zero(reflect.TypeOf(c.AppController).Elem().FieldByIndex(index).Type))
	}
}

func findControllers(appControllerType reflect.Type) (indexes [][]int) {
	// It might be a multi-level embedding. To find the controllers, we follow
	// every anonymous field, using breadth-first search.
	type nodeType struct {
		val   reflect.Value
		index []int
	}
	appControllerPtr := reflect.New(appControllerType)
	queue := []nodeType{{appControllerPtr, []int{}}}
	for len(queue) > 0 {
		// Get the next value and de-reference it if necessary.
		var (
			node = queue[0]
			elem = node.val
			elemType = elem.Type()
		)
		if elemType.Kind() == reflect.Ptr {
			elem = elem.Elem()
			elemType = elem.Type()
		}
		queue = queue[1:]

		// #944 if the type's Kind is not `Struct` move on,
		// otherwise `elem.NumField()` will panic
		if elemType.Kind() != reflect.Struct {
			continue
		}

		// Look at all the struct fields.
		for i := 0; i < elem.NumField(); i++ {
			// If this is not an anonymous field, skip it.
			structField := elemType.Field(i)
			if !structField.Anonymous {
				continue
			}

			fieldValue := elem.Field(i)
			fieldType := structField.Type

			// If it's a Controller, record the field indexes to get here.
			if fieldType == controllerPtrType {
				indexes = append(indexes, append(node.index, i))
				continue
			}

			queue = append(queue,
				nodeType{fieldValue, append(append([]int{}, node.index...), i)})
		}
	}
	return
}

// RegisterController registers a Controller and its Methods with Revel.
func RegisterController(c interface{}, methods []*MethodType) {
	// De-star the controller type
	// (e.g. given TypeOf((*Application)(nil)), want TypeOf(Application))
	elem := reflect.TypeOf(c).Elem()

	// De-star all of the method arg types too.
	for _, m := range methods {
		m.lowerName = strings.ToLower(m.Name)
		for _, arg := range m.Args {
			arg.Type = arg.Type.Elem()
		}
	}

	// Fetch module for controller, if none found controller must be part of the app
	controllerModule := ModuleFromPath(elem.PkgPath(), true)

	controllerType := AddControllerType(controllerModule, elem, methods)

	controllerLog.Debug("RegisterController:Registered controller", "controller", controllerType.Name(), "module-name", controllerModule.Name)
}
