/*
 * Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
 */

#pragma once

#if __has_include(<imgui.h>)
#include <imgui.h>
#endif

#include <ox/model/def.hpp>
#include <ox/std/assert.hpp>
#include <ox/std/bit.hpp>
#include <ox/std/def.hpp>
#include <ox/std/error.hpp>
#include <ox/std/iterator.hpp>
#include <ox/std/math.hpp>
#include <ox/std/types.hpp>

namespace nostalgia::geo {

template<typename T>
struct Vec {
	public:
		using value_type = T;
		using size_type = std::size_t;

		static constexpr auto TypeName = "net.drinkingtea.nostalgia.geo.Point";
		static constexpr auto TypeVersion = 1;

		T x = 0;
		T y = 0;

		template<typename RefType = value_type&, typename PtrType = value_type*, bool reverse = false>
		struct iterator: public ox::Iterator<std::bidirectional_iterator_tag, value_type> {
			private:
				PtrType m_t = nullptr;
				size_type m_offset = 0;
				size_type m_max = 0;

			public:
				constexpr iterator() noexcept = default;

				constexpr iterator(PtrType t, size_type offset, size_type max) noexcept {
					m_t = t;
					m_offset = offset;
					m_max = max;
				}

				[[nodiscard]]
				constexpr auto offset() const noexcept {
					return m_offset;
				}

				constexpr iterator operator+(size_type s) const noexcept {
					 if constexpr(reverse) {
					 return iterator(m_t, ox::max<size_type>(m_offset - s, 0), m_max);
					 } else {
					 return iterator(m_t, ox::min<size_type>(m_offset + s, m_max), m_max);
					 }
				}

				constexpr typename ox::Iterator<std::bidirectional_iterator_tag, value_type>::difference_type
				operator-(const iterator &other) const noexcept {
					if constexpr(reverse) {
						return m_offset + other.m_offset;
					} else {
						return m_offset - other.m_offset;
					}
				}

				constexpr iterator operator-(size_type s) const noexcept {
					if constexpr(reverse) {
						return iterator(m_t, ox::min<size_type>(m_offset + s, m_max), m_max);
					} else {
						return iterator(m_t, ox::max<size_type>(m_offset - s, 0), m_max);
					}
				}

				constexpr iterator &operator+=(size_type s) noexcept {
					if constexpr(reverse) {
						m_offset = ox::max<size_type>(m_offset - s, 0);
					} else {
						m_offset = ox::min(m_offset + s, m_max);
					}
					return *this;
				}

				constexpr iterator &operator-=(size_type s) noexcept {
					if constexpr(reverse) {
						m_offset = ox::min(m_offset + s, m_max);
					} else {
						m_offset = ox::max<size_type>(m_offset - s, 0);
					}
					return *this;
				}

				constexpr iterator &operator++() noexcept {
					return operator+=(1);
				}

				constexpr iterator &operator--() noexcept {
					return operator-=(1);
				}

				constexpr RefType operator*() const noexcept {
					return m_t[m_offset];
				}

				constexpr RefType operator[](size_type s) const noexcept {
					return m_t[s];
				}

				constexpr bool operator<(const iterator &other) const noexcept {
					return m_offset < other.m_offset;
				}

				constexpr bool operator>(const iterator &other) const noexcept {
					return m_offset > other.m_offset;
				}

				constexpr bool operator<=(const iterator &other) const noexcept {
					return m_offset <= other.m_offset;
				}

				constexpr bool operator>=(const iterator &other) const noexcept {
					return m_offset >= other.m_offset;
				}

				constexpr bool operator==(const iterator &other) const noexcept {
					return m_t == other.m_t && m_offset == other.m_offset && m_max == other.m_max;
				}

				constexpr bool operator!=(const iterator &other) const noexcept {
					return m_t != other.m_t || m_offset != other.m_offset || m_max != other.m_max;
				}

		};

		constexpr Vec() noexcept = default;

		template<typename ...Args>
		constexpr Vec(T pX, T pY) noexcept: x(pX), y(pY) {
		}

#if __has_include(<imgui.h>)
		explicit constexpr Vec(const ImVec2 &v) noexcept: Vec(v.x, v.y) {
		}

		explicit inline operator ImVec2() const noexcept {
			return {x, y};
		}
#endif

		[[nodiscard]]
		constexpr iterator<> begin() noexcept {
			return {start(), 0, size()};
		}

		[[nodiscard]]
		constexpr iterator<> end() noexcept {
			return {start(), size(), size()};
		}

		[[nodiscard]]
		constexpr iterator<const value_type&, const value_type*> begin() const noexcept {
			return {start(), 0, size()};
		}

		[[nodiscard]]
		constexpr iterator<const value_type&, const value_type*> end() const noexcept {
			return {start(), size(), size()};
		}

		[[nodiscard]]
		constexpr iterator<value_type&, value_type*, true> rbegin() noexcept {
			return {start(), size() - 1, size()};
		}

		[[nodiscard]]
		constexpr iterator<value_type&, value_type*, true> rend() noexcept {
			return {start(), ox::MaxValue<size_type>, size()};
		}

		[[nodiscard]]
		constexpr iterator<const value_type&, const value_type*, true> rbegin() const noexcept {
			return {start(), size() - 1, size()};
		}

		[[nodiscard]]
		constexpr iterator<const value_type&, const value_type*, true> rend() const noexcept {
			return {start(), ox::MaxValue<size_type>, size()};
		}

		constexpr auto &operator[](std::size_t i) noexcept {
			if (std::is_constant_evaluated()) {
				switch (i) {
					case 0:
						return x;
					case 1:
						return y;
					default:
						oxAssert(false, "Read past end of Vec2");
						return y;
				}
			} else {
				return start()[i];
			}
		}

		constexpr const auto &operator[](std::size_t i) const noexcept {
			if (std::is_constant_evaluated()) {
				switch (i) {
					case 0:
						return x;
					case 1:
						return y;
					default:
						oxAssert(false, "Read past end of Vec2");
						return y;
				}
			} else {
				return start()[i];
			}
		}

		constexpr auto operator==(const Vec &v) const noexcept {
			for (auto i = 0u; i < v.size(); ++i) {
				if ((*this)[i] != v[i]) {
					return false;
				}
			}
			return true;
		}

		constexpr auto operator!=(const Vec &v) const noexcept {
			return !operator==(v);
		}

		[[nodiscard]]
		constexpr std::size_t size() const noexcept {
			return 2;
		}

	protected:
		[[nodiscard]]
		constexpr T *start() noexcept {
			return &x;
		}

		[[nodiscard]]
		constexpr const T *start() const noexcept {
			return &x;
		}
};

using Vec2 = Vec<float>;

oxModelBegin(Vec2)
	oxModelField(x)
	oxModelField(y)
oxModelEnd()

}
