﻿#pragma once

#include <NsGui/ObservableCollection.h>
#include <NsCore/Ptr.h>

namespace Noesis
{
	template<typename T>
	class ObservableCollectionIterator
	{
	public:
		using iterator_category = std::forward_iterator_tag;
		using value_type = T*;
		using difference_type = std::ptrdiff_t;
		using pointer = T**;
		using reference = T*&;

		ObservableCollectionIterator(ObservableCollection<T>* collection, uint32_t index)
			: mCollection(collection), mIndex(index)
		{
		}

		T* operator*() const { return mCollection->Get(mIndex); }
		ObservableCollectionIterator& operator++() { ++mIndex; return *this; }

		bool operator==(const ObservableCollectionIterator& other) const
		{
			return mIndex == other.mIndex;
		}

		bool operator!=(const ObservableCollectionIterator& other) const
		{
			return !(*this == other);
		}
	private:
		ObservableCollection<T>* mCollection;
		uint32_t mIndex;
	};


	template<typename T> 
	ObservableCollectionIterator<T> begin(ObservableCollection<T>* collection) 
	{ 
		return { collection, 0 }; 
	}

	template<typename T> 
	ObservableCollectionIterator<T> begin(const ObservableCollection<T>* collection) 
	{ 
		return { collection, 0 };
	}

	template<typename T>
	ObservableCollectionIterator<T> begin(Ptr<ObservableCollection<T>>& collection)
	{
		return { collection.GetPtr(), 0 };
	}

	template<typename T>
	ObservableCollectionIterator<T> begin(const Ptr<ObservableCollection<T>>& collection)
	{
		return { collection.GetPtr(), 0 };
	}

	template<typename T>
	ObservableCollectionIterator<T>  end(ObservableCollection<T>* collection) 
	{ 
		return { collection, static_cast<uint32_t>(collection->Count()) };
	}

	template<typename T>
	ObservableCollectionIterator<T>  end(const ObservableCollection<T>* collection)
	{
		return { collection, static_cast<uint32_t>(collection->Count()) };
	}

	template<typename T>
	ObservableCollectionIterator<T>  end(Ptr<ObservableCollection<T>>& collection)
	{
		return { collection.GetPtr(), static_cast<uint32_t>(collection->Count())};
	}

	template<typename T>
	ObservableCollectionIterator<T>  end(const Ptr<ObservableCollection<T>>& collection)
	{
		return { collection.GetPtr(), static_cast<uint32_t>(collection->Count()) };
	}
}

namespace Noesis
{
	//Adapters for begin/end
	namespace Detail {
		using std::begin; using std::end;

		template<class Range>
		auto Begin(Range&& r) -> decltype(begin(std::forward<Range>(r))) {
			return begin(std::forward<Range>(r));
		}

		template<class Range>
		auto End(Range&& r) -> decltype(end(std::forward<Range>(r))) {
			return end(std::forward<Range>(r));
		}

		// Skip with offset
		template<class Range>
		auto Begin(Range&& r, std::size_t offset) -> decltype(begin(std::forward<Range>(r))) {
			auto b = begin(std::forward<Range>(r));
			auto e = end(std::forward<Range>(r));
			while (offset-- > 0 && b != e) ++b;
			return b;
		}

		// Skip specializations for ObservableCollection
		template<typename T>
		ObservableCollectionIterator<T> Begin(ObservableCollection<T>* collection, std::size_t offset) {
			if (!collection) return { nullptr, 0 };
			const uint32_t count = collection->Count();
			const uint32_t startIndex = (offset < count) ? static_cast<uint32_t>(offset) : count;
			return { collection, startIndex };
		}

		template<typename T>
		ObservableCollectionIterator<T> Begin(const ObservableCollection<T>* collection, std::size_t offset) {
			return Begin(const_cast<ObservableCollection<T>*>(collection), offset);
		}

		template<typename T>
		ObservableCollectionIterator<T> Begin(Ptr<ObservableCollection<T>>& collection, std::size_t offset) {
			return Begin(collection.GetPtr(), offset);
		}

		template<typename T>
		ObservableCollectionIterator<T> Begin(const Ptr<ObservableCollection<T>>& collection, std::size_t offset) {
			return Begin(collection.GetPtr(), offset);
		}

		template<typename T>
		ObservableCollectionIterator<T> Begin(ObservableCollection<T>& collection, std::size_t offset) {
			return Begin(&collection, offset);
		}

		template<typename T>
		ObservableCollectionIterator<T> Begin(const ObservableCollection<T>& collection, std::size_t offset) {
			return Begin(const_cast<ObservableCollection<T>*>(&collection), offset);
		}
	}
	
	template<typename TDerived, typename TRange>
	class CastRange
	{
		using BaseIterator = decltype(Detail::Begin(std::declval<TRange>()));
		using BaseType = std::remove_pointer_t<decltype(*std::declval<BaseIterator>())>;

		static_assert(std::is_pointer_v<decltype(*std::declval<BaseIterator>())>,
			"CastRange can only be used with ranges of pointers");

		static_assert(IsDerived<TDerived, BaseType>::Result,
			"Derived must inherit from range element type");

	public:
		explicit CastRange(TRange&& range)
			: mRange(std::forward<TRange>(range)) {
		}

		class Iterator
		{
		public:
			using BaseIterator = decltype(Detail::Begin(std::declval<TRange>()));

			Iterator(BaseIterator it) : mIt(it) {
			}

			TDerived* operator*() const {
				return static_cast<TDerived*>(*mIt);
			}

			Iterator& operator++() {
				++mIt;
				return *this;
			}

			bool operator==(const Iterator& other) const {
				return mIt == other.mIt;
			}

			bool operator!=(const Iterator& other) const {
				return mIt != other.mIt;
			}

		private:
			BaseIterator mIt;
		};

		Iterator begin() const {
			return Iterator(Detail::Begin(mRange));
		}

		Iterator end() const {
			return Iterator(Detail::End(mRange));
		}

	private:
		TRange mRange;
	};

	template<typename TDerived, typename TRange>
	auto Cast(TRange&& range)
	{
		return CastRange<TDerived, TRange>(std::forward<TRange>(range));
	}
	// 	// Cast functions for ObservableCollection
	// 
	// 	template<typename TDerived, typename T>
	// 	auto Cast(ObservableCollection<T>* collection)
	// 	{
	// 		return CastRange<TDerived, T>(collection);
	// 	}
	// 
	// 	template<typename TDerived, typename T>
	// 	auto Cast(Ptr<ObservableCollection<T>>& collection)
	// 	{
	// 		return CastRange<TDerived, T>(collection);
	// 	}
	// 
	// 	template<typename TDerived, typename T>
	// 	auto Cast(const Ptr<ObservableCollection<T>>& collection)
	// 	{
	// 		return CastRange<TDerived, T>(collection);
	// 	}
}

namespace Noesis
{
	template<std::size_t Offset, class TRange>
	class SkipRange
	{
		using Range = std::decay_t<TRange>;
	public:
		explicit SkipRange(Range range) : mRange(std::move(range)) {}

		auto begin() const {
			return Detail::Begin(mRange, Offset);
		}

		auto end() const {
			return Detail::End(mRange);
		}

	private:
		Range mRange;
	};

	template<std::size_t Offset, class Range>
	auto Skip(Range&& range) {
		using R = std::decay_t<Range>;
		return SkipRange<Offset, R>(std::forward<Range>(range));
	}

}