// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
// Copyright (C) 2010-2020  Winch Gate Property Limited
//
// This source file has been modified by the following contributors:
// Copyright (C) 2013  Laszlo KIS-ADAM (dfighter) <dfighter1985@gmail.com>
// Copyright (C) 2013-2020  Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.




/////////////
// INCLUDE //
/////////////
#include "stdpch.h"
// Misc.
#include "nel/misc/debug.h"
#include "nel/misc/async_file_manager.h"
#include "nel/misc/system_utils.h"
#include "nel/misc/streamed_package_manager.h"
#include "nel/web/http_package_provider.h"
// 3D Interface.
#include "nel/3d/bloom_effect.h"
#include "nel/3d/fxaa.h"
#include "nel/3d/fasthls_modifier.h"
#include "nel/3d/particle_system_manager.h"
#include "nel/3d/particle_system.h"
#include "nel/3d/particle_system_shape.h"
#include "nel/3d/u_driver.h"
#include "nel/3d/u_scene.h"
#include "nel/3d/u_visual_collision_manager.h"
#include "nel/3d/u_shape_bank.h"
#include "nel/3d/stereo_hmd.h"
#include "nel/3d/async_file_manager_3d.h"
// Client
#include "global.h"
#include "release.h"
#include "actions.h"
#include "ig_client.h"
#include "entities.h"
#include "net_manager.h"
#include "pacs_client.h"
#include "world_database_manager.h"
#include "continent_manager.h"
#include "sound_manager.h"
#include "weather.h"
#include "weather_manager_client.h"
#include "prim_file.h"
#include "interface_v3/input_handler_manager.h"
#include "interface_v3/interface_manager.h"
#include "interface_v3/people_interraction.h"
#include "connection.h"
#include "commands.h"
#include "timed_fx_manager.h"
#include "interface_v3/sphrase_manager.h"
#include "interface_v3/chat_text_manager.h"
#include "interface_v3/dbctrl_sheet.h"
#include "projectile_manager.h"
#include "init_main_loop.h"
#include "fx_manager.h"
#include "animation_fx_misc.h"
#include "fx_manager.h"
#include "micro_life_manager.h"
#include "attack_list.h"
#include "auto_anim.h"
#include "string_manager_client.h"
#include "precipitation_clip_grid.h"
#include "interface_v3/music_player.h"
#include "login.h"
#include "actions_client.h"
#include "login_progress_post_thread.h"
//
#include "r2/editor.h"
#include "nel/misc/big_file.h"
#include "nel/net/module_manager.h"
#include "game_share/fame.h"
#include "interface_v3/sbrick_manager.h"
#include "interface_v3/skill_manager.h"
#include "sheet_manager.h"
#include "interface_v3/macrocmd_manager.h"
#include "game_share/scenario_entry_points.h"
#include "interface_v3/bar_manager.h"
#include "landscape_poly_drawer.h"
#include "game_share/visual_slot_manager.h"
#include "door_manager.h"
#include "interface_v3/encyclopedia_manager.h"
#include "faction_war_manager.h"
#include "interface_v3/interface_ddx.h"
#include "bg_downloader_access.h"
#include "nel/gui/lua_manager.h"
#include "item_group_manager.h"
#include "nel/gui/http_cache.h"
#include "nel/gui/http_hsts.h"

///////////
// USING //
///////////
using namespace NL3D;
using namespace NLMISC;

#ifdef DEBUG_NEW
#define new DEBUG_NEW
#endif

////////////
// EXTERN //
////////////
extern UDriver					*Driver;
extern UScene					*Scene;
extern UCloudScape				*CloudScape;
extern UVisualCollisionManager	*CollisionManager;
extern CEventsListener			EventsListener;				// Inputs Manager
extern ULandscape				*Landscape;
extern UCamera					MainCam;
extern UScene					*SceneRoot;
extern UScene					*SkyScene;
extern UInstanceGroup			*BackgroundIG;
extern bool						LastScreenSaverEnabled;
extern bool						IsInRingSession;
extern bool noUserChar;
extern bool userChar;
extern bool serverReceivedReady;
extern bool CharNameValidArrived;

extern void releaseContextualCursor();
extern void selectTipsOfTheDay (uint tips);

///////////////
// FUNCTIONS //
///////////////

// ***************************************************************************
// Saving ingame resolution when in windowed mode
void saveIngameResolution()
{
	if (ClientCfg.Windowed)
	{
		uint32 width, height;
		Driver->getWindowSize(width, height);
		ClientCfg.writeInt("Width", std::max((sint)width, 800));
		ClientCfg.writeInt("Height", std::max((sint)height, 600));
	}
}

// ***************************************************************************
// 3D element release, called from both releaseMainLoopReselect() and releaseMainLoop()
static void releaseMainLoopScenes()
{
	if(!Driver)
		return;

	// Delete the main scene
	if(Scene)
	{
		// Release water envmap
#ifdef USE_WATER_ENV_MAP
		Driver->deleteWaterEnvMap(WaterEnvMap);
#endif

		// Release FX manager
		CTimedFXManager::getInstance().reset();

		WeatherManager.release();

		// Release the landscape. NB: all pending async loading zones are deleted here
		if (Landscape)
		{
			Scene->deleteLandscape (Landscape);
			Landscape = NULL;
		}

		// Release the collision manager
		Scene->deleteVisualCollisionManager(CollisionManager);
		CollisionManager = NULL;

		// release cloud scape
		if (CloudScape)
		{
			Scene->deleteCloudScape(CloudScape);
			CloudScape = NULL;
		}

		// remove the scene from the sound lib
		if (SoundMngr != NULL)
			SoundMngr->getMixer()->initClusteredSound((UScene*)NULL, 0.01f, 100.0f, 1.0f);

		// Stop any async loading. Actually all should have been stop before
		CAsyncFileManager::terminate();

		// Release the scene.
		Driver->deleteScene(Scene);
		Scene = NULL;
		MainCam = NULL;

		// remove scene from bloom
		CBloomEffect::getInstance().setScene(NULL);
	}

	// Delete the scene with the Big Root
	if (SceneRoot)
	{
		if (BackgroundIG)
		{
			BackgroundIG->removeFromScene (*SceneRoot);
			SceneRoot->deleteInstanceGroup (BackgroundIG);
			BackgroundIG = NULL;

		}
		Driver->deleteScene(SceneRoot);
		SceneRoot = NULL;
	}
}

volatile bool TempResetShapeBankOnRetCharSelect = false;

// ***************************************************************************
// Release all the memory before come back to out game (real thing!)
void	releaseMainLoopReselect()
{
	ProgressBar.release();

	saveIngameResolution();

	CInterfaceManager	*pIM= CInterfaceManager::getInstance();

	// save keys loaded and interface cfg (not done in releaseMainLoop() because done at end of mainLoop()...)
	pIM->uninitInGame0();
	CItemGroupManager::getInstance()->uninit();

	// alredy called from farTPMainLoop()
	// --R2::getEditor().autoConfigRelease(IsInRingSession);

	// stop any user played music
	MusicPlayer.stop();

	// only really needed at exit
	// --STRING_MANAGER::CStringManagerClient::instance()->flushStringCache();

	// Remove all entities.
	if (Driver)
	{
		nldebug("RCSR1: %u textures", Driver->getTotalAsyncTextureSizeAsked());
	}

	EntitiesMngr.release();

	if (Driver)
	{
		nldebug("RCSR2: %u textures", Driver->getTotalAsyncTextureSizeAsked());
	}

	// Reset Fx manager (must be done after EntitiesMngr.release()) Important because may still point to 3D elements
	FXMngr.reset();

	// Interface release
	CInterfaceManager::getInstance()->uninitInGame1();

	// Remove key / action / some interface stuff (done through CInterfaceManager::destroy() in releaseMainLoop I think)
	ActionsContext.removeAllCombos();
	EditActions.releaseAllKeyNoRunning();
	Actions.releaseAllKeyNoRunning();
	CWidgetManager::getInstance()->getParser()->removeAllTemplates();
	CWidgetManager::getInstance()->setCaptureKeyboard(NULL);
	CWidgetManager::getInstance()->setCapturePointerLeft(NULL);
	CWidgetManager::getInstance()->setCapturePointerRight(NULL);

	// Yoyo: Don't release attack list manager, because I think it only owns static data (and 3D data created from Driver, not Scenes)
	// Note that in initMainLoop(), CAttackListManager::getInstance().init() will do nothing (since already created and not released here)
	// --CAttackListManager::getInstance().release();

	// Still release the AnimFXMisc, because btw will still be released at AnimFXMisc.init() time init initMainLoop()
	AnimFXMisc.release();

	// Don't care!
	// --selectTipsOfTheDay (rand());

	// Reset the continent manager. NB: btw will be done at initMainLoop() (from preloadSheets)
	// and important to do before release of timedfx manager
	ContinentMngr.reset();

	// Remove all projectile
	CProjectileManager::getInstance().reset();

	// Remove micro-life (btw done at each CContinent::unselect())
	CMicroLifeManager::getInstance().release();

	// not really needed (static GuildMat Material created from Driver)
	// --CDBCtrlSheet::release ();

	// Release the Entities Animation Manager (Yoyo: fuckingly important because keep a pointer
	// on a _PlayListManager that is created from Scene)
	if (Driver)
	{
		nldebug("RCSR3: %u textures", Driver->getTotalAsyncTextureSizeAsked());
	}

	CEntityAnimationManager::delInstance();
	EAM= NULL;

	if (Driver)
	{
		nldebug("RCSR4: %u textures", Driver->getTotalAsyncTextureSizeAsked());
	}

	// Not necessary I think because owns only static data (string + function ptrs)
	// --releaseContextualCursor();

	// Release 3D components
	if(Driver)
	{
		// Release Scene, SceneRoot and their elements
		// Yoyo: important to do it else leak because recreated/reloaded in initMainLoop() on same pointer (=> old one not released)!!!
		releaseMainLoopScenes();

		// release the auto animation (btw done in initMainLoop()...)
		releaseAutoAnimation();

		// Don't Release the shape bank! => optimisation: preloading of character/objects shapes will not be redone!
		if (ClientCfg.ResetShapeBankOnRetCharSelect)
		{
			Driver->getShapeBank()->reset();
			nldebug("RCSR5: %u textures", Driver->getTotalAsyncTextureSizeAsked());
		}

	}

	// Release FX manager
	CTimedFXManager::getInstance().reset();

	// Don't Purge memory because PointLight may still be present in other Scenes (sky scene...) for instance
	// --UDriver::purgeMemory();

	// String manager: remove all waiting callbacks and removers
	// (if some interface stuff has not received its string yet, its remover will get useless)
	STRING_MANAGER::CStringManagerClient::release( false );

	// release titles info
	CSkillManager::getInstance()->uninitInGame();

	// Ugly globals
	userChar = false;
	noUserChar = false;
	serverReceivedReady = false;
	CharNameValidArrived = false;
	UserCharPosReceived = false;
	SabrinaPhraseBookLoaded = false;

	// Unlink the net manager
	NetMngr.setDataBase (NULL);

	// reset the client database and clear all observers. must do this while we are disconnected!
	// First remove the auto copy observers
	pIM->releaseServerToLocalAutoCopyObservers();
	// Then remove the SERVER and LOCAL database (NB: "UI" node was removed by uninitIngame1())
	ICDBNode::CTextId serverId("SERVER"), localId("LOCAL");
	NLGUI::CDBManager::getInstance()->getDB()->removeNode(serverId);
	NLGUI::CDBManager::getInstance()->getDB()->removeNode(localId);
	nlassert(IngameDbMngr.getNodePtr()==NULL);	// actually it is the "SERVER" node kept by CRefPtr => should be NULL
	IngameDbMngr.clear();						// still important for CDBBranch statics data release
	// NB: "SERVER" and "LOCAL" node will be recreated by initMainLoop


	// Don't destroy the whole interface manager. Hope it is not important....
	// --CInterfaceManager::destroy ();

	// Leave Connection stuff to farTPMainLoop
	// --NetMngr.....
}


// ***************************************************************************
// Release all the memory before come back to out game.
// Yoyo: actually, because of change by AJM, this method is called only when the user quit the app.
// see releaseMainLoopReselect() for the actual method called for reselection
// Btw the 2 methods should have strong similarities
void releaseMainLoop(bool closeConnection)
{
	ProgressBar.release();

	// Release R2 editor if applicable
	R2::getEditor().autoConfigRelease(IsInRingSession);

	// stop any user played music
	MusicPlayer.stop();

	// flush the server string cache
	STRING_MANAGER::CStringManagerClient::instance()->flushStringCache();

	// Remove all entities.
	EntitiesMngr.release();

	// Reset Fx manager (must be done after EntitiesMngr.release())
	FXMngr.reset();

	// Interface release
	CInterfaceManager::getInstance()->uninitInGame1();

	// release attack list manager
	CAttackListManager::getInstance().release();

	AnimFXMisc.release();

	// Change the tips
	selectTipsOfTheDay (rand());

	// Reset the continent manager
	ContinentMngr.reset();

	// Remove all projectile
	CProjectileManager::getInstance().reset();

	if (Landscape)
	{
		//Landscape->removeTileCallback(&HeightGrid);
	}

	// Remove micro-life
	CMicroLifeManager::getInstance().release();

	// CCtrlSheetInfo release
	CDBCtrlSheet::release ();

	// Release the Entities Animation Manager
	CEntityAnimationManager::delInstance();
	EAM= NULL;

	// Release the cursors
	releaseContextualCursor();


	// Release 3D
	if(Driver)
	{
		// Release Scene, SceneRoot and their elements
		releaseMainLoopScenes();

		// release the auto animation
		releaseAutoAnimation();

		// Release the shape bank
		Driver->getShapeBank()->reset();
	}

	// Release FX manager
	CTimedFXManager::getInstance().reset();

	// Purge memory
	UDriver::purgeMemory();

	// Unlink the net manager
	NetMngr.setDataBase (NULL);

	// Send a msg to server
	if(!ClientCfg.Local)
	{
		if (closeConnection)
			NetMngr.disconnect();
		else
			NetMngr.quit();
/*
		// Quit game, return to select character
		CBitMemStream out;
		if(GenericMsgHeaderMngr.pushNameToStream("CONNECTION:QUIT_GAME", out))
		{
			NetMngr.push(out);
		}
		else
			nlwarning(" unknown message name 'CONNECTION:QUIT_GAME'");
*/

		// to be sure server crash is not fault of client
		ConnectionReadySent= false;
	}

}// release //

// ***************************************************************************
// Called when Quit from OutGame
void releaseOutGame()
{
#ifdef RYZOM_BG_DOWNLOADER
	CBGDownloaderAccess::getInstance().release();
#endif

	ProgressBar.release();

	// flush the server string cache
	STRING_MANAGER::CStringManagerClient::instance()->flushStringCache();

	// Disconnect the client from the server.
	NetMngr.disconnect();

	// Interface release
	CInterfaceManager::getInstance()->uninitOutGame();

	// delete the sound manager
	if(SoundMngr)
	{
		delete SoundMngr;
		SoundMngr = NULL;
	}

	// Delete the driver.
	if(Driver)
	{
		// Stop any async loading. Actually nohting should be async loaded in outgame.
		CAsyncFileManager::terminate();

		// If there is a scene.
		if(Scene)
		{
			// Release the scene.
			Driver->deleteScene(Scene);
			Scene = 0;
		}

		// Remove the Actions listener from the Events Server.
		EventsListener.removeFromServer(CInputHandlerManager::getInstance()->FilteredEventServer);

		// Release effects
		delete FXAA; FXAA = NULL;
		CBloomEffect::releaseInstance();

		// Release Scene, textcontexts, materials, ...
		Driver->release();

		// Delete the driver.
		delete Driver;
		Driver = 0;
	}

	ContinentMngr.reset();
}

void releaseStereoDisplayDevice()
{
	if (StereoDisplay)
	{
		StereoDisplay->getOriginalFrustum(0, &MainCam);
		if (SceneRoot)
		{
			UCamera cam = SceneRoot->getCam();
			StereoDisplay->getOriginalFrustum(1, &cam);
		}
		nlassert(Driver);
		Driver->setViewport(NL3D::CViewport());
		nlassert(Scene);
		Scene->setViewport(NL3D::CViewport());
		delete StereoDisplay;
		StereoDisplay = NULL;
		StereoHMD = NULL;
	}
	IStereoDisplay::releaseAllLibraries();
}

// ***************************************************************************
// final release : Release before exit.
void release()
{
	if (StartPlayTime != 0)
	{
		CLoginProgressPostThread::getInstance().step(CLoginStep(LoginStep_GameExit, "login_step_game_exit&play_time=" + toString((NLMISC::CTime::getLocalTime() - StartPlayTime) / 1000)));
	}
	
	setCrashCallback(NULL);

#ifdef RYZOM_BG_DOWNLOADER
	CBGDownloaderAccess::getInstance().release();
#endif

	ProgressBar.release();

	R2::CEditor::releaseInstance();

	// flush the server string cache
	STRING_MANAGER::CStringManagerClient::instance()->flushStringCache();
	STRING_MANAGER::CStringManagerClient::release(true);

	// restore screensaver state
	CSystemUtils::enableScreensaver(LastScreenSaverEnabled);
	CSystemUtils::uninit();

	// release PACS primitives
	deletePrimitiveBlocks();

	// Release the commands
	releaseCommands();

	// Exit config file stuff
	ClientCfg.release();

	// Disconnect the client from the server.
	NetMngr.disconnect();

	// delete the sound manager
	if (SoundMngr)
	{
		delete SoundMngr;
		SoundMngr = NULL;
	}

	// Release the Entities Animation Manager
	CEntityAnimationManager::delInstance();
	EAM = NULL;

	nldebug("VR [C]: VR Shutting down");
	releaseStereoDisplayDevice();

	// Delete the driver.
	if (Driver)
	{
		// Release the prim
		PrimFiles.release(*Driver);

		if (TextContext != NULL)
			Driver->deleteTextContext(TextContext);
		TextContext = NULL;

		// Release effects
		delete FXAA; FXAA = NULL;
		CBloomEffect::releaseInstance();

		// Release texture
		endLoading();

		// Release Scene, textcontexts, materials, ...
		Driver->release();

		// Delete the driver.
		delete Driver;
		Driver = NULL;
	}

	NetMngr.getConnection().close();
	HttpClient.disconnect();

	// Remove the Actions listener from the Events Server.
	EventsListener.removeFromServer(CInputHandlerManager::getInstance()->FilteredEventServer);

	IDisplayer *clientLogDisplayer = ErrorLog->getDisplayer("CLIENT.LOG");
	if (clientLogDisplayer)
	{
		DebugLog->removeDisplayer(clientLogDisplayer);
		InfoLog->removeDisplayer(clientLogDisplayer);
		WarningLog->removeDisplayer(clientLogDisplayer);
		ErrorLog->removeDisplayer(clientLogDisplayer);
		AssertLog->removeDisplayer(clientLogDisplayer);
		delete clientLogDisplayer;
	}

	CSheetId::uninit();

	// shutdown a few other singletons
	CLoginProgressPostThread::releaseInstance();
	CAttackListManager::releaseInstance();
	CFactionWarManager::release();
	CEncyclopediaManager::releaseInstance();
	CDoorManager::releaseInstance();
	NL3D::CParticleSystemManager::release();
	CUserCommand::release();
	CStaticFames::releaseInstance();
	CSPhraseManager::releaseInstance(); // must release before BrickManager, SkillManager
	CSBrickManager::releaseInstance();
	CSkillManager::releaseInstance();
	CVisualSlotManager::releaseInstance();
	CEntityAnimationManager::delInstance();
	CBarManager::releaseInstance();
	CInterfaceManager::destroy();
	CDDXManager::releaseInstance();
	R2::CObjectSerializer::releaseInstance();
	NLMISC::CBigFile::getInstance().removeAll();
	NLMISC::CBigFile::releaseInstance();
	NLMISC::CStreamedPackageManager::releaseInstance();
	delete HttpPackageProvider;
	HttpPackageProvider = NULL;
	NL3D::CFastHLSModifier::releaseInstance();
	CLandscapePolyDrawer::releaseInstance();
	NL3D::CParticleSystemShape::releaseInstance();
	NLMISC::CPath::releaseInstance();
	SheetMngr.release();
//	releaseWeather();	// AJM FIXME conflicting ownership with SheetManager
	R2::CScenarioEntryPoints::releaseInstance();
	CMacroCmdManager::releaseInstance();
	CInputHandlerManager::releaseInstance();
	ICDBNode::releaseStringMapper();
	CClassRegistry::release();
	CReflectSystem::release();
	CInterfaceExpr::release();
	CPdrTokenRegistry::releaseInstance();
	NLNET::IModuleManager::releaseInstance();
	CLuaManager::releaseInstance();
	NLGUI::CDBManager::release();
	CWidgetManager::release();
	CViewRenderer::release();
	CIXml::releaseLibXml();
	CHttpCache::release();
	CStrictTransportSecurity::release();
	CAsyncFileManager3D::releaseInstance();

#if FINAL_VERSION
	// openURL ("http://ryzom.com/exit/");
#endif

}// release //