#pragma once

#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <memory_resource>
#include <span>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>

#ifndef LIB_DECL
#define LIB_DECL
#endif

namespace renderive {

struct Point {
    int x{};
    int y{};
};

struct PointF {
    double x{};
    double y{};
};

struct Size {
    int width{};
    int height{};

    [[nodiscard]] bool empty() const noexcept { return width <= 0 || height <= 0; }
    friend bool operator==(Size, Size) = default;
};

struct Rect {
    int x{};
    int y{};
    int width{};
    int height{};

    [[nodiscard]] bool empty() const noexcept { return width <= 0 || height <= 0; }
    [[nodiscard]] int right() const noexcept { return x + width; }
    [[nodiscard]] int bottom() const noexcept { return y + height; }
    [[nodiscard]] bool contains(Point point) const noexcept {
        return point.x >= x && point.x <= right() && point.y >= y && point.y <= bottom();
    }
};

struct RectF {
    double x{};
    double y{};
    double width{};
    double height{};

    [[nodiscard]] bool empty() const noexcept { return width <= 0.0 || height <= 0.0; }
    [[nodiscard]] double right() const noexcept { return x + width; }
    [[nodiscard]] double bottom() const noexcept { return y + height; }
    [[nodiscard]] bool contains(PointF point) const noexcept {
        return point.x >= x && point.x <= right() && point.y >= y && point.y <= bottom();
    }
    [[nodiscard]] RectF normalized() const noexcept {
        RectF value = *this;
        if (value.width < 0.0) {
            value.x += value.width;
            value.width = -value.width;
        }
        if (value.height < 0.0) {
            value.y += value.height;
            value.height = -value.height;
        }
        return value;
    }
};

enum class Orientation : std::uint8_t { Horizontal, Vertical };

struct Range {
    double origin{};
    double target{};

    [[nodiscard]] double size() const noexcept { return std::abs(target - origin); }
    [[nodiscard]] double length() const noexcept { return target - origin; }
    [[nodiscard]] double center() const noexcept { return origin + length() * 0.5; }
    [[nodiscard]] bool contains(double value) const noexcept {
        const auto [low, high] = std::minmax(origin, target);
        return value >= low - 1e-9 && value <= high + 1e-9;
    }
    friend bool operator==(Range, Range) = default;
};

struct Color {
    std::uint8_t r{};
    std::uint8_t g{};
    std::uint8_t b{};
    std::uint8_t a{255};

    static constexpr Color transparent() noexcept { return {0, 0, 0, 0}; }
    static constexpr Color black() noexcept { return {0, 0, 0, 255}; }
    static constexpr Color white() noexcept { return {255, 255, 255, 255}; }
    static constexpr Color red() noexcept { return {255, 0, 0, 255}; }
    static constexpr Color green() noexcept { return {0, 255, 0, 255}; }
    static constexpr Color yellow() noexcept { return {255, 255, 0, 255}; }
    friend bool operator==(Color, Color) = default;
};

using Pixel = std::uint32_t;

constexpr Pixel premultiply(Color color) noexcept {
    const auto r = static_cast<std::uint32_t>((color.r * color.a + 127) / 255);
    const auto g = static_cast<std::uint32_t>((color.g * color.a + 127) / 255);
    const auto b = static_cast<std::uint32_t>((color.b * color.a + 127) / 255);
    return (static_cast<std::uint32_t>(color.a) << 24) | (r << 16) | (g << 8) | b;
}

constexpr Pixel pack_rgba(std::uint8_t r, std::uint8_t g, std::uint8_t b, std::uint8_t a = 255) noexcept {
    return premultiply(Color{r, g, b, a});
}

enum class Line_Style : std::uint8_t { None, Solid, Dash, Dot };
enum class Line_Cap : std::uint8_t { Butt, Square, Round };
enum class Line_Join : std::uint8_t { Miter, Bevel, Round };

struct Pen {
    Color color = Color::white();
    double width = 1.0;
    Line_Style style = Line_Style::Solid;
    Line_Cap cap = Line_Cap::Butt;
    Line_Join join = Line_Join::Miter;

    [[nodiscard]] bool enabled() const noexcept {
        return style != Line_Style::None && width > 0.0 && color.a != 0;
    }
    friend bool operator==(const Pen&, const Pen&) = default;
};

enum class Brush_Style : std::uint8_t { None, Solid };

struct Brush {
    Color color = Color::transparent();
    Brush_Style style = Brush_Style::None;

    [[nodiscard]] bool enabled() const noexcept {
        return style != Brush_Style::None && color.a != 0;
    }
    friend bool operator==(const Brush&, const Brush&) = default;
};

struct Font {
    double size = 12.0;
    int weight = 400;
    bool italic{};

    friend bool operator==(const Font&, const Font&) = default;
};

struct Number_Locale {
    char decimal_point = '.';

    friend bool operator==(Number_Locale, Number_Locale) = default;
};

struct Time_Of_Day {
    std::int64_t milliseconds = -1;

    [[nodiscard]] bool valid() const noexcept {
        return milliseconds >= 0 && milliseconds < 24LL * 60LL * 60LL * 1000LL;
    }
    friend bool operator==(Time_Of_Day, Time_Of_Day) = default;
};

enum class Image_Interpolation_Mode : std::uint8_t { Nearest, Bilinear, Bicubic };
enum class Line_Interpolation_Mode : std::uint8_t {
    Nearest_Sample,
    Linear_Value,
    Linear_Power_Domain,
    Step_Left,
    Step_Right,
    Cubic_Value
};
enum class Renderable_Cache_Mode : std::uint8_t { Direct, Local_Pixel };

enum class Pixel_Format : std::uint8_t { Premultiplied_32 };

struct Image_View {
    const std::byte* data{};
    int width{};
    int height{};
    int stride{};
    Pixel_Format format = Pixel_Format::Premultiplied_32;

    [[nodiscard]] bool empty() const noexcept {
        return data == nullptr || width <= 0 || height <= 0;
    }
};

enum class Memory_Domain : std::uint8_t {
    Renderable,
    Input_Buffer,
    Waterfall,
    Spectrum,
    Frame_Arena,
    Ring_Buffer,
    Image,
    Audio,
    Afterglow,
    Constellation_Diagram,
    Curve,
    Point_Set,
    Axis,
    Scheduler_Task,
    Renderable_Cache_Image,
    Plot_Frame,
    Update_Completion,
    Other,
    Count
};

LIB_DECL std::pmr::memory_resource* memory_resource() noexcept;
LIB_DECL std::pmr::memory_resource* memory_resource(Memory_Domain domain) noexcept;

template <class T, class... Args>
std::shared_ptr<T> make_shared(Memory_Domain domain, Args&&... args) {
    return std::allocate_shared<T>(std::pmr::polymorphic_allocator<T>(memory_resource(domain)),
                                   std::forward<Args>(args)...);
}

class LIB_DECL Color_Map {
public:
    Color_Map();
    explicit Color_Map(std::vector<Pixel> colors);

    [[nodiscard]] const std::vector<Pixel>& colors() const noexcept { return colors_; }
    void set_colors(std::vector<Pixel> colors);
    [[nodiscard]] int size() const noexcept { return static_cast<int>(colors_.size()); }
    [[nodiscard]] Pixel at_normalized(double value) const noexcept;

private:
    std::vector<Pixel> colors_;
};

} // namespace renderive
