package lua

import (
	"context"
	"reflect"
	"sync"
	"testing"
	"time"
)

func TestChannelMake(t *testing.T) {
	L := NewState()
	defer L.Close()
	errorIfScriptFail(t, L, `
    ch = channel.make()
    `)
	obj := L.GetGlobal("ch")
	ch, ok := obj.(LChannel)
	errorIfFalse(t, ok, "channel expected")
	errorIfNotEqual(t, 0, reflect.ValueOf(ch).Cap())
	close(ch)

	errorIfScriptFail(t, L, `
    ch = channel.make(10)
    `)
	obj = L.GetGlobal("ch")
	ch, _ = obj.(LChannel)
	errorIfNotEqual(t, 10, reflect.ValueOf(ch).Cap())
	close(ch)
}

func TestChannelSelectError(t *testing.T) {
	L := NewState()
	defer L.Close()
	errorIfScriptFail(t, L, `ch = channel.make()`)
	errorIfScriptNotFail(t, L, `channel.select({1,2,3})`, "invalid select case")
	errorIfScriptNotFail(t, L, `channel.select({"<-|", 1, 3})`, "invalid select case")
	errorIfScriptNotFail(t, L, `channel.select({"<-|", ch, function() end})`, "can not send a function")
	errorIfScriptNotFail(t, L, `channel.select({"|<-", 1, 3})`, "invalid select case")
	errorIfScriptNotFail(t, L, `channel.select({"<-->", 1, 3})`, "invalid channel direction")
	errorIfScriptFail(t, L, `ch:close()`)
}

func TestChannelSelect1(t *testing.T) {
	var result LValue
	var wg sync.WaitGroup
	receiver := func(ch, quit chan LValue) {
		defer wg.Done()
		L := NewState()
		defer L.Close()
		L.SetGlobal("ch", LChannel(ch))
		L.SetGlobal("quit", LChannel(quit))
		if err := L.DoString(`
    buf = ""
    local exit = false
    while not exit do
      channel.select(
        {"|<-", ch, function(ok, v)
          if not ok then
            buf = buf .. "channel closed"
            exit = true
          else
            buf = buf .. "received:" .. v
          end
        end},
        {"|<-", quit, function(ok, v)
            buf = buf .. "quit"
        end}
      )
    end
  `); err != nil {
			panic(err)
		}
		result = L.GetGlobal("buf")
	}

	sender := func(ch, quit chan LValue) {
		defer wg.Done()
		L := NewState()
		defer L.Close()
		L.SetGlobal("ch", LChannel(ch))
		L.SetGlobal("quit", LChannel(quit))
		if err := L.DoString(`
    ch:send("1")
    ch:send("2")
  `); err != nil {
			panic(err)
		}
		ch <- LString("3")
		quit <- LTrue
		time.Sleep(1 * time.Second)
		close(ch)
	}

	ch := make(chan LValue)
	quit := make(chan LValue)
	wg.Add(2)
	go receiver(ch, quit)
	go sender(ch, quit)
	wg.Wait()
	lstr, ok := result.(LString)
	errorIfFalse(t, ok, "must be string")
	str := string(lstr)
	errorIfNotEqual(t, "received:1received:2received:3quitchannel closed", str)

}

func TestChannelSelect2(t *testing.T) {
	var wg sync.WaitGroup
	receiver := func(ch, quit chan LValue) {
		defer wg.Done()
		L := NewState()
		defer L.Close()
		L.SetGlobal("ch", LChannel(ch))
		L.SetGlobal("quit", LChannel(quit))
		errorIfScriptFail(t, L, `
           idx, rcv, ok = channel.select(
               {"|<-", ch},
               {"|<-", quit}
           )
           assert(idx == 1)
           assert(rcv == "1")
           assert(ok)
           idx, rcv, ok = channel.select(
               {"|<-", ch},
               {"|<-", quit}
           )
           assert(idx == 1)
           assert(rcv == nil)
           assert(not ok)
       `)
	}

	sender := func(ch, quit chan LValue) {
		defer wg.Done()
		L := NewState()
		defer L.Close()
		L.SetGlobal("ch", LChannel(ch))
		L.SetGlobal("quit", LChannel(quit))
		errorIfScriptFail(t, L, `ch:send("1")`)
		errorIfScriptFail(t, L, `ch:close()`)
	}

	ch := make(chan LValue)
	quit := make(chan LValue)
	wg.Add(2)
	go receiver(ch, quit)
	go sender(ch, quit)
	wg.Wait()
}

func TestChannelSelect3(t *testing.T) {
	var wg sync.WaitGroup
	receiver := func(ch chan LValue) {
		defer wg.Done()
		L := NewState()
		defer L.Close()
		L.SetGlobal("ch", LChannel(ch))
		errorIfScriptFail(t, L, `
           ok = true
           while ok do
             idx, rcv, ok = channel.select(
                 {"|<-", ch}
             )
           end
       `)
	}

	sender := func(ch chan LValue) {
		defer wg.Done()
		L := NewState()
		defer L.Close()
		L.SetGlobal("ch", LChannel(ch))
		errorIfScriptFail(t, L, `
           ok = false
           channel.select(
               {"<-|", ch, "1", function(v)
                 ok = true
               end}
           )
           assert(ok)
           idx, rcv, ok = channel.select(
               {"<-|", ch, "1"}
           )
           assert(idx == 1)
           ch:close()
       `)
	}

	ch := make(chan LValue)
	wg.Add(2)
	go receiver(ch)
	time.Sleep(1)
	go sender(ch)
	wg.Wait()
}

func TestChannelSelect4(t *testing.T) {
	var wg sync.WaitGroup
	receiver := func(ch chan LValue) {
		defer wg.Done()
		L := NewState()
		defer L.Close()
		L.SetGlobal("ch", LChannel(ch))
		errorIfScriptFail(t, L, `
           idx, rcv, ok = channel.select(
                 {"|<-", ch},
                 {"default"}
           )
           assert(idx == 2)
           called = false
           idx, rcv, ok = channel.select(
                 {"|<-", ch},
                 {"default", function()
                    called = true
                 end}
           )
           assert(called)
           ch:close()
       `)
	}

	ch := make(chan LValue)
	wg.Add(1)
	go receiver(ch)
	wg.Wait()
}

func TestChannelSendReceive1(t *testing.T) {
	var wg sync.WaitGroup
	receiver := func(ch chan LValue) {
		defer wg.Done()
		L := NewState()
		defer L.Close()
		L.SetGlobal("ch", LChannel(ch))
		errorIfScriptFail(t, L, `
          local ok, v = ch:receive()
          assert(ok)
          assert(v == "1")
        `)
		time.Sleep(1 * time.Second)
		errorIfScriptFail(t, L, `
          local ok, v = ch:receive()
          assert(not ok)
          assert(v == nil)
        `)
	}
	sender := func(ch chan LValue) {
		defer wg.Done()
		L := NewState()
		defer L.Close()
		L.SetGlobal("ch", LChannel(ch))
		errorIfScriptFail(t, L, `ch:send("1")`)
		errorIfScriptNotFail(t, L, `ch:send(function() end)`, "can not send a function")
		errorIfScriptFail(t, L, `ch:close()`)
	}
	ch := make(chan LValue)
	wg.Add(2)
	go receiver(ch)
	go sender(ch)
	wg.Wait()
}

func TestCancelChannelReceive(t *testing.T) {
	done := make(chan struct{})
	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		defer close(done)
		L := NewState()
		L.SetContext(ctx)
		defer L.Close()
		L.SetGlobal("ch", LChannel(make(chan LValue)))
		errorIfScriptNotFail(t, L, `ch:receive()`, context.Canceled.Error())
	}()
	time.Sleep(time.Second)
	cancel()
	<-done
}

func TestCancelChannelReceive2(t *testing.T) {
	done := make(chan struct{})
	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		defer close(done)
		L := NewState()
		L.SetContext(ctx)
		defer L.Close()
		L.SetGlobal("ch", LChannel(make(chan LValue)))
		errorIfScriptNotFail(t, L, `channel.select({"|<-", ch})`, context.Canceled.Error())
	}()
	time.Sleep(time.Second)
	cancel()
	<-done
}
