/*
	Copyright (C) 2021-2025 Mark Tyler

	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 should have received a copy of the GNU General Public License
	along with this program in the file COPYING.
*/

#include "lanter_sdl.h"



Mainwindow::Mainwindow ( CommandLine & cline )
	:
	m_cline		( cline ),
	m_animate	( cline ),
	m_gin		( SDL_INIT_VIDEO ),
	m_window	( GIN_WINDOW_CANVAS_OPENGL )
{
	int width = m_cline.get_output_width();
	int height = m_cline.get_output_height();

	if (	width >= OUTPUT_WH_MIN		&& width <= OUTPUT_WH_MAX
		&& height >= OUTPUT_WH_MIN	&& height <= OUTPUT_WH_MAX
		)
	{
		m_store_wh = 0;
	}
	else
	{
		width = m_cline.get_memprefs().window_w;
		height = m_cline.get_memprefs().window_h;
		m_store_wh = 1;
	}

	// Antialiase edges if glEnable(GL_MULTISAMPLE) is used during render
	SDL_GL_SetAttribute ( SDL_GL_MULTISAMPLESAMPLES, 8 );

//	SDL_CaptureMouse ( SDL_TRUE );

	Uint32 const flags = m_store_wh ?
		(m_cline.get_memprefs().window_maximized ? SDL_WINDOW_MAXIMIZED : 0)
			| SDL_WINDOW_RESIZABLE : 0;

	char const * const animtsv = m_cline.get_anim_tsv ();

	if ( ! animtsv || 0 == animtsv[0] )
	{
		m_gin.set_fps_max ( 60 );
	}
	else
	{
		// Try to maximize the speed of frame creation
		setenv ( "vblank_mode", "0", 1 );
		m_gin.set_fps_max ( GIN_FPS_MAX );
	}

	m_window.init ( "mtLanter - Landscape Terraformer",
		m_cline.get_memprefs().window_x, m_cline.get_memprefs().window_y,
		width, height, flags );

	m_camera.set_view_geometry ( width, height, m_gl_range );

	m_gin.window_add ( m_window );

	m_window.set_size_min ( 100, 100 );

	m_window.add_signal ( GIN_SIGNAL_SDL_EVENT, [this]
	(mtGin::CB_Data const * const data)
	{
		auto const d = dynamic_cast<mtGin::CB_SDLEvent const *>(data);

		if ( ! d || ! d->sdl_event )
		{
			return 1;	// Not actioned
		}

		return window_event ( d->sdl_event );
	} );

	m_window.add_signal( GIN_SIGNAL_DESTROY_WINDOW, [this]
	(void *)
	{
		window_close ();
		return 0;
	} );

	m_window.m_render.set ( [this]()
	{
		window_render ();
	} );
	m_window.m_render.start ();

	m_gin.frame_callback.set ( [this]()
	{
		frame_cycle ();
	} );
	m_gin.frame_callback.start ();

	m_gin.set_sdl_event_callback ( []( SDL_Event const & event )
	{
		switch ( event.type )
		{
		case SDL_QUIT:
			// Ignore this here. window_event deals with closures.
			break;
		}
	} );

	m_map.set_seed ( (uint64_t)m_cline.get_map_seed () );
	if ( init_map () )
	{
		return;
	}

	m_scene.init_buffers ( m_map );

	move_camera_home (0);

	if ( 0 == m_animate.init () )
	{
		set_mode ( MODE_ANIMATION );
	}
	else
	{
		set_mode ( MODE_INTERACTIVE );
	}

	m_gin.main_loop ();
}

void Mainwindow::move_camera_home ( int const top )
{
	if ( top )
	{
		m_camera.set_position ( 0, 0, -15 );
		m_camera.set_angle ( 0, 0, 0 );
	}
	else
	{
		m_camera.set_position ( 1180, -1600, 910 );
//		m_camera.set_position ( -327, -207, 160 );

//		m_camera.set_position ( -1100, 1900, -700 );
//		m_camera.set_position ( 1100, 1900, -700 );
//		m_camera.set_position ( 1100, -1900, -700 );
//		m_camera.set_position ( -1100, -1900, -700 );

//		m_camera.set_angle ( 245, 0, 325 );
//		m_camera.set_angle ( 252, 0, 325 );

		m_camera.look_at ( {0,0,0} );
//		m_camera.look_at ( {0, -100, 1000} );

/*
		m_camera.look_at ( {LANDSCAPE_VIEW_RANGE * 0.5,
			LANDSCAPE_VIEW_RANGE * 0.5,
			LANDSCAPE_VIEW_RANGE * 0.7} );
*/
	}
}

int Mainwindow::init_map ()
{
	if ( m_map.create_landscape ( m_cline.get_map_size () ) )
	{
		return 1;
	}

	char	const * const	filename = m_cline.get_save_map ();

	if ( filename )
	{
		if ( m_map.save_tsv ( filename ) )
		{
			return 1;
		}
	}

	return 0;
}

void Mainwindow::window_render () const
{
	m_scene.render ( m_camera );

	SDL_GL_SwapWindow ( m_window.sdl() );

	m_animate.save_frame( m_anim_frame, [this](std::string const & filename)
		{
			return this->export_image ( filename );
		});
}

int Mainwindow::window_event ( SDL_Event const * const event )
{
	switch ( event->type )
	{
	case SDL_MOUSEBUTTONDOWN:
		mouse_click ( 0, 0 );
		break;

	case SDL_MOUSEMOTION:
		mouse_click ( event->motion.xrel, event->motion.yrel );
		break;

	case SDL_KEYDOWN:
		key_click ( event );
		break;

	case SDL_WINDOWEVENT:
		switch ( event->window.event )
		{
		case SDL_WINDOWEVENT_CLOSE:
			quit ();
			break;

		case SDL_WINDOWEVENT_RESIZED:
			m_camera.set_view_geometry (
				event->window.data1,	// width
				event->window.data2,	// height
				m_gl_range );
			break;
		}

		break;
	}

	return 0;		// Actioned
}

void Mainwindow::window_close ()
{
	int x, y, w, h;
	Uint32 flags;
	m_window.get_geometry ( x, y, w, h, flags );

	int const maximized = (flags & SDL_WINDOW_MAXIMIZED) ? 1 : 0;

	m_cline.userprefs().set ( PREFS_WINDOW_MAXIMIZED, maximized );

	if ( 0 == maximized )
	{
		m_cline.userprefs().set ( PREFS_WINDOW_X, x );
		m_cline.userprefs().set ( PREFS_WINDOW_Y, y );

		if ( m_store_wh )
		{
			m_cline.userprefs().set ( PREFS_WINDOW_W, w );
			m_cline.userprefs().set ( PREFS_WINDOW_H, h );
		}
	}
}

void Mainwindow::frame_cycle ()
{
	if ( MODE_ANIMATION == m_mode )
	{
		if ( ! m_quit )
		{
			m_anim_frame++;

			if ( m_animate.prepare_frame ( m_anim_frame ) )
			{
				quit();
			}
			else
			{
				m_camera.set_position(m_animate.get_position());
				m_camera.look_at ( m_animate.get_focus() );
			}
		}

		if ( m_quit )
		{
			m_gin.stop_main_loop ();
		}
	}
	else if ( MODE_INTERACTIVE == m_mode )
	{
		if ( m_quit )
		{
			m_gin.stop_main_loop ();
		}
	}
}

void Mainwindow::mouse_click (
	int	const	dx,
	int	const	dy
	)
{
	if ( MODE_ANIMATION == m_mode )
	{
		return;
	}

	SDL_Keymod const mod = SDL_GetModState ();
	int const scale =
		(mod & KMOD_LSHIFT) ? 10 :
		(mod & KMOD_RSHIFT) ? 10 : 1;

	int ax, ay;
	Uint32 const button = SDL_GetMouseState ( &ax, &ay );

	if ( button & SDL_BUTTON_LMASK )
	{
		double const nudge = m_camera.get_nudge() * scale;

		m_camera.move ( -dx * nudge, dy * nudge, 0 );
	}
	else if ( button & SDL_BUTTON_RMASK )
	{
		double const nudge = scale / 8.0 / 4.0;

		m_camera.turn ( dy * nudge, 0, -dx * nudge );
	}
	else
	{
		return;		// Nothing to do
	}
}

void Mainwindow::key_click ( SDL_Event const * const event )
{
	if ( MODE_ANIMATION == m_mode )
	{
		switch ( event->key.keysym.sym )
		{
		case SDLK_ESCAPE:
		case SDLK_DELETE:
			quit ();
			break;
		}
	}
	else if ( MODE_INTERACTIVE == m_mode )
	{
		int const scale =
			(event->key.keysym.mod & KMOD_LSHIFT) ? 10 :
			(event->key.keysym.mod & KMOD_RSHIFT) ? 10 : 1;

		int const ctrl =
			(event->key.keysym.mod & KMOD_LCTRL) ? 1 :
			(event->key.keysym.mod & KMOD_RCTRL) ? 1 : 0;

		if ( ctrl )
		{
			double const nudge = (double)scale / 8.0;

			switch ( event->key.keysym.sym )
			{
			case SDLK_UP:
				m_camera.turn ( nudge, 0, 0 );
				break;

			case SDLK_DOWN:
				m_camera.turn ( -nudge, 0, 0 );
				break;

			case SDLK_LEFT:
				m_camera.turn ( 0, 0, -nudge );
				break;

			case SDLK_RIGHT:
				m_camera.turn ( 0, 0, nudge );
				break;

			case SDLK_HOME:
				move_camera_home ( ctrl );
				return;

			case SDLK_ESCAPE:
			case SDLK_q:
				quit ();
				return;
			}

			return;
		}

		double const nudge = m_camera.get_nudge() * scale;

		switch ( event->key.keysym.sym )
		{
		case SDLK_PAGEUP:
			m_camera.move ( 0, 0, nudge );
			break;

		case SDLK_PAGEDOWN:
			m_camera.move ( 0, 0, -nudge );
			break;

		case SDLK_UP:
			m_camera.move ( 0, nudge, 0 );
			break;

		case SDLK_DOWN:
			m_camera.move ( 0, -nudge, 0 );
			break;

		case SDLK_LEFT:
			m_camera.move ( -nudge, 0, 0 );
			break;

		case SDLK_RIGHT:
			m_camera.move ( nudge, 0, 0 );
			break;

		case SDLK_HOME:
			move_camera_home ( ctrl );
			return;

		case SDLK_END:
			m_camera.turn_around ();
			return;

		case SDLK_ESCAPE:
		case SDLK_q:
			quit ();
			return;

		case SDLK_F1:
			printf ( "x=%.15g y=%.15g z=%.15g "
				"xrot=%.15g yrot=%.15g zrot=%.15g\n",
				m_camera.get_x(), m_camera.get_y(),
				m_camera.get_z(),
				m_camera.get_rot_x(), m_camera.get_rot_y(),
				m_camera.get_rot_z() );
			return;

		case SDLK_F5:
			m_scene.next_mode ();
			break;

		case SDLK_s:
			output_image_file ();
			break;
		}
	}
}

void Mainwindow::set_mode ( int const mode )
{
	m_mode = mode;
}

void Mainwindow::quit ()
{
	m_quit = 1;
}

void Mainwindow::output_image_file ()
{
	char buf[256];
	snprintf ( buf, sizeof(buf), BIN_NAME "_%.15g_%.15g_%.15g.png",
		m_camera.get_x(),
		m_camera.get_y(),
		m_camera.get_z() );

	std::string filename ( mtkit_file_home () );

	filename += MTKIT_DIR_SEP;
	filename += buf;

	export_image ( filename );
}

int Mainwindow::export_image ( std::string const & filename ) const
{
	if ( m_cline.get_verbose() )
	{
		std::cout << filename << "\n";
	}

	mtPixy::Pixmap pm ( m_window.dump_to_pixmap () );

	if ( pixy_pixmap_save_png ( pm.get(), filename.c_str(), 5 ) )
	{
		std::cerr << "export_image: Unable to save pixmap\n";
		return 1;
	}

	return 0;
}
