#include "monoformat_parseonly.hpp"
#include "monoformat_parsehelpers.hpp"
#include "monoformat_fontreader.hpp"
#include "monoformat_bithelpers.hpp"

#include <string_view>
#include <string>
#include <cstdio>
#include <iostream>

namespace monoformat {

struct AlwaysDrawnSectionMetaData {
    bool drawOnFront{};
    bool drawOnBack{};
    bool clearBeforeDrawing{};
    std::span<std::byte const> elementData;
};

struct TimeBasedDrawnSectionMetaData {
    AlwaysDrawnSectionMetaData base;
    std::int64_t startTimestamp{};
    std::int64_t endTimestamp{};
};

struct MultiTimeBasedDrawnSectionMetaData {
    AlwaysDrawnSectionMetaData base;
    std::size_t rangeCount{};
    std::span<std::byte const> rangeData;
};

static std::expected<void, ParseError> handleImage(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts, std::uint32_t formatVersion);
static std::expected<void, ParseError> handleAnimation(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts, std::uint32_t formatVersion);
static std::expected<void, ParseError> handleHScrollImage(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts, std::uint32_t formatVersion);
static std::expected<void, ParseError> handleVScrollImage(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts, std::uint32_t formatVersion);
static std::expected<void, ParseError> handleLine(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts, std::uint32_t formatVersion);
static std::expected<void, ParseError> handleBox(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts, std::uint32_t formatVersion);
static std::expected<void, ParseError> handleClippedText(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts, std::uint32_t formatVersion);
static std::expected<void, ParseError> handleHScrollText(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts, std::uint32_t formatVersion);
static std::expected<void, ParseError> handleCurrentTime(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts, std::uint32_t formatVersion);

static std::expected<AlwaysDrawnSectionMetaData, ParseError> parseAlwaysDrawnSection(std::span<std::byte const>& data, std::uint32_t formatVersion) {
    std::ignore = formatVersion;

    auto type = readU8LE(data);
    if (!type) {
        return std::unexpected(type.error());
    }
    if (*type != static_cast<std::uint8_t>(SectionType::AlwaysDrawn)) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto size = readU24LE(data);
    if (!size) {
        return std::unexpected(size.error());
    }
    if (*size < 8) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto flags = readU16LE(data);
    if (!flags) {
        return std::unexpected(flags.error());
    }
    auto elementCount = readU16LE(data);
    if (!elementCount) {
        return std::unexpected(elementCount.error());
    }
    auto sectionData = data.subspan(0, *size - 8);
    data = data.subspan(*size - 8);

    AlwaysDrawnSectionMetaData result;
    result.drawOnFront = isBitSet(*flags, 0);
    result.drawOnBack = isBitSet(*flags, 1);
    result.clearBeforeDrawing = isBitSet(*flags, 2);
    result.elementData = sectionData;

#if defined(DEBUG)
    std::cerr << "[Debug] AlwaysDrawnSection, drawOnFront = " << result.drawOnFront << ", drawOnBack = " << result.drawOnBack << ", clearBeforeDrawing = " << result.clearBeforeDrawing << ", element data size = " << result.elementData.size() << " bytes" << std::endl;
#endif

    return result;
}

static std::expected<TimeBasedDrawnSectionMetaData, ParseError> parseTimeBasedDrawnSection(std::span<std::byte const>& data, std::uint32_t formatVersion) {
    std::ignore = formatVersion;

    auto type = readU8LE(data);
    if (!type) {
        return std::unexpected(type.error());
    }
    if (*type != static_cast<std::uint8_t>(SectionType::TimeBasedDrawn)) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto size = readU24LE(data);
    if (!size) {
        return std::unexpected(size.error());
    }
    if (*size < 24) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto flags = readU16LE(data);
    if (!flags) {
        return std::unexpected(flags.error());
    }
    auto elementCount = readU16LE(data);
    if (!elementCount) {
        return std::unexpected(elementCount.error());
    }
    auto startTimestamp = readU64LE(data);
    if (!startTimestamp) {
        return std::unexpected(startTimestamp.error());
    }
    auto endTimestamp = readU64LE(data);
    if (!endTimestamp) {
        return std::unexpected(endTimestamp.error());
    }
    auto sectionData = data.subspan(0, *size - 24);
    data = data.subspan(*size - 24);

    TimeBasedDrawnSectionMetaData result;
    result.base.drawOnFront = isBitSet(*flags, 0);
    result.base.drawOnBack = isBitSet(*flags, 1);
    result.base.clearBeforeDrawing = isBitSet(*flags, 2);
    result.base.elementData = sectionData;
    result.startTimestamp = std::bit_cast<std::int64_t>(*startTimestamp);
    result.endTimestamp = std::bit_cast<std::int64_t>(*endTimestamp);

#if defined(DEBUG)
    std::cerr << "[Debug] TimeBasedDrawnSection, drawOnFront = " << result.base.drawOnFront << ", drawOnBack = " << result.base.drawOnBack << ", clearBeforeDrawing = " << result.base.clearBeforeDrawing << ", element data size = " << result.base.elementData.size() << " bytes, timestamp between " << result.startTimestamp << " and " << result.endTimestamp << std::endl;
#endif

    return result;
}

static std::expected<MultiTimeBasedDrawnSectionMetaData, ParseError> parseMultiTimeBasedDrawnSection(std::span<std::byte const>& data, std::uint32_t formatVersion) {
    std::ignore = formatVersion;

    auto type = readU8LE(data);
    if (!type) {
        return std::unexpected(type.error());
    }
    if (*type != static_cast<std::uint8_t>(SectionType::MultiTimeBasedDrawn)) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto size = readU24LE(data);
    if (!size) {
        return std::unexpected(size.error());
    }
    if (*size < 12) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto flags = readU16LE(data);
    if (!flags) {
        return std::unexpected(flags.error());
    }
    auto elementCount = readU16LE(data);
    if (!elementCount) {
        return std::unexpected(elementCount.error());
    }
    auto rangeCount = readU16LE(data);
    if (!rangeCount) {
        return std::unexpected(rangeCount.error());
    }
    if (((*rangeCount) * 16u + 12u) > *size) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto reserved1 = readU16LE(data);
    if (!reserved1) {
        return std::unexpected(reserved1.error());
    }

    auto sectionData = data.subspan(0, *size - 12 - 16 * (*rangeCount));
    auto rangeData = data.subspan(12, 16 * (*rangeCount));
    data = data.subspan(*size - 12 - 16 * (*rangeCount));

    MultiTimeBasedDrawnSectionMetaData result;
    result.base.drawOnFront = isBitSet(*flags, 0);
    result.base.drawOnBack = isBitSet(*flags, 1);
    result.base.clearBeforeDrawing = isBitSet(*flags, 2);
    result.base.elementData = sectionData;
    result.rangeCount = *rangeCount;
    result.rangeData = rangeData;

#if defined(DEBUG)
    std::cerr << "[Debug] MultiTimeBasedDrawnSection, drawOnFront = " << result.base.drawOnFront << ", drawOnBack = " << result.base.drawOnBack << ", clearBeforeDrawing = " << result.base.clearBeforeDrawing << ", element data size = " << result.base.elementData.size() << " bytes, # timestamps = " << result.rangeCount << std::endl;
#endif

    return result;
}

static std::expected<std::span<std::byte const>, ParseError> parseCustomFontSection(std::span<std::byte const>& data, std::uint32_t formatVersion) {
    std::ignore = formatVersion;

    auto type = readU8LE(data);
    if (!type) {
        return std::unexpected(type.error());
    }
    if (*type != static_cast<std::uint8_t>(SectionType::CustomFont)) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto size = readU24LE(data);
    if (!size) {
        return std::unexpected(size.error());
    }
    if (*size < 8) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto actualSize = readU24LE(data);
    if (!actualSize) {
        return std::unexpected(actualSize.error());
    }
    auto reserved_ = readU8LE(data);
    if (!reserved_) {
        return std::unexpected(reserved_.error());
    }
    auto fontData = data.subspan(0, *size - 8);
    data = data.subspan(*size - 8);

#if defined(DEBUG)
    std::cerr << "[Debug] CustomFontSection, font data size = " << *actualSize << " bytes" << std::endl;
#endif

    return readBuffer(fontData, *actualSize);
}

std::expected<void, ParseError> parseAndApply(std::span<std::byte const> data, OneBitBufferInterface* screen,
                                              std::uint16_t screenWidth, std::uint16_t screenHeight,
                                              bool isFront, std::size_t animationTick, std::int64_t currentTimestamp) {
    std::array<std::span<std::byte const>, 16> customFonts;
    std::size_t customFontIndex{};

    auto magic = readU32LE(data);
    if (!magic) {
        return std::unexpected(magic.error());
    }
    if (*magic != UINT32_C(0x632B7EAF)) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto version = readU32LE(data);
    if (!version) {
        return std::unexpected(version.error());
    }
    if (*version < 1 || *version > 2) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto sectionCount = readU16LE(data);
    if (!sectionCount) {
        return std::unexpected(sectionCount.error());
    }
    auto reserved_ = readU16LE(data);
    if (!reserved_) {
        return std::unexpected(reserved_.error());
    }

    for (std::size_t i = 0; i < *sectionCount; ++i) {
        auto sectionType = peekU8LE(data);
        if (!sectionType) {
            return std::unexpected(sectionType.error());
        }
        auto sectionSize = peekU24LE(data.subspan(1));
        if (!sectionSize) {
            return std::unexpected(sectionSize.error());
        }
        auto rawSectionData = readBuffer(data, *sectionSize);
        if (!rawSectionData) {
            return std::unexpected(rawSectionData.error());
        }

        if (static_cast<SectionType>(*sectionType) == SectionType::CustomFont) {
            auto fontData = parseCustomFontSection(*rawSectionData, *version);
            if (!fontData) {
                return std::unexpected(fontData.error());
            }
            if (customFontIndex < 16) {
                customFonts[customFontIndex++] = *fontData;
            }
            continue;
        } else if (static_cast<SectionType>(*sectionType) == SectionType::ExpiryDate) {
            // Ignore expiry date sections here
            continue;
        }

        AlwaysDrawnSectionMetaData section;
        if (static_cast<SectionType>(*sectionType) == SectionType::AlwaysDrawn) {
            auto metaData = parseAlwaysDrawnSection(*rawSectionData, *version);
            if (!metaData) {
                return std::unexpected(metaData.error());
            }
            section = *metaData;
        } else if (static_cast<SectionType>(*sectionType) == SectionType::TimeBasedDrawn) {
            auto metaData = parseTimeBasedDrawnSection(*rawSectionData, *version);
            if (!metaData) {
                return std::unexpected(metaData.error());
            }
            if (currentTimestamp < metaData->startTimestamp || currentTimestamp >= metaData->endTimestamp) {
                continue;
            }
            section = metaData->base;
        } else if (static_cast<SectionType>(*sectionType) == SectionType::MultiTimeBasedDrawn) {
            auto metaData = parseMultiTimeBasedDrawnSection(*rawSectionData, *version);
            if (!metaData) {
                return std::unexpected(metaData.error());
            }
            bool inRange = false;
            for (std::size_t i = 0; i < metaData->rangeCount; ++i) {
                auto startTimestamp = readU64LE(metaData->rangeData);
                if (!startTimestamp) {
                    return std::unexpected(startTimestamp.error());
                }
                auto endTimestamp = readU64LE(metaData->rangeData);
                if (!endTimestamp) {
                    return std::unexpected(endTimestamp.error());
                }
                if (currentTimestamp >= std::bit_cast<std::int64_t>(*startTimestamp) && currentTimestamp < std::bit_cast<std::int64_t>(*endTimestamp)) {
                    inRange = true;
                    break;
                }
            }
            if (!inRange) {
                continue;
            }
            section = metaData->base;
        } else {
            continue;
        }

        if ((!section.drawOnFront && isFront)
            || (!section.drawOnBack && !isFront)) {
            continue;
        }

        if (section.clearBeforeDrawing) {
            for (std::uint16_t y = 0; y < screenHeight; ++y) {
                for (std::uint16_t x = 0; x < screenWidth; ++x) {
                    screen->setPixel(x, y, false);
                }
            }
        }

        while (section.elementData.size() > 0) {
            auto type = peekU16LE(section.elementData);
            if (!type) {
                return std::unexpected(type.error());
            }

            auto eType = static_cast<ElementType>(*type);
            std::expected<void, ParseError> parseResult;
            switch (eType) {
                case ElementType::Image: parseResult = handleImage(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex), *version); break;
            case ElementType::Animation: parseResult = handleAnimation(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex), *version); break;
            case ElementType::HScrollImage: parseResult = handleHScrollImage(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex), *version); break;
            case ElementType::VScrollImage: parseResult = handleVScrollImage(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex), *version); break;
            case ElementType::Line: parseResult = handleLine(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex), *version); break;
            case ElementType::Box: parseResult = handleBox(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex), *version); break;
            case ElementType::ClippedText: parseResult = handleClippedText(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex), *version); break;
            case ElementType::HScrollText: parseResult = handleHScrollText(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex), *version); break;
            case ElementType::CurrentTime: parseResult = handleCurrentTime(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex), *version); break;
            default: parseResult = std::unexpected(ParseError::InvalidValue);
            }

            if (!parseResult) {
                return std::unexpected(parseResult.error());
            }
        }
    }

    return {};
}

std::expected<bool, ParseError> parseAndCheckExpiry(std::span<std::byte const> data, std::int64_t currentTimestamp) {
    auto magic = readU32LE(data);
    if (!magic) {
        return std::unexpected(magic.error());
    }
    if (*magic != UINT32_C(0x632B7EAF)) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto version = readU32LE(data);
    if (!version) {
        return std::unexpected(version.error());
    }
    if (*version == 1) {
        // Version 1 doesn't ever expire
        return false;
    }
    if (*version < 1 || *version > 2) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto sectionCount = readU16LE(data);
    if (!sectionCount) {
        return std::unexpected(sectionCount.error());
    }
    auto reserved_ = readU16LE(data);
    if (!reserved_) {
        return std::unexpected(reserved_.error());
    }

    for (std::size_t i = 0; i < *sectionCount; ++i) {
        auto sectionType = peekU8LE(data);
        if (!sectionType) {
            return std::unexpected(sectionType.error());
        }
        auto sectionSize = peekU24LE(data.subspan(1));
        if (!sectionSize) {
            return std::unexpected(sectionSize.error());
        }
        auto rawSectionData = readBuffer(data, *sectionSize);
        if (!rawSectionData) {
            return std::unexpected(rawSectionData.error());
        }

        if (static_cast<SectionType>(*sectionType) != SectionType::ExpiryDate) {
            // Ignore expiry date sections here
            continue;
        }

        auto headerData = rawSectionData->subspan(4);
        auto expiryDate = readU64LE(headerData);
        if (!expiryDate) {
            return std::unexpected(expiryDate.error());
        }

        if (currentTimestamp >= std::bit_cast<std::int64_t>(*expiryDate)) {
            return true;
        }

        // Don't abort here, there could be multiple expiry dates in the file
    }

    // The file isn't expired
    return false;
}

std::expected<void, ParseError> handleImage(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts, std::uint32_t formatVersion) {
    std::ignore = formatVersion;

    auto type = readU16LE(buffer);
    if (!type) {
        return std::unexpected(type.error());
    }
    if (*type != static_cast<std::uint16_t>(ElementType::Image)) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto x = readU16LE(buffer);
    if (!x) {
        return std::unexpected(x.error());
    }
    auto y = readU16LE(buffer);
    if (!y) {
        return std::unexpected(y.error());
    }
    auto width = readU16LE(buffer);
    if (!width) {
        return std::unexpected(width.error());
    }
    auto height = readU16LE(buffer);
    if (!height) {
        return std::unexpected(height.error());
    }
    auto reserved_ = readU16LE(buffer);
    if (!reserved_) {
        return std::unexpected(reserved_.error());
    }
    std::size_t imageSize = ((*width) * (*height) + 8 - 1) / 8;
    auto imageData = readBuffer(buffer, imageSize);
    if (!imageData) {
        return std::unexpected(reserved_.error());
    }
    std::size_t rounded = (imageSize + 4 - 1) / 4 * 4;
    if (imageSize < rounded) {
        buffer = buffer.subspan(rounded - imageSize);
    }

#if defined(DEBUG)
    std::cerr << "[Debug] - Image, x = " << (*x) << ", y = " << (*y) << ", width = " << (*width) << ", height = " << (*height) << std::endl;
#endif

    std::ignore = animationTick;
    std::ignore = currentTimestamp;
    std::ignore = customFonts;
    auto ownImage = ConstMemoryOneBitBuffer{*imageData, *width, *height};
    for (std::uint16_t dy = 0; dy < *height; ++dy) {
        for (std::uint16_t dx = 0; dx < *width; ++dx) {
            screen->setPixel(*x + dx, *y + dy, ownImage.isPixelSet(dx, dy));
        }
    }

    return {};
}

std::expected<void, ParseError> handleAnimation(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts, std::uint32_t formatVersion) {
    std::ignore = formatVersion;
    std::ignore = currentTimestamp;
    std::ignore = customFonts;

    auto type = readU16LE(buffer);
    if (!type) {
        return std::unexpected(type.error());
    }
    if (*type != static_cast<std::uint16_t>(ElementType::Animation)) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto x = readU16LE(buffer);
    if (!x) {
        return std::unexpected(x.error());
    }
    auto y = readU16LE(buffer);
    if (!y) {
        return std::unexpected(y.error());
    }
    auto width = readU16LE(buffer);
    if (!width) {
        return std::unexpected(width.error());
    }
    auto height = readU16LE(buffer);
    if (!height) {
        return std::unexpected(height.error());
    }
    auto numberOfFrames = readU16LE(buffer);
    if (!numberOfFrames) {
        return std::unexpected(numberOfFrames.error());
    }
    auto updateInterval = readU16LE(buffer);
    if (!updateInterval) {
        return std::unexpected(updateInterval.error());
    }
    auto reserved_ = readU16LE(buffer);
    if (!reserved_) {
        return std::unexpected(reserved_.error());
    }
    std::size_t imageSize = ((*width) * (*height) + 8 - 1) / 8;
    auto imageData = readBuffer(buffer, imageSize * (*numberOfFrames));
    if (!imageData) {
        return std::unexpected(reserved_.error());
    }
    std::size_t rounded = (imageSize * (*numberOfFrames) + 4 - 1) / 4 * 4;
    if (imageSize * (*numberOfFrames) < rounded) {
        buffer = buffer.subspan(rounded - imageSize * (*numberOfFrames));
    }

#if defined(DEBUG)
    std::cerr << "[Debug] - Animation, x = " << (*x) << ", y = " << (*y) << ", width = " << (*width) << ", height = " << (*height)
              << ", numberOfFrames = " << (*numberOfFrames)
              << ", updateInterval = " << (*updateInterval)
              << std::endl;
#endif

    if (*numberOfFrames == 0) {
        return {};
    }

    std::size_t const tick = animationTick / (static_cast<std::size_t>(*updateInterval) + 1);
    std::size_t const frameIndex = tick % *numberOfFrames;
    auto ownImage = ConstMemoryOneBitBuffer{std::span{*imageData}.subspan(frameIndex * imageSize, imageSize), *width, *height};
    for (std::uint16_t dy = 0; dy < *height; ++dy) {
        for (std::uint16_t dx = 0; dx < *width; ++dx) {
            screen->setPixel(*x + dx, *y + dy, ownImage.isPixelSet(dx, dy));
        }
    }

    return {};
}

std::expected<void, ParseError> handleHScrollImage(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts, std::uint32_t formatVersion) {
    std::ignore = formatVersion;
    std::ignore = currentTimestamp;
    std::ignore = customFonts;

    auto type = readU16LE(buffer);
    if (!type) {
        return std::unexpected(type.error());
    }
    if (*type != static_cast<std::uint16_t>(ElementType::HScrollImage)) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto x = readU16LE(buffer);
    if (!x) {
        return std::unexpected(x.error());
    }
    auto y = readU16LE(buffer);
    if (!y) {
        return std::unexpected(y.error());
    }
    auto width = readU16LE(buffer);
    if (!width) {
        return std::unexpected(width.error());
    }
    auto height = readU16LE(buffer);
    if (!height) {
        return std::unexpected(height.error());
    }
    auto contentWidth = readU16LE(buffer);
    if (!contentWidth) {
        return std::unexpected(contentWidth.error());
    }
    auto flags = readU8LE(buffer);
    if (!flags) {
        return std::unexpected(flags.error());
    }
    auto scrollSpeed = readU8LE(buffer);
    if (!scrollSpeed) {
        return std::unexpected(scrollSpeed.error());
    }
    auto reserved_ = readU16LE(buffer);
    if (!reserved_) {
        return std::unexpected(reserved_.error());
    }
    std::size_t imageSize = ((*contentWidth) * (*height) + 8 - 1) / 8;
    auto imageData = readBuffer(buffer, imageSize);
    if (!imageData) {
        return std::unexpected(reserved_.error());
    }
    std::size_t rounded = (imageSize + 4 - 1) / 4 * 4;
    if (imageSize < rounded) {
        buffer = buffer.subspan(rounded - imageSize);
    }

    auto ownImage = ConstMemoryOneBitBuffer{*imageData, *contentWidth, *height};
    ClippedImage target{screen, *x, *y, *width, *height};

#if defined(DEBUG)
    std::cerr << "[Debug] - HScrollImage, x = " << (*x) << ", y = " << (*y) << ", width = " << (*width) << ", height = " << (*height)
              << ", contentWidth = " << (*contentWidth)
              << ", scrollSpeed = " << int(*scrollSpeed)
              << ", flags = " << int(*flags)
              << std::endl;
#endif

    if (*contentWidth == 0) {
        return {};
    }

    ScrollFlags scrollFlags{*flags};

    bool restarting = !scrollFlags.endless;
    bool invert = scrollFlags.invertDirection;
    bool padBefore = scrollFlags.padBefore;
    bool padAfter = scrollFlags.padAfter;

    if (!padBefore && !padAfter && *contentWidth < *width) {
        std::int16_t offset = invert ? (*width - *contentWidth) : 0;
        for (std::size_t dy = 0; dy < *height; ++dy) {
            for (std::size_t dx = 0; dx < *contentWidth; ++dx) {
                target.setPixel(dx + offset, dy, ownImage.isPixelSet(dx, dy));
            }
        }
        return {};
    }

    std::int32_t totalSize = *contentWidth;
    if (padBefore) {
        totalSize += *width;
    }
    if (padAfter) {
        totalSize += *width;
    }

    // FIXME: handle overflow here...
    std::int32_t offset = static_cast<std::int32_t>(animationTick * (*scrollSpeed + 1) / 8) % totalSize;
    if (invert) {
        offset = totalSize - offset - 1;
    }
    if (restarting) {
        offset = std::min(offset, totalSize - *width);
    }

    std::int32_t partOffset = 0;
    if (padBefore) {
        for (std::int32_t dy = 0; dy < *height; ++dy) {
            for (std::int32_t dx = 0; dx < *width; ++dx) {
                std::int32_t ddx = dx + partOffset;
                if ((ddx - offset) < 0) {
                    continue;
                }
                target.setPixel(ddx - offset, dy, false);
            }
        }
        partOffset += *width;
    }
    for (std::int32_t dy = 0; dy < *height; ++dy) {
        for (std::int32_t dx = 0; dx < *contentWidth; ++dx) {
            std::int32_t ddx = dx + partOffset;
            if ((ddx - offset) < 0) {
                continue;
            }
            target.setPixel(ddx - offset, dy, ownImage.isPixelSet(dx, dy));
        }
        partOffset += *contentWidth;
    }
    if (padAfter) {
        for (std::int32_t dy = 0; dy < *height; ++dy) {
            for (std::int32_t dx = 0; dx < *width; ++dx) {
                std::int32_t ddx = dx + partOffset;
                if ((ddx - offset) < 0) {
                    continue;
                }
                target.setPixel(ddx - offset, dy, false);
            }
        }
        partOffset += *width;
    }
    if (!restarting) {
        if (padBefore) {
            for (std::int32_t dy = 0; dy < *height; ++dy) {
                for (std::int32_t dx = 0; dx < *width; ++dx) {
                    std::int32_t ddx = dx + partOffset;
                    if ((ddx - offset) < 0) {
                        continue;
                    }
                    target.setPixel(ddx - offset, dy, false);
                }
            }
            partOffset += *width;
        }
        for (std::int32_t dy = 0; dy < *height; ++dy) {
            for (std::int32_t dx = 0; dx < *contentWidth; ++dx) {
                std::int32_t ddx = dx + partOffset;
                if ((ddx - offset) < 0) {
                    continue;
                }
                target.setPixel(ddx - offset, dy, ownImage.isPixelSet(dx, dy));
            }
        }
        partOffset += *contentWidth;
    }

    return {};
}

std::expected<void, ParseError> handleVScrollImage(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts, std::uint32_t formatVersion) {
    std::ignore = formatVersion;
    std::ignore = currentTimestamp;
    std::ignore = customFonts;

    auto type = readU16LE(buffer);
    if (!type) {
        return std::unexpected(type.error());
    }
    if (*type != static_cast<std::uint16_t>(ElementType::VScrollImage)) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto x = readU16LE(buffer);
    if (!x) {
        return std::unexpected(x.error());
    }
    auto y = readU16LE(buffer);
    if (!y) {
        return std::unexpected(y.error());
    }
    auto width = readU16LE(buffer);
    if (!width) {
        return std::unexpected(width.error());
    }
    auto height = readU16LE(buffer);
    if (!height) {
        return std::unexpected(height.error());
    }
    auto contentHeight = readU16LE(buffer);
    if (!contentHeight) {
        return std::unexpected(contentHeight.error());
    }
    auto flags = readU8LE(buffer);
    if (!flags) {
        return std::unexpected(flags.error());
    }
    auto scrollSpeed = readU8LE(buffer);
    if (!scrollSpeed) {
        return std::unexpected(scrollSpeed.error());
    }
    auto reserved_ = readU16LE(buffer);
    if (!reserved_) {
        return std::unexpected(reserved_.error());
    }
    std::size_t imageSize = ((*width) * (*contentHeight) + 8 - 1) / 8;
    auto imageData = readBuffer(buffer, imageSize);
    if (!imageData) {
        return std::unexpected(reserved_.error());
    }
    std::size_t rounded = (imageSize + 4 - 1) / 4 * 4;
    if (imageSize < rounded) {
        buffer = buffer.subspan(rounded - imageSize);
    }

    auto ownImage = ConstMemoryOneBitBuffer{*imageData, *width, *contentHeight};
    ClippedImage target{screen, *x, *y, *width, *height};

#if defined(DEBUG)
    std::cerr << "[Debug] - VScrollImage, x = " << (*x) << ", y = " << (*y) << ", width = " << (*width) << ", height = " << (*height)
              << ", contentHeight = " << (*contentHeight)
              << ", scrollSpeed = " << int(*scrollSpeed)
              << ", flags = " << int(*flags)
              << std::endl;
#endif

    if (*contentHeight == 0) {
        return {};
    }

    ScrollFlags scrollFlags{*flags};

    bool restarting = !scrollFlags.endless;
    bool invert = scrollFlags.invertDirection;
    bool padBefore = scrollFlags.padBefore;
    bool padAfter = scrollFlags.padAfter;

    if (!padBefore && !padAfter && *contentHeight < *height) {
        std::int16_t offset = invert ? (*height - *contentHeight) : 0;
        for (std::size_t dy = 0; dy < *contentHeight; ++dy) {
            for (std::size_t dx = 0; dx < *width; ++dx) {
                target.setPixel(dx, dy + offset, ownImage.isPixelSet(dx, dy));
            }
        }
        return {};
    }

    std::int32_t totalSize = *contentHeight;
    if (padBefore) {
        totalSize += *height;
    }
    if (padAfter) {
        totalSize += *height;
    }

    // FIXME: handle overflow here...
    std::int32_t offset = static_cast<std::int32_t>(animationTick * (*scrollSpeed + 1) / 8) % totalSize;
    if (invert) {
        offset = totalSize - offset - 1;
    }
    if (restarting) {
        offset = std::min(offset, totalSize - *height);
    }

    std::int32_t partOffset = 0;
    if (padBefore) {
        for (std::int32_t dy = 0; dy < *height; ++dy) {
            std::int32_t ddy = dy + partOffset;
            if ((ddy - offset) < 0) {
                continue;
            }
            for (std::int32_t dx = 0; dx < *width; ++dx) {
                target.setPixel(dx, ddy - offset, false);
            }
        }
        partOffset += *height;
    }
    for (std::int32_t dy = 0; dy < *contentHeight; ++dy) {
        std::int32_t ddy = dy + partOffset;
        if ((ddy - offset) < 0) {
            continue;
        }
        for (std::int32_t dx = 0; dx < *width; ++dx) {
            target.setPixel(dx, ddy - offset, ownImage.isPixelSet(dx, dy));
        }
        partOffset += *contentHeight;
    }
    if (padAfter) {
        for (std::int32_t dy = 0; dy < *height; ++dy) {
            std::int32_t ddy = dy + partOffset;
            if ((ddy - offset) < 0) {
                continue;
            }
            for (std::int32_t dx = 0; dx < *width; ++dx) {
                target.setPixel(dx, ddy - offset, false);
            }
        }
        partOffset += *height;
    }
    if (!restarting) {
        if (padBefore) {
            for (std::int32_t dy = 0; dy < *height; ++dy) {
                std::int32_t ddy = dy + partOffset;
                if ((ddy - offset) < 0) {
                    continue;
                }
                for (std::int32_t dx = 0; dx < *width; ++dx) {
                    target.setPixel(dx, ddy - offset, false);
                }
            }
            partOffset += *height;
        }
        for (std::int32_t dy = 0; dy < *contentHeight; ++dy) {
            std::int32_t ddy = dy + partOffset;
            if ((ddy - offset) < 0) {
                continue;
            }
            for (std::int32_t dx = 0; dx < *width; ++dx) {
                target.setPixel(dx, ddy - offset, ownImage.isPixelSet(dx, dy));
            }
        }
        partOffset += *contentHeight;
    }

    return {};
}

std::expected<void, ParseError> handleLine(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts, std::uint32_t formatVersion) {
    std::ignore = formatVersion;
    std::ignore = animationTick;
    std::ignore = currentTimestamp;
    std::ignore = customFonts;

    auto type = readU16LE(buffer);
    if (!type) {
        return std::unexpected(type.error());
    }
    if (*type != static_cast<std::uint16_t>(ElementType::Line)) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto originX = readU16LE(buffer);
    if (!originX) {
        return std::unexpected(originX.error());
    }
    auto originY = readU16LE(buffer);
    if (!originY) {
        return std::unexpected(originY.error());
    }
    auto targetX = readU16LE(buffer);
    if (!targetX) {
        return std::unexpected(targetX.error());
    }
    auto targetY = readU16LE(buffer);
    if (!targetY) {
        return std::unexpected(targetY.error());
    }
    auto lineStyle = readU8LE(buffer);
    if (!lineStyle) {
        return std::unexpected(lineStyle.error());
    }
    if (*lineStyle != static_cast<std::uint8_t>(LineStyle::Solid)) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto flags = readU8LE(buffer);
    if (!flags) {
        return std::unexpected(flags.error());
    }

#if defined(DEBUG)
    std::cerr << "[Debug] - Line, originX = " << (*originX) << ", originY = " << (*originY) << ", targetX = " << (*targetX) << ", targetY = " << (*targetY)
              << ", lineStyle = " << int(*lineStyle)
              << std::endl;
#endif

    LineFlags lineFlags{*flags};

    std::int32_t dx = static_cast<std::int32_t>(*targetX) - static_cast<std::int32_t>(*originX);
    std::int32_t dy = static_cast<std::int32_t>(*targetY) - static_cast<std::int32_t>(*originY);
    bool value = !lineFlags.dark;

    if (dx == 0) {
        if (dy < 0) {
            std::swap(originY, targetY);
            dy = -dy;
        }
        for (std::int32_t i = 0; i <= dy; ++i) {
            screen->setPixel(*originX, *originY + i, value);
        }
    } else if (dy == 0) {
        if (dx < 0) {
            std::swap(originX, targetX);
            dx = -dx;
        }
        for (std::int32_t i = 0; i <= dx; ++i) {
            screen->setPixel(*originX + i, *originY, value);
        }
    } else {
        for (std::int32_t i = 0; i <= dx; ++i) {
            std::int32_t x = *originX + i;
            std::int32_t y = i * dy / dx + *originY;
            screen->setPixel(static_cast<std::uint16_t>(x), static_cast<std::uint16_t>(y), value);
        }
    }

    return {};
}

std::expected<void, ParseError> handleBox(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts, std::uint32_t formatVersion) {
    std::ignore = formatVersion;
    std::ignore = animationTick;
    std::ignore = currentTimestamp;
    std::ignore = customFonts;

    auto type = readU16LE(buffer);
    if (!type) {
        return std::unexpected(type.error());
    }
    if (*type != static_cast<std::uint16_t>(ElementType::Box)) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto x = readU16LE(buffer);
    if (!x) {
        return std::unexpected(x.error());
    }
    auto y = readU16LE(buffer);
    if (!y) {
        return std::unexpected(y.error());
    }
    auto width = readU16LE(buffer);
    if (!width) {
        return std::unexpected(width.error());
    }
    auto height = readU16LE(buffer);
    if (!height) {
        return std::unexpected(height.error());
    }
    auto fillPattern = readU8LE(buffer);
    if (!fillPattern) {
        return std::unexpected(fillPattern.error());
    }
    if (*fillPattern != static_cast<std::uint8_t>(FillPattern::Solid)) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto flags = readU8LE(buffer);
    if (!flags) {
        return std::unexpected(flags.error());
    }

#if defined(DEBUG)
    std::cerr << "[Debug] - Box, x = " << (*x) << ", y = " << (*y) << ", width = " << (*width) << ", height = " << (*height)
              << ", fillPattern = " << int(*fillPattern)
              << std::endl;
#endif

    FillFlags fillFlags{*flags};

    bool value = !fillFlags.dark;

    for (std::int32_t dy = 0; dy < *height; ++dy) {
        for (std::int32_t dx = 0; dx < *width; ++dx) {
            screen->setPixel(dx + *x, dy + *y, value);
        }
    }

    return {};
}

std::expected<void, ParseError> handleClippedText(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts, std::uint32_t formatVersion) {
    std::ignore = formatVersion;
    std::ignore = animationTick;
    std::ignore = currentTimestamp;

    auto type = readU16LE(buffer);
    if (!type) {
        return std::unexpected(type.error());
    }
    if (*type != static_cast<std::uint16_t>(ElementType::ClippedText)) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto x = readU16LE(buffer);
    if (!x) {
        return std::unexpected(x.error());
    }
    auto y = readU16LE(buffer);
    if (!y) {
        return std::unexpected(y.error());
    }
    auto width = readU16LE(buffer);
    if (!width) {
        return std::unexpected(width.error());
    }
    auto height = readU16LE(buffer);
    if (!height) {
        return std::unexpected(height.error());
    }
    std::expected<std::uint8_t, ParseError> textFlags_ = 0;
    if (formatVersion >= 2) {
        textFlags_ = readU8LE(buffer);
        if (!textFlags_) {
            return std::unexpected(textFlags_.error());
        }
        auto reserved1 = readU8LE(buffer);
        if (!reserved1) {
            return std::unexpected(reserved1.error());
        }
    }
    TextFlags textFlags{*textFlags_};
    auto fontIndex = readU16LE(buffer);
    if (!fontIndex) {
        return std::unexpected(fontIndex.error());
    }
    auto textLength = readU16LE(buffer);
    if (!textLength) {
        return std::unexpected(textLength.error());
    }
    std::size_t alignedRoundedUp = (*textLength + 2 + 4 - 1) / 4 * 4 - 2;
    if (formatVersion >= 2) {
        alignedRoundedUp = (*textLength + 4 - 1) / 4 * 4;
    }
    auto textData = readBuffer(buffer, alignedRoundedUp);
    if (!textData) {
        return std::unexpected(textData.error());
    }
    std::string_view text{reinterpret_cast<char const*>(textData->data()), *textLength};

#if defined(DEBUG)
    std::cerr << "[Debug] - ClippedText, x = " << (*x) << ", y = " << (*y) << ", width = " << (*width) << ", height = " << (*height)
              << ", fontIndex = " << int(*fontIndex)
              << ", text = \"" << text << "\""
              << std::endl;
#endif

    std::span<std::byte const> fontData;
    if (isBitSet(*fontIndex, 15)) {
        unsetBit(*fontIndex, 15);
        if (*fontIndex >= customFonts.size()) {
            return {};
        }
        fontData = customFonts[*fontIndex];
    } else {
        fontData = findEmbeddedFont(*fontIndex);
    }
    if (fontData.size() < 23) {
        return {};
    }

    std::optional<bool> bgColor;
    bgColor = textFlags.dark;
    bool fgColor = !textFlags.dark;

    ClippedImage target{screen, *x, *y, *width, *height};
    auto renderer = FontRenderer{fontData}.withIgnoreUnknownChars(true);
    if (textFlags.autoWordWrap) {
        auto dimensions = renderer.getRenderedDimensions(text, Point{0, 0});
        if (!dimensions) {
            return {};
        }
        std::int32_t y = 0;
        while (dimensions->boundingBox && dimensions->boundingBox->size.width > (*width)) {
            std::size_t pos = text.find('\n');
            if (pos == std::string_view::npos) {
                pos = text.size();
            }
            std::string_view currentLine = text.substr(0, pos);
            if (pos < text.size()) {
                text = text.substr(pos + 1);
            } else {
                text = text.substr(pos);
            }
            while (currentLine.size() > 0) {
                dimensions = renderer.getRenderedDimensions(currentLine, Point{0, 0});
                if (!dimensions) {
                    return {};
                }
                std::size_t pos = currentLine.size();
                while (pos > 0 && dimensions->boundingBox && dimensions->boundingBox->size.width > (*width)) {
                    std::size_t prevPos = pos;
                    pos = currentLine.rfind(' ', prevPos - 1);
                    if (pos == std::string_view::npos) {
                        pos = prevPos;
                        break;
                    }
                    dimensions = renderer.getRenderedDimensions(currentLine.substr(0, pos), Point{0, 0});
                    if (!dimensions) {
                        return {};
                    }
                }
                renderer.render(currentLine.substr(0, pos), Point{0, y + static_cast<std::int32_t>(renderer.lineHeight() - 2)}, target, fgColor, bgColor);
                y += renderer.lineHeight();
                if (pos < currentLine.size()) {
                    currentLine = currentLine.substr(pos + 1);
                } else {
                    currentLine = currentLine.substr(pos);
                }
            }
            dimensions = renderer.getRenderedDimensions(text, Point{0, 0});
            if (!dimensions) {
                return {};
            }
        }
        if (text.size() > 0) {
            renderer.render(text, Point{0, y + static_cast<std::int32_t>(renderer.lineHeight() - 2)}, target, fgColor, bgColor);
            y += renderer.lineHeight();
        }
    } else {
        renderer.render(text, Point{0, static_cast<std::int32_t>(renderer.lineHeight() - 2)}, target, fgColor, bgColor);
    }

    return {};
}

std::expected<void, ParseError> handleHScrollText(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts, std::uint32_t formatVersion) {
    std::ignore = formatVersion;
    std::ignore = currentTimestamp;

    auto type = readU16LE(buffer);
    if (!type) {
        return std::unexpected(type.error());
    }
    if (*type != static_cast<std::uint16_t>(ElementType::HScrollText)) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto x = readU16LE(buffer);
    if (!x) {
        return std::unexpected(x.error());
    }
    auto y = readU16LE(buffer);
    if (!y) {
        return std::unexpected(y.error());
    }
    auto width = readU16LE(buffer);
    if (!width) {
        return std::unexpected(width.error());
    }
    auto height = readU16LE(buffer);
    if (!height) {
        return std::unexpected(height.error());
    }
    std::expected<std::uint8_t, ParseError> textFlags_ = 0;
    if (formatVersion >= 2) {
        textFlags_ = readU8LE(buffer);
        if (!textFlags_) {
            return std::unexpected(textFlags_.error());
        }
        auto reserved1 = readU8LE(buffer);
        if (!reserved1) {
            return std::unexpected(reserved1.error());
        }
    }
    TextFlags textFlags{*textFlags_};
    auto flags = readU8LE(buffer);
    if (!flags) {
        return std::unexpected(flags.error());
    }
    auto scrollSpeed = readU8LE(buffer);
    if (!scrollSpeed) {
        return std::unexpected(scrollSpeed.error());
    }
    if (formatVersion >= 2) {
        auto reserved2 = readU16LE(buffer);
        if (!reserved2) {
            return std::unexpected(reserved2.error());
        }
    }
    auto fontIndex = readU16LE(buffer);
    if (!fontIndex) {
        return std::unexpected(fontIndex.error());
    }
    auto textLength = readU16LE(buffer);
    if (!textLength) {
        return std::unexpected(textLength.error());
    }
    std::size_t alignedRoundedUp = (*textLength + 4 - 1) / 4 * 4;
    auto textData = readBuffer(buffer, alignedRoundedUp);
    if (!textData) {
        return std::unexpected(textData.error());
    }
    std::string_view text{reinterpret_cast<char const*>(textData->data()), *textLength};

#if defined(DEBUG)
    std::cerr << "[Debug] - HScrollText, x = " << (*x) << ", y = " << (*y) << ", width = " << (*width) << ", height = " << (*height)
              << ", scrollSpeed = " << int(*scrollSpeed)
              << ", flags = " << int(*flags)
              << ", fontIndex = " << int(*fontIndex)
              << ", text = \"" << text << "\""
              << std::endl;
#endif

    std::span<std::byte const> fontData;
    if (isBitSet(*fontIndex, 15)) {
        unsetBit(*fontIndex, 15);
        if (*fontIndex >= customFonts.size()) {
            return {};
        }
        fontData = customFonts[*fontIndex];
    } else {
        fontData = findEmbeddedFont(*fontIndex);
    }
    if (fontData.size() < 23) {
        return {};
    }
    ClippedImage target{screen, *x, *y, *width, *height};
    auto renderer = FontRenderer{fontData}.withIgnoreUnknownChars(true);
    auto dimensions = renderer.getRenderedDimensions(text, {0, static_cast<std::int32_t>(renderer.lineHeight() - 2)});
    if (!dimensions || !dimensions->boundingBox) {
        return {};
    }

    std::uint16_t contentWidth = dimensions->boundingBox->size.width;

    if (contentWidth == 0) {
        return {};
    }

    ScrollFlags scrollFlags{*flags};

    std::optional<bool> bgColor;
    bgColor = textFlags.dark;
    bool fgColor = !textFlags.dark;

    bool restarting = !scrollFlags.endless;
    bool invert = scrollFlags.invertDirection;
    bool padBefore = scrollFlags.padBefore;
    bool padAfter = scrollFlags.padAfter;

    if (!padBefore && !padAfter && contentWidth < *width) {
        std::uint16_t offset = invert ? (*width - contentWidth) : 0;
        renderer.render(text, Point{offset, static_cast<std::int32_t>(renderer.lineHeight() - 2)}, target, fgColor, bgColor);
        return {};
    }

    std::int32_t totalSize = contentWidth;
    if (padBefore) {
        totalSize += *width;
    }
    if (padAfter) {
        totalSize += *width;
    }

    // FIXME: handle overflow here...
    std::int32_t offset = static_cast<std::int32_t>(animationTick * (*scrollSpeed + 1) / 8) % totalSize;

    if (invert) {
        offset = totalSize - offset - 1;
    }
    if (restarting) {
        offset = std::min(offset, totalSize - *width);
    }

    std::int32_t partOffset = 0;
    if (padBefore) {
        if (bgColor) {
            for (std::int32_t dy = 0; dy < *height; ++dy) {
                for (std::int32_t dx = 0; dx < *width; ++dx) {
                    std::int32_t ddx = dx + partOffset;
                    if ((ddx - offset) < 0) {
                        continue;
                    }
                    target.setPixel(ddx - offset, dy, *bgColor);
                }
            }
        }
        partOffset += *width;
    }
    renderer.render(text, Point{partOffset - offset, static_cast<std::int32_t>(renderer.lineHeight() - 2)}, target, fgColor, bgColor);
    partOffset += contentWidth;
    if (padAfter) {
        if (bgColor) {
            for (std::int32_t dy = 0; dy < *height; ++dy) {
                for (std::int32_t dx = 0; dx < *width; ++dx) {
                    std::int32_t ddx = dx + partOffset;
                    if ((ddx - offset) < 0) {
                        continue;
                    }
                    target.setPixel(ddx - offset, dy, *bgColor);
                }
            }
        }
        partOffset += *width;
    }
    if (!restarting) {
        if (padBefore) {
            if (bgColor) {
                for (std::int32_t dy = 0; dy < *height; ++dy) {
                    for (std::int32_t dx = 0; dx < *width; ++dx) {
                        std::int32_t ddx = dx + partOffset;
                        if ((ddx - offset) < 0) {
                            continue;
                        }
                        target.setPixel(ddx - offset, dy, *bgColor);
                    }
                }
            }
            partOffset += *width;
        }
        renderer.render(text, Point{partOffset - offset, static_cast<std::int32_t>(renderer.lineHeight() - 2)}, target, fgColor, bgColor);
    }

    return {};
}

std::expected<void, ParseError> handleCurrentTime(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts, std::uint32_t formatVersion) {
    std::ignore = formatVersion;
    std::ignore = animationTick;

    auto type = readU16LE(buffer);
    if (!type) {
        return std::unexpected(type.error());
    }
    if (*type != static_cast<std::uint16_t>(ElementType::CurrentTime)) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto x = readU16LE(buffer);
    if (!x) {
        return std::unexpected(x.error());
    }
    auto y = readU16LE(buffer);
    if (!y) {
        return std::unexpected(y.error());
    }
    auto width = readU16LE(buffer);
    if (!width) {
        return std::unexpected(width.error());
    }
    auto height = readU16LE(buffer);
    if (!height) {
        return std::unexpected(height.error());
    }
    std::expected<std::uint8_t, ParseError> textFlags_ = 0;
    if (formatVersion >= 2) {
        textFlags_ = readU8LE(buffer);
        if (!textFlags_) {
            return std::unexpected(textFlags_.error());
        }
        auto reserved1 = readU8LE(buffer);
        if (!reserved1) {
            return std::unexpected(reserved1.error());
        }
    }
    TextFlags textFlags{*textFlags_};
    auto fontIndex = readU16LE(buffer);
    if (!fontIndex) {
        return std::unexpected(fontIndex.error());
    }
    auto utcOffset = readU16LE(buffer);
    if (!utcOffset) {
        return std::unexpected(utcOffset.error());
    }
    auto flags = readU16LE(buffer);
    if (!flags) {
        return std::unexpected(flags.error());
    }
    if (formatVersion >= 2) {
        auto reserved2 = readU16LE(buffer);
        if (!reserved2) {
            return std::unexpected(reserved2.error());
        }
    }

#if defined(DEBUG)
    std::cerr << "[Debug] - CurrentTime, x = " << (*x) << ", y = " << (*y) << ", width = " << (*width) << ", height = " << (*height)
              << ", fontIndex = " << int(*fontIndex)
              << ", utcOffset = " << int(*utcOffset)
              << ", flags = " << int(*flags)
              << std::endl;
#endif

    std::span<std::byte const> fontData;
    if (isBitSet(*fontIndex, 15)) {
        unsetBit(*fontIndex, 15);
        if (*fontIndex >= customFonts.size()) {
            return {};
        }
        fontData = customFonts[*fontIndex];
    } else {
        fontData = findEmbeddedFont(*fontIndex);
    }
    if (fontData.size() < 23) {
        return {};
    }

    std::optional<bool> bgColor;
    bgColor = textFlags.dark;
    bool fgColor = !textFlags.dark;

    ClippedImage target{screen, *x, *y, *width, *height};

    TimeDisplayFlags timeDisplayFlags{*flags};

    bool use12h = timeDisplayFlags.use12h;
    bool showHours = timeDisplayFlags.showHours;
    bool showMinutes = timeDisplayFlags.showMinutes;
    bool showSeconds = timeDisplayFlags.showSeconds;
    if (showHours && showSeconds) {
        showMinutes = true;
    }

    auto renderer = FontRenderer{fontData}.withIgnoreUnknownChars(true);
    std::uint32_t seconds = (currentTimestamp + static_cast<std::int64_t>(*utcOffset) * 60) % 86400;
    std::uint32_t hours = seconds / 3600;
    std::uint32_t minutes = (seconds - hours * 3600) / 60;
    seconds = seconds % 60;
    if (use12h) {
        hours = hours % 12;
        if (hours == 0) {
            hours = 12;
        }
    }

    char text[32] = {};
    if (showHours && showMinutes && showSeconds) {
        std::snprintf(text, sizeof(text), "%02d:%02d:%02d", int(hours), int(minutes), int(seconds));
    } else if (showHours && showMinutes) {
        std::snprintf(text, sizeof(text), "%02d:%02d", int(hours), int(minutes));
    } else if (showHours) {
        std::snprintf(text, sizeof(text), "%02d", int(hours));
    } else if (showMinutes && showSeconds) {
        std::snprintf(text, sizeof(text), "%02d:%02d", int(minutes), int(seconds));
    } else if (showSeconds) {
        std::snprintf(text, sizeof(text), "%02d", int(seconds));
    } else if (showMinutes) {
        std::snprintf(text, sizeof(text), "%02d", int(minutes));
    } else {
        return {};
    }

    renderer.render(text, Point{0, static_cast<std::int32_t>(renderer.lineHeight() - 2)}, target, fgColor, bgColor);

    return {};
}

} // namespace monoformat

