#pragma once
#include "../axis/Axis.h"
#include <algorithm>
#include <cmath>
#include <optional>
namespace renderive::detail {
inline bool axes_are_orthogonal(const Axis_Transform& first,
                                const Axis_Transform& second) noexcept {
    return first.orientation != second.orientation;
}

inline PointF mapped_point(const Axis_Transform& first,
                           double first_coordinate,
                           const Axis_Transform& second,
                           double second_coordinate) noexcept {
    if (!axes_are_orthogonal(first, second))
        return {};
    const double first_pixel = first.coord_to_pixel(first_coordinate);
    const double second_pixel = second.coord_to_pixel(second_coordinate);
    return first.orientation == Orientation::Horizontal
               ? PointF{first_pixel, second_pixel}
               : PointF{second_pixel, first_pixel};
}

inline RectF mapped_rect(const Axis_Transform& first,
                         const Axis_Transform& second,
                         Range first_range,
                         Range second_range) noexcept {
    if (!axes_are_orthogonal(first, second))
        return {};
    const PointF origin = mapped_point(first, first_range.origin, second, second_range.origin);
    const PointF target = mapped_point(first, first_range.target, second, second_range.target);
    return {std::min(origin.x, target.x), std::min(origin.y, target.y),
            std::abs(target.x - origin.x), std::abs(target.y - origin.y)};
}

struct Axis_Raster_Layout {
    int width{};
    int height{};
    RectF target;
    bool first_reversed{};
    bool second_reversed{};
    bool first_is_horizontal{};

    [[nodiscard]] bool valid() const noexcept {
        return width > 0 && height > 0 && !target.empty();
    }

    [[nodiscard]] std::size_t index(int first, int second,
                                    int first_count, int second_count) const noexcept {
        if (first_reversed)
            first = first_count - 1 - first;
        if (second_reversed)
            second = second_count - 1 - second;
        const int x = first_is_horizontal ? first : second;
        const int y = first_is_horizontal ? second : first;
        return static_cast<std::size_t>(y) * width + x;
    }
};

inline Axis_Raster_Layout axis_raster_layout(const Axis_Transform& first,
                                             const Axis_Transform& second,
                                             Range first_range,
                                             Range second_range,
                                             int first_count,
                                             int second_count) noexcept {
    if (!axes_are_orthogonal(first, second) || first_count <= 0 || second_count <= 0)
        return {};
    const bool first_horizontal = first.orientation == Orientation::Horizontal;
    return {
        first_horizontal ? first_count : second_count,
        first_horizontal ? second_count : first_count,
        mapped_rect(first, second, first_range, second_range),
        first.coord_to_pixel(first_range.origin) > first.coord_to_pixel(first_range.target),
        second.coord_to_pixel(second_range.origin) > second.coord_to_pixel(second_range.target),
        first_horizontal
    };
}
inline double normalized_value(double value, Range range) {
    if(range.length() == 0.0)
        return 0.0;
    return std::clamp((value - range.origin) / range.length(), 0.0, 1.0);
}
struct Frequency_Columns {
    int first{};
    int last{};
    Range range;
};
inline std::optional<Frequency_Columns> frequency_columns(Range data_range, Range visible_range, int column_count, bool visible_only) {
    if(column_count <= 0)
        return std::nullopt;
    if(!visible_only || column_count == 1 || data_range.length() == 0.0)
        return Frequency_Columns{0, column_count - 1, data_range};
    const auto [data_low, data_high] = std::minmax(data_range.origin, data_range.target);
    const auto [visible_low, visible_high] = std::minmax(visible_range.origin, visible_range.target);
    const double clipped_low = std::max(data_low, visible_low);
    const double clipped_high = std::min(data_high, visible_high);
    if(clipped_low > clipped_high)
        return std::nullopt;
    const auto position = [data_range, column_count](double coordinate) { return (coordinate - data_range.origin) / data_range.length() * static_cast<double>(column_count - 1); };
    const double first_position = position(clipped_low);
    const double last_position = position(clipped_high);
    const double position_low = std::min(first_position, last_position);
    const double position_high = std::max(first_position, last_position);
    int first = std::clamp(static_cast<int>(std::floor(position_low)), 0, column_count - 1);
    int last = std::clamp(static_cast<int>(std::ceil(position_high)), first, column_count - 1);
    if(first == last) {
        if(last + 1 < column_count)
            ++last;
        else if(first > 0)
            --first;
    }
    const auto coordinate = [data_range, column_count](int index) { return data_range.origin + data_range.length() * static_cast<double>(index) / static_cast<double>(column_count - 1); };
    return Frequency_Columns{first, last, {coordinate(first), coordinate(last)}};
}
}
