/*
 * Portions of this file are copyright Rebirth contributors and licensed as
 * described in COPYING.txt.
 * Portions of this file are copyright Parallax Software and licensed
 * according to the Parallax license below.
 * See COPYING.txt for license details.

THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
COPYRIGHT 1993-1998 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
*/

/*
 *
 * Texture map assignment.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <math.h>
#include <string.h>
#include "segment.h"
#include "seguvs.h"
#include "editor.h"
#include "editor/esegment.h"
#include "maths.h"
#include "kdefs.h"

#include "compiler-range_for.h"

static uvl compute_uv_side_center(const unique_segment &segp, sidenum_t sidenum);
static void rotate_uv_points_on_side(unique_segment &segp, sidenum_t sidenum, const std::array<fix, 4> &rotmat, const uvl &uvcenter);

//	-----------------------------------------------------------
int	TexFlipX()
{
	const auto uvcenter = compute_uv_side_center(Cursegp, Curside);
	std::array<fix, 4> rotmat;
	//	Create a rotation matrix
	rotmat[0] = -0xffff;
	rotmat[1] = 0;
	rotmat[2] = 0;
	rotmat[3] = 0xffff;

	rotate_uv_points_on_side(Cursegp, Curside, rotmat, uvcenter);

 	Update_flags |= UF_WORLD_CHANGED;

	return	1;
}

//	-----------------------------------------------------------
int	TexFlipY()
{
	const auto uvcenter = compute_uv_side_center(Cursegp, Curside);
	std::array<fix, 4> rotmat;
	//	Create a rotation matrix
	rotmat[0] = 0xffff;
	rotmat[1] = 0;
	rotmat[2] = 0;
	rotmat[3] = -0xffff;

	rotate_uv_points_on_side(Cursegp, Curside, rotmat, uvcenter);

 	Update_flags |= UF_WORLD_CHANGED;

	return	1;
}

//	-----------------------------------------------------------
static int DoTexSlideLeft(int value)
{
	auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
	auto &Vertices = LevelSharedVertexState.get_vertices();
	uvl	duvl03;
	fix	dist;
	auto &vp = Side_to_verts[Curside];
	auto &uvls = Cursegp->unique_segment::sides[Curside].uvls;

	auto &vcvertptr = Vertices.vcptr;
	dist = vm_vec_dist(vcvertptr(Cursegp->verts[vp[side_relative_vertnum::_3]]), vcvertptr(Cursegp->verts[vp[side_relative_vertnum::_0]]));
	dist *= value;
	if (dist < F1_0/(64*value))
		dist = F1_0/(64*value);

	auto &u3 = uvls[side_relative_vertnum::_3];
	auto &u0 = uvls[side_relative_vertnum::_0];
	duvl03.u = fixdiv(u3.u - u0.u, dist);
	duvl03.v = fixdiv(u3.v - u0.v, dist);

	range_for (auto &v, uvls)
	{
		v.u -= duvl03.u;
		v.v -= duvl03.v;
	}

	Update_flags |= UF_WORLD_CHANGED;

	return	1;
}

int TexSlideLeft()
{
	return DoTexSlideLeft(3);
}

int TexSlideLeftBig()
{
	return DoTexSlideLeft(1);
}

//	-----------------------------------------------------------
static int DoTexSlideUp(int value)
{
	auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
	auto &Vertices = LevelSharedVertexState.get_vertices();
	uvl	duvl03;
	fix	dist;
	auto &vp = Side_to_verts[Curside];
	auto &uvls = Cursegp->unique_segment::sides[Curside].uvls;

	auto &vcvertptr = Vertices.vcptr;
	dist = vm_vec_dist(vcvertptr(Cursegp->verts[vp[side_relative_vertnum::_1]]), vcvertptr(Cursegp->verts[vp[side_relative_vertnum::_0]]));
	dist *= value;

	if (dist < F1_0/(64*value))
		dist = F1_0/(64*value);

	auto &u1 = uvls[side_relative_vertnum::_1];
	auto &u0 = uvls[side_relative_vertnum::_0];
	duvl03.u = fixdiv(u1.u - u0.u, dist);
	duvl03.v = fixdiv(u1.v - u0.v, dist);

	range_for (auto &v, uvls)
	{
		v.u -= duvl03.u;
		v.v -= duvl03.v;
	}

	Update_flags |= UF_WORLD_CHANGED;

	return	1;
}

int TexSlideUp()
{
	return DoTexSlideUp(3);
}

int TexSlideUpBig()
{
	return DoTexSlideUp(1);
}


//	-----------------------------------------------------------
static int DoTexSlideDown(int value)
{
	auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
	auto &Vertices = LevelSharedVertexState.get_vertices();
	uvl	duvl03;
	fix	dist;
	auto &vp = Side_to_verts[Curside];
	auto &uvls = Cursegp->unique_segment::sides[Curside].uvls;

	auto &vcvertptr = Vertices.vcptr;
	dist = vm_vec_dist(vcvertptr(Cursegp->verts[vp[side_relative_vertnum::_1]]), vcvertptr(Cursegp->verts[vp[side_relative_vertnum::_0]]));
	dist *= value;
	if (dist < F1_0/(64*value))
		dist = F1_0/(64*value);

	auto &u1 = uvls[side_relative_vertnum::_1];
	auto &u0 = uvls[side_relative_vertnum::_0];
	duvl03.u = fixdiv(u1.u - u0.u, dist);
	duvl03.v = fixdiv(u1.v - u0.v, dist);

	range_for (auto &v, uvls)
	{
		v.u += duvl03.u;
		v.v += duvl03.v;
	}

	Update_flags |= UF_WORLD_CHANGED;

	return	1;
}

int TexSlideDown()
{
	return DoTexSlideDown(3);
}

int TexSlideDownBig()
{
	return DoTexSlideDown(1);
}

//	-----------------------------------------------------------
//	Compute the center of the side in u,v coordinates.
static uvl compute_uv_side_center(const unique_segment &segp, const sidenum_t sidenum)
{
	uvl uvcenter{};
	range_for (auto &v, segp.sides[sidenum].uvls)
	{
		uvcenter.u += v.u;
		uvcenter.v += v.v;
	}
	uvcenter.u /= 4;
	uvcenter.v /= 4;
	return uvcenter;
}

//	-----------------------------------------------------------
//	rotate point *uv by matrix rotmat, return *uvrot
static uvl rotate_uv_point(const std::array<fix, 4> &rotmat, const uvl &uv, const uvl &uvcenter)
{
	const auto centered_u = uv.u - uvcenter.u;
	const auto centered_v = uv.v - uvcenter.v;
	return {
		fixmul(centered_u, rotmat[0]) + fixmul(centered_v, rotmat[1]) + uvcenter.u,
		fixmul(centered_u, rotmat[2]) + fixmul(centered_v, rotmat[3]) + uvcenter.v,
		0
	};
}

//	-----------------------------------------------------------
//	Compute the center of the side in u,v coordinates.
static void rotate_uv_points_on_side(unique_segment &segp, const sidenum_t sidenum, const std::array<fix, 4> &rotmat, const uvl &uvcenter)
{
	range_for (auto &v, segp.sides[sidenum].uvls)
	{
		v = rotate_uv_point(rotmat, v, uvcenter);
	}
}

//	-----------------------------------------------------------
//	ang is in 0..ffff = 0..359.999 degrees
//	rotmat is filled in with 4 fixes
static std::array<fix, 4> create_2d_rotation_matrix(const fixang ang)
{
	const auto &&a = fix_sincos(ang);
	const auto &sinang = a.sin;
	const auto &cosang = a.cos;
	return {{
		cosang,
		sinang,
		-sinang,
		cosang
	}};
}


//	-----------------------------------------------------------
static int DoTexRotateLeft(int value)
{
	const auto uvcenter = compute_uv_side_center(Cursegp, Curside);
	//	Create a rotation matrix
	const auto rotmat = create_2d_rotation_matrix(-F1_0/value);

	rotate_uv_points_on_side(Cursegp, Curside, rotmat, uvcenter);

 	Update_flags |= UF_WORLD_CHANGED;

	return	1;
}

int TexRotateLeft()
{
	return DoTexRotateLeft(192);
}

int TexRotateLeftBig()
{
	return DoTexRotateLeft(64);
}


//	-----------------------------------------------------------
static int DoTexSlideRight(int value)
{
	auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
	auto &Vertices = LevelSharedVertexState.get_vertices();
	uvl	duvl03;
	fix	dist;
	auto &vp = Side_to_verts[Curside];
	auto &uvls = Cursegp->unique_segment::sides[Curside].uvls;

	auto &vcvertptr = Vertices.vcptr;
	dist = vm_vec_dist(vcvertptr(Cursegp->verts[vp[side_relative_vertnum::_3]]), vcvertptr(Cursegp->verts[vp[side_relative_vertnum::_0]]));
	dist *= value;
	if (dist < F1_0/(64*value))
		dist = F1_0/(64*value);

	auto &u3 = uvls[side_relative_vertnum::_3];
	auto &u0 = uvls[side_relative_vertnum::_0];
	duvl03.u = fixdiv(u3.u - u0.u, dist);
	duvl03.v = fixdiv(u3.v - u0.v, dist);

	range_for (auto &v, uvls)
	{
		v.u += duvl03.u;
		v.v += duvl03.v;
	}

	Update_flags |= UF_WORLD_CHANGED;

	return	1;
}

int TexSlideRight()
{
	return DoTexSlideRight(3);
}

int TexSlideRightBig()
{
	return DoTexSlideRight(1);
}

//	-----------------------------------------------------------
static int DoTexRotateRight(int value)
{
	const auto uvcenter = compute_uv_side_center(Cursegp, Curside);

	//	Create a rotation matrix
	const auto rotmat = create_2d_rotation_matrix(F1_0/value);

	rotate_uv_points_on_side(Cursegp, Curside, rotmat, uvcenter);

 	Update_flags |= UF_WORLD_CHANGED;

	return	1;
}

int TexRotateRight()
{
	return DoTexRotateRight(192);
}

int TexRotateRightBig()
{
	return DoTexRotateRight(64);
}

//	-----------------------------------------------------------
int	TexSelectActiveEdge()
{
	return	1;
}

//	-----------------------------------------------------------
int	TexRotate90Degrees()
{
	const auto uvcenter = compute_uv_side_center(Cursegp, Curside);

	//	Create a rotation matrix
	const auto rotmat = create_2d_rotation_matrix(F1_0/4);

	rotate_uv_points_on_side(Cursegp, Curside, rotmat, uvcenter);

 	Update_flags |= UF_WORLD_CHANGED;

	return	1;
}

//	-----------------------------------------------------------
int	TexSetDefault()
{
	Num_tilings = 1;

	Stretch_scale_x = F1_0;
	Stretch_scale_y = F1_0;

	assign_default_uvs_to_side(Cursegp,Curside);

	Update_flags |= UF_GAME_VIEW_CHANGED;
	return	1;
}

//	-----------------------------------------------------------
int	TexIncreaseTiling()
{

	Num_tilings++;
	assign_default_uvs_to_side(Cursegp, Curside);
	Update_flags |= UF_GAME_VIEW_CHANGED;

	return	1;
}

//	-----------------------------------------------------------
int	TexDecreaseTiling()
{

	if (--Num_tilings < 1)
		Num_tilings = 1;

	assign_default_uvs_to_side(Cursegp, Curside);
	Update_flags |= UF_GAME_VIEW_CHANGED;

	return	1;
}


//	direction = -1 or 1 depending on direction
static int	TexStretchCommon(int direction)
{
	const auto sptr = (Curedge == side_relative_vertnum::_0 || Curedge == side_relative_vertnum::_2)
		? &Stretch_scale_x
		: &Stretch_scale_y;

	*sptr += direction*F1_0/64;

	if (*sptr < F1_0/16)
		*sptr = F1_0/16;

	if (*sptr > 2*F1_0)
		*sptr = 2*F1_0;

	stretch_uvs_from_curedge(Cursegp, Curside);

	editor_status_fmt("Stretch scale = %7.4f, use Set Default to return to 1.0", f2fl(*sptr));

	Update_flags |= UF_GAME_VIEW_CHANGED;
	return	1;

}

int	TexStretchDown(void)
{
	return TexStretchCommon(-1);

}

int	TexStretchUp(void)
{
	return TexStretchCommon(1);

}

