#include <gtest/gtest.h>
#include "../XYParserApi.h"

#include <array>
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>

namespace {

class ParserGuard {
public:
    explicit ParserGuard(XYParserHandle handle) : handle_(handle) {}

    ~ParserGuard()
    {
        if (handle_ != nullptr) {
            XYParser_DestroyParser(handle_);
        }
    }

    XYParserHandle get() const
    {
        return handle_;
    }

private:
    XYParserHandle handle_;
};

std::vector<std::uint8_t> BuildMinimalFrame(std::uint8_t channel_count)
{
    constexpr std::size_t kSamplesPerFrame = 5;
    constexpr std::uint8_t kHeader = 0xAA;
    constexpr std::uint8_t kTail = 0x55;
    constexpr std::size_t kTagLen = 25;

    const std::size_t sample_bytes = static_cast<std::size_t>(channel_count) * 3 + 2;
    const std::uint16_t payload_length = static_cast<std::uint16_t>(sample_bytes * kSamplesPerFrame);
    const std::size_t frame_size = 1 + kTagLen + payload_length + 2;

    std::vector<std::uint8_t> frame(frame_size, 0);
    std::size_t offset = 0;

    frame[offset++] = kHeader;

    frame[offset++] = 0x01;
    frame[offset++] = 0x00;
    frame[offset++] = 0x00;
    frame[offset++] = 0x00;

    frame[offset++] = static_cast<std::uint8_t>((payload_length >> 8) & 0xFF);
    frame[offset++] = static_cast<std::uint8_t>(payload_length & 0xFF);

    frame[offset++] = 95;
    frame[offset++] = channel_count;

    offset += 2 + 2 + 2 + 2 + 2 + 6;

    for (std::size_t sample = 0; sample < kSamplesPerFrame; ++sample) {
        for (std::size_t channel = 0; channel < channel_count; ++channel) {
            frame[offset++] = 0x00;
            frame[offset++] = 0x00;
            frame[offset++] = static_cast<std::uint8_t>(sample + channel + 1);
        }
        frame[offset++] = 0x00;
        frame[offset++] = 0x00;
    }

    frame[offset++] = 0x00;
    frame[offset++] = kTail;
    frame[offset++] = kTail;

    return frame;
}

} // namespace

TEST(XYParserApiTests, CreateParserRejectsUnsupportedChannelCount)
{
    EXPECT_EQ(XYParser_CreateParser(7), nullptr);
}

TEST(XYParserApiTests, GetLastErrorReturnsMessageForNullParser)
{
    EXPECT_EQ(std::string(XYParser_GetLastError(nullptr)), std::string("invalid parser handle"));
}

TEST(XYParserApiTests, FeedParsesAComplete8ChannelFrame)
{
    ParserGuard parser(XYParser_CreateParser(8));
    ASSERT_NE(parser.get(), nullptr);

    XYParser_SetAdcParams(parser.get(), 4.5, 6.0);
    XYParser_SetBypassChecksum(parser.get(), 1);

    const std::vector<std::uint8_t> bytes = BuildMinimalFrame(8);
    std::array<XYParserFrameSummary, 1> summaries{};

    const int frame_count = XYParser_Feed(
        parser.get(),
        bytes.data(),
        bytes.size(),
        summaries.data(),
        static_cast<int>(summaries.size()));

    ASSERT_EQ(frame_count, 1);
    EXPECT_EQ(summaries[0].frame_index, static_cast<std::uint32_t>(1));
    EXPECT_EQ(summaries[0].channel_count, static_cast<std::uint8_t>(8));
    EXPECT_EQ(summaries[0].battery, static_cast<std::uint8_t>(95));
    EXPECT_EQ(summaries[0].sample_count, static_cast<std::uint8_t>(5));
    EXPECT_GT(summaries[0].channel_values_uv[0][0], 0.0);
    EXPECT_EQ(summaries[0].trigger_types[0], static_cast<std::uint8_t>(0));
    EXPECT_EQ(summaries[0].trigger_indices[0], static_cast<std::uint8_t>(0));
}

TEST(XYParserApiTests, FeedBuffersPartialDataUntilAFullFrameIsAvailable)
{
    ParserGuard parser(XYParser_CreateParser(8));
    ASSERT_NE(parser.get(), nullptr);

    XYParser_SetBypassChecksum(parser.get(), 1);

    const std::vector<std::uint8_t> bytes = BuildMinimalFrame(8);
    const std::size_t split_index = bytes.size() / 2;
    std::array<XYParserFrameSummary, 1> summaries{};

    const int first_result = XYParser_Feed(
        parser.get(),
        bytes.data(),
        split_index,
        summaries.data(),
        static_cast<int>(summaries.size()));
    EXPECT_EQ(first_result, 0);

    const int second_result = XYParser_Feed(
        parser.get(),
        bytes.data() + split_index,
        bytes.size() - split_index,
        summaries.data(),
        static_cast<int>(summaries.size()));
    ASSERT_EQ(second_result, 1);
    EXPECT_EQ(summaries[0].frame_index, static_cast<std::uint32_t>(1));
}
