#ifndef MONOFORMAT_STRUCTURED_HPP
#define MONOFORMAT_STRUCTURED_HPP

#include "monoformat_parsehelpers.hpp"
#include "monoformat_schema.hpp"

#include <nlohmann/json.hpp>

#include <cstddef>
#include <cstdint>
#include <memory>
#include <vector>
#include <span>

namespace monoformat {

struct Section {
    virtual ~Section() = default;
    virtual SectionType sectionType() const = 0;
    virtual std::uint32_t minimumFormatVersion() const = 0;
    virtual std::size_t serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const = 0;
    virtual nlohmann::json serializeJSON(std::uint32_t formatVersion) const = 0;

protected:
    Section() = default;
};

class CustomFontSection;

struct Element {
    virtual ~Element() = default;
    virtual ElementType elementType() const = 0;
    virtual std::uint32_t minimumFormatVersion() const = 0;
    virtual std::size_t serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const = 0;
    virtual nlohmann::json serializeJSON(std::uint32_t formatVersion) const = 0;
    virtual void drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) = 0;

protected:
    Element() = default;
};

struct ImageElement : public Element {
    ImageElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height);

    std::uint16_t x() const noexcept;
    std::uint16_t y() const noexcept;
    std::uint16_t width() const noexcept;
    std::uint16_t height() const noexcept;
    std::span<std::byte> buffer() noexcept;
    std::span<std::byte const> buffer() const noexcept;
    MemoryOneBitBuffer image() noexcept;
    ConstMemoryOneBitBuffer image() const noexcept;
    void updateBuffer(std::span<std::byte const> newBufferData);

    virtual ~ImageElement() override;
    virtual ElementType elementType() const override;
    virtual std::uint32_t minimumFormatVersion() const override;
    virtual std::size_t serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const override;
    virtual nlohmann::json serializeJSON(std::uint32_t formatVersion) const override;
    virtual void drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) override;

    static std::expected<std::unique_ptr<ImageElement>, ParseError> parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion);
    static std::unique_ptr<ImageElement> parseJSON(nlohmann::json const& j, std::uint32_t formatVersion);

private:
    std::uint16_t m_x{};
    std::uint16_t m_y{};
    std::uint16_t m_width{};
    std::uint16_t m_height{};
    std::vector<std::byte> m_buffer;
};

struct AnimationElement : public Element {
    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);

    std::uint16_t x() const noexcept;
    std::uint16_t y() const noexcept;
    std::uint16_t width() const noexcept;
    std::uint16_t height() const noexcept;
    std::uint16_t numberOfFrames() const noexcept;
    std::uint16_t updateInterval() const noexcept;
    std::span<std::byte> buffers() noexcept;
    std::span<std::byte const> buffers() const noexcept;
    std::span<std::byte> buffer(std::uint16_t frameIndex) noexcept;
    std::span<std::byte const> buffer(std::uint16_t frameIndex) const noexcept;
    MemoryOneBitBuffer image(std::uint16_t frameIndex) noexcept;
    ConstMemoryOneBitBuffer image(std::uint16_t frameIndex) const noexcept;
    void updateBuffer(std::span<std::byte const> newBufferData);

    virtual ~AnimationElement() override;
    virtual ElementType elementType() const override;
    virtual std::uint32_t minimumFormatVersion() const override;
    virtual std::size_t serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const override;
    virtual nlohmann::json serializeJSON(std::uint32_t formatVersion) const override;
    virtual void drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) override;

    static std::expected<std::unique_ptr<AnimationElement>, ParseError> parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion);
    static std::unique_ptr<AnimationElement> parseJSON(nlohmann::json const& j, std::uint32_t formatVersion);

private:
    std::uint16_t m_x{};
    std::uint16_t m_y{};
    std::uint16_t m_width{};
    std::uint16_t m_height{};
    std::uint16_t m_numberOfFrames{};
    std::uint16_t m_updateInterval{};
    std::vector<std::byte> m_buffer;
};

struct HScrollImageElement : public Element {
    HScrollImageElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, std::uint16_t contentWidth, ScrollFlags flags, std::uint8_t scrollSpeed);

    std::uint16_t x() const noexcept;
    std::uint16_t y() const noexcept;
    std::uint16_t width() const noexcept;
    std::uint16_t height() const noexcept;
    std::uint16_t contentWidth() const noexcept;
    ScrollFlags flags() const noexcept;
    std::uint8_t scrollSpeed() const noexcept;
    std::span<std::byte> buffer() noexcept;
    std::span<std::byte const> buffer() const noexcept;
    MemoryOneBitBuffer image() noexcept;
    ConstMemoryOneBitBuffer image() const noexcept;
    void updateBuffer(std::span<std::byte const> newBufferData);

    virtual ~HScrollImageElement() override;
    virtual ElementType elementType() const override;
    virtual std::uint32_t minimumFormatVersion() const override;
    virtual std::size_t serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const override;
    virtual nlohmann::json serializeJSON(std::uint32_t formatVersion) const override;
    virtual void drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) override;

    static std::expected<std::unique_ptr<HScrollImageElement>, ParseError> parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion);
    static std::unique_ptr<HScrollImageElement> parseJSON(nlohmann::json const& j, std::uint32_t formatVersion);

private:
    std::uint16_t m_x{};
    std::uint16_t m_y{};
    std::uint16_t m_width{};
    std::uint16_t m_height{};
    std::uint16_t m_contentWidth{};
    ScrollFlags m_flags;
    std::uint8_t m_scrollSpeed{};
    std::vector<std::byte> m_buffer;
};

struct VScrollImageElement : public Element {
    VScrollImageElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, std::uint16_t contentHeight, ScrollFlags flags, std::uint8_t scrollSpeed);

    std::uint16_t x() const noexcept;
    std::uint16_t y() const noexcept;
    std::uint16_t width() const noexcept;
    std::uint16_t height() const noexcept;
    std::uint16_t contentHeight() const noexcept;
    ScrollFlags flags() const noexcept;
    std::uint8_t scrollSpeed() const noexcept;
    std::span<std::byte> buffer() noexcept;
    std::span<std::byte const> buffer() const noexcept;
    MemoryOneBitBuffer image() noexcept;
    ConstMemoryOneBitBuffer image() const noexcept;
    void updateBuffer(std::span<std::byte const> newBufferData);

    virtual ~VScrollImageElement() override;
    virtual ElementType elementType() const override;
    virtual std::uint32_t minimumFormatVersion() const override;
    virtual std::size_t serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const override;
    virtual nlohmann::json serializeJSON(std::uint32_t formatVersion) const override;
    virtual void drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) override;

    static std::expected<std::unique_ptr<VScrollImageElement>, ParseError> parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion);
    static std::unique_ptr<VScrollImageElement> parseJSON(nlohmann::json const& j, std::uint32_t formatVersion);

private:
    std::uint16_t m_x{};
    std::uint16_t m_y{};
    std::uint16_t m_width{};
    std::uint16_t m_height{};
    std::uint16_t m_contentHeight{};
    ScrollFlags m_flags;
    std::uint8_t m_scrollSpeed{};
    std::vector<std::byte> m_buffer;
};

struct LineElement : public Element {
    LineElement(std::uint16_t originX, std::uint16_t originY, std::uint16_t targetX, std::uint16_t targetY, LineStyle lineStyle, LineFlags flags);

    std::uint16_t originX() const noexcept;
    std::uint16_t originY() const noexcept;
    std::uint16_t targetX() const noexcept;
    std::uint16_t targetY() const noexcept;
    LineStyle lineStyle() const noexcept;
    LineFlags flags() const noexcept;

    virtual ~LineElement() override;
    virtual ElementType elementType() const override;
    virtual std::uint32_t minimumFormatVersion() const override;
    virtual std::size_t serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const override;
    virtual nlohmann::json serializeJSON(std::uint32_t formatVersion) const override;
    virtual void drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) override;

    static std::expected<std::unique_ptr<LineElement>, ParseError> parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion);
    static std::unique_ptr<LineElement> parseJSON(nlohmann::json const& j, std::uint32_t formatVersion);

private:
    std::uint16_t m_originX{};
    std::uint16_t m_originY{};
    std::uint16_t m_targetX{};
    std::uint16_t m_targetY{};
    LineStyle m_lineStyle{};
    LineFlags m_flags;
};

struct BoxElement : public Element {
    BoxElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, FillPattern fillPattern, FillFlags flags);

    std::uint16_t x() const noexcept;
    std::uint16_t y() const noexcept;
    std::uint16_t width() const noexcept;
    std::uint16_t height() const noexcept;
    FillPattern fillPattern() const noexcept;
    FillFlags flags() const noexcept;

    virtual ~BoxElement() override;
    virtual ElementType elementType() const override;
    virtual std::uint32_t minimumFormatVersion() const override;
    virtual std::size_t serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const override;
    virtual nlohmann::json serializeJSON(std::uint32_t formatVersion) const override;
    virtual void drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) override;

    static std::expected<std::unique_ptr<BoxElement>, ParseError> parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion);
    static std::unique_ptr<BoxElement> parseJSON(nlohmann::json const& j, std::uint32_t formatVersion);

private:
    std::uint16_t m_x{};
    std::uint16_t m_y{};
    std::uint16_t m_width{};
    std::uint16_t m_height{};
    FillPattern m_fillPattern{};
    FillFlags m_flags;
};

struct ClippedTextElement : public Element {
    ClippedTextElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, TextFlags textFlags, std::uint16_t fontIndex, std::string text);

    std::uint16_t x() const noexcept;
    std::uint16_t y() const noexcept;
    std::uint16_t width() const noexcept;
    std::uint16_t height() const noexcept;
    TextFlags textFlags() const noexcept;
    std::uint16_t fontIndex() const noexcept;
    std::string text() const;

    virtual ~ClippedTextElement() override;
    virtual ElementType elementType() const override;
    virtual std::uint32_t minimumFormatVersion() const override;
    virtual std::size_t serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const override;
    virtual nlohmann::json serializeJSON(std::uint32_t formatVersion) const override;
    virtual void drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) override;

    static std::expected<std::unique_ptr<ClippedTextElement>, ParseError> parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion);
    static std::unique_ptr<ClippedTextElement> parseJSON(nlohmann::json const& j, std::uint32_t formatVersion);

private:
    std::uint16_t m_x{};
    std::uint16_t m_y{};
    std::uint16_t m_width{};
    std::uint16_t m_height{};
    TextFlags m_textFlags;
    std::uint16_t m_fontIndex{};
    std::string m_text;
};

struct HScrollTextElement : public Element {
    HScrollTextElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, TextFlags textFlags, ScrollFlags flags, std::uint8_t scrollSpeed, std::uint16_t fontIndex, std::string text);

    std::uint16_t x() const noexcept;
    std::uint16_t y() const noexcept;
    std::uint16_t width() const noexcept;
    std::uint16_t height() const noexcept;
    TextFlags textFlags() const noexcept;
    ScrollFlags flags() const noexcept;
    std::uint8_t scrollSpeed() const noexcept;
    std::uint16_t fontIndex() const noexcept;
    std::string text() const;

    virtual ~HScrollTextElement() override;
    virtual ElementType elementType() const override;
    virtual std::uint32_t minimumFormatVersion() const override;
    virtual std::size_t serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const override;
    virtual nlohmann::json serializeJSON(std::uint32_t formatVersion) const override;
    virtual void drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) override;

    static std::expected<std::unique_ptr<HScrollTextElement>, ParseError> parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion);
    static std::unique_ptr<HScrollTextElement> parseJSON(nlohmann::json const& j, std::uint32_t formatVersion);

private:
    std::uint16_t m_x{};
    std::uint16_t m_y{};
    std::uint16_t m_width{};
    std::uint16_t m_height{};
    TextFlags m_textFlags;
    ScrollFlags m_flags;
    std::uint8_t m_scrollSpeed{};
    std::uint16_t m_fontIndex{};
    std::string m_text;
};

struct CurrentTimeElement : public Element {
    CurrentTimeElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, TextFlags textFlags, std::uint16_t fontIndex, std::uint16_t utcOffset, TimeDisplayFlags flags);

    std::uint16_t x() const noexcept;
    std::uint16_t y() const noexcept;
    std::uint16_t width() const noexcept;
    std::uint16_t height() const noexcept;
    TextFlags textFlags() const noexcept;
    std::uint16_t fontIndex() const noexcept;
    std::uint16_t utcOffset() const noexcept;
    TimeDisplayFlags flags() const noexcept;

    virtual ~CurrentTimeElement() override;
    virtual ElementType elementType() const override;
    virtual std::uint32_t minimumFormatVersion() const override;
    virtual std::size_t serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const override;
    virtual nlohmann::json serializeJSON(std::uint32_t formatVersion) const override;
    virtual void drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) override;

    static std::expected<std::unique_ptr<CurrentTimeElement>, ParseError> parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion);
    static std::unique_ptr<CurrentTimeElement> parseJSON(nlohmann::json const& j, std::uint32_t formatVersion);

private:
    std::uint16_t m_x{};
    std::uint16_t m_y{};
    std::uint16_t m_width{};
    std::uint16_t m_height{};
    TextFlags m_textFlags{};
    std::uint16_t m_fontIndex{};
    std::uint16_t m_utcOffset{};
    TimeDisplayFlags m_flags;
};

struct AlwaysDrawnSection : public Section {
    AlwaysDrawnSection() = default;
    virtual ~AlwaysDrawnSection() override;

    bool doesDrawOnFront() const noexcept;
    bool doesDrawOnBack() const noexcept;
    bool doesClearBeforeDrawing() const noexcept;

    void setDrawOnFront(bool value) noexcept;
    void setDrawOnBack(bool value) noexcept;
    void setClearBeforeDrawing(bool value) noexcept;

    std::size_t elementCount() const noexcept;
    Element* elementAt(std::size_t index) const;

    void appendElement(std::unique_ptr<Element> element);
    void insertElement(std::size_t index, std::unique_ptr<Element> element);
    std::unique_ptr<Element> replaceElement(std::size_t index, std::unique_ptr<Element> element);
    std::unique_ptr<Element> eraseElement(std::size_t index);

    virtual SectionType sectionType() const override;
    virtual std::uint32_t minimumFormatVersion() const override;
    virtual std::size_t serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const override;
    virtual nlohmann::json serializeJSON(std::uint32_t formatVersion) const override;

    static std::expected<std::unique_ptr<AlwaysDrawnSection>, ParseError> parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion);
    static std::unique_ptr<AlwaysDrawnSection> parseJSON(nlohmann::json const& j, std::uint32_t formatVersion);

private:
    std::vector<std::unique_ptr<Element>> m_elements;
    bool m_drawOnFront{true};
    bool m_drawOnBack{true};
    bool m_clearBeforeDrawing{true};
};

struct TimeBasedDrawnSection : public Section {
    TimeBasedDrawnSection() = default;
    virtual ~TimeBasedDrawnSection() override;

    bool doesDrawOnFront() const noexcept;
    bool doesDrawOnBack() const noexcept;
    bool doesClearBeforeDrawing() const noexcept;

    void setDrawOnFront(bool value) noexcept;
    void setDrawOnBack(bool value) noexcept;
    void setClearBeforeDrawing(bool value) noexcept;

    std::size_t elementCount() const noexcept;
    Element* elementAt(std::size_t index) const;

    void appendElement(std::unique_ptr<Element> element);
    void insertElement(std::size_t index, std::unique_ptr<Element> element);
    std::unique_ptr<Element> replaceElement(std::size_t index, std::unique_ptr<Element> element);
    std::unique_ptr<Element> eraseElement(std::size_t index);

    std::int64_t startTimestamp() const noexcept;
    std::int64_t endTimestamp() const noexcept;
    void setStartTimestamp(std::int64_t value);
    void setEndTimestamp(std::int64_t value);

    virtual SectionType sectionType() const override;
    virtual std::uint32_t minimumFormatVersion() const override;
    virtual std::size_t serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const override;
    virtual nlohmann::json serializeJSON(std::uint32_t formatVersion) const override;

    static std::expected<std::unique_ptr<TimeBasedDrawnSection>, ParseError> parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion);
    static std::unique_ptr<TimeBasedDrawnSection> parseJSON(nlohmann::json const& j, std::uint32_t formatVersion);

private:
    std::vector<std::unique_ptr<Element>> m_elements;
    std::int64_t m_startTimestamp{};
    std::int64_t m_endTimestamp{};
    bool m_drawOnFront{true};
    bool m_drawOnBack{true};
    bool m_clearBeforeDrawing{true};
};

struct MultiTimeBasedDrawnSection : public Section {
    MultiTimeBasedDrawnSection() = default;
    virtual ~MultiTimeBasedDrawnSection() override;

    bool doesDrawOnFront() const noexcept;
    bool doesDrawOnBack() const noexcept;
    bool doesClearBeforeDrawing() const noexcept;

    void setDrawOnFront(bool value) noexcept;
    void setDrawOnBack(bool value) noexcept;
    void setClearBeforeDrawing(bool value) noexcept;

    std::size_t elementCount() const noexcept;
    Element* elementAt(std::size_t index) const;

    void appendElement(std::unique_ptr<Element> element);
    void insertElement(std::size_t index, std::unique_ptr<Element> element);
    std::unique_ptr<Element> replaceElement(std::size_t index, std::unique_ptr<Element> element);
    std::unique_ptr<Element> eraseElement(std::size_t index);

    std::vector<std::pair<std::int64_t, std::int64_t>> timeRanges() const;
    void setTimeRanges(std::vector<std::pair<std::int64_t, std::int64_t>> ranges);

    virtual SectionType sectionType() const override;
    virtual std::uint32_t minimumFormatVersion() const override;
    virtual std::size_t serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const override;
    virtual nlohmann::json serializeJSON(std::uint32_t formatVersion) const override;

    static std::expected<std::unique_ptr<MultiTimeBasedDrawnSection>, ParseError> parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion);
    static std::unique_ptr<MultiTimeBasedDrawnSection> parseJSON(nlohmann::json const& j, std::uint32_t formatVersion);

private:
    std::vector<std::unique_ptr<Element>> m_elements;
    std::vector<std::pair<std::int64_t, std::int64_t>> m_timeRanges;
    bool m_drawOnFront{true};
    bool m_drawOnBack{true};
    bool m_clearBeforeDrawing{true};
};

struct ExpiryDateSection : public Section {
    ExpiryDateSection() = default;
    virtual ~ExpiryDateSection() override;

    std::int64_t expiryDate() const noexcept;
    void setExpiryDate(std::int64_t value) noexcept;

    virtual SectionType sectionType() const override;
    virtual std::uint32_t minimumFormatVersion() const override;
    virtual std::size_t serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const override;
    virtual nlohmann::json serializeJSON(std::uint32_t formatVersion) const override;

    static std::expected<std::unique_ptr<ExpiryDateSection>, ParseError> parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion);
    static std::unique_ptr<ExpiryDateSection> parseJSON(nlohmann::json const& j, std::uint32_t formatVersion);

private:
    std::int64_t m_expiryDate{};
};

struct CustomFontSection : public Section {
    CustomFontSection() = default;
    virtual ~CustomFontSection() override;

    std::span<std::byte const> fontData() const;
    void setFontData(std::span<std::byte const> fontData);

    virtual SectionType sectionType() const override;
    virtual std::uint32_t minimumFormatVersion() const override;
    virtual std::size_t serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const override;
    virtual nlohmann::json serializeJSON(std::uint32_t formatVersion) const override;

    static std::expected<std::unique_ptr<CustomFontSection>, ParseError> parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion);
    static std::unique_ptr<CustomFontSection> parseJSON(nlohmann::json const& j, std::uint32_t formatVersion);

private:
    std::vector<std::byte> m_fontData;
};

struct File {
    std::vector<std::unique_ptr<Section>> sections;
};

extern std::size_t serializeFile(std::span<std::byte>& buffer, File const& file);
extern nlohmann::json serializeFileJSON(File const& file);
extern std::expected<File, ParseError> parseFile(std::span<std::byte const> data);
extern File parseFileJSON(nlohmann::json const& j);

} // namespace monoformat

#endif // MONOFORMAT_STRUCTURED_HPP
