/********************************************************
 *  ██████╗  ██████╗████████╗██╗
 * ██╔════╝ ██╔════╝╚══██╔══╝██║
 * ██║  ███╗██║        ██║   ██║
 * ██║   ██║██║        ██║   ██║
 * ╚██████╔╝╚██████╗   ██║   ███████╗
 *  ╚═════╝  ╚═════╝   ╚═╝   ╚══════╝
 * Geophysical Computational Tools & Library (GCTL)
 *
 * Copyright (c) 2022  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.
 ******************************************************/

#include "gkernel_rectangle2d.h"
#include "cmath"


/**
 * @brief      callback interface of the gravitational kernel of a rectangle element
 *
 * @param[in]  out_kernel      Output gravitational kernel.
 * @param[in]  ele             Element array.
 * @param[in]  obsp            observation point array.
 * @param[in]  show_progress   Show progress or not.
 *
 */
typedef void (*gkernel_rectangle_ptr)(gctl::matrix<double> &out_kernel, const gctl::array<gctl::rectangle2d> &ele, 
	const gctl::array<gctl::point2dc> &obsp, gctl::verbose_type_e verbose);

void gkernel_rectangle_vz(gctl::matrix<double> &out_kernel, const gctl::array<gctl::rectangle2d> &ele, 
	const gctl::array<gctl::point2dc> &obsp, gctl::verbose_type_e verbose);
void gkernel_rectangle_vzx(gctl::matrix<double> &out_kernel, const gctl::array<gctl::rectangle2d> &ele, 
	const gctl::array<gctl::point2dc> &obsp, gctl::verbose_type_e verbose);
void gkernel_rectangle_vzz(gctl::matrix<double> &out_kernel, const gctl::array<gctl::rectangle2d> &ele, 
	const gctl::array<gctl::point2dc> &obsp, gctl::verbose_type_e verbose);

/**
 * @brief      Integrated callback function of the gravitational kernel of given elements type.
 *
 * @param      out_kernel     Output gravitational kernel
 * @param[in]  ele            element array
 * @param[in]  obsp           observation point array
 * @param[in]  comp_id        The component identifier
 * @param[in]  show_progress  Show progress or not
 */
void gctl::gkernel(matrix<double> &out_kernel, const array<rectangle2d> &ele, 
	const array<point2dc> &obsp, gravitational_field_type_e comp_id, verbose_type_e verbose)
{
	gkernel_rectangle_ptr rectangle_kernel;
	switch (comp_id)
	{
		case Vz:
			rectangle_kernel = gkernel_rectangle_vz;
			break;
		case Tzx:
			rectangle_kernel = gkernel_rectangle_vzx;
			break;
		case Tzz:
			rectangle_kernel = gkernel_rectangle_vzz;
			break;
		default:
			rectangle_kernel = gkernel_rectangle_vz;
			break;
	}
	return rectangle_kernel(out_kernel, ele, obsp, verbose);
}


typedef void (*gobser_rectangle_ptr)(gctl::array<double> &out_obs, const gctl::array<gctl::rectangle2d> &ele_ptr, 
	const gctl::array<gctl::point2dc> &op_ptr, const gctl::array<double> &rho_ptr, gctl::verbose_type_e verbose);

void gobser_rectangle_vz(gctl::array<double> &out_obs, const gctl::array<gctl::rectangle2d> &ele_ptr, 
	const gctl::array<gctl::point2dc> &op_ptr, const gctl::array<double> &rho_ptr, gctl::verbose_type_e verbose);
void gobser_rectangle_vzx(gctl::array<double> &out_obs, const gctl::array<gctl::rectangle2d> &ele_ptr, 
	const gctl::array<gctl::point2dc> &op_ptr, const gctl::array<double> &rho_ptr, gctl::verbose_type_e verbose);
void gobser_rectangle_vzz(gctl::array<double> &out_obs, const gctl::array<gctl::rectangle2d> &ele_ptr, 
	const gctl::array<gctl::point2dc> &op_ptr, const gctl::array<double> &rho_ptr, gctl::verbose_type_e verbose);

void gctl::gobser(array<double> &out_obs, const array<rectangle2d> &ele, const array<point2dc> &obsp, 
	const array<double> &rho, gravitational_field_type_e comp_id, verbose_type_e verbose)
{
	gobser_rectangle_ptr rectangle_obser;
	switch (comp_id)
	{
		case Vz:
			rectangle_obser = gobser_rectangle_vz;
			break;
		case Tzx:
			rectangle_obser = gobser_rectangle_vzx;
			break;
		case Tzz:
			rectangle_obser = gobser_rectangle_vzz;
			break;
		default:
			rectangle_obser = gobser_rectangle_vz;
			break;
	}

	rectangle_obser(out_obs, ele, obsp, rho, verbose);
	return;
}


double gkernel_rectangle_vz_sig(gctl::rectangle2d *ele_ptr, gctl::point2dc *op_ptr);
double gkernel_rectangle_vzx_sig(gctl::rectangle2d *ele_ptr, gctl::point2dc *op_ptr);
double gkernel_rectangle_vzz_sig(gctl::rectangle2d *ele_ptr, gctl::point2dc *op_ptr);


void gkernel_rectangle_vz(gctl::matrix<double> &out_kernel, const gctl::array<gctl::rectangle2d> &ele, 
	const gctl::array<gctl::point2dc> &obsp, gctl::verbose_type_e verbose)
{
	int i, j;
	out_kernel.resize(obsp.size(), ele.size());

	int o_size = obsp.size();
	int e_size = ele.size();
	gctl::progress_bar bar(o_size, "gkernel_vz");
	for (i = 0; i < o_size; i++)
	{
		if (verbose == gctl::FullMsg) bar.progressed(i);
		else if (verbose == gctl::ShortMsg) bar.progressed_simple(i);

		for (j = 0; j < e_size; j++)
		{
			out_kernel.at(i, j) = gkernel_rectangle_vz_sig(ele.get(j), obsp.get(i));
		}
	}
	return;
}

void gkernel_rectangle_vzx(gctl::matrix<double> &out_kernel, const gctl::array<gctl::rectangle2d> &ele, 
	const gctl::array<gctl::point2dc> &obsp, gctl::verbose_type_e verbose)
{
	int i, j;
	out_kernel.resize(obsp.size(), ele.size());

	int o_size = obsp.size();
	int e_size = ele.size();
	gctl::progress_bar bar(o_size, "gkernel_vzx");
	for (i = 0; i < o_size; i++)
	{
		if (verbose == gctl::FullMsg) bar.progressed(i);
		else if (verbose == gctl::ShortMsg) bar.progressed_simple(i);

		for (j = 0; j < e_size; j++)
		{
			out_kernel.at(i, j) = gkernel_rectangle_vzx_sig(ele.get(j), obsp.get(i));
		}
	}
	return;
}

void gkernel_rectangle_vzz(gctl::matrix<double> &out_kernel, const gctl::array<gctl::rectangle2d> &ele, 
	const gctl::array<gctl::point2dc> &obsp, gctl::verbose_type_e verbose)
{
	int i, j;
	out_kernel.resize(obsp.size(), ele.size());

	int o_size = obsp.size();
	int e_size = ele.size();
	gctl::progress_bar bar(o_size, "gkernel_vzz");
	for (i = 0; i < o_size; i++)
	{
		if (verbose == gctl::FullMsg) bar.progressed(i);
		else if (verbose == gctl::ShortMsg) bar.progressed_simple(i);

		for (j = 0; j < e_size; j++)
		{
			out_kernel.at(i, j) = gkernel_rectangle_vzz_sig(ele.get(j), obsp.get(i));
		}
	}
	return;
}

void gobser_rectangle_vz(gctl::array<double> &out_obs, const gctl::array<gctl::rectangle2d> &ele_ptr, 
	const gctl::array<gctl::point2dc> &op_ptr, const gctl::array<double> &rho_ptr, 
	gctl::verbose_type_e verbose)
{
	int i, j;

	out_obs.resize(op_ptr.size(), 0.0);

	int o_size = op_ptr.size();
	int e_size = ele_ptr.size();
	gctl::progress_bar bar(e_size, "gobser_vz");
	for (j = 0; j < e_size; j++)
	{
		if (verbose == gctl::FullMsg) bar.progressed(j);
		else if (verbose == gctl::ShortMsg) bar.progressed_simple(j);

		for (i = 0; i < o_size; i++)
		{
			out_obs[i] += gkernel_rectangle_vz_sig(ele_ptr.get(j), op_ptr.get(i)) * rho_ptr[j];
		}
	}
	return;
}

void gobser_rectangle_vzx(gctl::array<double> &out_obs, const gctl::array<gctl::rectangle2d> &ele_ptr, 
	const gctl::array<gctl::point2dc> &op_ptr, const gctl::array<double> &rho_ptr, 
	gctl::verbose_type_e verbose)
{
	int i, j;

	out_obs.resize(op_ptr.size(), 0.0);

	int o_size = op_ptr.size();
	int e_size = ele_ptr.size();
	gctl::progress_bar bar(e_size, "gobser_vzx");
	for (j = 0; j < e_size; j++)
	{
		if (verbose == gctl::FullMsg) bar.progressed(j);
		else if (verbose == gctl::ShortMsg) bar.progressed_simple(j);

		for (i = 0; i < o_size; i++)
		{
			out_obs[i] += gkernel_rectangle_vzx_sig(ele_ptr.get(j), op_ptr.get(i)) * rho_ptr[j];
		}
	}
	return;
}

void gobser_rectangle_vzz(gctl::array<double> &out_obs, const gctl::array<gctl::rectangle2d> &ele_ptr, 
	const gctl::array<gctl::point2dc> &op_ptr, const gctl::array<double> &rho_ptr, 
	gctl::verbose_type_e verbose)
{
	int i, j;

	out_obs.resize(op_ptr.size(), 0.0);

	int o_size = op_ptr.size();
	int e_size = ele_ptr.size();
	gctl::progress_bar bar(e_size, "gobser_vzz");
	for (j = 0; j < e_size; j++)
	{
		if (verbose == gctl::FullMsg) bar.progressed(j);
		else if (verbose == gctl::ShortMsg) bar.progressed_simple(j);

		for (i = 0; i < o_size; i++)
		{
			out_obs[i] += gkernel_rectangle_vzz_sig(ele_ptr.get(j), op_ptr.get(i)) * rho_ptr[j];
		}
	}
	return;
}


double gkernel_rectangle_vz_sig(gctl::rectangle2d *ele_ptr, gctl::point2dc *op_ptr)
{
	double x1, x2, y1, y2;
	double G11, G12, G21, G22;

	x1 = ele_ptr->dl->x - op_ptr->x;
	x2 = ele_ptr->ur->x - op_ptr->x;
	y1 = ele_ptr->dl->y - op_ptr->y;
	y2 = ele_ptr->ur->y - op_ptr->y;

	G11 = 2.0*y1*atan(x1/y1) + x1*log(x1*x1 + y1*y1);
	G12 = 2.0*y2*atan(x1/y2) + x1*log(x1*x1 + y2*y2);
	G21 = 2.0*y1*atan(x2/y1) + x2*log(x2*x2 + y1*y1);
	G22 = 2.0*y2*atan(x2/y2) + x2*log(x2*x2 + y2*y2);

	return -1.0e+8*GCTL_G0*(G11+G22-G12-G21);
}

double gkernel_rectangle_vzx_sig(gctl::rectangle2d *ele_ptr, gctl::point2dc *op_ptr)
{
	double x1, x2, y1, y2;
	double G11, G12, G21, G22;

	x1 = ele_ptr->dl->x - op_ptr->x;
	x2 = ele_ptr->ur->x - op_ptr->x;
	y1 = ele_ptr->dl->y - op_ptr->y;
	y2 = ele_ptr->ur->y - op_ptr->y;

	G11 = x1*x1 + y1*y1;
	G12 = x1*x1 + y2*y2;
	G21 = x2*x2 + y1*y1;
	G22 = x2*x2 + y2*y2;

	return 1.0e+8*GCTL_G0*(log(G22/G21) - log(G12/G11));
}

double gkernel_rectangle_vzz_sig(gctl::rectangle2d *ele_ptr, gctl::point2dc *op_ptr)
{
	double x1, x2, y1, y2;
	double G11, G12, G21, G22;

	x1 = ele_ptr->dl->x - op_ptr->x;
	x2 = ele_ptr->ur->x - op_ptr->x;
	y1 = ele_ptr->dl->y - op_ptr->y;
	y2 = ele_ptr->ur->y - op_ptr->y;

	G11 = x1/y1;
	G12 = x1/y2;
	G21 = x2/y1;
	G22 = x2/y2;

	return -1.0e+8*GCTL_G0*(atan(G11) + atan(G22) - atan(G12) - atan(G21));
}