local core = require("luaevent.core")

describe("Buffer tests", function()
    local buffer, buffer2
    before_each(function()
        buffer = core.buffer.new()
        buffer2 = core.buffer.new()
    end)

    after_each(function()
        buffer:close()
        buffer2:close()
    end)

    local function testDataEqual(expected, actual, msg)
        msg = msg or ''
        assert.message("Buffer not the same: " .. msg).are.equal(expected, actual:get_data())
        assert.message("Buffer length not the same: " .. msg).are.equal(#expected, actual:length())
        assert.message("Buffer (from tostring) not the same: " .. msg).are.equal(expected, tostring(actual))
        assert.message("Buffer length (from #) not zero: " .. msg).are.equal(#expected, #actual)
    end

    it("should be empty on startup", function()
        testDataEqual("", buffer, "Buffer not empty")
        testDataEqual("", buffer2, "Buffer2 not empty")
    end)

    it("should support trivial simple string add", function()
        buffer:add("Hello")
        testDataEqual("Hello", buffer)
        buffer:add("Hello")
        testDataEqual("HelloHello", buffer)
    end)

    it("should support adding multiple strings", function()
        buffer:add("Hello", "Hello")
        testDataEqual("HelloHello", buffer)
    end)

    it("should be able to add digits", function()
        buffer:add(1,2,3,4)
        testDataEqual("1234", buffer)
        buffer:add(100)
        testDataEqual("1234100", buffer)
        buffer:add(1.1)
        testDataEqual("12341001.1", buffer)
    end)

    it("should support addBuffer", function()
        buffer:add(buffer2)
        testDataEqual("", buffer)
        testDataEqual("", buffer2)
        buffer2:add("Hello")
        testDataEqual("Hello", buffer2)
        buffer:add(buffer2)
        testDataEqual("Hello", buffer)
        testDataEqual("", buffer2)
        assert.message("Cannot self-add buffers").has.errors(function()
            buffer:add(buffer)
        end)
        assert.message("Cannot self-add buffers").has.errors(function()
            buffer2:add(buffer2)
        end)
        testDataEqual("Hello", buffer, "Failures should not affect data content")
        testDataEqual("", buffer2, "Failures should not affect data content")
    end)

    it("should fail when bad values are added", function()
        assert.message("Should not be able to add no values").has.errors(function()
            buffer:add()
        end)
        assert.message("Should not be able to add boolean true").has.errors(function()
            buffer:add(true)
        end)
        assert.message("Should not be able to add boolean false").has.errors(function()
            buffer:add(false)
        end)
        assert.message("Should not be able to add functions").has.errors(function()
            buffer:add(function() end)
        end)
        assert.message("Should not be able to add threads").has.errors(function()
            buffer:add(coroutine.create(function() end))
        end)
        assert.message("Should not be able to add non-buffer userdata").has.errors(function()
            buffer:add(newproxy())
        end)
        assert.message("Should not be able to add nil values").has.errors(function()
            buffer:add(nil)
        end)
        assert.message("Multiples including valid values should not affect failure results").has.errors(function()
            buffer:add("Hello", 1, bufferb, true, false, function() end, newproxy(), nil)
        end)
        testDataEqual("", buffer, "Buffer not empty after failing additions")
    end)

    it("should properly support draining", function()
        buffer:add("123456789")
        testDataEqual("123456789", buffer)
        assert.message("Cannot call drain w/ no args").has.errors(function()
            buffer:drain()
        end)
        buffer:drain(1)
        testDataEqual("23456789", buffer)
        buffer:drain(4)
        testDataEqual("6789", buffer)
        assert.message("Should be able to apply draining beyond actual buffer length").has.no.errors(function()
            buffer:drain(5)
        end)
        testDataEqual("", buffer)
        buffer:add("123456789")
        testDataEqual("123456789", buffer)
        assert.message([[Should be able to apply negative draining to cause draining `all data`
        (see source comments for why)]]).has.no.errors(function()
            buffer:drain(-1)
        end)
        testDataEqual("", buffer)
    end)

    it("should have working partial reads", function()
        buffer:add("123456789")
        assert.are.equal("1234", buffer:get_data(4))
        assert.are.equal("1234", buffer:get_data(1,4))
        assert.are.equal("5678", buffer:get_data(5,4))
        assert.are.equal("5", buffer:get_data(5,1))
        assert.message("Data length is capped at max obtainable").are.equal("56789", buffer:get_data(5,100000000))
        assert.message("Negative sizes capture entire remaining string").are.equal("56789", buffer:get_data(5,-100))
        assert.message("Negative position causes wraparound").are.equal("9", buffer:get_data(-1, 1))
        assert.message("Negative wraparound does not cause length inversion").are.equal("89", buffer:get_data(-2,2))
    end)

    local lineData = [[1
2
3]]
    local splitLineData = {
        "1","2",nil
    }
    local mixedLineData = "1\r2\n3\r\n4\n\r5\r\r6\n\n7\r\n\r8\r\n\r9"
    local splitMixedLineData = {
        "1","2","3","4","5","6","7","8", nil
    }
    it("should have a working readline", function()
        buffer:add(lineData)
        testDataEqual(lineData, buffer)
        for _, data in ipairs(splitLineData) do
            assert.are.equal(data, buffer:readline())
        end
        testDataEqual("3", buffer, "Failed readline doesn't affect buffer contents")
        buffer:drain(-1)
        testDataEqual("", buffer)
        buffer:add(mixedLineData)
        testDataEqual(mixedLineData, buffer)
        for _, data in ipairs(splitMixedLineData) do
            assert.are.equal(data, buffer:readline())
        end
        testDataEqual("9", buffer)
    end)
end)
