--
-- Copyright (C) 2020  <fastrgv@gmail.com>
--
-- 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 3 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 may read the full text of the GNU General Public License
-- at <http://www.gnu.org/licenses/>.
--


with snd4ada_hpp;

with gl, gl.binding, gl.pointers;
with glu, glu.binding, glu.pointers;
with glext, glext.binding, glext.pointers;

-------------------------------------------------------------
with System;
with Interfaces.C;
use  type interfaces.c.unsigned;
with Interfaces.C.Pointers;
with interfaces.c.strings;


----------------------------------------------------------------



with glfw3; use glfw3;
with zoomwheel;



----------------------------------------------------------------

with matutils;
with utex;

with ada.unchecked_conversion;
with Ada.Command_Line;
with Ada.Strings.Fixed;
with Ada.Strings.Unbounded;
with Ada.Strings.Unbounded.Text_IO;
with ada.directories;
with ada.numerics;
----------------------------------------------------------------


with shader;  use shader;

with mroomobj;
with tunnelobj;
with mrectobj;
with rectobj;
with pictobj;
with twictobj;
with pict1obj;
with rectxobj;
with rectsurfobj;
with cyl2obj;
with cyl2lit;
with hcylobj;
with rectxfineobj;
with frameobj;

with w3treeobj;
with xtreeobj;
with circtexsurfobj;
with circsurfobj;
with usboxobj;

with cubemapobj;

with text_io;
with pngloader;
with gametypes;
with gameutils;
with matutils;

with ada.calendar;

with avatarolay;
--with avatarobj;



procedure adagate is


------------------------------------------------------------------



------------------------------------------------------------------

	use text_io;
	use pngloader;
	use gametypes;
	use gameutils;
	use matutils;

	use interfaces.c;
	use interfaces.c.strings;
	use glext;
	use glext.pointers;
	use glext.binding;
	use gl;
	use gl.binding;
	use gl.pointers;

	use gametypes.fmath;


	nerr: integer;

	levdebug: integer := 0;






	procedure InitGlfw( 
		name: string; 
		wid,hit,fwd,fht: out glint;
		hiDpiDesired: in boolean := true
		) is separate;

	procedure first_prep is separate;

	procedure drawroom(
		numport: integer; -- 0, 1, 2
		insideportal: boolean := false;
		avx,avy,avz,angl : float
	) is separate;


	procedure preplevel is separate;


	procedure handle_gamepad( nowTime: gldouble ) is separate;

xold, yold: gldouble := 0.0;
	procedure handle_mouse_click( nowTime : gldouble ) is separate;

oldMmTime: gldouble := 0.0;
	procedure handle_mouse_move( nowTime : gldouble ) is separate;

slewTime: gldouble := 0.0;
oldKbTime: gldouble := 0.0;
	procedure getKeyInputs( mainWin : access GLFWwindow ) is separate;





-- island prologue
--
-- involves a large cubemapped skybox
-- with round body of water and a annular island of sand at center
-- Walking about is done by keeping the eye @ (0,0,0) and moving
-- the sandy island in the opposite direction.  This is done
-- to preserve the proper skybox perspective.  Thus, within
-- this skybox, we maintain 2 distinct view matrices:
-- mviewMatrix [& mmvp] with eyepos @ (0,0,0)
-- iviewMatrix [& imvp] with eyepos @ island-virtual-position
--
	function drawisland( 
		ibkgd: integer:= 0;
		skipFly: boolean := true ) return integer is separate;




	oldx,oldy,oldz: float;
	ohang, ovang: float;
	draw1, draw2 : boolean := true;



	oxcam,ozcam,oycam, 
	ochang, ocxluk, oczluk: float;

	xrotn,yrotn,zrotn, solang: float;

	ort, nrt12, nrt21,
	olook, nulook12, nulook21,
	tg1,tg2,
	ome, me12, me21, d12,d21: vec4;

	vmt,vcc: vec4;
	xap,yap,zap: float;

	adagate_main_error : exception;

	onmusic: boolean := false;

-------------------------- main program begin ==========================
begin --adagate

	new_line;
	new_line;
	put_line("Please be patient...AdaGate is slow to load...");
	new_line;
	new_line;



	first_prep;  -- main program setup
	-- Here, we may begin testing for GLerrors



	nerr:=dumpGLerrorQueue("main 1"); 
	--prevents misleading messages in pngloader or loadshaders

	setup_textures; -- prep dungeon textures


	island_texture_setup; 

	updateCamera(true);
	updateMVP( float(winwidth), float(winheight) );




	level:=0;
	solved(0):=true;
	solved(5):=false; -- false until epilog is visited

	currentTime := glfwGetTime;

	nerr:=dumpGLerrorQueue("main 2");
	--prevents misleading messages within main loop




	-- main event loop begin: -----------------------------------------------
   while not userexit and not lavadead and not fireballdead loop

		direction:=0; --stop avatar's legs unless moving

		if (level<5) and  solved(level) then --prepare next level



			if level>0 then
				gldeletetextures(1, room_texid'address);
			end if;

			if level=1 then 
				gldeletetextures(1, ceil_texid'address); 
			end if;

			if (level=1) or (level=4) then 
				gldeletetextures(1, pic_texid'address);
			end if;

			snd4ada_hpp.stopLoops;


			-- Reset flags to initial state:

			-- elliminate gratuitous holes while shooting
			xtgt1:=25.0;
			ztgt1:=25.0;  --way outside dungeons
			xtgt2:=xtgt1;
			ztgt2:=ztgt1;

			showcross:=false;
			portalenabled:=false;
			lport_located:=false;
			rport_located:=false;
			lport_defined:=false;
			rport_defined:=false;
			lport_stable:=false;
			rport_stable:=false;
			lshooting:=false;
			rshooting:=false;
			worm_defined:=false;
			worm_active:=false;
			first_worm_active:=false;
			xit:=false;
			exitwait:=false;
			wall1:=none;
			wall2:=none;


			nko:=0;
			pko:=0;



			xme:=0.0; yme:=0.0; zme:=0.0;
			-- beach uses alternate paradigm:  Yeye = sandHt + eyeht
			-- whereas dungeons default is:    Yeye = -ymax + aheight

			-- play Xport if returning from dungeon:
			if level > 0 then snd4ada_hpp.playSnd(wormx); end if; -- 20nov19


			-- if levdebug>0 we skip beach
			-- and immediately jump to a 
			-- particular level for debugging:
			if levdebug>0 then
				interior:=true;
				level:=levdebug; --level to debug

			else

				-- Draw island ########################################
				-- user will select destination level on the DHD:
				interior:=false;
				onIsland:=true;
				level := drawisland(bkgd,skipflyover); 
					-- island-nexus (returns 1..5)
				onIsland:=false;
				skipflyover:=true; -- 9jul18 (was false)

				if level<5 then interior:=true; end if;

			end if;



			vertang:=0.0;
			horiang:=0.0;


			-- careful, these are ALSO used within island_ftn for rocks
			nko:=0;
			pko:=0;

			preplevel;
			onmusic:=false;



			if levdebug>0 then 
				worming:=false;

			else	
					
				-- New, narrower tunnel now makes this possible 
				-- even in level 5, but tunnel requires [destination] 
				-- horiang be a multiple of halfpi
				fromIsland:=true;
				prepwormhole( currenttime, 
					xme-40.0*xlook, yme, zme-40.0*zlook, xme,yme,zme, xme,yme,zme);
				-- wormhole trajectory begins 40 units "behind" starting location

			end if;


		end if; -- new level prep -----------------------------------




	-- main event loop middle: ######################################

		-- 20nov19 now wait until after arrival:
		if not worming and not onmusic then
			if level=1 then
				snd4ada_hpp.playLoop(water);
				snd4ada_hpp.playLoop(falls);
			elsif level=2 then
				snd4ada_hpp.playLoop(water);
				snd4ada_hpp.playLoop(dark);
			elsif level=3 then
				snd4ada_hpp.playLoop(lava);
				snd4ada_hpp.playLoop(gothic);
			elsif level=4 then
				snd4ada_hpp.playLoop(water);
				snd4ada_hpp.playLoop(neptune);
			elsif level=5 then
				snd4ada_hpp.playLoop(water);
				snd4ada_hpp.playLoop(choir);
			end if;
			onmusic:=true;
		end if;


		currentTime := glfwGetTime;


		------- begin response to user inputs

		if not worming then

			GlfwPollEvents;

			getKeyInputs(mainWindow);
			handle_mouse_move(currenttime);
			handle_mouse_click(currenttime);
			handle_gamepad(currenttime);

		else -- worming
			worm(currenttime, xme,yme,zme); --, worming);

			if not worming then
				updateCamera(true);
			end if;

		end if; -- not worming





		if not worming then
			updateCamera; --main call of updcam in dungeon
		end if;

		updateMVP( float(winwidth), float(winheight) );

		if level<5 then
			updategamestate( currentTime );
		end if;






--------- begin drawing =================================================

		glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);


		if 
			not worming   --skip this when passing thru wormhole

			and not userexit

			and worm_active

			and level<5

		then -- Draw views through 2 portals


			--for each portal we reset coords & look angles,
			--then updateMVP and draw views thru each portal;
			--
			--Note that the new drawing regimen is to use the
			--shaders to discard any object on the other side of
			--the portal that is NOT in its FOV!  This becomes
			--critical when 2 portals share the same wall!
			--
			--Finally, restore coords & looks, 
			--updateMVP, then draw normal view


			--first, save normal values here:
			ohang:=horiang;
			ovang:=vertang;
			oxme:=xme;
			oyme:=yme;
			ozme:=zme;

			ochang:=choriang;
			ocxluk:=cxlook;
			oczluk:=czlook;
			oxcam:=xcam;
			oycam:=ycam;
			ozcam:=zcam;


			---------- begin delta angle calculation ----------------
			-- note:  horiang=0 => north = +Z,  horiang=pi/2 => west = +X
			-----------------------------------------------------------------
			-- define the angular difference solang between 
			-- the outward normal at p2 and inward normal @ p1
			-- = pn2 - (-pn1) = pn2+pn1, 
			-- (+pi and -pi are equivalent)

			if wall1=oppo(wall2) then
				solang:=0.0;

			elsif wall1=wall2 then 
				solang:=onepi;

			else 
				solang:=halfpi;

			end if;


----------------------------------------------------------------------

			if thirdperson then
				oldx:=oxcam;
				oldy:=oycam;
				oldz:=ozcam;
				olook:=(cxlook,cylook,czlook,0.0);

				--old camera rightward vector:
				ort:=(
					fmath.sin(ochang-halfpi),0.0,
					fmath.cos(ochang-halfpi),0.0);

			else
				oldx:=oxme;
				oldy:=oyme;
				oldz:=ozme;
				olook:=(xlook,cylook,zlook,0.0);

				--old ME rightward vector:
				ort:=(
					fmath.sin(ohang-halfpi),0.0,
					fmath.cos(ohang-halfpi),0.0);

			end if;


			ome:=(oldx,oldy,oldz,0.0);
			tg1:=(xtgt1,ytgt1,ztgt1,0.0);
			tg2:=(xtgt2,ytgt2,ztgt2,0.0);

			-- we know the angle...
			-- get AxisOfRotation using cross-product
			if 
				(abs(onepi-abs(solang))>0.001) -- not +-pi
					and
				(abs(solang)>0.001)            -- not zero
			then
				cross(
					-p1n(1), -p1n(2), -p1n(3), -- wall1 inward normal
					+p2n(1), +p2n(2), +p2n(3), -- wall2 outward normal
					xrotn,yrotn,zrotn ); -- axis of rotation

				normalize(xrotn,yrotn,zrotn);

-- below this line, solang = 0 or pi ==> axis not critical

			elsif 
				wall1=up or wall1=dn or wall2=up or wall2=dn
			then
				xrotn:=0.0;
				yrotn:=0.0;
				zrotn:=1.0;

			else -- same or opposite vertical walls
				xrotn:=0.0;
				yrotn:=1.0;
				zrotn:=0.0;

			end if;


			solmat12:=identity;
			degRotate(solmat12, +solang*rad2deg, xrotn,yrotn,zrotn);
			matXvec(solmat12, ome-tg1, d12 );
			me12:=d12+tg2;
			matXvec(solmat12, olook, nulook12); --new look
			matXvec(solmat12, ort, nrt12); --new rightward

			solmat21:=identity;
			degRotate(solmat21, -solang*rad2deg, xrotn,yrotn,zrotn);
			matXvec(solmat21, ome-tg2, d21 );
			me21:=d21+tg1;
			matXvec(solmat21, olook, nulook21); --new look
			matXvec(solmat21, ort, nrt21); -- new rightward


----------------------------------------------------------------------




---------- 1st portal ----------------------------------

			updateMVP2( float(winwidth), float(winheight),
				me12(1), me12(2), me12(3),
				nulook12(1), nulook12(2), nulook12(3),
				nrt12(1), nrt12(2), nrt12(3) );
			--left portal#1


			xme:=me12(1); yme:=me12(2); zme:=me12(3);
			xcam:=xme; ycam:=yme; zcam:=zme;

			drawroom(1,true, oxme,oyme,ozme,ohang); 
			-- view into 1st (leftBtn) portal
			-- ...looking out thru 2nd.


---------- 2nd portal ----------------------------------

			updateMVP2( float(winwidth), float(winheight),
				me21(1), me21(2), me21(3),
				nulook21(1), nulook21(2), nulook21(3),
				nrt21(1), nrt21(2), nrt21(3) );
			--right portal#2


			xme:=me21(1); yme:=me21(2); zme:=me21(3);
			xcam:=xme; ycam:=yme; zcam:=zme;

			drawroom(2, true, oxme,oyme,ozme,ohang); 
			-- view into 2nd (rightBtn) portal
			-- ...looking out thru first

------------------ end of 2nd portal --------------------------------

-----  restore normal view, MVP matrix: ------------------------------
			xme:=oxme;
			yme:=oyme;
			zme:=ozme;
			horiang:=ohang;
			vertang:=ovang;


			if thirdperson then -- vertang represents camera
				xlook := fmath.cos(0.0)*fmath.sin(horiAng);
				zlook := fmath.cos(0.0)*fmath.cos(horiAng);
			else
				xlook := fmath.cos(vertAng)*fmath.sin(horiAng);
				zlook := fmath.cos(vertAng)*fmath.cos(horiAng);
			end if;
			cylook := fmath.sin(vertAng);


			choriang:=ochang;
			cxlook := ocxluk;
			czlook := oczluk;
			xcam:=oxcam;
			ycam:=oycam;
			zcam:=ozcam;

			updateMVP( float(winwidth), float(winheight) );



		end if; -- not worming and worm_active
		-- end of drawing views thru 2 portals ---------------------------




		---------- here is the primary drawing of current dungeon: --------
		if not userexit then
			drawroom(0,false, xme,yme,zme,horiang); --################
		end if;
		---------- here is the primary drawing of current dungeon: --------



		if worming and not userexit then -- draw passage thru wormhole
			drawWormHole( currentTime, mvp );

		elsif showCross then -- draw crosshair-cursor
			aim(xap,yap,zap);
			vmt:=(xap,yap,zap,1.0);
			matXvec(mvp,vmt,vcc);
			utex.print3d("+",vcc(1),vcc(2),vcc(3),vcc(4), 40, 1.0);
			drawbeams(currenttime);

		end if; -- worming


		if help then
			utex.print2d(" Help Screen Tips:",0.02, 0.70,15);
			utex.print2d(" m-key => toggle Avatar",0.02, 0.60,15);
			utex.print2d(" ...and if avatar is showing...",0.02, 0.55,15);
			utex.print2d(" n-key => zoom_Nearer;  f-key => zoom_Further;  z-key => default_zoom", 0.02, 0.5, 15);

			utex.print2d(" ...if portal guns are active, crosshairs appear and...",0.02, 0.35,15);
			utex.print2d(" LfMouseBtn/l-key => shoot_leftGun;  RtMouseBtn/r-key => shoot_rightGun",0.02, 0.30,15);

			utex.print2d(" <esc> => quit", 0.02, 0.20, 25);

		elsif details then -- initiated with <x>-key ...FOR DEBUGGING ONLY

			--put(" |cam: "&float'image(xcam)&","&float'image(ycam)&","&float'image(zcam));
			--put(" |me: "&float'image(xme)&","&float'image(yme)&","&float'image(zme));
			--new_line;

			--utex.print2d("Ioffset: "&float'image(ioffsetu), 0.02,0.95,15);
			--utex.print2d("Hoffset: "
			--	&float'image( fmath.sqrt( sqr(xcam-xme)+sqr(zcam-zme) )), 0.02,0.9,15);

			-- intent of this section is to show certain technical details 
			-- to check the status under OS-X in case of a MacBundle 
			-- rather than the command line version.

			--utex.print2d(" Ndim: " &
			--	interfaces.c.int'image(Nwid)&" X "
			--	& interfaces.c.int'image(Nhit), 0.02, 0.8, 15 );

			utex.print2d(" hdpi: " &
				interfaces.c.int'image(Fwid)&" X "
				& interfaces.c.int'image(Fhit), 0.02, 0.7, 15 );

--------- begin OGL queries -----------------------------------------

			glGetIntegerv(GL_CONTEXT_PROFILE_MASK, profile'address);
			if( profile = GL_CONTEXT_CORE_PROFILE_BIT ) then
				utex.print2d("ogl-query:  Core Profile", 0.02, 0.6, 10);
			end if;

			-- Note that OSX currently requires the forward_compatible flag!
			glGetIntegerv(GL_CONTEXT_FLAGS, flags'address);
			if( flags = GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT ) then
				utex.print2d("ogl-query:  Forward-Compatible bit is set", 0.02, 0.55, 10);
			end if;

			glgetintegerv(gl_major_version, major'address);
			glgetintegerv(gl_minor_version, minor'address);
			utex.print2d( "ogl-query: OGL-major: "&glint'image(major), 0.02, 0.5, 10);
			utex.print2d( "ogl-query: OGL-minor: "&glint'image(minor), 0.02, 0.45, 10);

			glgetintegerv(gl_max_texture_units, mtu'address);
			utex.print2d( "ogl-query: maxTexUnits: "&glint'image(mtu), 0.02, 0.4, 10);

			glgetintegerv(gl_max_texture_image_units, mtu'address);
			utex.print2d( "ogl-query: maxTexImgUnits: "&glint'image(mtu), 0.02, 0.35, 10);

			glgetintegerv(gl_max_combined_texture_image_units, mtu'address);
			utex.print2d( "ogl-query: maxCombTexImgUnits: "&glint'image(mtu), 0.02, 0.3, 10);

			glgetintegerv(gl_max_uniform_buffer_bindings, mul'address);
			utex.print2d( "ogl-query: maxUniformBufferBindings: "&glint'image(mul), 0.02, 0.25, 10);

			glgetintegerv(gl_max_uniform_locations, mul'address);
			utex.print2d( "ogl-query: maxUniformLocations: "&glint'image(mul), 0.02, 0.20, 10);

--------- end OGL queries -----------------------------------------


		end if;  -- end showing debugging data





		--output errors to console but raise exception only if dbug
		if dbug and then dumpGLerrorQueue("AG main loop end")>0 then
			null;
			--if dbug then raise adagate_main_error; end if;
		end if;



		glflush;
		glfwSwapBuffers( mainWindow );


-----------------------------------------------------------------------------
   end loop; ------------------------ main event loop end -------------------
-----------------------------------------------------------------------------



	if lavadead or fireballdead then
		snd4ada_hpp.playSnd(shriek); -- shriek
		utex.print2d("Sorry, Lava Killed You !", 0.25, 0.5, 50);

		glflush;
		glfwSwapBuffers( mainWindow );

		delay 4.0;
	end if;




	text_io.create(tfile, out_file, resfile);
	completed:=
		solved(1) and solved(2) and solved(3) and solved(4) and solved(5);
	if completed then

		put_line(tfile, integer'image( dod mod mxdeg + 1));
		put_line(tfile, integer'image(0) );
		put_line(tfile, integer'image(0) );
		put_line(tfile, integer'image(0) );
		put_line(tfile, integer'image(0) );

	else

		put_line(tfile, integer'image(dod));
		for i in 1..mxlev loop
		if solved(i) then
			put_line(tfile, integer'image(1) );
		else
			put_line(tfile, integer'image(0) );
		end if;
		end loop;

	end if;
	text_io.close(tfile);


	snd4ada_hpp.termSnds; -- stops any sound loops;  then deallocates

	release_textures;

	utex.cleanuptext;

	glfwdestroywindow(mainWindow);
	glfwTerminate;


end adagate;

