#ifndef _vector_n_
#define _vector_n_

#include "iostream"
#include <vector>
#include <random>

#include <initializer_list>  


#include <cstdint>
#include <type_traits>
#include <stdexcept>
#include <cmath>

namespace utils{
	//######################################
	//#            VECTOR TYPE             #
	//# 	 Backed by std::vector<T>      #
	//######################################
	template<typename T>
	class Vector{
public:
		using value_type = T;
		std::vector<T> v;

		// Default construtor utils::Vd vector;
	    Vector() = default; 

	    // Construtor utils::Vd vector(2, 3.15);
	    // Prevent implicit conversions like Vector<double> v = 5;
	    explicit Vector(uint64_t size, T value = T()) { 
	        v.resize(size, value);
	    }

		// Construct from a braced list: utils::Vf v{1,2,3};
		Vector(std::initializer_list<T> init) : v(init) {}



	    //##########################################################
		//#            VECTOR: --- basic properties ---            #
		//##########################################################
		// vector.clear();
		void clear() noexcept {v.clear();}

		// vector.push_back(2);
		void push_back(const T& val) { v.push_back(val); } 

		// vector[2] = 2;
		T& operator[](uint64_t idx) { return v[idx]; }
		// a = vector[2];
		const T& operator[](uint64_t idx) const { return v[idx]; }


		// Assign from a braced list after default construction:
		Vector& operator=(std::initializer_list<T> init) {
		    v = init;
		    return *this;
		}

		// vector.size();
		uint64_t size() const noexcept { return v.size(); } 

		void resize(uint64_t new_size, const T& value = T()) {
		    v.resize(new_size, value);
		}


	    T* data() noexcept { return v.data(); }
		const T* data() const noexcept { return v.data(); }

		//###########################################
		//#            VECTOR: == and !=            #
		//###########################################
		bool operator==(const Vector<T>& a) const {
		    if (v.size() != a.size()) {
		        return false;
		    }
		    const static T eps = static_cast<T>(1e-6); // tweak if you like
		    for (uint64_t i = 0; i < v.size(); ++i) {
		        if (std::fabs(v[i] - a[i]) > eps) {
		            return false;
		        }
		    }
		    return true;
		}

		bool operator!=(const Vector<T>& a) const { return !(*this == a); }

		//##################################################
		//#            VECTOR: nearly_equal_vec            #
		//##################################################

		bool nearly_equal_vec(const Vector<T>& a, double tol=1e-12) const {
		    if (a.size() != v.size()) return false;
		    for (uint64_t i=0;i<a.size();++i) {
		    	if (std::fabs(a[i]-v[i])>tol) {
		    		return false;
		    	}
		    }
		    return true;
		}

		//##################################################
		//#            VECTOR: Scalar Addition             #
		//##################################################
		template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value>::type>
	    void inplace_add(const U a){
	    	const T a_hat = static_cast<T>(a);
	    	const uint64_t n = v.size();
	    	for (uint64_t i = 0; i < n; ++i){
	    		v[i] += a_hat;
	    	}
	    }
	    template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value>::type>
	    Vector<T> add(const U a) const{   	
	    	Vector<T> result = *this;
	    	result.inplace_add(a);
	    	return result;
	    }
		template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value>::type>
		Vector<T> operator+(const U a) const {
		    return add(a);
		}
		template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value>::type> 
		friend Vector<T> operator+(U a, const Vector<T>& b) {
		 return b + a; 
		}
		template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value>::type>
		Vector<T>& operator+=(const U a) {
		    inplace_add(a);
		    return *this;
		}
		//##################################################
		//#            VECTOR: Vector Addition             #
		//##################################################
	    void inplace_add(const Vector<T>& a){
	    	if (a.size() != v.size()){
	    		throw std::runtime_error("utill:Vector.inplace_add -> Dimensions does not fit");
	    	}
	    	const uint64_t n = v.size();
	    	for (uint64_t i = 0; i < n; ++i){
	    		v[i] += a[i];
	    	}
	    }
	    Vector<T> add(const Vector<T>& a) const{   	
	    	Vector<T> result = *this;
	    	result.inplace_add(a);
	    	return result;
	    }
		Vector<T> operator+(const Vector<T>& a) const {
		    return add(a);
		}
		Vector<T>& operator+=(const Vector<T>& a) {
		    inplace_add(a);
		    return *this;
		}
		//##################################################
		//#            VECTOR: Scalar Subtract             #
		//##################################################
		template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value>::type>
	    void inplace_subtract(const U a){
	    	const T a_hat = static_cast<T>(a);
	    	const uint64_t n = v.size();
	    	for (uint64_t i = 0; i < n; ++i){
	    		v[i] -= a_hat;
	    	}
	    }
	    template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value>::type>
	    Vector<T> subtract(const U a) const{   	
	    	Vector<T> result = *this;
	    	result.inplace_subtract(a);
	    	return result;
	    }
		template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value>::type>
		Vector<T> operator-(const U a) const {
		    return subtract(a);
		}
		template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value>::type>
		Vector<T>& operator-=(const U a) {
		    inplace_subtract(a);
		    return *this;
		}
		//##################################################
		//#            VECTOR: Vector Subtract             #
		//##################################################
	    void inplace_subtract(const Vector<T>& a){
	    	if (a.size() != v.size()){
	    		throw std::runtime_error("utill:Vector.inplace_subtract -> Dimensions does not fit");
	    	}
	    	const uint64_t n = v.size();
	    	for (uint64_t i = 0; i < n; ++i){
	    		v[i] -= a[i];
	    	}
	    }
	    Vector<T> subtract(const Vector<T>& a) const{   	
	    	Vector<T> result = *this;
	    	result.inplace_subtract(a);
	    	return result;
	    }
		Vector<T> operator-(const Vector<T>& a) const {
		    return subtract(a);
		}
		Vector<T>& operator-=(const Vector<T>& a) {
		    inplace_subtract(a);
		    return *this;
		}
		//##################################################
		//#            VECTOR: Scalar Multiply             #
		//##################################################
		template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value>::type>
	    void inplace_multiply(const U a){
	    	const T a_hat = static_cast<T>(a);
	    	const uint64_t n = v.size();
	    	for (uint64_t i = 0; i < n; ++i){
	    		v[i] *= a_hat;
	    	}
	    }
	    template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value>::type>
	    Vector<T> multiply(const U a) const{   	
	    	Vector<T> result = *this;
	    	result.inplace_multiply(a);
	    	return result;
	    }
		template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value>::type>
		Vector<T> operator*(const U a) const {
		    return multiply(a);
		}
		template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value>::type> 
		friend Vector<T> operator*(U a, const Vector<T>& b) {
		 return b * a; 
		}
		template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value>::type>
		Vector<T>& operator*=(const U a) {
		    inplace_multiply(a);
		    return *this;
		}
		//##################################################
		//#            VECTOR: Vector Multiply             #
		//##################################################
	    void inplace_multiply(const Vector<T>& a){
	    	if (a.size() != v.size()){
	    		throw std::runtime_error("utill:Vector.inplace_multiply -> Dimensions does not fit");
	    	}
	    	const uint64_t n = v.size();
	    	for (uint64_t i = 0; i < n; ++i){
	    		v[i] *= a[i];
	    	}
	    }
	    Vector<T> multiply(const Vector<T>& a) const{   	
	    	Vector<T> result = *this;
	    	result.inplace_multiply(a);
	    	return result;
	    }
		Vector<T> operator*(const Vector<T>& a) const {
		    return multiply(a);
		}
		Vector<T>& operator*=(const Vector<T>& a) {
		    inplace_multiply(a);
		    return *this;
		}
		//################################################
		//#            VECTOR: Scalar Divide             #
		//################################################
		template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value>::type>
	    void inplace_divide(const U a){
	    	const T a_hat = static_cast<T>(a);
	    	const uint64_t n = v.size();
	    	for (uint64_t i = 0; i < n; ++i){
	    		v[i] /= a_hat;
	    	}
	    }
	    template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value>::type>
	    Vector<T> divide(const U a) const{   	
	    	Vector<T> result = *this;
	    	result.inplace_divide(a);
	    	return result;
	    }
		template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value>::type>
		Vector<T> operator/(const U a) const {
		    return divide(a);
		}
		template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value>::type>
		Vector<T>& operator/=(const U a) {
		    inplace_divide(a);
		    return *this;
		}
		//##################################################
		//#            VECTOR: Vector Divide               #
		//##################################################
	    void inplace_divide(const Vector<T>& a){
	    	if (a.size() != v.size()){
	    		throw std::runtime_error("utill:Vector.inplace_divide -> Dimensions does not fit");
	    	}
	    	uint64_t n = a.size();
	    	for (uint64_t i = 0; i < n; ++i){
	    		v[i] /= a[i];
	    	}
	    }
	    Vector<T> divide(const Vector<T>& a) const{   	
	    	Vector<T> result = *this;
	    	result.inplace_divide(a);
	    	return result;
	    }
		Vector<T> operator/(const Vector<T>& a) const {
		    return divide(a);
		}
		Vector<T>& operator/=(const Vector<T>& a) {
		    inplace_divide(a);
		    return *this;
		}
		//###############################################
		//#            VECTOR: Scalar Power             #
		//###############################################
		template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value>::type>
	    void inplace_power(const U a){
	    	const uint64_t n = v.size();
	    	for (uint64_t i = 0; i < n; ++i){
	    		v[i] = static_cast<T>(std::pow(v[i], a));
	    	}
	    }
	    template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value>::type>
	    Vector<T> power(const U a) const{   	
	    	Vector<T> result = *this;
	    	result.inplace_power(a);
	    	return result;
	    }
		//###############################################
		//#            VECTOR: Vector Power             #
		//###############################################
	    void inplace_power(const Vector<T>& a){
	    	if (a.size() != v.size()){
	    		throw std::runtime_error("utill:Vector.inplace_power -> Dimensions does not fit");
	    	}
	    	uint64_t n = a.size();
	    	for (uint64_t i = 0; i < n; ++i){
	    		v[i] = static_cast<T>(std::pow(v[i], a[i]));
	    	}
	    }
	    Vector<T> power(const Vector<T>& a) const{   	
	    	Vector<T> result = *this;
	    	result.inplace_power(a);
	    	return result;
	    }
		//################################################
		//#            VECTOR: Vector square             #
		//################################################
	    void inplace_sqrt(){
	    	uint64_t n = v.size();
	    	for (uint64_t i = 0; i < n; ++i){
	    		v[i] = static_cast<T>(std::sqrt(v[i]));
	    	}
	    }
	    Vector<T> sqrt() const{   	
	    	Vector<T> result = *this;
	    	result.inplace_sqrt();
	    	return result;
	    }
		//###################################################
		//#            VECTOR: Dot Product                  #
		//###################################################
		T dot(const Vector<T>& a)const {
	    	if (a.size() != v.size()){
	    		throw std::runtime_error("utill:Vector.dot -> Dimensions does not fit");
	    	}
			T result = T{0};
			const uint64_t n = v.size();
			for (uint64_t i = 0; i < n; ++i){
				result += a[i]*v[i];
			}
			return result;
		}
		//############################################
		//#            VECTOR: Sum                   #
		//############################################
		T sum()const{
			T result = T{0};
			const uint64_t n = v.size();
			for (uint64_t i = 0; i < n; ++i){
				result += v[i];
			}
			return result;
		}
		//############################################
		//#            VECTOR: Norm                  #
		//############################################
		T norm() const{
			return static_cast<T>(std::sqrt(this->dot(*this)));
		}
		//############################################
		//#            VECTOR: Normalize             #
		//############################################
		void inplace_normalize() {
			T norm = this->norm();
			if (norm == T{0}){
				throw std::runtime_error("utils::Vector.normalize -> zero norm");
			}
			this->inplace_divide(norm);
		}
		Vector<T> normalize() const{
			Vector<T> result = *this;
			result.inplace_normalize();
			return result;
		}
		//######################################################
		//#            VECTOR: Support Functions               #
		//######################################################
		inline friend std::ostream& operator << (std::ostream& out, const Vector& vec){
			out << "[";
			for (uint64_t i = 0; i < vec.v.size(); i++){
				out << vec.v[i];
		        if (i != vec.v.size() - 1) {
		            out << ", ";
		        }
		    }
		    out << "]";
			return out;
		}
		
		void print() const{
			std::cout << *this << std::endl;
		}


		bool nearly_equal(const Vector<T>& a, T tol = static_cast<T>(1e-9)) const {
		    if (v.size() != a.size()){
		    	return false;
		    }
		    for (uint64_t i = 0; i < v.size(); ++i){
		            T val1 = v[i];
		            T val2 = a[i];
				if (std::is_floating_point<T>::value) {
	                if (std::fabs(val1 - val2) > tol) return false;
	            } else {
	                if (val1 != val2) return false;
				}
			}
			return true;
		}



	};

	typedef Vector<int> Vi;
	typedef Vector<float> Vf;
	typedef Vector<double> Vd;
/*
if (std::is_floating_point<T>::value) {
		                if (std::fabs(a - b) > tol) return false;
		            } else {
		                if (a != b) return false;
}*/
} // namespace utils

#endif // _vector_n_