#include "monoformat_structured.hpp"
#include "monoformat_fontreader.hpp"

#include <iostream>

namespace monoformat {

ImageElement::ImageElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height)
    : m_x{x}
    , m_y{y}
    , m_width{width}
    , m_height{height}
{
    std::size_t const bufferSize = (m_width * m_height + 8 - 1) / 8;\
    m_buffer.resize(bufferSize);
}

std::uint16_t ImageElement::x() const noexcept {
    return m_x;
}

std::uint16_t ImageElement::y() const noexcept {
    return m_y;
}

std::uint16_t ImageElement::width() const noexcept {
    return m_width;
}

std::uint16_t ImageElement::height() const noexcept {
    return m_height;
}

std::span<std::byte> ImageElement::buffer() noexcept {
    return m_buffer;
}

std::span<std::byte const> ImageElement::buffer() const noexcept {
    return m_buffer;
}

MemoryOneBitBuffer ImageElement::image() noexcept {
    return MemoryOneBitBuffer{m_buffer, m_width, m_height};
}

ConstMemoryOneBitBuffer ImageElement::image() const noexcept {
    return ConstMemoryOneBitBuffer{m_buffer, m_width, m_height};
}

void ImageElement::updateBuffer(std::span<std::byte const> newBufferData) {
    std::size_t n = std::min(m_buffer.size(), newBufferData.size());
    std::copy(newBufferData.begin(), newBufferData.begin() + n, m_buffer.begin());
    std::fill(m_buffer.begin() + n, m_buffer.end(), std::byte{});
}

ImageElement::~ImageElement() {
}

ElementType ImageElement::elementType() const {
    return ElementType::Image;
}

std::size_t ImageElement::serializeTo(std::span<std::byte> target) const {
    std::size_t pos = 0;
    pos = writeU16LE(target, pos, static_cast<std::uint16_t>(ElementType::Image));
    pos = writeU16LE(target, pos, m_x);
    pos = writeU16LE(target, pos, m_y);
    pos = writeU16LE(target, pos, m_width);
    pos = writeU16LE(target, pos, m_height);
    pos = writeU16LE(target, pos, 0);
    pos = writeBuffer(target, pos, m_buffer);
    return alignNextWrite(target, pos, 4);
}

void ImageElement::drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) {
    std::ignore = animationTick;
    std::ignore = currentTimestamp;
    std::ignore = customFonts;
    auto ownImage = image();
    for (std::uint16_t dy = 0; dy < m_height; ++dy) {
        for (std::uint16_t dx = 0; dx < m_width; ++dx) {
            imageBuffer->setPixel(m_x + dx, m_y + dy, ownImage.isPixelSet(dx, dy));
        }
    }
}

std::expected<std::unique_ptr<ImageElement>, ParseError> ImageElement::parse(std::span<std::byte const>& buffer) {
    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);
    }

    auto result = std::make_unique<ImageElement>(*x, *y, *width, *height);
    result->updateBuffer(*imageData);
    return result;
}

AnimationElement::AnimationElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, std::uint16_t numberOfFrames, std::uint16_t updateInterval)
    : m_x{x}
    , m_y{y}
    , m_width{width}
    , m_height{height}
    , m_numberOfFrames{numberOfFrames}
    , m_updateInterval{updateInterval}
{
    std::size_t const imageBufferSize = (m_width * m_height + 8 - 1) / 8;
    m_buffer.resize(imageBufferSize * numberOfFrames);
}

std::uint16_t AnimationElement::x() const noexcept {
    return m_x;
}

std::uint16_t AnimationElement::y() const noexcept {
    return m_y;
}

std::uint16_t AnimationElement::width() const noexcept {
    return m_width;
}

std::uint16_t AnimationElement::height() const noexcept {
    return m_height;
}

std::uint16_t AnimationElement::numberOfFrames() const noexcept {
    return m_numberOfFrames;
}

std::uint16_t AnimationElement::updateInterval() const noexcept {
    return m_updateInterval;
}

std::span<std::byte> AnimationElement::buffers() noexcept {
    return m_buffer;
}

std::span<std::byte const> AnimationElement::buffers() const noexcept {
    return m_buffer;
}

std::span<std::byte> AnimationElement::buffer(std::uint16_t frameIndex) noexcept {
    if (frameIndex >= m_numberOfFrames) {
        return {};
    }
    std::size_t const imageBufferSize = (m_width * m_height + 8 - 1) / 8;
    return std::span{m_buffer}.subspan(frameIndex * imageBufferSize, imageBufferSize);
}

std::span<std::byte const> AnimationElement::buffer(std::uint16_t frameIndex) const noexcept {
    if (frameIndex >= m_numberOfFrames) {
        return {};
    }
    std::size_t const imageBufferSize = (m_width * m_height + 8 - 1) / 8;
    return std::span{m_buffer}.subspan(frameIndex * imageBufferSize, imageBufferSize);
}

MemoryOneBitBuffer AnimationElement::image(std::uint16_t frameIndex) noexcept {
    return MemoryOneBitBuffer{buffer(frameIndex), m_width, m_height};
}

ConstMemoryOneBitBuffer AnimationElement::image(std::uint16_t frameIndex) const noexcept {
    return ConstMemoryOneBitBuffer{buffer(frameIndex), m_width, m_height};
}

void AnimationElement::updateBuffer(std::span<std::byte const> newBufferData) {
    std::size_t n = std::min(m_buffer.size(), newBufferData.size());
    std::copy(newBufferData.begin(), newBufferData.begin() + n, m_buffer.begin());
    std::fill(m_buffer.begin() + n, m_buffer.end(), std::byte{});
}

AnimationElement::~AnimationElement() {
}

ElementType AnimationElement::elementType() const {
    return ElementType::Animation;
}

std::size_t AnimationElement::serializeTo(std::span<std::byte> target) const {
    std::size_t pos = 0;
    pos = writeU16LE(target, pos, static_cast<std::uint16_t>(ElementType::Animation));
    pos = writeU16LE(target, pos, m_x);
    pos = writeU16LE(target, pos, m_y);
    pos = writeU16LE(target, pos, m_width);
    pos = writeU16LE(target, pos, m_height);
    pos = writeU16LE(target, pos, m_numberOfFrames);
    pos = writeU16LE(target, pos, m_updateInterval);
    pos = writeU16LE(target, pos, 0);
    pos = writeBuffer(target, pos, m_buffer);
    return alignNextWrite(target, pos, 4);
}

void AnimationElement::drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) {
    std::ignore = currentTimestamp;
    std::ignore = customFonts;

    if (m_numberOfFrames == 0) {
        return;
    }

    std::size_t const tick = animationTick / (static_cast<std::size_t>(m_updateInterval) + 1);
    std::size_t const frameIndex = tick % m_numberOfFrames;

    auto ownImage = image(frameIndex);
    for (std::uint16_t dy = 0; dy < m_height; ++dy) {
        for (std::uint16_t dx = 0; dx < m_width; ++dx) {
            imageBuffer->setPixel(m_x + dx, m_y + dy, ownImage.isPixelSet(dx, dy));
        }
    }
}

std::expected<std::unique_ptr<AnimationElement>, ParseError> AnimationElement::parse(std::span<std::byte const>& buffer) {
    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);
    }

    auto result = std::make_unique<AnimationElement>(*x, *y, *width, *height, *numberOfFrames, *updateInterval);
    result->updateBuffer(*imageData);
    return result;
}

HScrollImageElement::HScrollImageElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, std::uint16_t contentWidth, std::uint8_t flags, std::uint8_t scrollSpeed)
    : m_x{x}
    , m_y{y}
    , m_width{width}
    , m_height{height}
    , m_contentWidth{contentWidth}
    , m_flags{flags}
    , m_scrollSpeed{scrollSpeed}
{
    std::size_t const imageBufferSize = (m_contentWidth * m_height + 8 - 1) / 8;
    m_buffer.resize(imageBufferSize);
}

std::uint16_t HScrollImageElement::x() const noexcept {
    return m_x;
}

std::uint16_t HScrollImageElement::y() const noexcept {
    return m_y;
}

std::uint16_t HScrollImageElement::width() const noexcept {
    return m_width;
}

std::uint16_t HScrollImageElement::height() const noexcept {
    return m_height;
}

std::uint16_t HScrollImageElement::contentWidth() const noexcept {
    return m_contentWidth;
}

std::uint8_t HScrollImageElement::flags() const noexcept {
    return m_flags;
}

std::uint8_t HScrollImageElement::scrollSpeed() const noexcept {
    return m_scrollSpeed;
}

std::span<std::byte> HScrollImageElement::buffer() noexcept {
    return std::span{m_buffer};
}

std::span<std::byte const> HScrollImageElement::buffer() const noexcept {
    return std::span{m_buffer};
}

MemoryOneBitBuffer HScrollImageElement::image() noexcept {
    return MemoryOneBitBuffer{m_buffer, m_contentWidth, m_height};
}

ConstMemoryOneBitBuffer HScrollImageElement::image() const noexcept {
    return ConstMemoryOneBitBuffer{m_buffer, m_contentWidth, m_height};
}

void HScrollImageElement::updateBuffer(std::span<std::byte const> newBufferData) {
    std::size_t n = std::min(m_buffer.size(), newBufferData.size());
    std::copy(newBufferData.begin(), newBufferData.begin() + n, m_buffer.begin());
    std::fill(m_buffer.begin() + n, m_buffer.end(), std::byte{});
}

HScrollImageElement::~HScrollImageElement() {
}

ElementType HScrollImageElement::elementType() const {
    return ElementType::HScrollImage;
}

std::size_t HScrollImageElement::serializeTo(std::span<std::byte> target) const {
    std::size_t pos = 0;
    pos = writeU16LE(target, pos, static_cast<std::uint16_t>(ElementType::HScrollImage));
    pos = writeU16LE(target, pos, m_x);
    pos = writeU16LE(target, pos, m_y);
    pos = writeU16LE(target, pos, m_width);
    pos = writeU16LE(target, pos, m_height);
    pos = writeU16LE(target, pos, m_contentWidth);
    pos = writeU8LE(target, pos, m_flags);
    pos = writeU8LE(target, pos, m_scrollSpeed);
    pos = writeU16LE(target, pos, 0);
    pos = writeBuffer(target, pos, m_buffer);
    return alignNextWrite(target, pos, 4);
}

void HScrollImageElement::drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) {
    std::ignore = currentTimestamp;
    std::ignore = customFonts;

    auto ownImage = image();
    ClippedImage target{imageBuffer, m_x, m_y, m_width, m_height};

    if (m_contentWidth == 0) {
        return;
    }

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

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

    std::int32_t totalSize = m_contentWidth;
    if (padBefore) {
        totalSize += m_width;
    }
    if (padAfter) {
        totalSize += m_width;
    }

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

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

std::expected<std::unique_ptr<HScrollImageElement>, ParseError> HScrollImageElement::parse(std::span<std::byte const>& buffer) {
    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 result = std::make_unique<HScrollImageElement>(*x, *y, *width, *height, *contentWidth, *flags, *scrollSpeed);
    result->updateBuffer(*imageData);
    return result;
}

VScrollImageElement::VScrollImageElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, std::uint16_t contentHeight, std::uint8_t flags, std::uint8_t scrollSpeed)
    : m_x{x}
    , m_y{y}
    , m_width{width}
    , m_height{height}
    , m_contentHeight{contentHeight}
    , m_flags{flags}
    , m_scrollSpeed{scrollSpeed}
{
    std::size_t const imageBufferSize = (m_width * m_contentHeight + 8 - 1) / 8;
    m_buffer.resize(imageBufferSize);
}

std::uint16_t VScrollImageElement::x() const noexcept {
    return m_x;
}

std::uint16_t VScrollImageElement::y() const noexcept {
    return m_y;
}

std::uint16_t VScrollImageElement::width() const noexcept {
    return m_width;
}

std::uint16_t VScrollImageElement::height() const noexcept {
    return m_height;
}

std::uint16_t VScrollImageElement::contentHeight() const noexcept {
    return m_contentHeight;
}

std::uint8_t VScrollImageElement::flags() const noexcept {
    return m_flags;
}

std::uint8_t VScrollImageElement::scrollSpeed() const noexcept {
    return m_scrollSpeed;
}

std::span<std::byte> VScrollImageElement::buffer() noexcept {
    return std::span{m_buffer};
}

std::span<std::byte const> VScrollImageElement::buffer() const noexcept {
    return std::span{m_buffer};
}

MemoryOneBitBuffer VScrollImageElement::image() noexcept {
    return MemoryOneBitBuffer{m_buffer, m_width, m_contentHeight};
}

ConstMemoryOneBitBuffer VScrollImageElement::image() const noexcept {
    return ConstMemoryOneBitBuffer{m_buffer, m_width, m_contentHeight};
}

void VScrollImageElement::updateBuffer(std::span<std::byte const> newBufferData) {
    std::size_t n = std::min(m_buffer.size(), newBufferData.size());
    std::copy(newBufferData.begin(), newBufferData.begin() + n, m_buffer.begin());
    std::fill(m_buffer.begin() + n, m_buffer.end(), std::byte{});
}

VScrollImageElement::~VScrollImageElement() {
}

ElementType VScrollImageElement::elementType() const {
    return ElementType::VScrollImage;
}

std::size_t VScrollImageElement::serializeTo(std::span<std::byte> target) const {
    std::size_t pos = 0;
    pos = writeU16LE(target, pos, static_cast<std::uint16_t>(ElementType::VScrollImage));
    pos = writeU16LE(target, pos, m_x);
    pos = writeU16LE(target, pos, m_y);
    pos = writeU16LE(target, pos, m_width);
    pos = writeU16LE(target, pos, m_height);
    pos = writeU16LE(target, pos, m_contentHeight);
    pos = writeU8LE(target, pos, m_flags);
    pos = writeU8LE(target, pos, m_scrollSpeed);
    pos = writeU16LE(target, pos, 0);
    pos = writeBuffer(target, pos, m_buffer);
    return alignNextWrite(target, pos, 4);
}

void VScrollImageElement::drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) {
    std::ignore = currentTimestamp;
    std::ignore = customFonts;

    auto ownImage = image();
    ClippedImage target{imageBuffer, m_x, m_y, m_width, m_height};

    if (m_contentHeight == 0) {
        return;
    }

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

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

    std::int32_t totalSize = m_contentHeight;
    if (padBefore) {
        totalSize += m_height;
    }
    if (padAfter) {
        totalSize += m_height;
    }

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

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

std::expected<std::unique_ptr<VScrollImageElement>, ParseError> VScrollImageElement::parse(std::span<std::byte const>& buffer) {
    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 result = std::make_unique<VScrollImageElement>(*x, *y, *width, *height, *contentHeight, *flags, *scrollSpeed);
    result->updateBuffer(*imageData);
    return result;
}


LineElement::LineElement(std::uint16_t originX, std::uint16_t originY, std::uint16_t targetX, std::uint16_t targetY, LineStyle lineStyle, std::uint8_t flags)
    : m_originX{originX}
    , m_originY{originY}
    , m_targetX{targetX}
    , m_targetY{targetY}
    , m_lineStyle{lineStyle}
    , m_flags{flags}
{
}

std::uint16_t LineElement::originX() const noexcept {
    return m_originX;
}

std::uint16_t LineElement::originY() const noexcept {
    return m_originY;
}

std::uint16_t LineElement::targetX() const noexcept {
    return m_targetX;
}

std::uint16_t LineElement::targetY() const noexcept {
    return m_targetY;
}

LineStyle LineElement::lineStyle() const noexcept {
    return m_lineStyle;
}

std::uint8_t LineElement::flags() const noexcept {
    return m_flags;
}

LineElement::~LineElement() {
}

ElementType LineElement::elementType() const {
    return ElementType::Line;
}

std::size_t LineElement::serializeTo(std::span<std::byte> target) const {
    std::size_t pos = 0;
    pos = writeU16LE(target, pos, static_cast<std::uint16_t>(ElementType::Line));
    pos = writeU16LE(target, pos, m_originX);
    pos = writeU16LE(target, pos, m_originY);
    pos = writeU16LE(target, pos, m_targetX);
    pos = writeU16LE(target, pos, m_targetY);
    pos = writeU8LE(target, pos, static_cast<std::uint8_t>(m_lineStyle));
    pos = writeU8LE(target, pos, m_flags);
    return alignNextWrite(target, pos, 4);
}

void LineElement::drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) {
    std::ignore = animationTick;
    std::ignore = currentTimestamp;
    std::ignore = customFonts;

    std::int32_t dx = static_cast<std::int32_t>(m_targetX) - static_cast<std::int32_t>(m_originX);
    std::int32_t dy = static_cast<std::int32_t>(m_targetY) - static_cast<std::int32_t>(m_originY);
    bool value = true;

    if (dx == 0) {
        for (std::int32_t i = 0; i <= dy; ++i) {
            imageBuffer->setPixel(m_originX, m_originY + i, value);
        }
    } else if (dy == 0) {
        for (std::int32_t i = 0; i <= dx; ++i) {
            imageBuffer->setPixel(m_originX + i, m_originY, value);
        }
    } else {
        for (std::int32_t i = 0; i <= dx; ++i) {
            std::int32_t x = m_originX + i;
            std::int32_t y = i * dy / dx + m_originY;
            imageBuffer->setPixel(static_cast<std::uint16_t>(x), static_cast<std::uint16_t>(y), value);
        }
    }
}

std::expected<std::unique_ptr<LineElement>, ParseError> LineElement::parse(std::span<std::byte const>& buffer) {
    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());
    }
    auto result = std::make_unique<LineElement>(*originX, *originY, *targetX, *targetY, static_cast<LineStyle>(*lineStyle), *flags);
    return result;
}

ClippedTextElement::ClippedTextElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, std::uint16_t fontIndex, std::string text)
    : m_x{x}
    , m_y{y}
    , m_width{width}
    , m_height{height}
    , m_fontIndex{fontIndex}
    , m_text{std::move(text)}
{
}

std::uint16_t ClippedTextElement::x() const noexcept {
    return m_x;
}

std::uint16_t ClippedTextElement::y() const noexcept {
    return m_y;
}

std::uint16_t ClippedTextElement::width() const noexcept {
    return m_width;
}

std::uint16_t ClippedTextElement::height() const noexcept {
    return m_height;
}

std::uint16_t ClippedTextElement::fontIndex() const noexcept {
    return m_fontIndex;
}

std::string ClippedTextElement::text() const {
    return m_text;
}

ClippedTextElement::~ClippedTextElement() {
}

ElementType ClippedTextElement::elementType() const {
    return ElementType::ClippedText;
}

std::size_t ClippedTextElement::serializeTo(std::span<std::byte> target) const {
    std::size_t pos = 0;
    pos = writeU16LE(target, pos, static_cast<std::uint16_t>(ElementType::ClippedText));
    pos = writeU16LE(target, pos, m_x);
    pos = writeU16LE(target, pos, m_y);
    pos = writeU16LE(target, pos, m_width);
    pos = writeU16LE(target, pos, m_height);
    pos = writeU16LE(target, pos, m_fontIndex);
    pos = writeU16LE(target, pos, m_text.size());
    pos = writeBuffer(target, pos, std::as_bytes(std::span{m_text}));
    return alignNextWrite(target, pos, 4);
}

void ClippedTextElement::drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) {
    std::ignore = animationTick;
    std::ignore = currentTimestamp;

    std::span<std::byte const> fontData;
    if (m_fontIndex & 0x8000u) {
        std::size_t fontIndex = m_fontIndex & ~0x8000u;
        if (fontIndex >= customFonts.size()) {
            return;
        }
        fontData = customFonts[fontIndex]->fontData();
    } else {
        fontData = findEmbeddedFont(m_fontIndex);
    }
    if (fontData.size() < 23) {
        return;
    }

    ClippedImage target{imageBuffer, m_x, m_y, m_width, m_height};

    auto renderer = FontRenderer{fontData}.withIgnoreUnknownChars(true);
    renderer.render(m_text, Point{0, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, true, false);
}

std::expected<std::unique_ptr<ClippedTextElement>, ParseError> ClippedTextElement::parse(std::span<std::byte const>& buffer) {
    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};
    auto result = std::make_unique<ClippedTextElement>(*x, *y, *width, *height, *fontIndex, std::string{text});
    return result;
}

HScrollTextElement::HScrollTextElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, std::uint8_t flags, std::uint8_t scrollSpeed, std::uint16_t fontIndex, std::string text)
    : m_x{x}
    , m_y{y}
    , m_width{width}
    , m_height{height}
    , m_flags{flags}
    , m_scrollSpeed{scrollSpeed}
    , m_fontIndex{fontIndex}
    , m_text{std::move(text)}
{
}

std::uint16_t HScrollTextElement::x() const noexcept {
    return m_x;
}

std::uint16_t HScrollTextElement::y() const noexcept {
    return m_y;
}

std::uint16_t HScrollTextElement::width() const noexcept {
    return m_width;
}

std::uint16_t HScrollTextElement::height() const noexcept {
    return m_height;
}

std::uint8_t HScrollTextElement::flags() const noexcept {
    return m_flags;
}

std::uint8_t HScrollTextElement::scrollSpeed() const noexcept {
    return m_scrollSpeed;
}

std::uint16_t HScrollTextElement::fontIndex() const noexcept {
    return m_fontIndex;
}

std::string HScrollTextElement::text() const {
    return m_text;
}

HScrollTextElement::~HScrollTextElement() {
}

ElementType HScrollTextElement::elementType() const {
    return ElementType::HScrollText;
}

std::size_t HScrollTextElement::serializeTo(std::span<std::byte> target) const {
    std::size_t pos = 0;
    pos = writeU16LE(target, pos, static_cast<std::uint16_t>(ElementType::HScrollText));
    pos = writeU16LE(target, pos, m_x);
    pos = writeU16LE(target, pos, m_y);
    pos = writeU16LE(target, pos, m_width);
    pos = writeU16LE(target, pos, m_height);
    pos = writeU8LE(target, pos, m_flags);
    pos = writeU8LE(target, pos, m_scrollSpeed);
    pos = writeU16LE(target, pos, m_fontIndex);
    pos = writeU16LE(target, pos, m_text.size());
    pos = writeBuffer(target, pos, std::as_bytes(std::span{m_text}));
    return alignNextWrite(target, pos, 4);
}

void HScrollTextElement::drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) {
    std::ignore = currentTimestamp;

    std::span<std::byte const> fontData;
    if (m_fontIndex & 0x8000u) {
        std::size_t fontIndex = m_fontIndex & ~0x8000u;
        if (fontIndex >= customFonts.size()) {
            return;
        }
        fontData = customFonts[fontIndex]->fontData();
    } else {
        fontData = findEmbeddedFont(m_fontIndex);
    }
    if (fontData.size() < 23) {
        return;
    }

    auto renderer = FontRenderer{fontData}.withIgnoreUnknownChars(true);
    auto dimensions = renderer.getRenderedDimensions(m_text, {0, static_cast<std::int32_t>(renderer.lineHeight() - 1)});
    if (!dimensions || !dimensions->boundingBox) {
        return;
    }

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

    ClippedImage target{imageBuffer, m_x, m_y, m_width, m_height};

    if (contentWidth == 0) {
        return;
    }

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

    if (!padBefore && !padAfter && contentWidth < m_width) {
        std::uint16_t offset = invert ? (m_width - contentWidth) : 0;
        renderer.render(m_text, Point{offset, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, true, false);
        return;
    }

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

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

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

    std::int32_t partOffset = 0;
    if (padBefore) {
        for (std::int32_t dy = 0; dy < m_height; ++dy) {
            for (std::int32_t dx = 0; dx < m_width; ++dx) {
                std::int32_t ddx = dx + partOffset;
                if ((ddx - offset) < 0) {
                    continue;
                }
                target.setPixel(ddx - offset, dy, false);
            }
        }
        partOffset += m_width;
    }
    renderer.render(m_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 < m_height; ++dy) {
            for (std::int32_t dx = 0; dx < m_width; ++dx) {
                std::int32_t ddx = dx + partOffset;
                if ((ddx - offset) < 0) {
                    continue;
                }
                target.setPixel(ddx - offset, dy, false);
            }
        }
        partOffset += m_width;
    }
    if (!restarting) {
        if (padBefore) {
            for (std::int32_t dy = 0; dy < m_height; ++dy) {
                for (std::int32_t dx = 0; dx < m_width; ++dx) {
                    std::int32_t ddx = dx + partOffset;
                    if ((ddx - offset) < 0) {
                        continue;
                    }
                    target.setPixel(ddx - offset, dy, false);
                }
            }
            partOffset += m_width;
        }
        renderer.render(m_text, Point{partOffset - offset, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, true, false);
    }
}

std::expected<std::unique_ptr<HScrollTextElement>, ParseError> HScrollTextElement::parse(std::span<std::byte const>& buffer) {
    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};
    auto result = std::make_unique<HScrollTextElement>(*x, *y, *width, *height, *flags, *scrollSpeed, *fontIndex, std::string{text});
    return result;
}

CurrentTimeElement::CurrentTimeElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, std::uint16_t fontIndex, std::uint16_t utcOffset, std::uint16_t flags)
    : m_x{x}
    , m_y{y}
    , m_width{width}
    , m_height{height}
    , m_fontIndex{fontIndex}
    , m_utcOffset{utcOffset}
    , m_flags{flags}
{
}

std::uint16_t CurrentTimeElement::x() const noexcept {
    return m_x;
}

std::uint16_t CurrentTimeElement::y() const noexcept {
    return m_y;
}

std::uint16_t CurrentTimeElement::width() const noexcept {
    return m_width;
}

std::uint16_t CurrentTimeElement::height() const noexcept {
    return m_height;
}

std::uint16_t CurrentTimeElement::fontIndex() const noexcept {
    return m_fontIndex;
}

std::uint16_t CurrentTimeElement::utcOffset() const noexcept {
    return m_utcOffset;
}

std::uint16_t CurrentTimeElement::flags() const noexcept {
    return m_flags;
}

CurrentTimeElement::~CurrentTimeElement() {
}

ElementType CurrentTimeElement::elementType() const {
    return ElementType::CurrentTime;
}

std::size_t CurrentTimeElement::serializeTo(std::span<std::byte> target) const {
    std::size_t pos = 0;
    pos = writeU16LE(target, pos, static_cast<std::uint16_t>(ElementType::CurrentTime));
    pos = writeU16LE(target, pos, m_x);
    pos = writeU16LE(target, pos, m_y);
    pos = writeU16LE(target, pos, m_width);
    pos = writeU16LE(target, pos, m_height);
    pos = writeU16LE(target, pos, m_fontIndex);
    pos = writeU16LE(target, pos, m_utcOffset);
    pos = writeU16LE(target, pos, m_flags);
    return alignNextWrite(target, pos, 4);
}

void CurrentTimeElement::drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) {
    std::ignore = animationTick;

    std::span<std::byte const> fontData;
    if (m_fontIndex & 0x8000u) {
        std::size_t fontIndex = m_fontIndex & ~0x8000u;
        if (fontIndex >= customFonts.size()) {
            return;
        }
        fontData = customFonts[fontIndex]->fontData();
    } else {
        fontData = findEmbeddedFont(m_fontIndex);
    }
    if (fontData.size() < 23) {
        return;
    }

    bool use12h = (m_flags & 0x01u) == 0x01u;
    bool showHours = (m_flags & 0x02u) == 0x02u;
    bool showMinutes = (m_flags & 0x04u) == 0x04u;
    bool showSeconds = (m_flags & 0x08u) == 0x08u;
    if (showHours && showSeconds) {
        showMinutes = true;
    }

    ClippedImage target{imageBuffer, m_x, m_y, m_width, m_height};

    auto renderer = FontRenderer{fontData}.withIgnoreUnknownChars(true);

    std::uint32_t seconds = (currentTimestamp + static_cast<std::int64_t>(m_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;
        }
    }
    std::string text;
    if (showHours && showMinutes && showSeconds) {
        text = std::format("{:02d}:{:02d}:{:02d}", hours, minutes, seconds);
    } else if (showHours && showMinutes) {
        text = std::format("{:02d}:{:02d}", hours, minutes);
    } else if (showHours) {
        text = std::format("{:02d}", hours);
    } else if (showMinutes && showSeconds) {
        text = std::format("{:02d}:{:02d}", minutes, seconds);
    } else if (showSeconds) {
        text = std::format("{:02d}", seconds);
    } else {
        return;
    }

    renderer.render(text, Point{0, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, true, false);
}

std::expected<std::unique_ptr<CurrentTimeElement>, ParseError> CurrentTimeElement::parse(std::span<std::byte const>& buffer) {
    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());
    }
    auto result = std::make_unique<CurrentTimeElement>(*x, *y, *width, *height, *fontIndex, *utcOffset, *flags);
    return result;
}

AlwaysDrawnSection::~AlwaysDrawnSection() {
}

bool AlwaysDrawnSection::doesDrawOnFront() const noexcept {
    return m_drawOnFront;
}

bool AlwaysDrawnSection::doesDrawOnBack() const noexcept {
    return m_drawOnBack;
}

bool AlwaysDrawnSection::doesClearBeforeDrawing() const noexcept {
    return m_clearBeforeDrawing;
}

void AlwaysDrawnSection::setDrawOnFront(bool value) noexcept {
    m_drawOnFront = value;
}

void AlwaysDrawnSection::setDrawOnBack(bool value) noexcept {
    m_drawOnBack = value;
}

void AlwaysDrawnSection::setClearBeforeDrawing(bool value) noexcept {
    m_clearBeforeDrawing = value;
}

std::size_t AlwaysDrawnSection::elementCount() const noexcept {
    return m_elements.size();
}

Element* AlwaysDrawnSection::elementAt(std::size_t index) const {
    if (index < m_elements.size()) {
        return m_elements[index].get();
    } else {
        return nullptr;
    }
}

void AlwaysDrawnSection::appendElement(std::unique_ptr<Element> element) {
    m_elements.push_back(std::move(element));
}

void AlwaysDrawnSection::insertElement(std::size_t index, std::unique_ptr<Element> element) {
    if (index < m_elements.size()) {
        m_elements.insert(m_elements.begin() + index, std::move(element));
    } else {
        m_elements.push_back(std::move(element));
    }
}

std::unique_ptr<Element> AlwaysDrawnSection::replaceElement(std::size_t index, std::unique_ptr<Element> element) {
    std::unique_ptr<Element> result;
    if (index < m_elements.size()) {
        result = std::move(m_elements[index]);
        m_elements[index] = std::move(element);
    }
    return result;
}

std::unique_ptr<Element> AlwaysDrawnSection::eraseElement(std::size_t index) {
    std::unique_ptr<Element> result;
    if (index < m_elements.size()) {
        result = std::move(m_elements[index]);
        m_elements.erase(m_elements.begin() + index);
    }
    return result;
}

SectionType AlwaysDrawnSection::sectionType() const {
    return SectionType::AlwaysDrawn;
}

std::size_t AlwaysDrawnSection::serializeTo(std::span<std::byte> target) const {
    std::size_t pos = 0;
    pos = writeU8LE(target, pos, static_cast<std::uint16_t>(SectionType::AlwaysDrawn));
    // Will be replaced later
    pos = writeU24LE(target, pos, 0);
    std::uint16_t flags = 0;
    if (m_drawOnFront) {
        flags |= std::uint16_t{1};
    }
    if (m_drawOnBack) {
        flags |= std::uint16_t{2};
    }
    if (m_clearBeforeDrawing) {
        flags |= std::uint16_t{4};
    }
    pos = writeU16LE(target, pos, flags);
    pos = writeU16LE(target, pos, m_elements.size());
    for (auto const& element : m_elements) {
        if (pos < target.size()) {
            pos += element->serializeTo(target.subspan(pos));
        } else {
            pos += element->serializeTo({});
        }
    }
    pos = alignNextWrite(target, pos, 4);
    std::ignore = writeU24LE(target, 1, pos);
    return pos;
}

std::expected<std::unique_ptr<AlwaysDrawnSection>, ParseError> AlwaysDrawnSection::parse(std::span<std::byte const>& buffer) {
    auto type = readU8LE(buffer);
    if (!type) {
        return std::unexpected(type.error());
    }
    if (*type != static_cast<std::uint8_t>(SectionType::AlwaysDrawn)) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto size = readU24LE(buffer);
    if (!size) {
        return std::unexpected(size.error());
    }
    if (*size < 8) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto flags = readU16LE(buffer);
    if (!flags) {
        return std::unexpected(flags.error());
    }
    auto elementCount = readU16LE(buffer);
    if (!elementCount) {
        return std::unexpected(elementCount.error());
    }
    auto sectionData = buffer.subspan(0, *size - 8);
    buffer = buffer.subspan(*size - 8);
    auto section = std::make_unique<AlwaysDrawnSection>();
    section->setDrawOnFront((((*flags) >> 0u) & 0x01u) == 0x01u);
    section->setDrawOnBack((((*flags) >> 1u) & 0x01u) == 0x01u);
    section->setClearBeforeDrawing((((*flags) >> 2u) & 0x01u) == 0x01u);
    section->m_elements.reserve(*elementCount);
    for (std::size_t i = 0; i < *elementCount; ++i) {
        auto type = peekU16LE(sectionData);
        if (!type) {
            return std::unexpected(type.error());
        }
        std::expected<std::unique_ptr<Element>, ParseError> element;
        switch (static_cast<ElementType>(*type)) {
        case ElementType::Image:        element = ImageElement::parse(sectionData);        break;
        case ElementType::Animation:    element = AnimationElement::parse(sectionData);    break;
        case ElementType::HScrollImage: element = HScrollImageElement::parse(sectionData); break;
        case ElementType::VScrollImage: element = VScrollImageElement::parse(sectionData); break;
        case ElementType::Line:         element = LineElement::parse(sectionData);         break;
        case ElementType::ClippedText:  element = ClippedTextElement::parse(sectionData);  break;
        case ElementType::HScrollText:  element = HScrollTextElement::parse(sectionData);  break;
        case ElementType::CurrentTime:  element = CurrentTimeElement::parse(sectionData);  break;
        default:
            return std::unexpected(ParseError::InvalidValue);
        }
        if (!element) {
            return std::unexpected(element.error());
        }
        section->m_elements.push_back(std::move(*element));
    }
    return section;
}

TimeBasedDrawnSection::~TimeBasedDrawnSection() {
}

bool TimeBasedDrawnSection::doesDrawOnFront() const noexcept {
    return m_drawOnFront;
}

bool TimeBasedDrawnSection::doesDrawOnBack() const noexcept {
    return m_drawOnBack;
}

bool TimeBasedDrawnSection::doesClearBeforeDrawing() const noexcept {
    return m_clearBeforeDrawing;
}

void TimeBasedDrawnSection::setDrawOnFront(bool value) noexcept {
    m_drawOnFront = value;
}

void TimeBasedDrawnSection::setDrawOnBack(bool value) noexcept {
    m_drawOnBack = value;
}

void TimeBasedDrawnSection::setClearBeforeDrawing(bool value) noexcept {
    m_clearBeforeDrawing = value;
}

std::size_t TimeBasedDrawnSection::elementCount() const noexcept {
    return m_elements.size();
}

Element* TimeBasedDrawnSection::elementAt(std::size_t index) const {
    if (index < m_elements.size()) {
        return m_elements[index].get();
    } else {
        return nullptr;
    }
}

void TimeBasedDrawnSection::appendElement(std::unique_ptr<Element> element) {
    m_elements.push_back(std::move(element));
}

void TimeBasedDrawnSection::insertElement(std::size_t index, std::unique_ptr<Element> element) {
    if (index < m_elements.size()) {
        m_elements.insert(m_elements.begin() + index, std::move(element));
    } else {
        m_elements.push_back(std::move(element));
    }
}

std::unique_ptr<Element> TimeBasedDrawnSection::replaceElement(std::size_t index, std::unique_ptr<Element> element) {
    std::unique_ptr<Element> result;
    if (index < m_elements.size()) {
        result = std::move(m_elements[index]);
        m_elements[index] = std::move(element);
    }
    return result;
}

std::unique_ptr<Element> TimeBasedDrawnSection::eraseElement(std::size_t index) {
    std::unique_ptr<Element> result;
    if (index < m_elements.size()) {
        result = std::move(m_elements[index]);
        m_elements.erase(m_elements.begin() + index);
    }
    return result;
}

std::int64_t TimeBasedDrawnSection::startTimestamp() const noexcept {
    return m_startTimestamp;
}

std::int64_t TimeBasedDrawnSection::endTimestamp() const noexcept {
    return m_endTimestamp;
}

void TimeBasedDrawnSection::setStartTimestamp(std::int64_t value) {
    m_startTimestamp = value;
}

void TimeBasedDrawnSection::setEndTimestamp(std::int64_t value) {
    m_endTimestamp = value;
}

SectionType TimeBasedDrawnSection::sectionType() const {
    return SectionType::TimeBasedDrawn;
}

std::size_t TimeBasedDrawnSection::serializeTo(std::span<std::byte> target) const {
    std::size_t pos = 0;
    pos = writeU8LE(target, pos, static_cast<std::uint16_t>(SectionType::AlwaysDrawn));
    // Will be replaced later
    pos = writeU24LE(target, pos, 0);
    std::uint16_t flags = 0;
    if (m_drawOnFront) {
        flags |= std::uint16_t{1};
    }
    if (m_drawOnBack) {
        flags |= std::uint16_t{2};
    }
    if (m_clearBeforeDrawing) {
        flags |= std::uint16_t{4};
    }
    pos = writeU16LE(target, pos, flags);
    pos = writeU16LE(target, pos, m_elements.size());
    pos = writeU64LE(target, pos, std::bit_cast<std::uint64_t>(m_startTimestamp));
    pos = writeU64LE(target, pos, std::bit_cast<std::uint64_t>(m_endTimestamp));
    for (auto const& element : m_elements) {
        if (pos < target.size()) {
            pos += element->serializeTo(target.subspan(pos));
        } else {
            pos += element->serializeTo({});
        }
    }
    pos = alignNextWrite(target, pos, 4);
    std::ignore = writeU24LE(target, 1, pos);
    return pos;
}

std::expected<std::unique_ptr<TimeBasedDrawnSection>, ParseError> TimeBasedDrawnSection::parse(std::span<std::byte const>& buffer) {
    auto type = readU8LE(buffer);
    if (!type) {
        return std::unexpected(type.error());
    }
    if (*type != static_cast<std::uint8_t>(SectionType::TimeBasedDrawn)) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto size = readU24LE(buffer);
    if (!size) {
        return std::unexpected(size.error());
    }
    if (*size < 24) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto flags = readU16LE(buffer);
    if (!flags) {
        return std::unexpected(flags.error());
    }
    auto elementCount = readU16LE(buffer);
    if (!elementCount) {
        return std::unexpected(elementCount.error());
    }
    auto startTimestamp = readU64LE(buffer);
    if (!startTimestamp) {
        return std::unexpected(startTimestamp.error());
    }
    auto endTimestamp = readU64LE(buffer);
    if (!endTimestamp) {
        return std::unexpected(endTimestamp.error());
    }
    auto sectionData = buffer.subspan(0, *size - 24);
    buffer = buffer.subspan(*size - 24);
    auto section = std::make_unique<TimeBasedDrawnSection>();
    section->setDrawOnFront((((*flags) >> 0u) & 0x01u) == 0x01u);
    section->setDrawOnBack((((*flags) >> 1u) & 0x01u) == 0x01u);
    section->setClearBeforeDrawing((((*flags) >> 2u) & 0x01u) == 0x01u);
    section->setStartTimestamp(std::bit_cast<std::int64_t>(*startTimestamp));
    section->setEndTimestamp(std::bit_cast<std::int64_t>(*endTimestamp));
    section->m_elements.reserve(*elementCount);
    for (std::size_t i = 0; i < *elementCount; ++i) {
        auto type = peekU16LE(sectionData);
        if (!type) {
            return std::unexpected(type.error());
        }
        std::expected<std::unique_ptr<Element>, ParseError> element;
        switch (static_cast<ElementType>(*type)) {
        case ElementType::Image:        element = ImageElement::parse(sectionData);        break;
        case ElementType::Animation:    element = AnimationElement::parse(sectionData);    break;
        case ElementType::HScrollImage: element = HScrollImageElement::parse(sectionData); break;
        case ElementType::VScrollImage: element = VScrollImageElement::parse(sectionData); break;
        case ElementType::Line:         element = LineElement::parse(sectionData);         break;
        case ElementType::ClippedText:  element = ClippedTextElement::parse(sectionData);  break;
        case ElementType::HScrollText:  element = HScrollTextElement::parse(sectionData);  break;
        case ElementType::CurrentTime:  element = CurrentTimeElement::parse(sectionData);  break;
        default:
            return std::unexpected(ParseError::InvalidValue);
        }
        if (!element) {
            return std::unexpected(element.error());
        }
        section->m_elements.push_back(std::move(*element));
    }
    return section;
}

CustomFontSection::~CustomFontSection() {
}

std::span<std::byte const> CustomFontSection::fontData() const {
    return std::span{m_fontData};
}

void CustomFontSection::setFontData(std::span<std::byte const> fontData) {
    m_fontData.resize(fontData.size());
    std::copy(fontData.begin(), fontData.end(), m_fontData.begin());
}

SectionType CustomFontSection::sectionType() const {
    return SectionType::CustomFont;
}

std::size_t CustomFontSection::serializeTo(std::span<std::byte> target) const {
    std::size_t pos = 0;
    pos = writeU8LE(target, pos, static_cast<std::uint16_t>(SectionType::AlwaysDrawn));
    // Will be replaced later
    pos = writeU24LE(target, pos, 0);
    pos = writeU24LE(target, pos, m_fontData.size());
    pos = writeU8LE(target, pos, 0);
    pos = writeBuffer(target, pos, m_fontData);
    pos = alignNextWrite(target, pos, 4);
    std::ignore = writeU24LE(target, 1, pos);
    return pos;
}

std::expected<std::unique_ptr<CustomFontSection>, ParseError> CustomFontSection::parse(std::span<std::byte const>& buffer) {
    auto type = readU8LE(buffer);
    if (!type) {
        return std::unexpected(type.error());
    }
    if (*type != static_cast<std::uint8_t>(SectionType::CustomFont)) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto size = readU24LE(buffer);
    if (!size) {
        return std::unexpected(size.error());
    }
    if (*size < 8) {
        return std::unexpected(ParseError::InvalidValue);
    }
    auto actualSize = readU24LE(buffer);
    if (!actualSize) {
        return std::unexpected(actualSize.error());
    }
    auto reserved_ = readU8LE(buffer);
    if (!reserved_) {
        return std::unexpected(reserved_.error());
    }
    auto fontData = buffer.subspan(0, *size - 8);
    buffer = buffer.subspan(*size - 8);

    auto actualFontData = readBuffer(fontData, *actualSize);
    if (!actualFontData) {
        return std::unexpected(actualFontData.error());
    }

    auto section = std::make_unique<CustomFontSection>();
    section->setFontData(*actualFontData);
    return section;
}

std::size_t serializeFile(std::span<std::byte>& buffer, File const& file) {
    std::size_t pos = 0;
    pos = writeU8LE(buffer, pos, 0xAF);
    pos = writeU8LE(buffer, pos, 0x7E);
    pos = writeU8LE(buffer, pos, 0x2B);
    pos = writeU8LE(buffer, pos, 0x63);
    pos = writeU32LE(buffer, pos, 1);
    pos = writeU16LE(buffer, pos, file.sections.size());
    pos = writeU16LE(buffer, pos, 0);
    pos = alignNextWrite(buffer, pos, 4);
    for (auto const& section : file.sections) {
        if (pos < buffer.size()) {
            pos += section->serializeTo(buffer.subspan(pos));
        } else {
            pos += section->serializeTo({});
        }
    }
    return pos;
}

std::expected<File, ParseError> parseFile(std::span<std::byte const> data) {
    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());
    }

    File result;
    result.sections.reserve(*sectionCount);

    for (std::size_t i = 0; i < *sectionCount; ++i) {
        auto sectionType = peekU8LE(data);
        if (!sectionType) {
            return std::unexpected(sectionType.error());
        }

        std::expected<std::unique_ptr<Section>, ParseError> section;

        switch (static_cast<SectionType>(*sectionType)) {
        case SectionType::AlwaysDrawn:     section = AlwaysDrawnSection::parse(data);    break;
        case SectionType::TimeBasedDrawn:  section = TimeBasedDrawnSection::parse(data); break;
        case SectionType::CustomFont:      section = CustomFontSection::parse(data);     break;
        default: return std::unexpected(ParseError::InvalidValue);
    }

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

        result.sections.push_back(std::move(*section));
    }

    return result;
}

} // namespace monoformat
