

--
-- Copyright (C) 2022  <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/>.
--


-- Reliquarium -- The Four Tombs:
-- Crystal Skull-themed block slider puzzles;
-- GLFW3 version;  Retina compatible.


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



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; use zoomwheel;

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

with gametypes;
with matutils;
with gtex;

with ada.unchecked_conversion;
with Ada.Command_Line;
with Ada.Directories;

with SysUtils;

with ada.command_line;
with Ada.Environment_Variables;
with ada.strings.fixed;
with Ada.Strings.Unbounded;
with Ada.Strings.Unbounded.Text_IO;
with ada.numerics.generic_elementary_functions;
with text_io;

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


with shader;  use shader;

with pngloader;
with matutils;

with pictobj;
with gnat.os_lib;

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





procedure reliquarium is


use Ada.Strings.Unbounded;
use Ada.Strings.Unbounded.Text_IO;


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

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


	package btex is new gtex; -- Black lettering

	mswin: constant boolean := (gnat.os_lib.directory_separator='\'); 
	-- => compiled for Windows

	-- Treat Windows & OS-X the same as GNU-Linux, given 
	-- that like-binaries will be together in same directory:
	dirname: constant string := ada.directories.current_directory;


------ begin insert ----------------------------------------------------------------
	exestr: constant string := ada.command_line.command_name;

	macExe : constant boolean := (ada.strings.fixed.index( exestr, "osx", 1 ) > 1);
	gnuExe : constant boolean := (ada.strings.fixed.index( exestr, "gnu", 1 ) > 1);
	gnatExe : constant boolean := (ada.strings.fixed.index( exestr, "gnat", 1 ) > 1);
	w64Exe : constant boolean := (ada.strings.fixed.index( exestr, "win", 1 ) > 1);
	w32Exe : constant boolean := (ada.strings.fixed.index( exestr, "w32", 1 ) > 1);
------ end insert ----------------------------------------------------------------




	onMac  : constant boolean := (ada.strings.fixed.index( dirname, "MacOS", 1 ) > 1);
	-- => executing osx binary

	onGnat : constant boolean := (ada.strings.fixed.index( dirname, "gnat", 1 ) > 1);
	onGnu  : constant boolean := (ada.strings.fixed.index( dirname, "gnu", 1 ) > 1);
	-- => executing linux binary

	onWin64 : constant boolean := (ada.strings.fixed.index( dirname, "win", 1 ) > 1);
	onWin32 : constant boolean := (ada.strings.fixed.index( dirname, "w32", 1 ) > 1);
	-- => executing Windows binary

	--atHome: boolean := (ada.strings.fixed.index( dirname, "relic", 1 ) > 1);

	onWin: constant boolean := onWin64 or onWin32;
	-- => executing Windows binary

	t2name,t4name,t5name,t6name: unbounded_string;

	winpath: string := "bin\win\";
	w32path: string := "bin\w32\";
	gnupath: string := "bin/gnu/";
	gnatpath: string := "bin/gnat/";
	--homepath: string := "";

	exepath: unbounded_string;













	pgmtexshadid, pgmtexid : gluint := 0;

	uniftex, matid : glint;



	type vec3 is array(1..3) of float;


	package fmath is new
			Ada.Numerics.generic_elementary_functions( float );
	use fmath;


  onepi : constant float     := 3.14159_26535_89793;
  halfpi : constant float    := onepi/2.0;
  fourthpi : constant float  := onepi/4.0;
  twopi : constant float     := onepi*2.0;
  deg2rad : constant float   := onepi/180.0;
  rad2deg : constant float   := 180.0/onepi;




-- begin string pointers for getUniformLocation:

	pmvp : chars_ptr := new_string("MVP"&ascii.nul);
	pmyts : chars_ptr := new_string("myTextureSampler"&ascii.nul);

-- end string pointers for getUniformLocation:









procedure myassert( condition : boolean;  flag: integer:=0 ) is
begin
  if condition=false then
  		put("ASSERTION Failed!  ");
		if flag /= 0 then
			put_line( "@ " & integer'image(flag) );
		end if;
		new_line;
  		raise program_error;
  end if;
end myassert;













procedure InitGLFW( 
	wid, hit : out glint; 
	fwd,fht : out glint; 
	name: string ) is

	use system;

	title : interfaces.c.strings.chars_ptr := new_string(name&ascii.nul);

	maj,min,rev : aliased glint;

	axs, ays : aliased float;
	awwid,awhit, afwid, afhit : aliased glint;

begin

	put_line("...using fastrgv's Ada Binding to GLFW...");

	GlfwGetVersion(maj'access,min'access,rev'access); --naturals
	put("GLFW ver: ");
	put(glint'image(maj));
	put(":"&glint'image(min));
	put(":"&glint'image(rev));
	New_Line;



	if GlfwInit /= gl_true then
		new_line;
		put_line("glfwInit failed");
		raise program_error;
	end if;

	-- use version here that your graphics card would support:
	GlfwWindowHint( glfw_context_version_major, 3);
	GlfwWindowHint( glfw_context_version_minor, 3);
	GlfwWindowHint( glfw_opengl_forward_compat, gl_true);
	GlfwWindowHint( glfw_opengl_profile, glfw_opengl_core_profile);

	GlfwWindowHint( glfw_samples, 4);
	GlfwWindowHint( glfw_client_api, glfw_opengl_api);

	-- this seems unnecessary...
	-- MacBook shows this app @ HiDpi by default!
	--GlfwWindowHint( glfw_cocoa_retina_framebuffer, glfw_true );


	wid:=800;
	hit:=800;

	mainWin := glfwcreatewindow(
		wid, hit,	title, 
		null, null );
		

	if mainWin = null then
		new_line;
		put_line("glfwCreateWindow failed");
		raise program_error;
	end if;

	glfwmakecontextcurrent( mainWin );


--HiDpi queries:
	glfwGetWindowSize(mainWin, awwid'access, awhit'access);
	glfwGetFramebufferSize(mainWin, afwid'access,afhit'access);
	glfwGetWindowContentScale(mainWin, axs'access,ays'access);

	wid:=awwid;
	hit:=awhit;

	fwd:=afwid;
	fht:=afhit;


	put_line("HighDpi Queries:");
	put_line("WI: "&glint'image(awwid)&","&glint'image(awhit));
	put_line("FB: "&glint'image(afwid)&","&glint'image(afhit));
	put_line("Sc: "&float'image(axs)&","&float'image(ays));

end InitGLFW;











procedure first_prep( mac: boolean ) is -- main program setup
      FileId : text_io.File_Type;

	macpath: string := "../../../data/"; -- osx
	xinpath: string := "../../data/"; -- windows or linux
	nopath: string := "./data/"; --below current dir
	path: unbounded_string;

begin

	if onmac then
		append(path,macpath);
	elsif ada.directories.exists(nopath) then
		append(path,nopath);
	else
		append(path,xinpath);
	end if;







	initGlfw(
		wwid,whit, fwid,fhit,
		"Reliquarium -- click to select,     <q> to exit");

	btex.inittext2D(to_string(path)&"rods3b.png", integer(Wwid),integer(Whit));


	glViewport(0,0,Fwid,Fhit);


	glgenvertexarrays(1, vertexarrayid'address );
	glbindvertexarray(vertexarrayid);

	glactivetexture(gl_texture0); -- moved here 5nov14 (outside main loop)

	glgenbuffers(1, vertbuff'address);
	glgenbuffers(1, uvbuff'address);
	glgenbuffers(1, elembuff'address);




	glenable(gl_depth_test);
	gldepthfunc( gl_lequal );
	glenable( gl_cull_face );


	glEnable(GL_MULTISAMPLE);
	glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
	glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);

	glClearColor(0.5, 0.5, 0.5, 1.0);

end first_prep;














function max( a, b : float ) return float is
begin
	if a>b then return a;
	else return b; end if;
end max;

MVP, ModelMatrix, ViewMatrix, ProjectionMatrix
	 : mat44 := identity;

procedure updateMVP( wid, hit : glint ) is
	xlook, ylook, zlook, xlk,ylk,zlk, xrt,yrt,zrt, xup,yup,zup : float;
	xme,yme,zme : float;

	woh : constant float := float(wid)/float(hit);
	how : constant float := float(hit)/float(wid);

	fovdeg : constant float := 45.0;
	fovrad : constant float := fovdeg*deg2rad;

	aspect : constant float := max(1.0,how);

	-- distance from eye so FOV encompasses proper field:
	eyeradius : constant float := aspect / fmath.tan(fovrad/2.0);

	near : constant float := 0.1;
	far  : constant float := 100.0;

-- New setup looks toward +Z direction (@ origin)
-- with +Y=up, +X=left:
	focus : constant vec3 := (0.0, 0.0, 0.0);
	eyepos: constant vec3 := (0.0, 0.0, -eyeradius);
	look  : constant vec3 := 
		( focus(1)-eyepos(1), focus(2)-eyepos(2), focus(3)-eyepos(3) );
	vertAng : constant float := 0.0;
	horiAng : constant float := fmath.arctan( look(1), look(3) ); --0.0;


begin

	ModelMatrix:=identity;
	--scale width versus height so pic fills window:
	if woh>1.0 then
		ModelMatrix(1,1):=woh;
	else
		ModelMatrix(3,3):=how;
	end if;

	xme:=eyepos(1);
	yme:=eyepos(2);
	zme:=eyepos(3);

	-- look direction:
	xlook := fmath.cos(vertang)*fmath.sin(horiang);
	ylook := fmath.sin(vertang);
	zlook := fmath.cos(vertang)*fmath.cos(horiang);

	xlk := xme+xlook;
	ylk := yme+ylook;
	zlk := zme+zlook;

	-- Right unit-Direction
	xrt:= fmath.sin(horiang-halfpi);
	yrt:= 0.0;
	zrt:= fmath.cos(horiang-halfpi);

	-- calculate UP unit-Direction
	cross( xrt,yrt,zrt, xlook,ylook,zlook, xup,yup,zup );

	perspective(ProjectionMatrix, fovdeg, woh,  near, far);

	lookat(ViewMatrix, xme,yme,zme, xlk,ylk,zlk, xup,yup,zup );

	MVP:=ModelMatrix;
	matXmat(MVP,ViewMatrix);
	matXmat(MVP,ProjectionMatrix);

end updateMVP;





npuz : constant integer := 4; -- t2, t4, t5, t6
texid    : array(0..4) of GLuint;
puzpiece : array(0..4) of pictobj.pictangle; -- 0=>enclosure



procedure release_stuff is -- prepare to close down
begin

	glext.binding.glDeleteBuffers(1, vertbuff'address);
	glext.binding.glDeleteBuffers(1, uvbuff'address);
	glext.binding.glDeleteBuffers(1, elembuff'address);

	glext.binding.glDeleteProgram( pgmtexshadid );

	glext.binding.glDeleteVertexArrays(1, vertexarrayid'address);

end release_stuff;


procedure setup_stuff( mac: boolean ) is  -- prepare dungeon textures
	xx,yy,zz,dx,dy,dz,nx,ny,nz : float;
	macpath: string := "../../../data/"; -- osx
	xinpath: string := "../../data/"; -- windows or linux
	nopath: string := "./data/"; --below current dir

	path: unbounded_string;
begin 

	if onmac then
		append(path,macpath);
	elsif ada.directories.exists(nopath) then
		append(path,nopath);
	else
		append(path,xinpath);
	end if;


	pgmtexshadid := 
		loadshaders(to_string(path)&"otexobj.vs", to_string(path)&"ttexobj.fs");
	matid := glgetuniformlocation(pgmtexshadid, pmvp);
	uniftex  := glgetuniformlocation(pgmtexshadid, pmyts);

	-- added flag=false on 31oct22 because I am now
	-- using "gtex" and the requisite enhanced pngloader...
	texid(0):=loadPng(repeat,to_string(path)&"skull.png");
	texid(1):=loadPng(repeat,to_string(path)&"t2a.png");
	texid(2):=loadPng(repeat,to_string(path)&"t4a.png");
	texid(3):=loadPng(repeat,to_string(path)&"t5a.png");
	texid(4):=loadPng(repeat,to_string(path)&"t6a.png");


-- orientation:  +Z look, +X=left, +Y=up
-- See "eyepos"
	myassert( npuz = 4 );
	zz := -0.001;
	dz := 0.001;
	nz := zz; -- slightly closer to eye than origin
	for row in 0..1 loop
	for col in 0..1 loop
		xx := 0.25 + float(1-col)/2.0; -- (+x is left)
		yy := 0.25 + float(1-row)/2.0; -- (+y is up)
		dx := 0.5 * 0.9;
		dy := 0.5 * 0.9;
		nx := 2.0*xx-1.0;
		ny := 2.0*yy-1.0;
		puzpiece(row*2+col+1).setRect(nx,ny,nz, dx,dy,dz, 0.0,0.0,0.0);
	end loop;
	end loop;

	zz := +0.001;
	nz := zz; -- slightly further away from eye
	puzpiece(0).setRect(0.0,0.0,nz, 1.0,1.0,dz, 0.0,0.0,0.0);

end setup_stuff;









--mousestate : Uint32;
--state, ileft, iright : integer;
userquit, details : boolean := false;
currenttime, keytime, btntime : float;
dwell : constant float := 0.2;


pselBlock : integer := -1;


procedure handle_mouse_pick( 
	xmouse, ymouse : gldouble ) is

 col : constant float := float(xmouse)/float(Wwid);
 row : constant float := float(ymouse)/float(Whit);

 ir,ic : integer := -1;

begin

	if( col>0.0 ) and ( col < 0.5 ) then
		ic:=0;
	elsif (col<1.0) then
		ic:=1;
	end if;

	if(row>0.0) and ( row < 0.5 ) then
		ir:=0;
	elsif (row<1.0) then
		ir:=1;
	end if;

	pselBlock := 2*ir+ic; -- Zero-based

	myassert( pselBlock >= 0 );
	myassert( pselBlock < 4 );

	userquit:=true;

end handle_mouse_pick;





procedure emptyEventQueue is
	msr,msl: glint;
begin

	loop

		glfwWaitEvents;

		msr := glfwGetMouseButton(mainWin, glfw_mouse_button_2); -- ?3?
		msl := glfwGetMouseButton(mainWin, glfw_mouse_button_1);

		exit when msl=glfw_release and msr=glfw_release;

	end loop;

end emptyEventQueue;






	pdlay : constant float := 0.20; --mousePickDelay interval

	-- 15nov17 addendum:  surrounding quotes to allow blanks in path
	Ok: boolean;

	msl,msr: glint;

	mousex, mousey : aliased gldouble;

begin -- reliquarium


	if onmac then --must use fullpaths
		append(exepath,dirname&"/"); 

	elsif onGnu or onGnat or onWin then --we are in a subdirectory so EXEs are local
		--append(exepath,homepath); --(homepath=="")
		null;

	--else we must be in root director [ ~/relic/ ] ---------------------------

	elsif w64exe and mswin then -- ./bin/win/
		append(exepath,winpath);
	elsif w32exe and mswin then -- ./bin/w32/
		append(exepath,w32path);

	elsif gnuexe then
		append(exepath,gnupath); -- ./bin/gnu/
	elsif gnatexe then
		append(exepath,gnatpath); -- ./bin/gnat/

	else --??
		raise program_error;
	end if;

	put_line("using exepath="&exepath);
	t2name := exepath&"tomb2";
	t4name := exepath&"tomb4";
	t5name := exepath&"tomb5";
	t6name := exepath&"tomb6";





	first_prep(onMac); -- init graphics/sound, defines fnum, flev

	setup_stuff(onMac);


	currentTime := float(glfwGetTime);
	keytime := currentTime;
	btntime := currentTime;


	updateMVP( Wwid, Whit );


	-- main event loop begin: -----------------------------------------------
   while not userquit loop

		glfwFocusWindow( mainWin ); -- bring to top; give it focus 26sep22

		--glfwPollEvents;
		glfwWaitEvents;

-------- here we should handle resized window ----------------------


		glfwGetWindowSize( mainWin, Nwid'access, Nhit'access );
		if( (Nwid /= wwid) or (Nhit /= whit) ) then
			wwid:=Nwid;  whit:=Nhit;

			glfwGetFramebufferSize(mainwin, fwid'access, fhit'access);
			glViewport(0,0,Fwid,Fhit);

		end if;



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

		updateMVP( Wwid, Whit );

		glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);



		-- use this to draw ordinary textured objects:
		glUseProgram(pgmTexShadID);
		glUniformMatrix4fv(MatID, 1, GL_FALSE, MVP(1,1)'address);
		glUniform1i(uniftex, 0);


		for k in 0..4 loop
			glBindTexture( GL_TEXTURE_2D, texid(k) );
			pictobj.draw(puzpiece(k),vertbuff,uvbuff,elembuff);
		end loop;



		if details then

			-- intent is to show technical details here in case a MacBundle
			-- is used rather than the command line version.

			btex.print2d(" Ndim: " &
				interfaces.c.int'image(Nwid)&" X "
				& interfaces.c.int'image(Nhit), 0.05, 0.85, 25 );

			btex.print2d(" hdpi: " &
				interfaces.c.int'image(Fwid)&" X "
				& interfaces.c.int'image(Fhit), 0.05, 0.75, 25 );


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

			glGetIntegerv(GL_CONTEXT_PROFILE_MASK, profile'address);
			if( profile = GL_CONTEXT_CORE_PROFILE_BIT ) then
				btex.print2d("ogl-query:  Core Profile", 0.1, 0.6, 20);
			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
				btex.print2d("ogl-query:  Forward-Compatible bit is set", 0.1, 0.5, 20);
			end if;

			glgetintegerv(gl_major_version, major'address);
			glgetintegerv(gl_minor_version, minor'address);
			btex.print2d( "ogl-query: OGL-major: "&glint'image(major), 0.1, 0.4, 20);
			btex.print2d( "ogl-query: OGL-minor: "&glint'image(minor), 0.1, 0.3, 20);

			glgetintegerv(gl_max_texture_units, mtu'address);
			btex.print2d( "ogl-query: maxTexUnits: "&glint'image(mtu), 0.01, 0.2, 20);

			glgetintegerv(gl_max_texture_image_units, mtu'address);
			btex.print2d( "ogl-query: maxTexImgUnits: "&glint'image(mtu), 0.01, 0.13, 20);

			glgetintegerv(gl_max_combined_texture_image_units, mtu'address);
			btex.print2d( "ogl-query: maxCombTexImgUnits: "&glint'image(mtu), 0.01, 0.06, 20);

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


		end if;


		glflush;
		glfwSwapBuffers( mainWin );

		--------- end drawing =============================





-------------- begin user inputs -----------------------------------------

		pselBlock:=-1;

		currentTime := float(glfwGetTime);

--check for <esc>,<q>:
--if glfwgetkey( mainWin, glfw_key_escape ) = Glfw_Press then
--		userquit:=true;

--els

	if glfwgetkey( mainWin, glfw_key_q ) = Glfw_Press then
			userquit:=true;

	end if;

	exit when glfwWindowShouldClose(mainWin) /= 0; --14may21 addendum


	glfwgetcursorpos(mainWin,mousex'access,mousey'access);
	msr := glfwGetMouseButton(mainWin, glfw_mouse_button_2); -- ?3?
	msl := glfwGetMouseButton(mainWin, glfw_mouse_button_1);

	if msl=glfw_press or msr=glfw_press then

		if (currenttime-btntime)>dwell then
			handle_mouse_pick(mousex, mousey);
			if pselBlock>=0 then
				btntime := currentTime;
			end if;
		end if;

	end if; --mouseBtnPress


-------------- end user inputs -----------------------------------------


		if    pselBlock=0 then

			SysUtils.bShell( to_string(t2name), Ok ); --my generic Ada method
			-- note to self:  the shell spawn above MUST be blocking
			--       because the nexus window stays small until done.
			userquit:=false;
			pselBlock:=-1;
			emptyEventQueue;


		elsif pselBlock=1 then

			SysUtils.bShell( to_string(t4name), Ok );
			userquit:=false;
			pselBlock:=-1;
			emptyEventQueue;

		elsif pselBlock=2 then

			SysUtils.bShell( to_string(t5name), Ok );
			userquit:=false;
			pselBlock:=-1;
			emptyEventQueue;

		elsif pselBlock=3 then

			SysUtils.bShell( to_string(t6name), Ok );
			userquit:=false;
			pselBlock:=-1;
			emptyEventQueue;

		end if;



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




	release_stuff;
	btex.cleanuptext;

	glfwdestroywindow(mainWin);
	glfwTerminate;


end reliquarium;

