// 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 (
	"net/url"
	"reflect"
	"testing"
)

// These tests verify that Controllers are initialized properly, given the range
// of embedding possibilities..

type P struct{ *Controller }

type PN struct{ P }

type PNN struct{ PN }

// Embedded via two paths
type P2 struct{ *Controller }
type PP2 struct {
	*Controller // Need to embed this explicitly to avoid duplicate selector.
	P
	P2
	PNN
}

func TestFindControllers(t *testing.T) {
	controllers = make(map[string]*ControllerType)
	RegisterController((*P)(nil), nil)
	RegisterController((*PN)(nil), nil)
	RegisterController((*PNN)(nil), nil)
	RegisterController((*PP2)(nil), nil)

	// Test construction of indexes to each *Controller
	checkSearchResults(t, P{}, [][]int{{0}})
	checkSearchResults(t, PN{}, [][]int{{0, 0}})
	checkSearchResults(t, PNN{}, [][]int{{0, 0, 0}})
	checkSearchResults(t, PP2{}, [][]int{{0}, {1, 0}, {2, 0}, {3, 0, 0, 0}})
}

func checkSearchResults(t *testing.T, obj interface{}, expected [][]int) {
	actual := findControllers(reflect.TypeOf(obj))
	if !reflect.DeepEqual(expected, actual) {
		t.Errorf("Indexes do not match.  expected %v actual %v", expected, actual)
	}
}

func TestSetAction(t *testing.T) {
	controllers = make(map[string]*ControllerType)
	RegisterController((*P)(nil), []*MethodType{{Name: "Method"}})
	RegisterController((*PNN)(nil), []*MethodType{{Name: "Method"}})
	RegisterController((*PP2)(nil), []*MethodType{{Name: "Method"}})

	// Test that all *revel.Controllers are initialized.
	c := &Controller{Name: "Test"}
	if err := c.SetAction("P", "Method"); err != nil {
		t.Error(err)
	} else if c.AppController.(*P).Controller != c {
		t.Errorf("P not initialized")
	}

	if err := c.SetAction("PNN", "Method"); err != nil {
		t.Error(err)
	} else if c.AppController.(*PNN).Controller != c {
		t.Errorf("PNN not initialized")
	}

	// PP2 has 4 different slots for *Controller.
	if err := c.SetAction("PP2", "Method"); err != nil {
		t.Error(err)
	} else if pp2 := c.AppController.(*PP2); pp2.Controller != c ||
		pp2.P.Controller != c ||
		pp2.P2.Controller != c ||
		pp2.PNN.Controller != c {
		t.Errorf("PP2 not initialized")
	}
}

func BenchmarkSetAction(b *testing.B) {
	type Mixin1 struct {
		*Controller
		x, y int
		foo  string
	}
	type Mixin2 struct {
		*Controller
		a, b float64
		bar  string
	}

	type Benchmark struct {
		*Controller
		Mixin1
		Mixin2
		user interface{}
		guy  string
	}

	RegisterController((*Mixin1)(nil), []*MethodType{{Name: "Method"}})
	RegisterController((*Mixin2)(nil), []*MethodType{{Name: "Method"}})
	RegisterController((*Benchmark)(nil), []*MethodType{{Name: "Method"}})
	c := Controller{
		ViewArgs: make(map[string]interface{}),
	}

	for i := 0; i < b.N; i++ {
		if err := c.SetAction("Benchmark", "Method"); err != nil {
			b.Errorf("Failed to set action: %s", err)
			return
		}
	}
}

func BenchmarkInvoker(b *testing.B) {
	startFakeBookingApp()
	c := NewTestController(nil, showRequest)
	c.ViewArgs = make(map[string]interface{})
	if err := c.SetAction("Hotels", "Show"); err != nil {
		b.Errorf("Failed to set action: %s", err)
		return
	}

	c.Params = &Params{Values: make(url.Values)}
	c.Params.Set("id", "3")

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		ActionInvoker(c, nil)
	}
}
