#include "monoformat_parseonly.hpp"
#include "monoformat_parsehelpers.hpp"
#include "monoformat_fontreader.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{};
};

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);
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);
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);
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);
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);
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);
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);
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);

static std::expected<AlwaysDrawnSectionMetaData, ParseError> parseAlwaysDrawnSection(std::span<std::byte const>& data) {
    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 = (((*flags) >> 0u) & 0x01u) == 0x01u;
    result.drawOnBack = (((*flags) >> 0u) & 0x02u) == 0x02u;
    result.clearBeforeDrawing = (((*flags) >> 0u) & 0x04u) == 0x04u;
    result.elementData = sectionData;
    return result;
}

static std::expected<TimeBasedDrawnSectionMetaData, ParseError> parseTimeBasedDrawnSection(std::span<std::byte const>& data) {
    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 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 = (((*flags) >> 0u) & 0x01u) == 0x01u;
    result.base.drawOnBack = (((*flags) >> 0u) & 0x02u) == 0x02u;
    result.base.clearBeforeDrawing = (((*flags) >> 0u) & 0x04u) == 0x04u;
    result.base.elementData = sectionData;
    result.startTimestamp = std::bit_cast<std::int64_t>(*startTimestamp);
    result.endTimestamp = std::bit_cast<std::int64_t>(*endTimestamp);
    return result;
}

static std::expected<std::span<std::byte const>, ParseError> parseCustomFontSection(std::span<std::byte const>& data) {
    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);

    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) {
        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);
            if (!fontData) {
                return std::unexpected(fontData.error());
            }
            if (customFontIndex < 16) {
                customFonts[customFontIndex++] = *fontData;
            }
            continue;
        }

        AlwaysDrawnSectionMetaData section;
        if (static_cast<SectionType>(*sectionType) == SectionType::AlwaysDrawn) {
            auto metaData = parseAlwaysDrawnSection(*rawSectionData);
            if (!metaData) {
                return std::unexpected(metaData.error());
            }
            section = *metaData;
        } else if (static_cast<SectionType>(*sectionType) == SectionType::TimeBasedDrawn) {
            auto metaData = parseTimeBasedDrawnSection(*rawSectionData);
            if (!metaData) {
                return std::unexpected(metaData.error());
            }
            if (currentTimestamp < metaData->startTimestamp || currentTimestamp >= metaData->endTimestamp) {
                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)); break;
            case ElementType::Animation: parseResult = handleAnimation(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex)); break;
            case ElementType::HScrollImage: parseResult = handleHScrollImage(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex)); break;
            case ElementType::VScrollImage: parseResult = handleVScrollImage(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex)); break;
            case ElementType::Line: parseResult = handleLine(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex)); break;
            case ElementType::ClippedText: parseResult = handleClippedText(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex)); break;
            case ElementType::HScrollText: parseResult = handleHScrollText(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex)); break;
            case ElementType::CurrentTime: parseResult = handleCurrentTime(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex)); break;
            default: parseResult = std::unexpected(ParseError::InvalidValue);
            }

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

    return {};
}

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) {
    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);
    }

    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::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);
    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 (*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::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 (*contentWidth == 0) {
        return {};
    }

    bool restarting = ((*flags) & 0x01) == 0x00;
    bool invert = ((*flags) & 0x02) == 0x02;
    bool padBefore = ((*flags) & 0x04) == 0x04;
    bool padAfter = ((*flags) & 0x08) == 0x08;

    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::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 (*contentHeight == 0) {
        return {};
    }

    bool restarting = ((*flags) & 0x01) == 0x00;
    bool invert = ((*flags) & 0x02) == 0x02;
    bool padBefore = ((*flags) & 0x04) == 0x04;
    bool padAfter = ((*flags) & 0x08) == 0x08;

    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::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());
    }

    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 = true;

    if (dx == 0) {
        for (std::int32_t i = 0; i <= dy; ++i) {
            screen->setPixel(*originX, *originY + i, value);
        }
    } else if (dy == 0) {
        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> 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::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());
    }
    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;
    auto textData = readBuffer(buffer, alignedRoundedUp);
    if (!textData) {
        return std::unexpected(textData.error());
    }
    std::string_view text{reinterpret_cast<char const*>(textData->data()), *textLength};

    std::span<std::byte const> fontData;
    if ((*fontIndex) & 0x8000u) {
        fontIndex = (*fontIndex) & ~0x8000u;
        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);
    renderer.render(text, Point{0, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, true, false);

    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::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());
    }
    auto flags = readU8LE(buffer);
    if (!flags) {
        return std::unexpected(flags.error());
    }
    auto scrollSpeed = readU8LE(buffer);
    if (!scrollSpeed) {
        return std::unexpected(scrollSpeed.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};

    std::span<std::byte const> fontData;
    if ((*fontIndex) & 0x8000u) {
        fontIndex = (*fontIndex) & ~0x8000u;
        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() - 1)});
    if (!dimensions || !dimensions->boundingBox) {
        return {};
    }

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

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

    bool restarting = ((*flags) & 0x01) == 0x00;
    bool invert = ((*flags) & 0x02) == 0x02;
    bool padBefore = ((*flags) & 0x04) == 0x04;
    bool padAfter = ((*flags) & 0x08) == 0x08;

    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() - 1)}, target, true, false);
        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;
    }
    renderer.render(text, Point{partOffset - offset, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, true, false);
    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;
        }
        renderer.render(text, Point{partOffset - offset, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, true, false);
    }

    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::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());
    }
    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());
    }

    std::span<std::byte const> fontData;
    if ((*fontIndex) & 0x8000u) {
        fontIndex = (*fontIndex) & ~0x8000u;
        if (*fontIndex >= customFonts.size()) {
            return {};
        }
        fontData = customFonts[*fontIndex];
    } else {
        fontData = findEmbeddedFont(*fontIndex);
    }
    if (fontData.size() < 23) {
        return {};
    }
    ClippedImage target{screen, *x, *y, *width, *height};

    bool use12h = ((*flags) & 0x01u) == 0x01u;
    bool showHours = ((*flags) & 0x02u) == 0x02u;
    bool showMinutes = ((*flags) & 0x04u) == 0x04u;
    bool showSeconds = ((*flags) & 0x08u) == 0x08u;
    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() - 1)}, target, true, false);

    return {};
}

} // namespace monoformat

