/*
 * Copyright (C) 2002-2004, 2006-2008 by the Widelands Development 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 the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "interactive_base.h"

#include "checkstep.h"
#include "cmd_queue.h"
#include "constants.h"
#include "font_handler.h"
#include "game.h"
#include "gamecontroller.h"
#include "graphic.h"
#include "immovable.h"
#include "interactive_player.h"
#include "maptriangleregion.h"
#include "mapviewpixelconstants.h"
#include "mapviewpixelfunctions.h"
#include "minimap.h"
#include "mapviewpixelconstants.h"
#include "mapviewpixelfunctions.h"
#include "overlay_manager.h"
#include "player.h"
#include "profile.h"
#include "transport.h"
#include "upcast.h"
#include "wlapplication.h"

using Widelands::Area;
using Widelands::CoordPath;
using Widelands::Coords;
using Widelands::Editor_Game_Base;
using Widelands::Game;
using Widelands::Map;
using Widelands::Map_Object;
using Widelands::TCoords;

Interactive_Base::Interactive_Base(Editor_Game_Base & the_egbase)
:
Map_View(0, 0, 0, get_xres(), get_yres(), *this),
m_mm                          (0),
m_egbase                      (the_egbase),
#ifdef DEBUG //  not in releases
m_display_flags               (dfDebug),
#else
m_display_flags               (0),
#endif
m_lastframe                   (WLApplication::get()->get_time()),
m_frametime                   (0),
m_avg_usframetime             (0),
m_jobid                       (Overlay_Manager::Job_Id::Null()),
m_road_buildhelp_overlay_jobid(Overlay_Manager::Job_Id::Null()),
m_buildroad                   (false),
m_road_build_player           (0),
m_toolbar                     (this, 0, 0, UI::Box::Horizontal),
m_label_speed                 (this, get_w(), 0, std::string(), Align_TopRight)
{
	warpview.set(this, &Interactive_Player::mainview_move);

	{
		Section & s = *g_options.pull_section("global");
		set_border_snap_distance(s.get_int("border_snap_distance", 0));
		set_panel_snap_distance (s.get_int("panel_snap_distance", 10));
		set_snap_windows_only_when_overlapping
			(s.get_bool("snap_windows_only_when_overlapping", false));
		set_dock_windows_to_edges(s.get_bool("dock_windows_to_edges", false));

		// Switch to the new graphics system now, if necessary
		WLApplication::get()->init_graphics
			(get_xres(), get_yres(),
			 s.get_int("depth", 16), s.get_bool("fullscreen", false));

		m_show_workarea_preview = s.get_bool("workareapreview", false);
	}

	//  Having this in the initializer list (before Sys_InitGraphics) will given
	//  funny results.
	m_sel.pic = g_gr->get_picture(PicMod_Game, "pics/fsel.png");

	m_label_speed.set_visible(false);
}

/*
===============
Interactive_Base::~Interactive_Base

cleanups
===============
*/
Interactive_Base::~Interactive_Base()
{
	if (m_buildroad)
		abort_build_road();
}


void Interactive_Base::set_sel_pos(Widelands::Node_and_Triangle<> const center)
{
	Map & map = egbase().map();
	Overlay_Manager & overlay_manager = map.overlay_manager();

	// Remove old sel pointer
	if (m_sel.jobid) overlay_manager.remove_overlay(m_sel.jobid);
	const Overlay_Manager::Job_Id jobid =
		m_sel.jobid = overlay_manager.get_a_job_id();

	m_sel.pos = center;

	//  register sel overlay position
	if (m_sel.triangles) {
		assert(center.triangle.t == TCoords<>::D or center.triangle.t == TCoords<>::R);
		Widelands::MapTriangleRegion<> mr
			(map, Area<TCoords<> >(center.triangle, m_sel.radius));
		do
			overlay_manager.register_overlay
				(mr.location(), m_sel.pic, 7, Point::invalid(), jobid);
		while (mr.advance(map));
	} else {
		Widelands::MapRegion<> mr(map, Area<>(center.node, m_sel.radius));
		do
			overlay_manager.register_overlay
				(mr.location(), m_sel.pic, 7, Point::invalid(), jobid);
		while (mr.advance(map));
	}

}

/*
 * Set the current sel selection radius.
 */
void Interactive_Base::set_sel_radius(const uint32_t n) {
	if (n != m_sel.radius) {
		m_sel.radius = n;
		set_sel_pos(get_sel_pos()); //  redraw
	}
}

/*
 * Set/Unset sel picture
 */
void Interactive_Base::set_sel_picture(const char * const file) {
	m_sel.pic = g_gr->get_picture(PicMod_Game, file);
	set_sel_pos(get_sel_pos()); //  redraw
}
void Interactive_Base::unset_sel_picture() {
	set_sel_picture("pics/fsel.png");
}


/*
===============
Interactive_Base::get_xres [static]
Interactive_Base::get_yres [static]

Retrieve in-game resolution from g_options.
===============
*/
int32_t Interactive_Base::get_xres()
{
	return g_options.pull_section("global")->get_int("xres", 640);
}

int32_t Interactive_Base::get_yres()
{
	return g_options.pull_section("global")->get_int("yres", 480);
}


/**
 * Called by \ref Game::postload at the end of the game loading
 * sequence.
 *
 * Default implementation does nothing.
 */
void Interactive_Base::postload() {}

static std::string speedString(uint32_t const speed)
{
	if (speed) {
		char buffer[32];
		snprintf(buffer, sizeof(buffer), _("%u.%ux"), speed/1000, (speed/100)%10);
		return buffer;
	}
	return _("PAUSE");
}

/**
 * Bring the label that display in-game speed uptodate.
 */
void Interactive_Base::update_speedlabel()
{
	if (get_display_flag(dfSpeed)) {
		upcast(Game, g, &m_egbase);
		if (g && g->gameController()) {
			uint32_t real = g->gameController()->realSpeed();
			uint32_t desired = g->gameController()->desiredSpeed();
			if (real == desired)
				m_label_speed.set_text
					(real == 1000 ? std::string() : speedString(real));
			else {
				char buffer[128];
				snprintf
					(buffer, sizeof(buffer),
					 _("%s (%s)"),
					 speedString(real).c_str(), speedString(desired).c_str());
				m_label_speed.set_text(buffer);
			}
		} else
			m_label_speed.set_text(_("NO GAME CONTROLLER"));
		m_label_speed.set_visible(true);
	} else
		m_label_speed.set_visible(false);
}


/*
===============
Interactive_Base::think

Called once per frame by the UI code
===============
*/
void Interactive_Base::think()
{
	// Timing
	uint32_t curframe = WLApplication::get()->get_time();

	m_frametime = curframe - m_lastframe;
	m_avg_usframetime = ((m_avg_usframetime * 15) + (m_frametime * 1000)) / 16;
	m_lastframe = curframe;

	// If one of the arrow keys is pressed, scroll here
	const uint32_t scrollval = 10;

	if (keyboard_free()) {
		if (get_key_state(SDLK_UP))
			set_rel_viewpoint(Point(0, -scrollval));
		if (get_key_state(SDLK_DOWN))
			set_rel_viewpoint(Point(0,  scrollval));
		if (get_key_state(SDLK_LEFT))
			set_rel_viewpoint(Point(-scrollval, 0));
		if (get_key_state(SDLK_RIGHT))
			set_rel_viewpoint(Point (scrollval, 0));
	}

	egbase().think(); // Call game logic here. The game advances.

	//  Update everything so and so many milliseconds, to make sure that the
	//  whole screen is synced (another user may have done something, and the
	//  screen was not redrawn).
	if (curframe & 1023) // % 1024
		need_complete_redraw();

	//  The entire screen needs to be redrawn (unit movement, tile animation,
	//  etc...)
	g_gr->update_fullscreen();

	update_speedlabel();

	UI::Panel::think();
}


/*
===============
Interactive_Base::draw_overlay

Draw debug overlay when appropriate.
===============
*/
void Interactive_Base::draw_overlay(RenderTarget & dst) {
	if
		(get_display_flag(dfDebug)
		 or
		 not dynamic_cast<const Game *>(&egbase()))
	{
		//  show sel coordinates
		char buf[100];

		snprintf(buf, sizeof(buf), "%3i %3i", m_sel.pos.node.x, m_sel.pos.node.y);
		g_fh->draw_string
			(dst, UI_FONT_BIG, UI_FONT_BIG_CLR, Point(5, 5), buf, Align_Left);
		assert(m_sel.pos.triangle.t < 2);
		const char * const triangle_string[] = {"down", "right"};
		snprintf
			(buf, sizeof(buf),
			 "%3i %3i %s",
			 m_sel.pos.triangle.x, m_sel.pos.triangle.y,
			 triangle_string[m_sel.pos.triangle.t]);
		g_fh->draw_string
			(dst, UI_FONT_BIG, UI_FONT_BIG_CLR, Point(5, 25), buf, Align_Left);
	}

	if (get_display_flag(dfDebug)) {
		//  show FPS
		char buffer[100];
		snprintf
			(buffer, sizeof(buffer),
			 "%5.1f fps (avg: %5.1f fps)",
			 1000.0 / m_frametime, 1000.0 / (m_avg_usframetime / 1000));
		g_fh->draw_string
			(dst, UI_FONT_BIG, UI_FONT_BIG_CLR, Point(85, 5), buffer, Align_Left);
	}
}


/** Interactive_Base::mainview_move(int32_t x, int32_t y)
 *
 * Signal handler for the main view's warpview updates the mini map's
 * viewpos marker position
 */
void Interactive_Base::mainview_move(int32_t x, int32_t y)
{
	if (m_minimap.window) {
		const Map & map = egbase().map();
		const int32_t maxx = MapviewPixelFunctions::get_map_end_screen_x(map);
		const int32_t maxy = MapviewPixelFunctions::get_map_end_screen_y(map);

		x += get_w() >> 1;
		if (x >= maxx) x -= maxx;
		y += get_h() >> 1;
		if (y >= maxy) y -= maxy;


		m_mm->set_view_pos(x, y);
	}
}


/*
===============
Interactive_Base::minimap_warp

Called whenever the player clicks on a location on the minimap.
Warps the main mapview position to the clicked location.
===============
*/
void Interactive_Base::minimap_warp(int32_t x, int32_t y)
{
	x -= get_w() >> 1;
	y -= get_h() >> 1;
	const Map & map = egbase().map();
	if (x < 0) x += map.get_width () * TRIANGLE_WIDTH;
	if (y < 0) y += map.get_height() * TRIANGLE_HEIGHT;
	set_viewpoint(Point(x, y));
}


/*
===============
Interactive_Base::move_view_to

Move the mainview to the given position (in field coordinates)
===============
*/
void Interactive_Base::move_view_to(const Coords c)
{
	int32_t x = c.x * TRIANGLE_WIDTH;
	int32_t y = c.y * TRIANGLE_HEIGHT;

	if (m_minimap.window) m_mm->set_view_pos(x, y);

	minimap_warp(x, y);
}


/*
===============
Interactive_Base::move_view_to_point

Center the mainview on the given position (in pixels)
===============
*/
void Interactive_Base::move_view_to_point(Point pos)
{
	if (m_minimap.window) m_mm->set_view_pos(pos.x, pos.y);

	set_viewpoint(pos - Point(get_w() / 2, get_h() / 2));
}


/*
===========
Interactive_Base::toggle_minimap()

Open the minimap or close it if it's open
===========
*/
void Interactive_Base::toggle_minimap() {
	if (m_minimap.window) {
		delete m_minimap.window;
	}
	else {
		m_mm = new MiniMap(*this, &m_minimap);
		m_mm->warpview.set(this, &Interactive_Base::minimap_warp);

		// make sure the viewpos marker is at the right pos to start with
		const Point p = get_viewpoint();

		mainview_move(p.x, p.y);
	}
}


/**
 * Hide the minimap if it is currently shown; otherwise, do nothing.
 */
void Interactive_Base::hide_minimap()
{
	if (m_minimap.window)
		delete m_minimap.window;
}


/*
===============
Interactive_Base::get_display_flags

Return display flags (dfXXX) that modify the view of the map.
===============
*/
uint32_t Interactive_Base::get_display_flags()
{
	return m_display_flags;
}


/*
===============
Interactive_Base::set_display_flags

Change the display flags that modify the view of the map.
===============
*/
void Interactive_Base::set_display_flags(uint32_t flags)
{
	m_display_flags = flags;

	update_speedlabel();
}


/*
===============
Interactive_Base::get_display_flag
Interactive_Base::set_display_flag

Get and set one individual flag of the display flags.
===============
*/
bool Interactive_Base::get_display_flag(uint32_t flag)
{
	return m_display_flags & flag;
}

void Interactive_Base::set_display_flag(uint32_t flag, bool on)
{
	m_display_flags &= ~flag;

	if (on)
		m_display_flags |= flag;

	update_speedlabel();
}

/*
===============
Interactive_Base::start_build_road

Begin building a road
===============
*/
void Interactive_Base::start_build_road
	(Coords _start, Widelands::Player_Number const player)
{
	// create an empty path
	assert(not m_buildroad);
	m_buildroad = new CoordPath(_start);

	m_road_build_player = player;

	//  If we are a game, we obviously build for the Interactive Player.
	assert
		(not dynamic_cast<const Game *>(&m_egbase)
		 or
		 player ==
		 static_cast<const Interactive_Player *>(this)->
		 get_player_number());

	roadb_add_overlay();
	need_complete_redraw();
}


/*
===============
Interactive_Base::abort_build_road

Stop building the road
===============
*/
void Interactive_Base::abort_build_road()
{
	assert(m_buildroad);

	roadb_remove_overlay();
	need_complete_redraw();

	m_road_build_player = 0;

	delete m_buildroad;
	m_buildroad = 0;
}


/*
===============
Interactive_Base::finish_build_road

Finally build the road
===============
*/
void Interactive_Base::finish_build_road()
{
	assert(m_buildroad);

	roadb_remove_overlay();
	need_complete_redraw();

	if (m_buildroad->get_nsteps()) {
		// Build the path as requested
		if (upcast(Game, game, &egbase())) {
			game->send_player_build_road
				(m_road_build_player, *new Widelands::Path(*m_buildroad));
			if (get_key_state(SDLK_LCTRL) || get_key_state(SDLK_RCTRL)) { //  place flags
				Map const & map = game->map();
				std::vector<Coords>         const &       c_vector =
					m_buildroad->get_coords();
				std::vector<Coords>::const_iterator const first    =
					c_vector.begin() + 2;
				std::vector<Coords>::const_iterator const last     =
					c_vector.end  () - 2;
				if (get_key_state(SDLK_LSHIFT) || get_key_state(SDLK_RSHIFT)) { //  start to end
					for
						(std::vector<Coords>::const_iterator it = first;
						 it <= last;
						 ++it)
						game->send_player_build_flag
							(m_road_build_player, map.get_fcoords(*it));
				} else            { //  end to start
					for
						(std::vector<Coords>::const_iterator it = last;
						 first <= it;
						 --it)
						game->send_player_build_flag
							(m_road_build_player, map.get_fcoords(*it));
				}
			}
		}
	}

	delete m_buildroad;
	m_buildroad = 0;
}


/*
===============
Interactive_Base::append_build_road

If field is on the path, remove tail of path.
Otherwise append if possible or return false.
===============
*/
bool Interactive_Base::append_build_road(Coords const field) {
	assert(m_buildroad);

	Map & map = egbase().map();
	Widelands::Player const & player = egbase().player(m_road_build_player);

	{ //  find a path to the clicked-on node
		Widelands::Path path;
		Widelands::CheckStepRoad cstep(player, Widelands::MOVECAPS_WALK);
		if
			(map.findpath
			 	(m_buildroad->get_end(), field, 0, path, cstep, Map::fpBidiCost)
			 <
			 0)
			return false; //  could not find a path
		m_buildroad->append(map, path);
	}

	{
		//  Fix the road by finding an optimal path through the set of nodes
		//  currently used by the road. This will not claim any new nodes, so it
		//  is guaranteed to not hinder building placement.
		Widelands::Path path;
		{
			Widelands::CheckStepLimited cstep;
			{
				std::vector<Coords> const & road_cp = m_buildroad->get_coords();
				std::vector<Coords>::const_iterator const road_cp_end =
					road_cp.end();
				for
					(std::vector<Coords>::const_iterator it = road_cp.begin();
					 it != road_cp_end;
					 ++it)
					cstep.add_allowed_location(*it);
			}
			map.findpath
				(m_buildroad->get_start(), field, 0, path, cstep, Map::fpBidiCost);
		}
		m_buildroad->truncate(0);
		m_buildroad->append(map, path);
	}

	roadb_remove_overlay();
	roadb_add_overlay();
	need_complete_redraw();

	return true;
}

/*
===============
Return the current road-building startpoint
===============
*/
Coords Interactive_Base::get_build_road_start() const throw () {
	assert(m_buildroad);

	return m_buildroad->get_start();
}

/*
===============
Return the current road-building endpoint
===============
*/
Coords Interactive_Base::get_build_road_end() const throw () {
	assert(m_buildroad);

	return m_buildroad->get_end();
}

/*
===============
Return the direction of the last step
===============
*/
Widelands::Direction Interactive_Base::get_build_road_end_dir() const throw ()
{
	assert(m_buildroad);

	if (!m_buildroad->get_nsteps())
		return 0;

	return (*m_buildroad)[m_buildroad->get_nsteps() - 1];
}

/*
===============
Add road building data to the road overlay
===============
*/
void Interactive_Base::roadb_add_overlay()
{
	assert(m_buildroad);

	//log("Add overlay\n");

	Map & map = egbase().map();
	Overlay_Manager & overlay_manager = map.overlay_manager();

	// preview of the road
	assert(not m_jobid);
	m_jobid = overlay_manager.get_a_job_id();
	const CoordPath::Step_Vector::size_type nr_steps = m_buildroad->get_nsteps();
	for (CoordPath::Step_Vector::size_type idx = 0; idx < nr_steps; ++idx) {
		Widelands::Direction dir = (*m_buildroad)[idx];
		Coords c = m_buildroad->get_coords()[idx];

		if (dir < Map_Object::WALK_E || dir > Map_Object::WALK_SW) {
			map.get_neighbour(c, dir, &c);
			dir = Widelands::get_reverse_dir(dir);
		}

		int32_t shift = 2*(dir - Map_Object::WALK_E);

		uint8_t set_to = overlay_manager.get_road_overlay(c);
		set_to|=  Widelands::Road_Normal << shift;
		overlay_manager.register_road_overlay(c, set_to, m_jobid);
	}

	// build hints
	Widelands::FCoords endpos = map.get_fcoords(m_buildroad->get_end());

	assert(not m_road_buildhelp_overlay_jobid);
	m_road_buildhelp_overlay_jobid = overlay_manager.get_a_job_id();
	for (int32_t dir = 1; dir <= 6; ++dir) {
		Widelands::FCoords neighb;
		int32_t caps;

		map.get_neighbour(endpos, dir, &neighb);
		caps = egbase().player(m_road_build_player).get_buildcaps(neighb);

		if (!(caps & Widelands::MOVECAPS_WALK))
			continue; // need to be able to walk there

		//  can't build on robusts
		Widelands::BaseImmovable * const imm = map.get_immovable(neighb);
		if (imm && imm->get_size() >= Widelands::BaseImmovable::SMALL) {
			if
				(not
				 (dynamic_cast<const Widelands::Flag *>(imm)
				  or
				  (dynamic_cast<const Widelands::Road *>(imm)
				   and
				   caps & Widelands::BUILDCAPS_FLAG)))
				continue;
		}

		if (m_buildroad->get_index(neighb) >= 0)
			continue; // the road can't cross itself

		int32_t slope = abs(endpos.field->get_height() - neighb.field->get_height());
		int32_t icon;

		if (slope < 2)
			icon = 1;
		else if (slope < 4)
			icon = 2;
		else
			icon = 3;

		char const * name;
		switch (icon) {
		case 1: name = "pics/roadb_green.png";  break;
		case 2: name = "pics/roadb_yellow.png"; break;
		case 3: name = "pics/roadb_red.png";    break;
		default:
			assert(false);
		}

		egbase().map().overlay_manager().register_overlay
			(neighb,
			 g_gr->get_picture(PicMod_Game, name),
			 7,
			 Point::invalid(),
			 m_road_buildhelp_overlay_jobid);
	}
}

/*
===============
Interactive_Base::roadb_remove_overlay

Remove road building data from road overlay
===============
*/
void Interactive_Base::roadb_remove_overlay()
{
	assert(m_buildroad);

	//log("Remove overlay\n");

	//  preview of the road
	Overlay_Manager & overlay_manager = egbase().map().overlay_manager();
	if (m_jobid) overlay_manager.remove_road_overlay(m_jobid);
	m_jobid = Overlay_Manager::Job_Id::Null();

	// build hints
	if (m_road_buildhelp_overlay_jobid)
		overlay_manager.remove_overlay(m_road_buildhelp_overlay_jobid);
	m_road_buildhelp_overlay_jobid = Overlay_Manager::Job_Id::Null();
}


bool Interactive_Base::handle_key(bool down, SDL_keysym code)
{
	switch (code.sym) {
	case SDLK_PAGEUP:
		if (!get_display_flag(dfSpeed))
			break;

		if (down) {
			if (upcast(Game, g, &m_egbase)) {
				if (GameController* ctrl = g->gameController()) {
					int32_t speed = ctrl->desiredSpeed();
					ctrl->setDesiredSpeed(speed+1000);
				}
			}
		}
		return true;

	case SDLK_PAGEDOWN:
		if (!get_display_flag(dfSpeed))
			break;

		if (down) {
			if (upcast(Widelands::Game, g, &m_egbase)) {
				if (GameController* ctrl = g->gameController()) {
					int32_t speed = ctrl->desiredSpeed();
					ctrl->setDesiredSpeed(std::max(0, speed - 1000));
				}
			}
		}
		return true;

	default:
		break;
	}

	return Map_View::handle_key(down, code);
}

