package gws

import (
	"errors"
	"io"
	"testing"
	"time"

	"github.com/lxzan/gws/internal"

	"github.com/stretchr/testify/assert"
)

func TestSlideWindow(t *testing.T) {
	t.Run("", func(t *testing.T) {
		var sw = new(slideWindow).initialize(nil, 3)
		sw.Write([]byte("abc"))
		assert.Equal(t, string(sw.dict), "abc")

		sw.Write([]byte("def"))
		assert.Equal(t, string(sw.dict), "abcdef")

		sw.Write([]byte("ghi"))
		assert.Equal(t, string(sw.dict), "bcdefghi")
	})

	t.Run("", func(t *testing.T) {
		var sw = new(slideWindow).initialize(nil, 3)
		sw.Write([]byte("abc"))
		assert.Equal(t, string(sw.dict), "abc")

		sw.Write([]byte("defgh123456789"))
		assert.Equal(t, string(sw.dict), "23456789")
	})

	t.Run("", func(t *testing.T) {
		const size = 4 * 1024
		var sw = slideWindow{enabled: true, size: size}
		for i := 0; i < 1000; i++ {
			var n = internal.AlphabetNumeric.Intn(100)
			sw.Write(internal.AlphabetNumeric.Generate(n))
		}
		assert.Equal(t, len(sw.dict), size)
	})

	t.Run("", func(t *testing.T) {
		const size = 4 * 1024
		for i := 0; i < 10; i++ {
			var sw = slideWindow{enabled: true, size: size, dict: make([]byte, internal.AlphabetNumeric.Intn(size))}
			for j := 0; j < 1000; j++ {
				var n = internal.AlphabetNumeric.Intn(100)
				sw.Write(internal.AlphabetNumeric.Generate(n))
			}
			assert.Equal(t, len(sw.dict), size)
		}
	})
}

func TestNegotiation(t *testing.T) {
	t.Run("", func(t *testing.T) {
		var pd = permessageNegotiation("permessage-deflate; client_no_context_takeover; client_max_window_bits=9")
		assert.Equal(t, pd.ClientMaxWindowBits, 9)
		assert.Equal(t, pd.ServerMaxWindowBits, 15)
		assert.True(t, pd.ServerContextTakeover)
		assert.False(t, pd.ClientContextTakeover)
	})

	t.Run("", func(t *testing.T) {
		var pd = permessageNegotiation("permessage-deflate; client_max_window_bits=9; server_max_window_bits=10")
		assert.Equal(t, pd.ClientMaxWindowBits, 9)
		assert.Equal(t, pd.ServerMaxWindowBits, 10)
		assert.True(t, pd.ServerContextTakeover)
		assert.True(t, pd.ClientContextTakeover)
	})
}

func TestPermessageNegotiation(t *testing.T) {
	t.Run("ok 1", func(t *testing.T) {
		var addr = ":" + nextPort()
		var server = NewServer(new(BuiltinEventHandler), &ServerOption{PermessageDeflate: PermessageDeflate{
			Enabled:               true,
			ServerContextTakeover: true,
			ClientContextTakeover: true,
			ServerMaxWindowBits:   10,
			ClientMaxWindowBits:   10,
		}})
		go server.Run(addr)

		time.Sleep(100 * time.Millisecond)
		client, _, err := NewClient(new(BuiltinEventHandler), &ClientOption{
			Addr: "ws://localhost" + addr,
			PermessageDeflate: PermessageDeflate{
				Enabled:               true,
				ServerContextTakeover: true,
				ClientContextTakeover: true,
			},
		})
		assert.NoError(t, err)
		assert.Equal(t, client.cpsWindow.size, 1024)
		assert.Equal(t, client.dpsWindow.size, 1024)
		assert.Equal(t, client.pd.ServerContextTakeover, true)
		assert.Equal(t, client.pd.ClientContextTakeover, true)
	})

	t.Run("ok 2", func(t *testing.T) {
		var addr = ":" + nextPort()
		var server = NewServer(new(BuiltinEventHandler), &ServerOption{PermessageDeflate: PermessageDeflate{
			Enabled:               true,
			ServerContextTakeover: false,
			ClientContextTakeover: false,
			ServerMaxWindowBits:   10,
			ClientMaxWindowBits:   10,
		}})
		go server.Run(addr)

		time.Sleep(100 * time.Millisecond)
		client, _, err := NewClient(new(BuiltinEventHandler), &ClientOption{
			Addr: "ws://localhost" + addr,
			PermessageDeflate: PermessageDeflate{
				Enabled:               true,
				ServerContextTakeover: true,
				ClientContextTakeover: true,
			},
		})
		assert.NoError(t, err)
		assert.Equal(t, client.cpsWindow.size, 0)
		assert.Equal(t, client.dpsWindow.size, 0)
		assert.Equal(t, client.pd.ServerContextTakeover, false)
		assert.Equal(t, client.pd.ClientContextTakeover, false)
	})

	t.Run("ok 3", func(t *testing.T) {
		var addr = ":" + nextPort()
		var server = NewServer(new(BuiltinEventHandler), &ServerOption{PermessageDeflate: PermessageDeflate{
			Enabled:               true,
			ServerContextTakeover: true,
			ClientContextTakeover: true,
			ServerMaxWindowBits:   10,
			ClientMaxWindowBits:   10,
		}})
		go server.Run(addr)

		time.Sleep(100 * time.Millisecond)
		client, _, err := NewClient(new(BuiltinEventHandler), &ClientOption{
			Addr: "ws://localhost" + addr,
			PermessageDeflate: PermessageDeflate{
				Enabled:               true,
				ServerContextTakeover: false,
				ClientContextTakeover: false,
			},
		})
		assert.Equal(t, client.cpsWindow.size, 0)
		assert.Equal(t, client.dpsWindow.size, 0)
		assert.Equal(t, client.pd.ServerContextTakeover, false)
		assert.Equal(t, client.pd.ClientContextTakeover, false)
		assert.NoError(t, err)
	})

	t.Run("ok 4", func(t *testing.T) {
		var addr = ":" + nextPort()
		var serverHandler = &webSocketMocker{}
		serverHandler.onOpen = func(socket *Conn) {
			socket.WriteMessage(OpcodeText, internal.AlphabetNumeric.Generate(1024))
		}
		var server = NewServer(serverHandler, &ServerOption{PermessageDeflate: PermessageDeflate{
			Enabled:               true,
			ServerContextTakeover: true,
			ClientContextTakeover: true,
			ServerMaxWindowBits:   10,
			ClientMaxWindowBits:   10,
		}})
		go server.Run(addr)

		time.Sleep(100 * time.Millisecond)
		client, _, err := NewClient(new(BuiltinEventHandler), &ClientOption{
			Addr: "ws://localhost" + addr,
			PermessageDeflate: PermessageDeflate{
				Enabled:               true,
				ServerContextTakeover: true,
				ClientContextTakeover: true,
			},
		})
		assert.NoError(t, err)
		client.WriteMessage(OpcodeText, internal.AlphabetNumeric.Generate(1024))
	})

	t.Run("ok 5", func(t *testing.T) {
		var addr = ":" + nextPort()
		var serverHandler = &webSocketMocker{}
		serverHandler.onMessage = func(socket *Conn, message *Message) {
			println(message.Data.String())
		}
		var server = NewServer(serverHandler, &ServerOption{PermessageDeflate: PermessageDeflate{
			Enabled:               true,
			ServerContextTakeover: true,
			ClientContextTakeover: true,
			ServerMaxWindowBits:   10,
			ClientMaxWindowBits:   10,
		}})
		go server.Run(addr)

		time.Sleep(100 * time.Millisecond)
		client, _, err := NewClient(new(BuiltinEventHandler), &ClientOption{
			Addr: "ws://localhost" + addr,
			PermessageDeflate: PermessageDeflate{
				Enabled:               true,
				ServerContextTakeover: true,
				ClientContextTakeover: true,
				Threshold:             1,
			},
		})
		assert.NoError(t, err)
		_ = client.WriteString("he")
		assert.Equal(t, string(client.cpsWindow.dict), "he")
		_ = client.WriteString("llo")
		assert.Equal(t, string(client.cpsWindow.dict), "hello")
		_ = client.Writev(OpcodeText, []byte(", "), []byte("world!"))
		assert.Equal(t, string(client.cpsWindow.dict), "hello, world!")
	})

	t.Run("fail", func(t *testing.T) {
		var addr = ":" + nextPort()
		var serverHandler = &webSocketMocker{}
		var server = NewServer(serverHandler, &ServerOption{PermessageDeflate: PermessageDeflate{
			Enabled:               true,
			ServerContextTakeover: true,
			ClientContextTakeover: true,
			ServerMaxWindowBits:   10,
			ClientMaxWindowBits:   10,
		}})
		go server.Run(addr)

		time.Sleep(100 * time.Millisecond)
		client, _, err := NewClient(new(BuiltinEventHandler), &ClientOption{
			Addr: "ws://localhost" + addr,
			PermessageDeflate: PermessageDeflate{
				Enabled:               true,
				ServerContextTakeover: true,
				ClientContextTakeover: true,
				Threshold:             1,
			},
		})
		assert.NoError(t, err)
		err = client.doWrite(OpcodeText, new(writerTo))
		assert.Equal(t, err.Error(), "1")
	})
}

type writerTo struct{}

func (c *writerTo) CheckEncoding(enabled bool, opcode uint8) bool {
	return true
}

func (c *writerTo) Len() int {
	return 10
}

func (c *writerTo) WriteTo(w io.Writer) (n int64, err error) {
	return 0, errors.New("1")
}

func (c *writerTo) Read(p []byte) (n int, err error) {
	return 0, errors.New("1")
}
