/*
	server/ai/fte/waypoints_core.qc

	FTE waypointing

	Copyright (C) 2021-2023 NZ:P Team

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

	See the GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to:

		Free Software Foundation, Inc.
		59 Temple Place - Suite 330
		Boston, MA  02111-1307, USA

*/

void() creator_way_touch =
{
	if (cvar("waypoint_mode")) {
		if (other.classname != "player") {
			return;
		}
		
		current_way = self;
	}
}

void () Create_New_Waypoint =
{
	float way_count;
	float tempf;
	entity tempe;
	entity new_way;

	way_count = -1;
	tempe = find (world, classname, "waypoint");
	while (tempe) {
		tempf = stof(tempe.waynum);
		if (tempf > way_count) {
			way_count = tempf;
		}
		tempe = find (tempe, classname, "waypoint");
	}
	new_way = spawn();

	setorigin(new_way, self.origin);
	
	
	//new_way.flags = FL_ITEM;
	new_way.solid = SOLID_TRIGGER;

	
	setmodel(new_way, "models/way/normal_way.spr");
	new_way.classname = "waypoint";
	
	new_way.waynum = ftos(way_count + 1);
	new_way.targetname = strzone(new_way.targetname);
	bprint (PRINT_HIGH, "Created waypoint  ");
	bprint (PRINT_HIGH, new_way.waynum);
	bprint (PRINT_HIGH, "\n");

	new_way.touch = creator_way_touch;
}


void () Make_Special_Waypoint =
{
	if (self.classname != "player" || !active_way) {
		return;
	}
	if (active_way.targetname != "") {//Toggling it back off
		setmodel(active_way, "models/way/current_way.spr");
		active_way.targetname = "";
		bprint (PRINT_HIGH, "Waypoint ");
		bprint (PRINT_HIGH, active_way.waynum);
		bprint (PRINT_HIGH, " is no longer a special waypoint\n");
		return;
	}

	if (active_way) {
		if(self.active_door == world) {
			bprint (PRINT_HIGH, "Error: no door selected!\n");
			return;
		}

		if(self.active_door.wayTarget == "") {
			bprint (PRINT_HIGH, "Error: Door has no wayTarget value!\n");
			return;
		}
		setmodel(active_way, "models/way/current_way_door.spr");
		active_way.targetname = self.active_door.wayTarget;

		bprint (PRINT_HIGH, "special waypoint ");
		bprint (PRINT_HIGH, active_way.waynum);
		bprint (PRINT_HIGH, " named ");
		bprint (PRINT_HIGH, active_way.targetname);
		bprint (PRINT_HIGH, "\n");
	}
}


void () Move_Waypoint =
{
	if (!active_way)
		return;

	setorigin (active_way, self.origin);
	bprint (PRINT_HIGH, "Moved waypoint ");
	bprint (PRINT_HIGH, active_way.waynum);
	bprint (PRINT_HIGH, "\n");
	
}

void () Select_Waypoint =
{
	if (self.classname != "player")
		return;
	if (!current_way)
		return;

	entity tempe;

	if (current_way == active_way)
		active_way = world;
	else
		active_way = current_way;

	tempe = find (world, classname, "waypoint");
	while (tempe)
	{
		if (tempe.targetname != "")
			setmodel(tempe, "models/way/normal_way_door.spr");
		else
			setmodel(tempe, "models/way/normal_way.spr");
		tempe = find (tempe, classname, "waypoint");
	}
	if (active_way)
	{
		if (active_way.targetname != "")
			setmodel(active_way, "models/way/current_way_door.spr");
		else
			setmodel(active_way, "models/way/current_way.spr");
		
		bprint (PRINT_HIGH, "Selected waypoint ");
		bprint (PRINT_HIGH, active_way.waynum);
		if(active_way.targetname != "")
		{
			bprint (PRINT_HIGH, ", special tag ");
			bprint (PRINT_HIGH, active_way.targetname);
		}
		bprint (PRINT_HIGH, "\n");
		
		float i;
		for	(i = 0; i < MAX_WAY_TARGETS; i++) {
			tempe = find (world, waynum, active_way.targets[i]);
			if (tempe) {
				if (tempe.targetname != "")
					setmodel(tempe, "models/way/last_way_door.spr");
				else
					setmodel(tempe, "models/way/last_way.spr");
			} else {
				active_way.targets[i] = "";
			}
		}
	}
}


void() Remove_Waypoint =
{
	entity tempe;
	float i;
	if (!active_way)
		return;
		
	tempe = find (world, classname, "waypoint");
	while (tempe) {
	
		for	(i = 0; i < MAX_WAY_TARGETS; i++) {
			if (tempe.targets[i] == active_way.waynum) {
				tempe.targets[i] = "";
			}
		}

		tempe = find (tempe, classname, "waypoint");
	}
		
	bprint(PRINT_HIGH, "Removed waypoint ");
	bprint(PRINT_HIGH, active_way.waynum);
	bprint(PRINT_HIGH, "\n");
	remove (active_way);
}

float Waypoint_Linked_To(entity from, entity to) {
	float i;
	for	(i = 0; i < MAX_WAY_TARGETS; i++) {
		if (from.waynum == to.targets[i]) {
			bprint(PRINT_HIGH, "These waypoints are already linked!\n");
			return 1;
		}
	}

	return 0;
}

float Link (entity from, entity to) {
	float i;
	entity tempe;
	for	(i = 0; i < MAX_WAY_TARGETS; i++) {
		tempe = find (world, waynum, from.targets[i]);

		if (tempe == world || tempe == to) {
			from.targets[i] = to.waynum;
			bprint(PRINT_HIGH, "Linked waypoint ");
			bprint(PRINT_HIGH, to.waynum);
			bprint(PRINT_HIGH, " to ");
			bprint(PRINT_HIGH, from.waynum);
			bprint(PRINT_HIGH, "\n");

			if (to.targetname != "") {
				setmodel(to, "models/way/last_way_door.spr");
			} else {
				setmodel(to, "models/way/last_way.spr");
			}
			
			return 1;
		}
	}

	return 0;
}

void () Link_Waypoints =
{
	if (self.classname != "player")
		return;
	if (!current_way)
		return;
	if (!active_way)
		return;
	if (current_way == active_way)
		return;
	
	if (Waypoint_Linked_To(current_way, active_way)) {
		bprint(PRINT_HIGH, "These waypoints are already linked!\n");
		return;
	}
	
	float i;
	entity tempe;
	for	(i = 0; i < MAX_WAY_TARGETS; i++) {
		tempe = findfloat (world, waynum, active_way.targets[i]);

		if (tempe == world) {
			if (Link(active_way, current_way)) {
				return;
			}
		}
	}

	
	bprint(PRINT_HIGH, "no targets remaining!\n");
}



void() Dual_Link_Waypoints =
{
	if (self.classname != "player" || !current_way || !active_way || current_way == active_way) {
		return;
	}
	
	float result1,result2;
	result1 = Waypoint_Linked_To(current_way,active_way);
	result2 = Waypoint_Linked_To(active_way,current_way);
	
	if(result1 && result2) {
		bprint(PRINT_HIGH, "Both waypoints already linked!\n");
		return;
	}
	
	if(!result1)
	{
		if (Link(current_way,active_way)) {
			bprint(PRINT_HIGH, strcat("Linked waypoint ", strcat(current_way.waynum, strcat(" to ",strcat(active_way.waynum, "\n")))));
		} else {
			bprint(PRINT_HIGH, strcat("ERROR: Could not link waypoint ", strcat(current_way.waynum, strcat(" to ", strcat(active_way.waynum, "\n")))));
		}
	}
	if(!result2)
	{
		if (Link(active_way,current_way)) {
			bprint(PRINT_HIGH, strcat("Linked waypoint ", strcat(active_way.waynum, strcat(" to ", strcat(current_way.waynum, "\n")))));
		} else {
			bprint(PRINT_HIGH, strcat("ERROR: Could not link waypoint ", strcat(active_way.waynum, strcat(" to ", strcat(current_way.waynum, "\n")))));
		}
	}
}



//alter auto_link_waypoints to iterate through the closest waypoints from closest to furthest
// on the innermost loop, we find the next closest waypoint that is further away from the last closest waypoint, and we use that!

void() Auto_Link_Waypoints =
{
	entity tempe1, tempe2;
	
	tempe1 = find(world,classname,"waypoint");
	while(tempe1 != world)
	{
		tempe2 = find(world,classname,"waypoint");
		while(tempe2 != world)
		{
			if(tempe1 == tempe2)
			{
				tempe2 = find(tempe2,classname,"waypoint");
				continue;
			}
			
			if(tracemove(tempe1.origin,VEC_HULL_MIN,VEC_HULL_MAX,tempe2.origin,TRUE,self))
			{
				Link(tempe1,tempe2);
			}
			
			tempe2 = find(tempe2,classname,"waypoint");
		}
		tempe1 = find(tempe1,classname,"waypoint");
	}
}
//alter auto_link_waypoints to iterate through the closest waypoints from closest to furthest
// on the innermost loop, we find the next closest waypoint that is further away from the last closest waypoint, and we use that!

void() Remove_Links =
{
	entity tempe;
	
	tempe = find(world,classname,"waypoint");
	while(tempe != world)
	{
		float i;
		for (i = 0; i < MAX_WAY_TARGETS; i = i + 1) {
			if (tempe.targetname != "")
				setmodel(tempe, "models/way/normal_way_door.spr");
			else
				setmodel(tempe, "models/way/normal_way.spr");
			tempe.targets[i] = "";
		}
		tempe = find(tempe,classname,"waypoint");
	}
}

string tempstest;
void() Save_Waypoints_Legacy =
{
	float file;
	string h;

	h = strcat(mappath, ".way");
	file = fopen (h, FILE_WRITE);
	
	entity dway;

	dway = find(world, classname, "waypoint");
	while (dway)
	{
		dprint ("Saving waypoints\n");
		fputs(file,"Waypoint\n");
		fputs(file,"{\norigin = ");

		// FTE users are just gonna have to deal with no float precision
		// for now. :s
		int x = ftoi(dway.origin_x);
		int y = ftoi(dway.origin_y);
		int z = ftoi(dway.origin_z);
		tempstest = strcat("'", itos(x), " ", itos(y), " ", itos(z), "'");
		tempstest = strzone(tempstest);
		fputs(file, tempstest);
		strunzone (tempstest);
		fputs(file,"\nid = ");
		fputs(file,dway.waynum);
		fputs(file,"\nspecial = ");
		fputs(file,dway.targetname);
		fputs(file,"\ntarget = ");
		fputs(file,dway.targets[0]);
		fputs(file,"\ntarget2 = ");
		fputs(file,dway.targets[1]);
		fputs(file,"\ntarget3 = ");
		fputs(file,dway.targets[2]);
		fputs(file,"\ntarget4 = ");
		fputs(file,dway.targets[3]);
		fputs(file,"\ntarget5 = ");
		fputs(file,dway.targets[4]);
		fputs(file,"\ntarget6 = ");
		fputs(file,dway.targets[5]);
		fputs(file,"\ntarget7 = ");
		fputs(file,dway.targets[6]);
		fputs(file,"\ntarget8 = ");
		fputs(file,dway.targets[7]);
		fputs(file,"\n");
		fputs(file,"}\n");
		
		
		dway = find(dway, classname, "waypoint");
		if (dway)
			fputs(file,"\n");
	}
	fclose(file);
}

void() Save_Waypoints
{
	float file;
	string h;
	float i;
	entity tempe;

	h = strcat(mappath, ".way");
	file = fopen (h, FILE_WRITE);
	
	dprint (strcat("Saving waypoints ", strcat(h, "\n")));
	
	local entity dway;
	//fputs(file, "begin\n");
	dway = find(world, classname, "waypoint");
	while (dway)
	{
		fputs(file,"waypoint\n");
		fputs(file,"{\n");
		fputs(file, strcat(" id: ", strcat(dway.waynum, "\n")));
		fputs(file, strcat(" origin: ", strcat(vtos(dway.origin), "\n")));
		if (dway.targetname != "") {
			fputs(file, strcat(" door: ", strcat(dway.targetname, "\n")));
		}

		fputs(file, " targets:\n");
		fputs(file, " [\n");
		for (i = 0; i < MAX_WAY_TARGETS; i++) {
			if (dway.targets[i] != "") {
				tempe = findfloat (world, waynum, dway.targets[i]);

				if (tempe != world) {
					fputs(file, strcat("  ", strcat(dway.targets[i], "\n")));
				} else {
					tempe = find (world, waynum, dway.targets[i]);
					if (tempe != world) {
						fputs(file, strcat("  ", strcat(dway.targets[i], "\n")));
					}
				}
			}
		}
		fputs(file, " ]\n");
		fputs(file,"}\n");
		
		
		dway = find(dway, classname, "waypoint");
		if (dway)
			fputs(file,"\n");
	}
	fclose(file);
}

void (vector here, float which, string special, string trg, string trg2, string trg3, string trg4, string trg5, string trg6, string trg7, string trg8) Create_Waypoint =
{
	entity new_way;
	new_way = spawn();

	setorigin(new_way, here);
	
	
	//new_way.flags = FL_ITEM;
	new_way.solid = SOLID_TRIGGER;

	if (cvar("waypoint_mode"))
		setmodel(new_way, "models/way/normal_way.spr");
	new_way.classname = "waypoint";

	new_way.waynum = ftos(which);
	dprint ("Created waypoint  ");
	dprint (new_way.waynum);
	dprint ("\n");
	
	if (special != "")
	{
		if (!cvar("waypoint_mode"))
			new_way.classname = "waypoint_s";
		if (cvar("waypoint_mode"))
			setmodel(new_way, "models/way/normal_way_door.spr");
		new_way.targetname = special;
		dprint ("Special waypoint  ");
		dprint (new_way.targetname);
		dprint ("\n");
		//current_special++;
	}

	new_way.targets[0] = trg;
	new_way.targets[1] = trg2;
	new_way.targets[2] = trg3;
	new_way.targets[3] = trg4;
	new_way.targets[4] = trg5;
	new_way.targets[5] = trg6;
	new_way.targets[6] = trg7;
	new_way.targets[7] = trg8;

	new_way.touch = creator_way_touch;
}

float waypoints_loaded;
void() Load_Waypoints
{
	float file, point;
	string h;
	float targetcount, loop;

	entity new_way;

	h = strcat(mappath, ".way");
	file = fopen (h, FILE_READ);

	if (file == -1)
	{

#ifdef FTE

		dprint("LoadWaypointData: No .way file found, trying Beta format..\n");
		Compat_TryLoadBetaWaypoints();

#endif // FTE

		return;
	}

	new_way = spawn();
	targetcount = 0;
	point = 0;
	loop = 1;
	while (loop)
	{
		string line;
		line = fgets(file);
		if not (line) {
			bprint(PRINT_HIGH, "End of file\n");
			loop = 0;
			break;
		}
		h = strtrim(line);

		//bprint(PRINT_HIGH, strcat(h, "\n"));
		if (h == "") {
			continue;
		}

		switch (point) {
			case 0:
				if (h == "waypoint") {
					new_way = spawn();
					new_way.solid = SOLID_TRIGGER;
					new_way.model = "models/way/normal_way.spr";
					setmodel(new_way, "models/way/normal_way.spr");
					new_way.classname = "waypoint";

					new_way.touch = creator_way_touch;
					point = 1;
					targetcount = 0;
				} else if (h == "Waypoint") {
					//bprint(PRINT_HIGH, "Identified .way as legacy..\n");
					point = 99;
					Load_Waypoints_Legacy();
					loop = 0;
					break;
				} else {
					bprint(PRINT_HIGH, strcat("Error: unknown point ", strcat(h, "\n")));
				}
				break;
			case 1:
				if (h == "{") {
					point = 2;
				} else {
					bprint(PRINT_HIGH, strcat("Error: unknown variable ", strcat(h, " expected {\n")));
				}
				break;
			case 2:
				tokenize(h);

				string value, variable;
				variable = strtrim(argv(0));
				value = strtrim(argv(2));

				//bprint(PRINT_HIGH, strcat(variable, "\n"));

				switch (variable) {
					case "origin":
						
						print(strcat(value, "\n"));
						new_way.origin = stov(value);
						setorigin(new_way, new_way.origin);
						break;
					case "id":
						new_way.waynum = value;
						break;
					case "door":
						new_way.targetname = value;
						
						setmodel(new_way, "models/way/normal_way_door.spr");
						break;
					case "targets":
						point = 3;
						break;
					case "}":
						point = 0;
						break;
					default:
						bprint(PRINT_HIGH, strcat("Error: unknown variable ", strcat(variable, "\n")));
						break;
				}
				break;
			case 3:
				if (h == "[") {
					point = 4;
				} else {
					bprint(PRINT_HIGH, strcat("Error: unknown variable ", strcat(h, " expected [\n")));
				}
				break;
			case 4:
				if (targetcount >= MAX_WAY_TARGETS) {
					bprint(PRINT_HIGH, "Error: Target count too high for waypoint\n");
				} else if (h == "]") {
					point = 2;
				} else  {
					bprint(PRINT_HIGH, strcat(strcat("WAYPOINT TARGET: ", strcat(strcat(ftos(targetcount), " "), h)), "\n"));
					new_way.targets[targetcount] = h;
					targetcount++;
				}
				break;
		}
	}

	fclose(file);
	waypoints_loaded = 1;
}

void() Load_Waypoints_Legacy
{
	float file, which;
	string h, special, trg, trg2, trg3, trg4, trg5, trg6, trg7, trg8;
	local vector where;

	h = strcat(mappath, ".way");
	file = fopen (h, FILE_READ);

	if (file == -1)
	{
		dprint("Error: file not found \n");
		return;
	}

	// Clear all waypoints
	for(float i = 0; i < MAX_WAYPOINTS; i++) {
		waypoints[i].id = -1;
	}

	while (1)
	{
		// the first line is just a comment, ignore it
		h = fgets(file);	
		if (h != "Waypoint")
		{
			fclose(file);
			return;
		}
		h = fgets(file);
		h = fgets(file);
		h = substring(h, 9, 50); // fix for high-precision vectors.
		where = stov(h);
		
		h = (fgets(file));
		h = substring(h, 5, 20);
		which = stof(h);
	
		h = (fgets(file));
		special = substring(h, 10, 20);
	
		h = (fgets(file));
		trg = substring(h, 9, 20);

		h = (fgets(file));
		trg2 = substring(h, 10, 20);

		h = (fgets(file));
		trg3 = substring(h, 10, 20);

		h = (fgets(file));
		trg4 = substring(h, 10, 20);

		h = (fgets(file));
		trg5 = substring(h, 10, 20);

		h = (fgets(file));
		trg6 = substring(h, 10, 20);

		h = (fgets(file));
		trg7 = substring(h, 10, 20);

		h = (fgets(file));
		trg8 = substring(h, 10, 20);
		
		waypoint_ai waypoint;
		waypoint = waypoints[which];

		waypoints[which].id = which;
		waypoints[which].org = where;
		waypoints[which].targetdoor = special;

		if (waypoint_mode) {
			entity new_way = spawn();
			new_way.solid = SOLID_TRIGGER;
			new_way.touch = creator_way_touch;
			new_way.classname = "waypoint";
			new_way.waynum = ftos(which);
			new_way.targets[0] = trg;
			new_way.targets[1] = trg2;
			new_way.targets[2] = trg3;
			new_way.targets[3] = trg4;
			new_way.targets[4] = trg5;
			new_way.targets[5] = trg6;
			new_way.targets[6] = trg7;
			new_way.targets[7] = trg8;
			new_way.targetname = special;
			setorigin(new_way, where);

			if (special != "" && special != " ") {
				setmodel(new_way, "models/way/normal_way_door.spr");
			} else {
				setmodel(new_way, "models/way/normal_way.spr");
			}
		}


		// Initialize neighbor slots to load
		waypoints[which].target_id[0] = -1;
		waypoints[which].target_id[1] = -1;
		waypoints[which].target_id[2] = -1;
		waypoints[which].target_id[3] = -1;
		waypoints[which].target_id[4] = -1;
		waypoints[which].target_id[5] = -1;
		waypoints[which].target_id[6] = -1;
		waypoints[which].target_id[7] = -1;
		// Initialize extra neighbor slots
		waypoints[which].target_id[8] = -1;
		waypoints[which].target_id[9] = -1;
		

		if(trg != "") {
			waypoints[which].target_id[0] = stof(trg);
		}
		if(trg2 != "") {
			waypoints[which].target_id[1] = stof(trg2);
		}
		if(trg3 != "") {
			waypoints[which].target_id[2] = stof(trg3);
		}
		if(trg4 != "") {
			waypoints[which].target_id[3] = stof(trg4);
		}
		if(trg5 != "") {
			waypoints[which].target_id[4] = stof(trg5);
		}
		if(trg6 != "") {
			waypoints[which].target_id[5] = stof(trg6);
		}
		if(trg7 != "") {
			waypoints[which].target_id[6] = stof(trg7);
		}
		if(trg8 != "") {
			waypoints[which].target_id[7] = stof(trg8);
		}

		h = (fgets(file));
		h = (fgets(file));
	}
}


void VisualizePathfind() {
	if (self.classname != "player")
		return;
	if (!current_way)
		return;
	if (!active_way)
		return;
	if (current_way == active_way)
		return;
	
	Pathfind(self, stof(active_way.waynum), stof(current_way.waynum));
}

.float waypoint_delay;

//Waypoint logic functions
float way_save_state;
void () Waypoint_Functions =
{

	switch (self.impulse) {
		case 24:
			if (way_save_state == 0) {
				bprint(PRINT_HIGH, "Are you sure you want to save? This will overwrite the old waypoints file, press Load to cancel!\n");
				way_save_state++;
			} else {
				bprint(PRINT_HIGH, "Saving Waypoints..\n");
				Save_Waypoints_Legacy();
				way_save_state = 0;
			}
			break;
		case 22:
			if (way_save_state == 1) {
				bprint(PRINT_HIGH, "Waypoint Save cancelled.\n");
				way_save_state = 0;
			} else {
				if (!waypoints_loaded)
					Load_Waypoints();
			}
			break;
		case 110:
			Move_Waypoint();
			break;
	}
	
	self.impulse = 0;

	// Match what we display on the screen
	if (self.button0 && self.waypoint_delay < time) { 
		Create_New_Waypoint(); 
		self.waypoint_delay = time + 1;
	}

	if (self.button7 && self.waypoint_delay < time) {
		Select_Waypoint();
		self.waypoint_delay = time + 0.25;	
	}

	if (self.button8 && self.waypoint_delay < time) {
		Link_Waypoints ();
		self.waypoint_delay = time + 0.5;	
	}

	if (self.button6 && self.waypoint_delay < time) {
		Remove_Waypoint();
		self.waypoint_delay = time + 0.5;	
	}

	if (self.button5 && self.waypoint_delay < time) {
		Make_Special_Waypoint();
		self.waypoint_delay = time + 1;
	}

};

void () Waypoint_Logic =
{
	if (!waypoint_mode) {
		waypoint_mode = 1;
		entity zent;
	
		zent = find (world, classname, "ai_zombie");
		while (zent)
		{
			remove (zent);
			zent = find (zent, classname, "ai_zombie");
		}

		zent = find (world, classname, "door_nzp_cost");
		while (zent)
		{
			zent.solid = SOLID_NOT;
			zent.touch = SUB_Null;
			zent = find (zent, classname, "door_nzp_cost");
		}
		zent = find (world, classname, "door_nzp");
		while (zent)
		{
			zent.solid = SOLID_NOT;
			zent.touch = SUB_Null;
			zent.solid = SOLID_NOT;
			zent = find (zent, classname, "door_nzp");
		}
		zent = find (world, classname, "window");
		while (zent)
		{
			zent.solid = SOLID_NOT;
			zent.touch = SUB_Null;
			zent = find (zent, classname, "window");
		}
		
	}
	Waypoint_Functions();
};