/*
 * Copyright 2015-2017 Michele "King_DuckZ" Santullo
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include "size_type.hpp"
#include "has_method.hpp"
#include <type_traits>

#if defined VWR_OUTER_NAMESPACE
namespace VWR_OUTER_NAMESPACE {
#endif

namespace vwr {
	template <typename V>
	struct VectorWrapperInfo;

	template <typename V, size_type S=VectorWrapperInfo<V>::dimensions>
	class Vec;

	namespace implem {
		define_has_typedef(lower_vector_type, LowerVec);
		define_has_typedef(higher_vector_type, HigherVec);
		define_has_typedef(storage_type, StorageType);
		define_has_enum(offset_x, OffsetX);
		define_has_method(get_at, GetAt);
		define_has_enum(cast_ignore_trailing_properties, CastIgnoreTrailingProperties);

		template <typename V, bool UserDefined=HasStorageTypeTypedef<VectorWrapperInfo<V>>::value> struct get_storage_type;
		template <typename V> struct get_storage_type<V, true> {
			typedef typename VectorWrapperInfo<V>::storage_type type;
			static_assert(alignof(type) >= alignof(V), "Requested storage type's alignment is not compatible with vector_type");
		};
		template <typename V> struct get_storage_type<V, false> {
			typedef V type;
		};

		template <typename T, bool=HasOffsetXEnum<VectorWrapperInfo<T>>::value and std::is_standard_layout<T>::value>
		struct VecGetter;
		template <typename T>
		struct VecGetter<T, true> {
			typedef typename get_storage_type<T>::type storage_type;
			static typename VectorWrapperInfo<T>::scalar_type& get_at ( storage_type& parVec, size_type parIndex );
		};
		template <typename T>
		struct VecGetter<T, false> {
			typedef typename get_storage_type<T>::type storage_type;
		private:
			static_assert(HasGetAtMethod<VectorWrapperInfo<T>>::value, "You must provide a get_at() static method for this vector_type");
			typedef typename VectorWrapperInfo<T>::scalar_type scalar_type;
			using get_at_func = decltype(&VectorWrapperInfo<T>::get_at)(size_type, storage_type&);
			using get_at_rettype = typename std::result_of<get_at_func>::type;

			static_assert(not std::is_rvalue_reference<get_at_rettype>::value, "rvalue ref return types not implemented");
			static_assert(std::is_lvalue_reference<get_at_rettype>::value, "Read-only vectors not implemented");

		public:
			static get_at_rettype get_at ( storage_type& parVec, size_type parIndex );
		};

		template <typename T, size_type I> struct get_offset_enum_from_index;
		template <typename T> struct get_offset_enum_from_index<T, 0> {
			enum { value = VectorWrapperInfo<T>::offset_x };
		};
		template <typename T> struct get_offset_enum_from_index<T, 1> {
			enum { value = VectorWrapperInfo<T>::offset_y };
		};
		template <typename T> struct get_offset_enum_from_index<T, 2> {
			enum { value = VectorWrapperInfo<T>::offset_z };
		};
		template <typename T> struct get_offset_enum_from_index<T, 3> {
			enum { value = VectorWrapperInfo<T>::offset_w };
		};

		template <typename T, size_type S=VectorWrapperInfo<T>::dimensions> struct min_offset {
			enum {
				value = (
					static_cast<int>(get_offset_enum_from_index<T, S-1>::value) < static_cast<int>(min_offset<T, S-1>::value) ?
						static_cast<int>(get_offset_enum_from_index<T, S-1>::value) :
						static_cast<int>(min_offset<T, S-1>::value)
					)
			};
		};
		template <typename T> struct min_offset<T, 1> {
			enum { value = get_offset_enum_from_index<T, 0>::value };
		};
	} //namespace implem
} //namespace vwr

#if defined VWR_OUTER_NAMESPACE
} //namespace VWR_OUTER_NAMESPACE
#endif
