/*
 * 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 "implem_vec_common.hpp"
#include "sequence_bt.hpp"
#include <type_traits>
#include <utility>
#include <functional>

#if defined VWR_OUTER_NAMESPACE
namespace VWR_OUTER_NAMESPACE {
#endif

namespace vwr {
	namespace implem {
		template <typename V>
		Vec<V>& assign_same_type ( Vec<V>& parLeft, const Vec<V>& parRight );

		template <typename V> struct is_vec {
			enum { value = false };
		};
		template <typename V> struct is_vec<Vec<V>> {
			typedef V vector_type;
			enum { value = true };
		};

		template <typename V> struct get_wrapped_ifn {
			typedef V type;
		};
		template <typename T> struct get_wrapped_ifn<Vec<T>> {
			typedef T type;
		};

		template <typename V1, typename V2>
		struct directly_convertible {
			enum {
				value =
					not HasGetAtMethod<VectorWrapperInfo<typename get_wrapped_ifn<V1>::type>>::value and
					not HasGetAtMethod<VectorWrapperInfo<typename get_wrapped_ifn<V2>::type>>::value
			};
		};

		template <typename V>
		class VecBase {
			friend Vec<V>& assign_same_type<V> ( Vec<V>& parLeft, const Vec<V>& parRight );
		public:
			typedef V vector_type;
			typedef typename get_storage_type<V>::type storage_type;

			typedef typename VectorWrapperInfo<V>::scalar_type scalar_type;

			enum {
				dimensions = VectorWrapperInfo<V>::dimensions
			};

			VecBase ( void ) = default;
			template <typename T>
			explicit 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=false );
			explicit VecBase ( const vector_type& parInit );
			template <typename... Args>
			VecBase ( scalar_type parX, scalar_type parY, Args... parArgs );
			~VecBase ( void ) = default;

			scalar_type& operator[] ( size_type parIndex );
			const scalar_type& operator[] ( size_type parIndex ) const;

			storage_type& data ( void ) { return m_wrapped; }
			const storage_type& data ( void ) const { return m_wrapped; }

			template <typename V2>
			const typename std::enable_if<is_vec<V2>::value and directly_convertible<V, V2>::value, V2>::type& cast ( void ) const;
			template <typename V2>
			typename std::enable_if<is_vec<V2>::value and directly_convertible<V, V2>::value, V2>::type& cast ( void );

			template <typename V2>
			const typename std::enable_if<is_vec<V2>::value and not directly_convertible<V, V2>::value, V2>::type& cast ( void ) const;
			template <typename V2>
			typename std::enable_if<is_vec<V2>::value and not directly_convertible<V, V2>::value, V2>::type& cast ( void );

			template <typename V2> VecBase& operator+= ( const VecBase<V2>& parOther );
			template <typename V2> VecBase& operator-= ( const VecBase<V2>& parOther );
			template <typename V2> VecBase& operator*= ( const VecBase<V2>& parOther );
			template <typename V2> VecBase& operator/= ( const VecBase<V2>& parOther );
			VecBase& operator+= ( const scalar_type& parOther );
			VecBase& operator-= ( const scalar_type& parOther );
			VecBase& operator*= ( const scalar_type& parOther );
			VecBase& operator/= ( const scalar_type& parOther );

		private:
			template <size_type... I, typename... Args>
			void assign_values (const bt::number_seq<size_type, I...>&, Args... parArgs);
			template <typename Op, typename V2, size_type... I>
			void assign_values_op (Op parOp, const bt::number_seq<size_type, I...>& parSeq, const VecBase<V2>& parOther);
			template <typename Op, size_type... I>
			void assign_values_op_scalar (Op parOp, const bt::number_seq<size_type, I...>& parSeq, const scalar_type& parOther);

			storage_type m_wrapped;
		};

		template <
			typename T,
			typename U,
			size_type S=(
				static_cast<int>(VectorWrapperInfo<T>::dimensions) < static_cast<int>(VectorWrapperInfo<U>::dimensions) ?
					static_cast<int>(VectorWrapperInfo<T>::dimensions)
				:
					static_cast<int>(VectorWrapperInfo<U>::dimensions)
				)
			> struct have_compat_offsets;
		template <typename T, typename U> struct have_compat_offsets<T, U, 1> {
			enum {
				value = true
			};
		};
		template <typename T, typename U> struct have_compat_offsets<T, U, 2> {
			enum {
				value =
					VectorWrapperInfo<T>::offset_x - min_offset<T>::value == VectorWrapperInfo<U>::offset_x - min_offset<U>::value and
					VectorWrapperInfo<T>::offset_y - min_offset<T>::value == VectorWrapperInfo<U>::offset_y - min_offset<U>::value
			};
		};
		template <typename T, typename U> struct have_compat_offsets<T, U, 3> {
			enum {
				value =
					VectorWrapperInfo<T>::offset_x - min_offset<T>::value == VectorWrapperInfo<U>::offset_x - min_offset<U>::value and
					VectorWrapperInfo<T>::offset_y - min_offset<T>::value == VectorWrapperInfo<U>::offset_y - min_offset<U>::value and
					VectorWrapperInfo<T>::offset_z - min_offset<T>::value == VectorWrapperInfo<U>::offset_z - min_offset<U>::value
			};
		};
		template <typename T, typename U> struct have_compat_offsets<T, U, 4> {
			enum {
				value =
					VectorWrapperInfo<T>::offset_x - min_offset<T>::value == VectorWrapperInfo<U>::offset_x - min_offset<U>::value and
					VectorWrapperInfo<T>::offset_y - min_offset<T>::value == VectorWrapperInfo<U>::offset_y - min_offset<U>::value and
					VectorWrapperInfo<T>::offset_z - min_offset<T>::value == VectorWrapperInfo<U>::offset_z - min_offset<U>::value and
					VectorWrapperInfo<T>::offset_w - min_offset<T>::value == VectorWrapperInfo<U>::offset_w - min_offset<U>::value
			};
		};

		template <typename T, typename U>
		struct have_compat_layout {
			enum {
				value =
					HasOffsetXEnum<VectorWrapperInfo<T>>::value and
					HasOffsetXEnum<VectorWrapperInfo<U>>::value and
					have_compat_offsets<T, U>::value
			};
		};
	} //namespace implem

	template <typename V> struct is_castable_to { };
} //namespace vwr

#if defined VWR_OUTER_NAMESPACE
} //namespace VWR_OUTER_NAMESPACE
#endif

#include "implem_vec_base.inl"
