#pragma once
#include "../renderable/Renderable_Builder.h"
#include <renderive/base/property/Attach_Builder.hpp>
#include <renderive/scene/base/Scene_Base.hpp>
#include <renderive/state/Double_State_Strategy.hpp>
#include <algorithm>
#include <cmath>
#include <limits>
#include <memory>
namespace renderive {
class Abs_Axis;

template <std::totally_ordered Value_Type, Value_Type Minimum, Value_Type Maximum, Value_Type Default>
class Clamped_Property {
public:
    using Value = Value_Type;
    Clamped_Property() : value(Default) {}
    Clamped_Property(Value_Type value) : value(std::clamp(value, Minimum, Maximum)) {}
    Clamped_Property& operator=(Value_Type input) {
        value = std::clamp(input, Minimum, Maximum);
        return *this;
    }
    const Value_Type& get() const noexcept {
        return value;
    }
    operator const Value_Type&() const noexcept {
        return value;
    }
private:
    Value_Type value;
};
using Nonnegative_Count = Clamped_Property<int, 0, std::numeric_limits<int>::max(), 0>;
using Positive_Count = Clamped_Property<int, 1, std::numeric_limits<int>::max(), 1>;
class Unit_Interval {
public:
    using Value = double;
    Unit_Interval() = default;
    Unit_Interval(double value) : value(std::isfinite(value) ? std::clamp(value, 0.0, 1.0) : 0.0) {}
    Unit_Interval& operator=(double input) {
        value = std::isfinite(input) ? std::clamp(input, 0.0, 1.0) : 0.0;
        return *this;
    }
    const double& get() const noexcept {
        return value;
    }
    operator const double&() const noexcept {
        return value;
    }
private:
    double value{};
};
namespace detail {
class Paint_Overlay {};

template <Property_Set Properties_Type>
class Plottable_State
    : public Double_State_Strategy<Renderable, Properties_Type, Atomic_Spin_Mutex,
                                   Observer_State<Renderable_State_Observer>> {
public:
    using Properties = Properties_Type;
    using State_Observer = Observer_State<Renderable_State_Observer>;
    using Base = Double_State_Strategy<Renderable, Properties, Atomic_Spin_Mutex,
                                       State_Observer>;
    explicit Plottable_State(const Properties& properties)
        : Base(properties,
               With_Observer<State_Observer>{
                   State_Observer(Renderable_State_Observer(this))}) {}
    template <class Implementation>
    Plottable_State(const Properties& properties,
                    std::unique_ptr<Implementation> implementation)
        : Base(properties,
               With_Observer<State_Observer>{
                   State_Observer(Renderable_State_Observer(this))},
               std::move(implementation), true) {}
    template <auto Member, Property_Member_Assignable<Properties, Member> Value>
    Plottable_State& set(Value&& value) {
        Base::template set<Member>(std::forward<Value>(value));
        if constexpr (requires { &Properties::partition_count; &Properties::partition_mode; }) {
            if constexpr (std::same_as<decltype(Member),
                                       decltype(&Properties::partition_count)>) {
                if constexpr (Member == &Properties::partition_count) {
                    this->render_graph_changed();
                    return *this;
                }
            }
            if constexpr (std::same_as<decltype(Member),
                                       decltype(&Properties::partition_mode)>) {
                if constexpr (Member == &Properties::partition_mode) {
                    this->render_graph_changed();
                    return *this;
                }
            }
        }
        using Member_Value = decltype(std::declval<Properties&>().*Member);
        if constexpr (Paint_Only_Property_Value<Member_Value>)
            this->paint_changed();
        else
            this->changed();
        return *this;
    }
    template <auto Member>
    auto get() const {
        return Base::template get<Member>();
    }
protected:
    Properties properties() const {
        return Base::read([](const Properties& value) { return value; });
    }
    template <auto Member>
    auto property_value() const {
        return Base::read([](const Properties& value) { return value.*Member; });
    }
    const Properties& render_properties(const Render_State_View& state) const noexcept {
        return state.get(static_cast<const Base&>(*this));
    }
    void publish_properties() {
        Base::publish();
    }
private:
    std::uint64_t state_revision() const override {
        return Base::state_revision();
    }
    using Base::read;
    using Base::update;
};
template <class Control, class Properties>
using Attach_Plottable = Attach_Builder<Control, Properties, Renderable_Builder>;

}
}
