/********************************************************
 *  ██████╗  ██████╗████████╗██╗
 * ██╔════╝ ██╔════╝╚══██╔══╝██║
 * ██║  ███╗██║        ██║   ██║
 * ██║   ██║██║        ██║   ██║
 * ╚██████╔╝╚██████╗   ██║   ███████╗
 *  ╚═════╝  ╚═════╝   ╚═╝   ╚══════╝
 * Geophysical Computational Tools & Library (GCTL)
 *
 * Copyright (c) 2023  Yi Zhang (yizhang-geo@zju.edu.cn)
 *
 * GCTL is distributed under a dual licensing scheme. You can redistribute 
 * it and/or modify it under the terms of the GNU Lesser General Public 
 * License as published by the Free Software Foundation, either version 2 
 * of the License, or (at your option) any later version. You should have 
 * received a copy of the GNU Lesser General Public License along with this 
 * program. If not, see <http://www.gnu.org/licenses/>.
 * 
 * If the terms and conditions of the LGPL v.2. would prevent you from using 
 * the GCTL, please consider the option to obtain a commercial license for a 
 * fee. These licenses are offered by the GCTL's original author. As a rule, 
 * licenses are provided "as-is", unlimited in time for a one time fee. Please 
 * send corresponding requests to: yizhang-geo@zju.edu.cn. Please do not forget 
 * to include some description of your company and the realm of its activities. 
 * Also add information on how to contact you by electronic and paper mail.
 ******************************************************/

#ifndef _GCTL_POINT2C_H
#define _GCTL_POINT2C_H

#include "iostream"
#include "string"
#include "cmath"
#include "iomanip"
#include "regex"

#include "../core/array.h"

namespace gctl
{
	// static variable for controlling the IO process
	static int p2c_io_psn = 6;

	template <typename T> class point2c;
	typedef point2c<double> point2dc;
	typedef point2c<float>  point2fc;

	/**
	 * @brief      A point under the 2D Cartesian coordinates (aka. 2D vector).
	 */
	template <typename T>
	struct point2c
	{
		T x; ///< x coordinate
		T y; ///< y coordinate
		/**
		 * @brief Constructor
		 */
		point2c();
		/**
		 * @brief Construct a new object with initial parameters
		 * 
		 * @param in_x Input x coordinate
		 * @param in_y Input y coordinate
		 */
		point2c(T in_x, T in_y);
		/**
		 * @brief Construct a new object from an existing object
		 * 
		 * @param b Input point2c object
		 */
		point2c(const point2c<T> &b);
		/**
		 * @brief De-constructor
		 */
		virtual ~point2c(){}
		/**
		 * @brief Whether the object is valid or not
		 * 
		 * @return true. The object is valid.
		 * @return false. The object is invalid.
		 */
		bool valid() const;
		/**
		 * @brief Set coordinates of the point2c object
		 * 
		 * @param in_x Input x coordinate
		 * @param in_y Input y coordinate
		 */
		void set(T in_x, T in_y);
		/**
		 * @brief Set coordinates of the point2c object
		 * 
		 * @param b Input point2c object
		 */
		void set(const point2c<T> &b);
		/**
		 * @brief Set the point's mode to the given value
		 * 
		 * @param mod       Desired mode length.
		 * @param cut_off   The cut off to round down the zero values.
		 */
		void set2module(T mod, T cut_off = GCTL_ZERO);
		/**
		 * @brief Return the mode length.
		 * 
		 * @return mode length.
		 */
		T module() const;
		/**
		 * @brief Return the normal vector.
		 * 
		 * @return The normalized vector.
		 */
		point2c<T> normal() const;
		/**
		 * @brief Return a point2dp object at the same position.
		 * 
		 * @return The point2dp object.
		 */
		//point2p<T> c2p() const;
		/**
		 * @brief Set a point2c object from a string. The accepted format: (x, y)
		 * 
		 * @note  The function searchs coordinates from an input string. 
		 * The accepted format is (x, y). The space after the comma is optional.
		 * 
		 * @param str The input string object.
		 */
		void str(std::string str);
		/**
		 * @brief      Sets the i/o precision of the type.
		 *
		 * @param[in]  psn   The desired precision
		 */
		void set_io_precision(int psn);
		/**
		 * @brief      计算旋转后的向量
		 *
		 * @param[in]  arc   旋转的弧度，逆时针为正
		 * @param[in]  origin 旋转的中心，默认为坐标原点
		 *
		 * @return     旋转后的向量
		 */
		point2c<T> rotate(double arc, const point2c<T> &origin = {0, 0});
		/**
		 * @brief      输出位置
		 *
		 * @param      os    输出流
		 */
		void out_loc(std::ostream &os, char deli) const;
		/**
		 * @brief      输入位置
		 *
		 * @param      os    输入流
		 */
		void in_loc(std::istream &os);
		/**
		 * @brief      返回位置
		 *
		 * @return     位置
		 */
		point2c<T> get_loc() const;
	};

	template <typename T>
	gctl::point2c<T>::point2c()
	{
		x = y = NAN;
	}

	template <typename T>
	gctl::point2c<T>::point2c(T in_x, T in_y)
	{
		set(in_x, in_y);
	}

	template <typename T>
	gctl::point2c<T>::point2c(const point2c<T> &b)
	{
		set(b);
	}

	template <typename T>
	bool gctl::point2c<T>::valid() const
	{
		if (std::isnan(x) || std::isnan(y)) return false;
		if (std::isinf(x) || std::isinf(y)) return false;
		return true;
	}

	template <typename T>
	void gctl::point2c<T>::set(T in_x, T in_y)
	{
		if (std::isnan(in_x) || std::isnan(in_y) || 
			std::isinf(in_x) || std::isinf(in_y))
		{
			throw invalid_argument("Invalid value detected. From point2c::set(...)");
		}

		x = in_x; y = in_y;
		return;
	}

	template <typename T>
	void gctl::point2c<T>::set(const point2c<T> &b)
	{
		if (std::isnan(b.x) || std::isnan(b.y) || 
			std::isinf(b.x) || std::isinf(b.y))
		{
			throw invalid_argument("Invalid value detected. From point2c::set(...)");
		}

		x = b.x; y = b.y;
		return;
	}

	template <typename T>
	void gctl::point2c<T>::set2module(T mod, T cut_off)
	{
		if (cut_off <= 0.0)
		{
			throw invalid_argument("Invalid cut-off value. From point2c::set2module(...)");
		}

		if (mod <= cut_off && mod >= -1.0*cut_off)
		{
			x = y = 0.0;
			return;
		}

		T old_mod = module();
		x = x*mod/old_mod;
		y = y*mod/old_mod;
		return;
	}

	template <typename T>
	T gctl::point2c<T>::module() const
	{
		return sqrt(x*x + y*y);
	}

	template <typename T>
	gctl::point2c<T> gctl::point2c<T>::normal() const
	{
		return point2c<T>(x/module(), y/module());
	}
/*
	template <typename T>
	gctl::point2p<T> gctl::point2c<T>::c2p() const
	{
		point2p<T> outp;
		outp.rad = module();
		if (y >= 0.0)
			outp.arc= atan2(y, x);
		else outp.arc = atan2(y, x) + 2.0*GCTL_Pi;
		return outp;
	}
*/
	template <typename T>
	void gctl::point2c<T>::str(std::string str)
	{
		std::smatch ret;
		std::regex pattern("\\((-?\\d*\\.?\\d+?),[ ]*(-?\\d*\\.?\\d+?)\\)");

		if (regex_search(str, ret, pattern))
		{
			x = atof(std::string(ret[1]).c_str());
			y = atof(std::string(ret[2]).c_str());
			return;
		}

		throw runtime_error("Fail to parse the input string: " + str + ". From point2c::str(...)");
	}

	template <typename T>
	void gctl::point2c<T>::set_io_precision(int psn)
	{
		if (psn < 0)
		{
			throw invalid_argument("Invalid precision. From point2c::set_io_precision(...)");
		}

		p2c_io_psn = psn;
		return;
	}

	template <typename T>
	gctl::point2c<T> gctl::point2c<T>::rotate(double arc, const point2c<T> &origin)
	{
		point2c<T> out;
		out.x = origin.x + cos(arc)*(x - origin.x) - sin(arc)*(y - origin.y);
		out.y = origin.y + sin(arc)*(x - origin.x) + cos(arc)*(y - origin.y);
		return out;
	}

	template <typename T>
	void gctl::point2c<T>::out_loc(std::ostream &os, char deli) const
	{
		os << std::setprecision(p2c_io_psn) << x << deli << y;
		return;
	}

	template <typename T>
	void gctl::point2c<T>::in_loc(std::istream &os)
	{
		os >> x >> y;
		return;
	}

	template <typename T>
	gctl::point2c<T> gctl::point2c<T>::get_loc() const
	{
		return point2c<T>(x, y);
	}

	template <typename T>
	bool operator ==(const point2c<T> &a, const point2c<T> &b)
	{
		if( fabs(a.x - b.x) < GCTL_ZERO && 
			fabs(a.y - b.y) < GCTL_ZERO)
		{
			return true;
		}
		else return false;
	}

	template <typename T>
	bool operator !=(const point2c<T> &a, const point2c<T> &b)
	{
		if( fabs(a.x - b.x) >= GCTL_ZERO || 
			fabs(a.y - b.y) >= GCTL_ZERO)
		{
			return true;
		}
		else return false;
	}

	template <typename T>
	point2c<T> operator-(const point2c<T> &a, const point2c<T> &b) //二维矢量减法
	{
		point2c<T> out;
		out.x = a.x - b.x;
		out.y = a.y - b.y;
		return out;
	}

	template <typename T>
	point2c<T> operator+(const point2c<T> &a, const point2c<T> &b) //二维矢量加法
	{
		point2c<T> out;
		out.x = a.x + b.x;
		out.y = a.y + b.y;
		return out;
	}

	template <typename T>
	point2c<T> operator*(int t, const point2c<T> &b)
	{
		point2c<T> out;
		out.x = (T) t*b.x;
		out.y = (T) t*b.y;
		return out;
	}

	template <typename T>
	point2c<T> operator*(float t, const point2c<T> &b)
	{
		point2c<T> out;
		out.x = (T) t*b.x;
		out.y = (T) t*b.y;
		return out;
	}

	template <typename T>
	point2c<T> operator*(double t, const point2c<T> &b) //标量乘矢量
	{
		point2c<T> out;
		out.x = t*b.x;
		out.y = t*b.y;
		return out;
	}

	template <typename T>
	point2c<T> operator*(const point2c<T> &b, int t)
	{
		point2c<T> out;
		out.x = (T) t*b.x;
		out.y = (T) t*b.y;
		return out;
	}

	template <typename T>
	point2c<T> operator*(const point2c<T> &b, float t)
	{
		point2c<T> out;
		out.x = (T) t*b.x;
		out.y = (T) t*b.y;
		return out;
	}

	template <typename T>
	point2c<T> operator*(const point2c<T> &b, double t)
	{
		point2c<T> out;
		out.x = t*b.x;
		out.y = t*b.y;
		return out;
	}

	template <typename T>
	std::ostream &operator <<(std::ostream & os, const point2c<T> &a)
	{
		os << std::setprecision(p2c_io_psn) << a.x << " " << a.y;
		return os;
	}

	template <typename T>
	std::istream &operator >>(std::istream & os, point2c<T> &a)
	{
		os >> a.x >> a.y;
		return os;
	}

	/**
	 * @brief      计算两个向量的点乘
	 *
	 * @param[in]  a     点a的引用
	 * @param[in]  b     点b的引用
	 *
	 * @return     点乘值
	 */
	template <typename T>
	double dot(const point2c<T> &a, const point2c<T> &b)
	{
		return a.x*b.x + a.y*b.y;
	}

	/**
	 * @brief      计算两个向量的叉乘，二维情况为结果是一个标量。
	 *
	 * @param[in]  a     点a的引用
	 * @param[in]  b     点b的引用
	 *
	 * @return     叉乘值
	 */
	template <typename T>
	double cross(const point2c<T> &a, const point2c<T> &b)
	{
		return a.x*b.y - a.y*b.x;
	}

	/**
	 * @brief      两点距离
	 *
	 * @param[in]  a     第一个点的引用
	 * @param[in]  b     第二个点的引用
	 *
	 * @return     两点距离
	 */
	template <typename T>
	double distance(const point2c<T> &a, const point2c<T> &b)
	{
		return (a - b).module();
	}

	/**
	 * @brief      两个向量之间的夹角。
	 *
	 * @param[in]  a     向量a
	 * @param[in]  b     向量b
	 *
	 * @return     夹角弧度值。
	 */
	template <typename T>
	double angle(const point2c<T> &a, const point2c<T> &b)
	{
		return acos(dot(a, b)/(a.module()*b.module()));
	}

	/**
	 * @brief      函数判断两个point2c类型是否等于
	 *
	 * @param[in]  a     二维空间内的一个向量或实点a。
	 * @param[in]  b     二维空间内的一个向量或实点b。
	 * @param[in]  cut_off  截断误差，默认值为 gctl_macro.h 中预设的 GCTL_ZERO 值，可在调用时改变。
	 * 注意比较的精度为 cut_off 减一位，比如 cut_off 为1e-10则比较的精度为小数点后9位。
	 *
	 * @return     是否相等。
	 */
	template <typename T>
	bool isequal(const point2c<T> &a, const point2c<T> &b, double cut_off = GCTL_ZERO)
	{
		if( fabs(a.x - b.x) < cut_off && 
			fabs(a.y - b.y) < cut_off)
		{
			return true;
		}
		return false;
	}

	/**
	 * @brief      函数判断两个point2c类型是否不等于
	 *
	 * @param[in]  a     二维空间内的一个向量或实点a。
	 * @param[in]  b     二维空间内的一个向量或实点b。
	 * @param[in]  cut_off  截断误差，默认值为 gctl_macro.h 中预设的 GCTL_ZERO 值，可在调用时改变。
	 *
	 * @return     是否不相等。
	 */
	template <typename T>
	bool notequal(const point2c<T> &a, const point2c<T> &b, double cut_off = GCTL_ZERO)
	{
		if( fabs(a.x - b.x) >= cut_off || 
			fabs(a.y - b.y) >= cut_off)
		{
			return true;
		}
		return false;
	}

	/**
	 * @brief      生成一维网格节点数组，其中每一个点都是一个二维坐标点。
	 * 
	 * @param      out_ps  返回网格节点数组
	 * @param[in]  x_st    x起始值
	 * @param[in]  x_ed    x终止值
	 * @param[in]  dx      x间隔
	 * @param[in]  ele     高程值
	 */
	template <typename T>
	void grid_points_1d(array<point2c<T>> &out_ps, T x_st, T x_ed, T dx, T ele)
	{
		int xnum = round(std::abs((x_ed - x_st)/dx)) + 1;

		out_ps.resize(xnum);	
		for (int i = 0; i < xnum; i++)
		{
			out_ps[i].x = x_st + dx*i;
			out_ps[i].y = ele;
		}
		return;
	}
};

#endif // _GCTL_POINT2C_H