package luar_test

import (
	"fmt"
	"strconv"

	"github.com/layeh/gopher-luar"
	"github.com/yuin/gopher-lua"
)

type Person struct {
	Name   string
	Age    int
	Friend *Person
}

func (p Person) Hello() string {
	return "Hello, " + p.Name
}

func (p Person) String() string {
	return p.Name + " (" + strconv.Itoa(p.Age) + ")"
}

func (p *Person) AddNumbers(L *luar.LState) int {
	sum := 0
	for i := L.GetTop(); i >= 1; i-- {
		sum += L.CheckInt(i)
	}
	L.Push(lua.LString("Tim counts: " + strconv.Itoa(sum)))
	return 1
}

type Proxy struct {
	XYZ string
}

func (p *Proxy) LuarCall(args ...lua.LValue) {
	fmt.Printf("I was called with %d arguments!\n", len(args))
}

func (p *Proxy) LuarNewIndex(key string, value lua.LValue) {
	str := value.String()
	p.XYZ = str + str
}

func Example__1() {
	const code = `
	print(user1.Name)
	print(user1.Age)
	print(user1:Hello())

	print(user2.Name)
	print(user2.Age)
	hello = user2.Hello
	print(hello(user2))
	`

	L := lua.NewState()
	defer L.Close()

	tim := &Person{
		Name: "Tim",
		Age:  30,
	}

	john := Person{
		Name: "John",
		Age:  40,
	}

	L.SetGlobal("user1", luar.New(L, tim))
	L.SetGlobal("user2", luar.New(L, john))

	if err := L.DoString(code); err != nil {
		panic(err)
	}
	// Output:
	// Tim
	// 30
	// Hello, Tim
	// John
	// 40
	// Hello, John
}

func Example__2() {
	const code = `
	for i = 1, #things do
		print(things[i])
	end
	things[1] = "cookie"

	print()

	print(thangs.ABC)
	print(thangs.DEF)
	print(thangs.GHI)
	thangs.GHI = 789
	thangs.ABC = nil
	`

	L := lua.NewState()
	defer L.Close()

	things := []string{
		"cake",
		"wallet",
		"calendar",
		"phone",
		"speaker",
	}

	thangs := map[string]int{
		"ABC": 123,
		"DEF": 456,
	}

	L.SetGlobal("things", luar.New(L, things))
	L.SetGlobal("thangs", luar.New(L, thangs))

	if err := L.DoString(code); err != nil {
		panic(err)
	}

	fmt.Println()
	fmt.Println(things[0])
	fmt.Println(thangs["GHI"])
	_, ok := thangs["ABC"]
	fmt.Println(ok)
	// Output:
	// cake
	// wallet
	// calendar
	// phone
	// speaker
	//
	// 123
	// 456
	// nil
	//
	// cookie
	// 789
	// false
}

func Example__3() {
	const code = `
	user2 = Person()
	user2.Name = "John"
	user2.Friend = user1
	print(user2.Name)
	print(user2.Friend.Name)

	everyone = People()
	everyone["tim"] = user1
	everyone["john"] = user2
	`

	L := lua.NewState()
	defer L.Close()

	tim := &Person{
		Name: "Tim",
	}

	L.SetGlobal("user1", luar.New(L, tim))
	L.SetGlobal("Person", luar.NewType(L, Person{}))
	L.SetGlobal("People", luar.NewType(L, map[string]*Person{}))

	if err := L.DoString(code); err != nil {
		panic(err)
	}

	everyone := L.GetGlobal("everyone").(*lua.LUserData).Value.(map[string]*Person)
	fmt.Println(len(everyone))
	// Output:
	// John
	// Tim
	// 2
}

func Example__4() {
	const code = `
	print(getHello(person))
	`

	L := lua.NewState()
	defer L.Close()

	tim := &Person{
		Name: "Tim",
	}

	fn := func(p *Person) string {
		return "Hello, " + p.Name
	}

	L.SetGlobal("person", luar.New(L, tim))
	L.SetGlobal("getHello", luar.New(L, fn))

	if err := L.DoString(code); err != nil {
		panic(err)
	}
	// Output:
	// Hello, Tim
}

func Example__5() {
	const code = `
	print(ch:receive())
	ch:send("John")
	print(ch:receive())
	`

	L := lua.NewState()
	defer L.Close()

	ch := make(chan string)
	go func() {
		ch <- "Tim"
		name, ok := <-ch
		fmt.Printf("%s\t%v\n", name, ok)
		close(ch)
	}()

	L.SetGlobal("ch", luar.New(L, ch))

	if err := L.DoString(code); err != nil {
		panic(err)
	}
	// Output:
	// Tim	true
	// John	true
	// nil	false
}

func Example__6() {
	const code = `
	local sorted = {}
	for k, v in countries() do
		table.insert(sorted, v)
	end
	table.sort(sorted)
	for i = 1, #sorted do
		print(sorted[i])
	end
	`

	L := lua.NewState()
	defer L.Close()

	countries := map[string]string{
		"JP": "Japan",
		"CA": "Canada",
		"FR": "France",
	}

	L.SetGlobal("countries", luar.New(L, countries))

	if err := L.DoString(code); err != nil {
		panic(err)
	}
	// Output:
	// Canada
	// France
	// Japan
}

func Example__7() {
	const code = `
	fn("a", 1, 2, 3)
	fn("b")
	fn("c", 4)
	`

	L := lua.NewState()
	defer L.Close()

	fn := func(str string, extra ...int) {
		fmt.Printf("%s\n", str)
		for _, x := range extra {
			fmt.Printf("%d\n", x)
		}
	}

	L.SetGlobal("fn", luar.New(L, fn))

	if err := L.DoString(code); err != nil {
		panic(err)
	}
	// Output:
	// a
	// 1
	// 2
	// 3
	// b
	// c
	// 4
}

func Example__8() {
	const code = `
	for _, x in ipairs(fn(1, 2, 3)) do
		print(x)
	end
	for _, x in ipairs(fn()) do
		print(x)
	end
	for _, x in ipairs(fn(4)) do
		print(x)
	end
	`

	L := lua.NewState()
	defer L.Close()

	fn := func(x ...float64) *lua.LTable {
		tbl := L.NewTable()
		for i := len(x) - 1; i >= 0; i-- {
			tbl.Insert(len(x)-i, lua.LNumber(x[i]))
		}
		return tbl
	}

	L.SetGlobal("fn", luar.New(L, fn))

	if err := L.DoString(code); err != nil {
		panic(err)
	}
	// Output:
	// 3
	// 2
	// 1
	// 4
}

func Example__9() {
	const code = `
	print(#items)
	print(items:capacity())
	items = items:append("hello", "world")
	print(#items)
	print(items:capacity())
	print(items[1])
	print(items[2])
	`

	L := lua.NewState()
	defer L.Close()

	items := make([]string, 0, 10)

	L.SetGlobal("items", luar.New(L, items))

	if err := L.DoString(code); err != nil {
		panic(err)
	}
	// Output:
	// 0
	// 10
	// 2
	// 10
	// hello
	// world
}

func Example__10() {
	const code = `
	ints = newInts(1)
	print(#ints, ints:capacity())

	ints = newInts(0, 10)
	print(#ints, ints:capacity())
	`

	L := lua.NewState()
	defer L.Close()

	type ints []int

	L.SetGlobal("newInts", luar.NewType(L, ints{}))

	if err := L.DoString(code); err != nil {
		panic(err)
	}
	// Output:
	// 1	1
	// 0	10
}

func Example__11() {
	const code = `
	print(p1 == p1)
	print(p1 == p1_alias)
	print(p1 == p2)
	`

	L := lua.NewState()
	defer L.Close()

	p1 := Person{
		Name: "Tim",
	}
	p2 := Person{
		Name: "John",
	}

	L.SetGlobal("p1", luar.New(L, &p1))
	L.SetGlobal("p1_alias", luar.New(L, &p1))
	L.SetGlobal("p2", luar.New(L, &p2))

	if err := L.DoString(code); err != nil {
		panic(err)
	}
	// Output:
	// true
	// true
	// false
}

func Example__12() {
	const code = `
	print(p1)
	print(p2)
	`

	L := lua.NewState()
	defer L.Close()

	p1 := Person{
		Name: "Tim",
		Age:  99,
	}
	p2 := Person{
		Name: "John",
		Age:  2,
	}

	L.SetGlobal("p1", luar.New(L, &p1))
	L.SetGlobal("p2", luar.New(L, &p2))

	if err := L.DoString(code); err != nil {
		panic(err)
	}
	// Output:
	// Tim (99)
	// John (2)
}

func Example__13() {
	const code = `
	print(p:AddNumbers(1, 2, 3, 4, 5))
	`

	L := lua.NewState()
	defer L.Close()

	p := Person{
		Name: "Tim",
	}

	L.SetGlobal("p", luar.New(L, &p))

	if err := L.DoString(code); err != nil {
		panic(err)
	}
	// Output:
	// Tim counts: 15
}

func Example__14() {
	const code = `
	print(p:hello())
	print(p.age)
	`

	L := lua.NewState()
	defer L.Close()

	p := Person{
		Name: "Tim",
		Age:  66,
	}

	L.SetGlobal("p", luar.New(L, &p))

	if err := L.DoString(code); err != nil {
		panic(err)
	}
	// Output:
	// Hello, Tim
	// 66
}

func Example__15() {
	const code = `
	print(p.XYZ)
	p("Hello", "World")
	p.nothing = "nice"
	`

	L := lua.NewState()
	defer L.Close()

	p := Proxy{
		XYZ: "1000+",
	}

	L.SetGlobal("p", luar.New(L, &p))

	if err := L.DoString(code); err != nil {
		panic(err)
	}
	fmt.Println(p.XYZ)
	// Output:
	// 1000+
	// I was called with 2 arguments!
	// nicenice
}

func Example__16() {
	const code = `
	print(fn("tim", 5))
	`

	L := lua.NewState()
	defer L.Close()

	fn := func(name string, count int) []lua.LValue {
		s := make([]lua.LValue, count)
		for i := 0; i < count; i++ {
			s[i] = lua.LString(name)
		}
		return s
	}

	L.SetGlobal("fn", luar.New(L, fn))

	if err := L.DoString(code); err != nil {
		panic(err)
	}
	// Output:
	// tim	tim	tim	tim	tim
}

func Example__17() {
	const code = `
	print(-ptr)
	`

	L := lua.NewState()
	defer L.Close()

	str := "hello"

	L.SetGlobal("ptr", luar.New(L, &str))

	if err := L.DoString(code); err != nil {
		panic(err)
	}
	// Output:
	// hello
}

func Example__18() {
	const code = `
	print(ptr1 == nil)
	print(ptr2 == nil)
	print(ptr1 == ptr2)
	`

	L := lua.NewState()
	defer L.Close()

	var ptr1 *string
	str := "hello"

	L.SetGlobal("ptr1", luar.New(L, ptr1))
	L.SetGlobal("ptr2", luar.New(L, &str))

	if err := L.DoString(code); err != nil {
		panic(err)
	}
	// Output:
	// true
	// false
	// false
}

func Example__19() {
	const code = `
	print(-str)
	print(str ^ "world")
	print(-str)
	`

	L := lua.NewState()
	defer L.Close()

	str := "hello"

	L.SetGlobal("str", luar.New(L, &str))

	if err := L.DoString(code); err != nil {
		panic(err)
	}
	// Output:
	// hello
	// world
	// world
}

type Example__20_A struct {
	*Example__20_B
}

type Example__20_B struct {
	Value *string
}

func Example__20() {
	const code = `
	print(a.Value == nil)
	a.Value = str_ptr()
	_ = a.Value ^ "hello"
	print(a.Value == nil)
	print(-a.Value)
	`

	L := lua.NewState()
	defer L.Close()

	a := Example__20_A{
		Example__20_B: &Example__20_B{},
	}

	L.SetGlobal("a", luar.New(L, a))
	L.SetGlobal("str_ptr", luar.NewType(L, ""))

	if err := L.DoString(code); err != nil {
		panic(err)
	}
	// Output:
	// true
	// false
	// hello
}

func Example__21() {
	const code = `
	print(fn == nil)
	`

	L := lua.NewState()
	defer L.Close()

	var fn func()

	L.SetGlobal("fn", luar.New(L, fn))

	if err := L.DoString(code); err != nil {
		panic(err)
	}
	// Output:
	// true
}

func Example__22() {
	const code = `
	fn(arr)
	`

	L := lua.NewState()
	defer L.Close()

	arr := [3]int{1, 2, 3}
	fn := func(val [3]int) {
		fmt.Printf("%d %d %d\n", val[0], val[1], val[2])
	}

	L.SetGlobal("fn", luar.New(L, fn))
	L.SetGlobal("arr", luar.New(L, arr))

	if err := L.DoString(code); err != nil {
		panic(err)
	}
	// Output:
	// 1 2 3
}

func Example__23() {
	const code = `
	b = a
	`

	L := lua.NewState()
	defer L.Close()

	a := complex(float64(1), float64(2))

	L.SetGlobal("a", luar.New(L, a))

	if err := L.DoString(code); err != nil {
		panic(err)
	}
	b := L.GetGlobal("b").(*lua.LUserData).Value.(complex128)
	fmt.Println(a == b)
	// Output:
	// true
}

func ExampleMeta() {
	const code = `
	proxy(234, nil, "asd", {})
	`

	L := lua.NewState()
	defer L.Close()

	// Proxy has the following method defined:
	//  func (p *Proxy) LuarCall(args ...lua.LValue) {
	//  	fmt.Printf("I was called with %d arguments!\n", len(args))
	//  }
	//
	proxy := &Proxy{}

	L.SetGlobal("proxy", luar.New(L, proxy))

	if err := L.DoString(code); err != nil {
		panic(err)
	}
	// Output:
	// I was called with 4 arguments!
}

func ExampleLState() {
	const code = `
	print(sum(1, 2, 3, 4, 5))
	`

	L := lua.NewState()
	defer L.Close()

	sum := func(L *luar.LState) int {
		total := 0
		for i := 1; i <= L.GetTop(); i++ {
			total += L.CheckInt(i)
		}
		L.Push(lua.LNumber(total))
		return 1
	}

	L.SetGlobal("sum", luar.New(L, sum))

	if err := L.DoString(code); err != nil {
		panic(err)
	}
	// Output:
	// 15
}

func ExampleNewType() {
	L := lua.NewState()
	defer L.Close()

	type Song struct {
		Title  string
		Artist string
	}

	L.SetGlobal("Song", luar.NewType(L, Song{}))
	L.DoString(`
		s = Song()
		s.Title = "Montana"
		s.Artist = "Tycho"
		print(s.Artist .. " - " .. s.Title)
	`)
	// Output:
	// Tycho - Montana
}
