/*
 * 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.
 */

#if defined VWR_OUTER_NAMESPACE
namespace VWR_OUTER_NAMESPACE {
#endif

namespace vwr {
	namespace implem {
		template <typename T, bool=HasCastIgnoreTrailingPropertiesEnum<VectorWrapperInfo<T>>::value> struct IsCastIgnoreTrailingPropertiesSet;
		template <typename T> struct IsCastIgnoreTrailingPropertiesSet<T, true> { static const bool value = static_cast<bool>(VectorWrapperInfo<T>::cast_ignore_trailing_properties); };
		template <typename T> struct IsCastIgnoreTrailingPropertiesSet<T, false> { static const bool value = false; };

		template <typename V>
		template <typename T>
		VecBase<V>::VecBase (const T& parInit, typename std::enable_if<std::is_same<T, scalar_type>::value and not std::is_same<scalar_type, vector_type>::value, bool>::type) {
			for (size_type z = 0; z < VectorWrapperInfo<V>::dimensions; ++z) {
				VecGetter<V>::get_at(m_wrapped, z) = parInit;
			}
		}

		template <typename V>
		VecBase<V>::VecBase (const vector_type& parInit) :
			m_wrapped(parInit)
		{
		}

		template <typename V>
		template <typename... Args>
		VecBase<V>::VecBase (scalar_type parX, scalar_type parY, Args... parArgs) {
			static_assert(2 + sizeof...(Args) == dimensions, "Wrong number of parameters received");
			VecGetter<V>::get_at(m_wrapped, 0) = parX;
			VecGetter<V>::get_at(m_wrapped, 1) = parY;

			assign_values(bt::number_range<size_type, 2, dimensions>(), std::forward<Args>(parArgs)...);
		}

		template <typename V>
		template <size_type... I, typename... Args>
		void VecBase<V>::assign_values (const bt::number_seq<size_type, I...>&, Args... parArgs) {
			static_assert(sizeof...(I) == sizeof...(Args), "Argument count and indices count mismatch");

			std::initializer_list<scalar_type> t {
				(VecGetter<V>::get_at(m_wrapped, I) = parArgs)...
			};
			static_cast<void>(t);
		}

		template <typename V>
		template <typename Op, typename V2, size_type... I>
		void VecBase<V>::assign_values_op (Op parOp, const bt::number_seq<size_type, I...>& parSeq, const VecBase<V2>& parOther) {
			this->assign_values(parSeq, parOp((*this)[I], parOther[I])...);
		}

		template <typename V>
		template <typename Op, size_type... I>
		void VecBase<V>::assign_values_op_scalar (Op parOp, const bt::number_seq<size_type, I...>& parSeq, const scalar_type& parOther) {
			this->assign_values(parSeq, parOp((*this)[I], parOther)...);
		}

		template <typename V>
		auto VecBase<V>::operator[] (size_type parIndex) -> scalar_type& {
			return VecGetter<V>::get_at(m_wrapped, parIndex);
		}

		template <typename V>
		auto VecBase<V>::operator[] (size_type parIndex) const -> const scalar_type& {
			return VecGetter<V>::get_at(const_cast<storage_type&>(m_wrapped), parIndex);
		}

		template <typename V>
		template <typename V2>
		auto VecBase<V>::cast() const -> const typename std::enable_if<is_vec<V2>::value and directly_convertible<V, V2>::value, V2>::type& {
			static_assert(sizeof(V2) <= sizeof(VecBase<V>) - min_offset<V>::value, "V2 must fit in V starting from the first coordinate");
			static_assert(std::is_standard_layout<V2>::value, "V2 must be a standard layout type");
			static_assert(min_offset<typename is_vec<V2>::vector_type>::value == 0, "V2 must not have any properties before the first coordinate");
			static_assert(have_compat_layout<V, typename is_vec<V2>::vector_type>::value, "V is not suitable for casting to V2");

			//Assert that V2 won't stomp on part of V's data, unless the user
			//has explicitly said he doesn't care.
			static_assert((sizeof(typename VectorWrapperInfo<typename is_vec<V2>::vector_type>::scalar_type) * VectorWrapperInfo<typename is_vec<V2>::vector_type>::dimensions == sizeof(V2)) or
				IsCastIgnoreTrailingPropertiesSet<typename is_vec<V2>::vector_type>::value,
				"V2 must not have any properties past the last coordinate");
			static_assert(alignof(typename VectorWrapperInfo<V>::scalar_type) == alignof(V2), "Casting to V2 would give you a misaligned variable");

			return *reinterpret_cast<const V2*>(
				reinterpret_cast<const char*>(this) + VectorWrapperInfo<V>::offset_x
			);
		}

		template <typename V>
		template <typename V2>
		auto VecBase<V>::cast() -> typename std::enable_if<is_vec<V2>::value and directly_convertible<V, V2>::value, V2>::type& {
			return const_cast<V2&>(const_cast<const VecBase<V>*>(this)->cast<V2>());
		}

		template <typename V>
		template <typename V2>
		auto VecBase<V>::cast() const -> const typename std::enable_if<is_vec<V2>::value and not directly_convertible<V, V2>::value, V2>::type& {
			static_assert(std::is_standard_layout<V2>::value, "V2 must be a standard layout type");
			typedef typename is_vec<V2>::vector_type v2_type;
			static_assert(std::is_base_of<is_castable_to<typename VectorWrapperInfo<v2_type>::vector_type>, VectorWrapperInfo<V>>::value, "Casting for this type has to be explicitly enabled");

			//Assert that V2 won't stomp on part of V's data, unless the user
			//has explicitly said he doesn't care.
			static_assert((sizeof(typename VectorWrapperInfo<typename is_vec<V2>::vector_type>::scalar_type) * VectorWrapperInfo<typename is_vec<V2>::vector_type>::dimensions == sizeof(V2)) or
				IsCastIgnoreTrailingPropertiesSet<typename is_vec<V2>::vector_type>::value,
				"V2 must not have any properties past the last coordinate");
			static_assert(alignof(typename VectorWrapperInfo<V>::scalar_type) == alignof(V2), "Casting to V2 would give you a misaligned variable");

			return *reinterpret_cast<const V2*>(
				reinterpret_cast<const char*>(&(*this)[0])
			);
		}

		template <typename V>
		template <typename V2>
		auto VecBase<V>::cast() -> typename std::enable_if<is_vec<V2>::value and not directly_convertible<V, V2>::value, V2>::type& {
			return const_cast<V2&>(const_cast<const VecBase<V>*>(this)->cast<V2>());
		}

		template <typename V>
		template <typename V2>
		VecBase<V>& VecBase<V>::operator+= (const VecBase<V2>& parOther) {
			static_assert(static_cast<int>(VectorWrapperInfo<V>::dimensions) == static_cast<int>(VectorWrapperInfo<V2>::dimensions), "Dimensions mismatch");
			this->assign_values_op(std::plus<scalar_type>(), bt::number_range<size_type, 0, VectorWrapperInfo<V>::dimensions>(), parOther);
			return *this;
		}
		template <typename V>
		template <typename V2>
		VecBase<V>& VecBase<V>::operator-= (const VecBase<V2>& parOther) {
			static_assert(static_cast<int>(VectorWrapperInfo<V>::dimensions) == static_cast<int>(VectorWrapperInfo<V2>::dimensions), "Dimensions mismatch");
			this->assign_values_op(std::minus<scalar_type>(), bt::number_range<size_type, 0, VectorWrapperInfo<V>::dimensions>(), parOther);
			return *this;
		}
		template <typename V>
		template <typename V2>
		VecBase<V>& VecBase<V>::operator*= (const VecBase<V2>& parOther) {
			static_assert(static_cast<int>(VectorWrapperInfo<V>::dimensions) == static_cast<int>(VectorWrapperInfo<V2>::dimensions), "Dimensions mismatch");
			this->assign_values_op(std::multiplies<scalar_type>(), bt::number_range<size_type, 0, VectorWrapperInfo<V>::dimensions>(), parOther);
			return *this;
		}
		template <typename V>
		template <typename V2>
		VecBase<V>& VecBase<V>::operator/= (const VecBase<V2>& parOther) {
			static_assert(static_cast<int>(VectorWrapperInfo<V>::dimensions) == static_cast<int>(VectorWrapperInfo<V2>::dimensions), "Dimensions mismatch");
			this->assign_values_op(std::divides<scalar_type>(), bt::number_range<size_type, 0, VectorWrapperInfo<V>::dimensions>(), parOther);
			return *this;
		}

		template <typename V>
		VecBase<V>& VecBase<V>::operator+= (const scalar_type& parOther) {
			this->assign_values_op_scalar(std::plus<scalar_type>(), bt::number_range<size_type, 0, VectorWrapperInfo<V>::dimensions>(), parOther);
			return *this;
		}
		template <typename V>
		VecBase<V>& VecBase<V>::operator-= (const scalar_type& parOther) {
			this->assign_values_op_scalar(std::minus<scalar_type>(), bt::number_range<size_type, 0, VectorWrapperInfo<V>::dimensions>(), parOther);
			return *this;
		}
		template <typename V>
		VecBase<V>& VecBase<V>::operator*= (const scalar_type& parOther) {
			this->assign_values_op_scalar(std::multiplies<scalar_type>(), bt::number_range<size_type, 0, VectorWrapperInfo<V>::dimensions>(), parOther);
			return *this;
		}
		template <typename V>
		VecBase<V>& VecBase<V>::operator/= (const scalar_type& parOther) {
			this->assign_values_op_scalar(std::divides<scalar_type>(), bt::number_range<size_type, 0, VectorWrapperInfo<V>::dimensions>(), parOther);
			return *this;
		}
	} //namespace implem
} //namespace vwr

#if defined VWR_OUTER_NAMESPACE
} //namespace VWR_OUTER_NAMESPACE
#endif
