local ffi = require 'ffi'

_G.love = {filesystem = {newFileData = function()
	return {getPointer = function()
		local buf = ffi.new("uint8_t[?]", #love.s)
		ffi.copy(buf, love.s, #love.s)
		return buf
	end, getSize = function()
		return #love.s
	end}
end, write = function(_, s)
	love.s = s
	return true
end}}

local bitser = require 'bitser'

local function serdeser(value)
	return bitser.loads(bitser.dumps(value))
end

local function test_serdeser(value)
	assert.are.same(serdeser(value), value)
end

local function test_serdeser_idempotent(value)
	assert.are.same(bitser.dumps(serdeser(value)), bitser.dumps(value))
end

describe("bitser", function()
	it("serializes simple values", function()
		test_serdeser(true)
		test_serdeser(false)
		test_serdeser(nil)
		test_serdeser(1)
		test_serdeser(-1)
		test_serdeser(0)
		test_serdeser(100000000)
		test_serdeser(1.234)
		test_serdeser(10 ^ 20)
		test_serdeser(1/0)
		test_serdeser(-1/0)
		test_serdeser("")
		test_serdeser("hullo")
		test_serdeser([[this
			is a longer string
			such a long string
			that it won't fit
			in the "short string" representation
			no it won't
			listen to me
			it won't]])
		local nan = serdeser(0/0)
		assert.is_not.equal(nan, nan)
	end)
	it("serializes simple tables", function()
		test_serdeser({})
		test_serdeser({10, 11, 12})
		test_serdeser({foo = 10, bar = 99, [true] = false})
		test_serdeser({[1000] = 9000})
		test_serdeser({{}})
	end)
	it("serializes tables with tables as keys", function()
		local thekey = {"Heyo"}
		assert.are.same(thekey, (next(serdeser({[thekey] = 12}))))
	end)
	it("serializes cyclic tables", function()
		local cthulhu = {{}, {}, {}}
		cthulhu.fhtagn = cthulhu
		--note: this does not test tables as keys because assert.are.same doesn't like that
		cthulhu[1].cthulhu = cthulhu[3]
		cthulhu[2].cthulhu = cthulhu[2]
		cthulhu[3].cthulhu = cthulhu

		test_serdeser(cthulhu)
	end)
	it("serializes resources", function()
		local temp_resource = {}
		bitser.register("temp_resource", temp_resource)
		assert.are.equal(serdeser({this = temp_resource}).this, temp_resource)
		bitser.unregister("temp_resource")
	end)
	it("serializes many resources", function()
		local max = 1000
		local t = {}
		for i = 1, max do
			bitser.register(tostring(i), i)
			t[i] = i
		end
		test_serdeser(t)
		for i = 1, max do
			bitser.unregister(tostring(i))
		end
	end)
	it("serializes deeply nested tables", function()
		local max = 1000
		local t = {}
		for _ = 1, max do
			t.t = {}
			t = t.t
		end
		test_serdeser(t)
	end)
	it("serializes MiddleClass instances", function()
		local class = require("middleclass")
		local Horse = bitser.registerClass(class('Horse'))
		function Horse:initialize(name)
			self.name = name
			self[1] = 'instance can be sequence'
		end
		local bojack = Horse('Bojack Horseman')
		test_serdeser(bojack)
		assert.is_true(serdeser(bojack):isInstanceOf(Horse))
		bitser.unregisterClass('Horse')
	end)
	it("serializes SECL instances", function()
		local class_mt = {}

		function class_mt:__index(key)
			return self.__baseclass[key]
		end

		local class = setmetatable({ __baseclass = {} }, class_mt)

		function class:new(...)
			local c = {}
			c.__baseclass = self
			setmetatable(c, getmetatable(self))
			if c.init then
				c:init(...)
			end
			return c
		end

		local Horse = bitser.registerClass('Horse', class:new())
		function Horse:init(name)
			self.name = name
			self[1] = 'instance can be sequence'
		end
		local bojack = Horse:new('Bojack Horseman')
		test_serdeser(bojack)
		assert.are.equal(serdeser(bojack).__baseclass, Horse)
		bitser.unregisterClass('Horse')
	end)
	it("serializes hump.class instances", function()
		local class = require("class")
		local Horse = bitser.registerClass('Horse', class{})
		function Horse:init(name)
			self.name = name
			self[1] = 'instance can be sequence'
		end
		local bojack = Horse('Bojack Horseman')
		test_serdeser(bojack)
		assert.are.equal(getmetatable(serdeser(bojack)), Horse)
		bitser.unregisterClass('Horse')
	end)
	it("serializes Slither instances", function()
		local class = require("slither")
		local Horse = class 'Horse' {
			__attributes__ = {bitser.registerClass},
			__init__ = function(self, name)
				self.name = name
				self[1] = 'instance can be sequence'
			end
		}
		local bojack = Horse('Bojack Horseman')
		test_serdeser(bojack)
		assert.is_true(class.isinstance(serdeser(bojack), Horse))
		bitser.unregisterClass('Horse')
	end)
	it("serializes Moonscript class instances", function()
		local Horse
		do
			local _class_0
			local _base_0 = {}
			_base_0.__index = _base_0
			_class_0 = setmetatable({
				__init = function(self, name)
					self.name = name
					self[1] = 'instance can be sequence'
				end,
				__base = _base_0,
				__name = "Horse"}, {
				__index = _base_0,
				__call = function(cls, ...)
						local _self_0 = setmetatable({}, _base_0)
						cls.__init(_self_0, ...)
						return _self_0
					end
				})
			_base_0.__class = _class_0
			Horse = _class_0
		end
		assert.are.same(Horse.__name, "Horse") -- to shut coveralls up
		bitser.registerClass(Horse)
		local bojack = Horse('Bojack Horseman')
		test_serdeser(bojack)
		local new_bojack = serdeser(bojack)
		assert.are.equal(new_bojack.__class, Horse)
		bitser.unregisterClass('Horse')
	end)
	it("serializes custom class instances", function()
		local Horse_mt = bitser.registerClass('Horse', {__index = {}}, nil, setmetatable)
		local function Horse(name)
			local self = {}
			self.name = name
			self[1] = 'instance can be sequence'
			return setmetatable(self, Horse_mt)
		end
		local bojack = Horse('Bojack Horseman')
		test_serdeser(bojack)
		assert.are.equal(getmetatable(serdeser(bojack)), Horse_mt)
		bitser.unregisterClass('Horse')
	end)
	it("serializes classes that repeat keys", function()
		local my_mt  = {"hi"}
		local works  = { foo = 'a', bar = {baz = 'b'}, }
		local broken = { foo = 'a', bar = {foo = 'b'}, }
		local more_broken = {
			foo = 'a',
			baz = {foo = 'b', bar = 'c'},
			quz = {bar = 'd', bam = 'e'}
		}
		setmetatable(works,  my_mt)
		setmetatable(broken, my_mt)
		setmetatable(more_broken, my_mt)
		bitser.registerClass("Horse", my_mt, nil, setmetatable)
		test_serdeser(works)
		test_serdeser(broken)
		test_serdeser(more_broken)
		bitser.unregisterClass('Horse')
	end)
	it("serializes big data", function()
		local text = "this is a lot of nonsense, please disregard, we need a lot of data to get past 4 KiB (114 characters should do it)"
		local t = {}
		for i = 1, 40 do
			t[i] = text .. i -- no references allowed!
		end
		test_serdeser(t)
	end)
	it("serializes many references", function()
		local max = 1000
		local t = {}
		local t2 = {}
		for i = 1, max do
			t.t = {}
			t = t.t
			t2[i] = t
		end
		test_serdeser({t, t2})
	end)
	it("serializes resources with long names", function()
		local temp_resource = {}
		bitser.register("temp_resource_or_whatever", temp_resource)
		assert.are.equal(serdeser({this = temp_resource}).this, temp_resource)
		bitser.unregister("temp_resource_or_whatever")
	end)
	it("serializes resources with the same name as serialized strings", function()
	    local temp_resource = {}
	    bitser.register('temp', temp_resource)
	    test_serdeser({temp='temp', {temp_resource}})
	    bitser.unregister('temp')
	end)
	it("serializes resources with the same long name as serialized strings", function()
	    local temp_resource = {}
	    bitser.register('temp_resource_or_whatever', temp_resource)
	    test_serdeser({temp_resource_or_whatever='temp_resource_or_whatever', {temp_resource}})
	    bitser.unregister('temp_resource_or_whatever')
	end)
	it("cannot serialize functions", function()
		assert.has_error(function() bitser.dumps(function() end) end, "cannot serialize type function")
	end)
	it("cannot serialize unsupported class libraries without explicit deserializer", function()
		assert.has_error(function() bitser.registerClass('Horse', {mane = 'majestic'}) end, "no deserializer given for unsupported class library")
	end)
	it("cannot deserialize values from unassigned type bytes", function()
		assert.has_error(function() bitser.loads("\254") end, "unsupported serialized type 254")
		assert.has_error(function() bitser.loads("\255") end, "unsupported serialized type 255")
	end)
	it("can load from raw data", function()
		assert.are.same(bitser.loadData(ffi.new("uint8_t[4]", 195, 103, 118, 120), 4), "gvx")
	end)
	it("will not read from zero length data", function()
		assert.has_error(function() bitser.loadData(ffi.new("uint8_t[1]", 0), 0) end)
	end)
	it("will not read from zero length string", function()
		assert.has_error(function() bitser.loads("") end)
	end)
	it("will not read past the end of the buffer", function()
		assert.has_error(function() bitser.loadData(ffi.new("uint8_t[4]", 196, 103, 118, 120), 4) end)
	end)
	it("can clear the buffer", function()
		bitser.clearBuffer()
	end)
	it("can write to new buffer after reading from read-only buffer", function()
		test_serdeser("bitser")
		bitser.loadData(ffi.new("uint8_t[4]", 195, 103, 118, 120), 4)
		test_serdeser("bitser")
	end)
	it("can dump and load LÖVE files", function()
		local v = {value = "value"}
		bitser.dumpLoveFile("some_file_name", v)
		assert.are.same(v, bitser.loadLoveFile("some_file_name"))
	end)
	it("can read and write simple cdata", function()
		test_serdeser(ffi.new('double', 42.5))
	end)
	it("can read and write cdata with a registered ctype", function()
		pcall(ffi.cdef,[[
			struct some_struct {
				int a;
				double b;
			};
		]])
		local value = ffi.new('struct some_struct', 42, 1.25)
		bitser.register('struct_type', ffi.typeof(value))
		test_serdeser_idempotent(value)
		bitser.unregister('struct_type')
	end)
	it("can read and write cdata without registering its ctype", function()
		pcall(ffi.cdef,[[
			struct some_struct {
				int a;
				double b;
			};
		]])
		local value = ffi.new('struct some_struct', 42, 1.25)
		test_serdeser_idempotent(value)
	end)
	it("cannot read from anonymous structs", function()
		local v = bitser.dumps(ffi.new('struct { int a; }'))
		assert.has_error(function() bitser.loads(v) end)
	end)
	it("can read and write simple multiple cdata of the same ctype without getting confused", function()
		test_serdeser({ffi.new('double', 42.5), ffi.new('double', 12), ffi.new('double', 0.01)})
	end)
	it("can read and write metatables by default", function()
		local t = setmetatable({foo="foo"}, {__index = {bar="bar"}})
		test_serdeser(t)
		assert.are.same(getmetatable(t), getmetatable(serdeser(t)))
		assert.are.same(serdeser(t).bar, "bar")
	end)
	it("ignores metatables if the feature is explicitly disabled", function()
		bitser.includeMetatables(false)
		local t = setmetatable({foo="foo"}, {__index = {bar="bar"}})
		test_serdeser(t)
		assert.is_nil(getmetatable(serdeser(t)))
		assert.is_nil(serdeser(t).bar)
		bitser.includeMetatables(true) -- revert back to default for potential other tests
	end)
end)
