/*
 * 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.
*/

/*
 *
 * Editor lighting functions.
 *
 */

#include <stdio.h>
#include "inferno.h"
#include "segment.h"
#include "editor.h"
#include "editor/esegment.h"
#include "seguvs.h"
#include "wall.h"
#include "textures.h"
#include "maths.h"
#include "dxxerror.h"
#include "kdefs.h"
#include "gameseg.h"
#include "texmap.h"

#include "compiler-range_for.h"
#include "d_range.h"

// -----------------------------------------------------------------------------
//	Return light intensity at an instance of a vertex on a side in a segment.
static fix get_light_intensity(const unique_side &s, const side_relative_vertnum vert)
{
	return s.uvls[vert].l;
}

static fix get_light_intensity(const unique_segment &segp, const sidenum_t sidenum, const side_relative_vertnum vert)
{
	return get_light_intensity(segp.sides[sidenum], vert);
}

static fix clamp_light_intensity(const fix intensity)
{
	if (intensity < MIN_LIGHTING_VALUE)
		return MIN_LIGHTING_VALUE;
	if (intensity > MAX_LIGHTING_VALUE)
		return MAX_LIGHTING_VALUE;
	return intensity;
}

// -----------------------------------------------------------------------------
//	Set light intensity at a vertex, saturating in .5 to 15.5
static void set_light_intensity(unique_side &s, const side_relative_vertnum vert, const fix intensity)
{
	s.uvls[vert].l = clamp_light_intensity(intensity);
	Update_flags |= UF_WORLD_CHANGED;
}

static void set_light_intensity(unique_segment &segp, const sidenum_t sidenum, const side_relative_vertnum vert, const fix intensity)
{
	set_light_intensity(segp.sides[sidenum], vert, intensity);
}

// -----------------------------------------------------------------------------
//	Add light intensity to a vertex, saturating in .5 to 15.5
static void add_light_intensity_all_verts(unique_side &s, const fix intensity)
{
	range_for (auto &u, s.uvls)
		u.l = clamp_light_intensity(u.l + intensity);
	Update_flags |= UF_WORLD_CHANGED;
}

// -----------------------------------------------------------------------------
//	Recursively apply light to segments.
//	If current side is a wall, apply light there.
//	If not a wall, apply light to child through that wall.
//	Notes:
//		It is possible to enter a segment twice by taking different paths.  It is easy
//		to prevent this by maintaining a list of visited segments, but it is important
//		to reach segments with the greatest light intensity.  This can be done by doing
//		a breadth-first-search, or by storing the applied intensity with a visited segment,
//		and if the current intensity is brighter, then apply the difference between it and
//		the previous intensity.
//		Note that it is also possible to visit the original light-casting segment, for example
//		going from segment 0 to 2, then from 2 to 0.  This is peculiar and probably not
//		desired, but not entirely invalid.  2 reflects some light back to 0.
static void apply_light_intensity(const csmusegment segp, const sidenum_t sidenum, fix intensity, const unsigned depth)
{
	if (intensity == 0)
		return;

	auto &Walls = LevelUniqueWallSubsystemState.Walls;
	auto &vcwallptr = Walls.vcptr;
	const auto wid_result = WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, segp, sidenum);
	if (!(wid_result & WALL_IS_DOORWAY_FLAG::rendpast)) {
		add_light_intensity_all_verts(segp.u.sides[sidenum], intensity);
		return;										// we return because there is a wall here, and light does not shine through walls
	}

	//	No wall here, so apply light recursively
	if (depth < 3) {
		intensity /= 3;
		if (!intensity)
			return;
		const csmusegment &&csegp = vmsegptr(segp.s.children[sidenum]);
		for (const auto s : MAX_SIDES_PER_SEGMENT)
			apply_light_intensity(csegp, s, intensity, depth+1);
	}

}

// -----------------------------------------------------------------------------
//	Top level recursive function for applying light.
//	Calls apply_light_intensity.
//	Uses light value on segp:sidenum (tmap_num2 defines light value) and applies
//	the associated intensity to segp.  It calls apply_light_intensity to apply intensity/3
//	to all neighbors.  apply_light_intensity recursively calls itself to apply light to
//	subsequent neighbors (and forming loops, see above).
static void propagate_light_intensity(const csmusegment segp, const sidenum_t sidenum)
{
	fix		intensity;

	intensity = 0;
	auto &us = segp.u.sides[sidenum];
	auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
	const auto texmap = get_texture_index(us.tmap_num);
	intensity += TmapInfo[texmap].lighting;
	intensity += TmapInfo[get_texture_index(us.tmap_num2)].lighting;

	if (intensity > 0) {
		add_light_intensity_all_verts(us, intensity);
	
		//	Now, for all sides which are not the same as sidenum (the side casting the light),
		//	add a light value to them (if they have no children, ie, they have a wall there).
		for (const auto s : MAX_SIDES_PER_SEGMENT)
			if (s != sidenum)
				apply_light_intensity(segp, s, intensity/2, 1);
	}

}


// -----------------------------------------------------------------------------
//	Highest level function, bound to a key.  Apply ambient light to all segments based
//	on user-defined light sources.
int LightAmbientLighting()
{
	for (auto &seg : vmsegptr)
	{
		for (const auto side : MAX_SIDES_PER_SEGMENT)
			propagate_light_intensity(seg, side);
	}
	return 0;
}


// -----------------------------------------------------------------------------
int LightSelectNextVertex(void)
{
	Curvert = next_side_vertex(Curvert);
	Update_flags |= UF_WORLD_CHANGED;
	return	1;
}

// -----------------------------------------------------------------------------
int LightSelectNextEdge(void)
{
	Curedge = next_side_vertex(Curedge);
	Update_flags |= UF_WORLD_CHANGED;
	return	1;
}

// -----------------------------------------------------------------------------
//	Copy intensity from current vertex to all other vertices on side.
int LightCopyIntensity(void)
{
	int	intensity;

	unique_segment &segp = Cursegp;
	intensity = get_light_intensity(segp, Curside, Curvert);

	for (const auto v : MAX_VERTICES_PER_SIDE)
		if (v != Curvert)
			set_light_intensity(segp, Curside, v, intensity);

	return	1;
}

// -----------------------------------------------------------------------------
//	Copy intensity from current vertex to all other vertices on side.
int LightCopyIntensitySegment(void)
{
	int	intensity;

	unique_segment &segp = Cursegp;
	intensity = get_light_intensity(segp, Curside, Curvert);

	for (const auto s : MAX_SIDES_PER_SEGMENT)
		for (const auto v : MAX_VERTICES_PER_SIDE)
			if ((s != Curside) || (v != Curvert))
				set_light_intensity(segp, s, v, intensity);

	return	1;
}

// -----------------------------------------------------------------------------
int LightDecreaseLightVertex(void)
{
	unique_segment &segp = Cursegp;
	set_light_intensity(segp, Curside, Curvert, get_light_intensity(segp, Curside, Curvert) - F1_0 / NUM_LIGHTING_LEVELS);

	return	1;
}

// -----------------------------------------------------------------------------
int LightIncreaseLightVertex(void)
{
	unique_segment &segp = Cursegp;
	set_light_intensity(segp, Curside, Curvert, get_light_intensity(segp, Curside, Curvert) + F1_0 / NUM_LIGHTING_LEVELS);

	return	1;
}

// -----------------------------------------------------------------------------
int LightDecreaseLightSide(void)
{
	unique_segment &segp = Cursegp;
	for (const auto v : MAX_VERTICES_PER_SIDE)
		set_light_intensity(segp, Curside, v, get_light_intensity(segp, Curside, v) - F1_0 / NUM_LIGHTING_LEVELS);

	return	1;
}

// -----------------------------------------------------------------------------
int LightIncreaseLightSide(void)
{
	unique_segment &segp = Cursegp;
	for (const auto v : MAX_VERTICES_PER_SIDE)
		set_light_intensity(segp, Curside, v, get_light_intensity(segp, Curside, v) + F1_0 / NUM_LIGHTING_LEVELS);

	return	1;
}

// -----------------------------------------------------------------------------
int LightDecreaseLightSegment(void)
{
	unique_segment &segp = Cursegp;
	for (const auto s : MAX_SIDES_PER_SEGMENT)
		for (const auto v : MAX_VERTICES_PER_SIDE)
			set_light_intensity(segp, s, v, get_light_intensity(segp, s, v) - F1_0 / NUM_LIGHTING_LEVELS);

	return	1;
}

// -----------------------------------------------------------------------------
int LightIncreaseLightSegment(void)
{
	unique_segment &segp = Cursegp;
	for (const auto s : MAX_SIDES_PER_SEGMENT)
		for (const auto v : MAX_VERTICES_PER_SIDE)
			set_light_intensity(segp, s, v, get_light_intensity(segp, s, v) + F1_0 / NUM_LIGHTING_LEVELS);

	return	1;
}

// -----------------------------------------------------------------------------
int LightSetDefault(void)
{
	unique_segment &segp = Cursegp;
	for (const auto v : MAX_VERTICES_PER_SIDE)
		set_light_intensity(segp, Curside, v, DEFAULT_LIGHTING);

	return	1;
}


// -----------------------------------------------------------------------------
int LightSetMaximum(void)
{
	unique_segment &segp = Cursegp;
	for (const auto v : MAX_VERTICES_PER_SIDE)
		set_light_intensity(segp, Curside, v, (NUM_LIGHTING_LEVELS - 1) * F1_0 / NUM_LIGHTING_LEVELS);

	return	1;
}


// -----------------------------------------------------------------------------
int LightSetDefaultAll(void)
{

	assign_default_lighting_all();

	Update_flags |= UF_WORLD_CHANGED;

	return	1;
}



