////////////////////////////////////////////////////////////////////////////////
// MechSim.h
// Simulation window for MechVM
// Copyright Bjoern Ganster
// Developed since 2007
////////////////////////////////////////////////////////////////////////////////

#ifndef MechSim__h
#define MechSim__h

#include "Archive.h"
#include "Audio.h"
#include "Font.h"
#include "GLWindow.h"
#include "RenderableObject.h"
#include "LightSource.h"
#include "MissionGoals.h"
#include "PlayerGroundVehicle.h"
#include "RenderToTexture.h"
#include "Shot.h"
#include "Sound.h"
#include "Vehicles.h"
#include "VehicleAI.h"
#include "XMLTree.h"

#include <vector>

struct ManagedObject: public VirtualClass {
	BGCPString name;
	VirtualClass* object;

	ManagedObject(const BGCPString aName = "", VirtualClass* aObject = nullptr)
	: VirtualClass("ManagedObject", ManagedObjectID),
	  name(aName),
	  object(aObject)
	{
	}
};

struct NavPoint {
	Point3D pos;
	BGCPString name;
	bool visited;
};

enum EnvironmentTypes {
	normal, // on a planet with atmosphere
	space, // microgravity, no atmosphere, breaches
	underwater, // on a planet, but underwater: slower motion, jump jets and most weapons don't work, breaches
};

struct QueuedSound {
	Sound* sound;
	size_t playOffset;
	bool repeat;
};

struct QueuedVoice {
	Sound* sound;
	DWORD startMS;
};

enum MfdMode {
	MFD_ShowWeapons,
	MFD_Goals,
	MFD_Comms,
	MFD_CommAll,
	MFD_Comm1,
	MFD_Comm2
};

enum MechSimViewingMode {
	NormalView,
	WireframeView,
	ThermalOptics,
	LowLightVision
};

class MechShell;

////////////////////////////////////////////////////////////////////////////////
// Mech simulator window

class MechSim : public GLWindow, public AudioProvider
{
public:
	bool mMissionSuccessful;
	bool mMechSimDebug;

	// Is the game paused?
	bool mPaused;

	// For finding additional files associated with the game
	BGCPString mGameDir;

	MechSimViewingMode mViewingMode;

	// Constructor
	MechSim();

	// Destructor
	~MechSim() OVERRIDE;

	// Receive messages
	void receiveMessage(VirtualClass* sender, int MessageType, VirtualClass* data = nullptr) OVERRIDE;

	// Update title with model graph name and framerate
	void updateTitle();

	// Render
	void render(int x, int y, int w, int h) OVERRIDE;

	// Keyboard event handlers
	void keypressed(BGKey key, bool shiftHeld, bool controlHeld, bool down, bool repeat) OVERRIDE;

	void mouseMoved (int /*x*/, int /*y*/) OVERRIDE;

	// Button state changed over a window: returns child that was clicked
	GLWindow* buttonStateChanged (int /*button*/, bool /*down*/, int /*x*/, int /*y*/) OVERRIDE;

	void setReturnVis(GLWindow* newVis);

	void addVehicle(RenderableObject* vehicle);

	bool loadLevel(const PathChar* levelDir, const PathChar* xml);

	void loadShotPrototypes();

	// Get prototype geometry for a shot
	Mesh* getShotPrototype(WeaponCategory whichOne);

	// Add shot to the scene
	void addShot(Shot* shot);

	// Find object of specified type
	template <typename T> T* find(const BGCPString& name)
	{
		for (size_t i = 0; i < mManagedObjects.size(); ++i) {
			ManagedObject& o = mManagedObjects[i];
			if (o.name == name) {
				T* casted = dynamic_cast<T*>(o.object);
				return casted;
			}
		}

		return nullptr;
	}

	// Add object
	void addManagedObject(VirtualClass* o, const BGCPString name = "unknown");

	void adjustSatelliteDist(double factor);

	void selectNextNavPoint();

	// Play a sound from a MW2.PRJ archive
	bool playSound(const char* filename);

	// Queue voice - no more than one person should be talking at once, even in war ...
	bool enqueueVoice(const BGCPString& filenames);

	void addArchive(MWArchive* archive);

	// Notify mission goals tracker of a destroyed object (Vehicle or GameObject)
	void updateObjectStatus(const BGCPString& id, SubObjectiveType newStatus, bool missionEnd);

	// Get a pointer to the entire scene ...
	RenderableObject* getScene();

	// Get number of milliseconds spend on the current mission
	DWORD missionTime() const;

	// Find the enemy or target closest to the viewing ray
	GameObject* findTarget();

	// Allow the shell to register with the sim so that the sim can ask variables from the shell
	void setShell(MechShell* shell);

	// Evaluate string with MechShell variables and replacements
	BGCPString evaluateMechShellVars(const BGCPString src);

	EnvironmentTypes getEnvironment() const;

	// Activate/deactivate light sources
	void activateLightSources(bool renderingShadows) const;
	void deactivateLightSources() const;

	// Simple debugging feature: by pressing F11, the variable below is set and can be queried wherever MechSim is available ...
	bool getMechSimDebug();

	// Add sound to the sound queue
	void queueSound(const Sound& sound, bool repeat = false);

	// Update the sound queue
	void calcSoundFrame();

	// Provide audio via SDL callback
	void provideAudio(void* userdata, Uint8* stream, int len) OVERRIDE;

	Vehicle* getPlayerVehicle();
	const Vehicle* getPlayerVehicle() const;

	// Find vehicle by name
	Vehicle* findVehicleByID(const BGCPString& vehicleName);

	void mouseWheelMoved(int /*x*/, int /*y*/) OVERRIDE;

	// Check if an event already happened
	bool eventHappened(const BGCPString& eventName);

	MechShell* getShell();

	// End the sim, return to calling screen
	void endSim();

	// Apply damage to Mechs and vehicles as an area effect
	void applyAreaDamage(const Point3D& explosionLocation, double damage, double maxDist, Vehicle* shooter);

	// Based on a simple specification, select a random Mech
	void selectRandomMech(const BGCPString& randomMechSpec, BGCPString& chassis, BGCPString& config, BGCPString& description);

	// Functions for free view
	void setFreeViewTheta(double newVal);
	void setFreeViewDeltaTheta(double newVal);
private:
	RenderableObject* m_sceneObjects;
	GLWindow* returnVis;
	PlayerVehicleAI* playerVehicleAI;
	int selectedObject;
	int frame;
	DWORD lastFrame, mMissionStarted;
	std::vector<AbstractVehicleAI*> mAIVehicles;
	std::vector<ManagedObject> mManagedObjects;
	std::vector<NavPoint> mNavPoints;
	double mSatelliteDist;
	RenderToTexture textureBuffer;
	double mLastMouseX, mLastMouseY;
	size_t mCurrentNavPoint;
	RenderableObject mShots;
	MWArchive* mArchive;
	MissionGoals mMissionGoalsTracker;
	MfdMode mMfdMode;
	Color mReticleColor;
	DWORD mNow; // time since mission start in ms

	// Free view
	double mFreeViewTheta, mFreeViewDeltaTheta;

	// Show instrument overlay at reduced resolution?
	int mOverlayDivider;

	// Allow to render shadows?
	bool mAllowRenderShadows;

	// Pointer to shell (used to set variables)
	MechShell* mShell;

	// Environment type for the mission (normal air, space, under water, ...)
	EnvironmentTypes mEnvironment;

	// Light sources - a level file may define any number of light sources, but the shadow system can handle only one light source ...
	std::vector<LightSource*> mLightSources;
	LightSource mDisplayLight;
	std::vector<QueuedVoice> mQueuedVoice;
	BGCPString mSuccessMission, mFailureMission;
	Texture* mPausedPic;

	// It is possible that a vehicle will have an event goal for an event that already happened.
	// In that case, this list can be used to check for previous events.
	std::vector<BGCPString> mPreviousEvents;

	// Data needed for the sound queue
	const DWORD mSoundFrameDurationMS; // used for SDL playback without callback
	std::vector<QueuedSound> mQueuedSounds;
	Sound* music;
	SDL_mutex* mSoundMutex;

	// Filename to use for loading vehicle/mech textures
	// This filename is the same for all mechs/vehicle, but its directory varies
	BGCPString mTextureFN;

	// Geometry and texture for rendering a skybox
	Mesh* mSkybox;

	// The background color for the map
	Color mapGroundColor;

	// Prototype objects for shot geometry
	Mesh mLaserShotPrototype, mBulletPrototype, mMissilePrototype, mArrow;

	// MechSim may a font different from the default ...
	Font* mFont;

	// Current cockpit zoom
	double mZoom;

	void addSceneObjectsRecursively(RenderableObject* container,
		const PathChar* levelDir, XMLTreeConstIterator& iter, const Matrix& m);
	void processTransform(RenderableObject* container, const Matrix& m, 
		XMLTreeConstIterator& iter, const PathChar* levelDir);
	void processObjectTag(RenderableObject* container, XMLTreeConstIterator& iter, const Matrix& m, const PathChar* levelDir);
	void processHeightmapTag(RenderableObject* container, XMLTreeConstIterator& iter);
	void processSpawn(XMLTreeConstIterator& iter);

	// Gravity of the current environment
	double mGravity;

	// Find the enemy or target closest to the viewing ray (recursive part)
	RenderableObject* findTarget(const Point3D& pos, const Point3D& dir, RenderableObject* ro, double& angle);

	// Render map or target
	void renderMap(Vehicle* vehicle, Texture* texture, DWORD now, double u1, double v1, double u2, double v2);
	void renderTarget(Vehicle* vehicle, Texture* texture, DWORD now, double u1, double v1, double u2, double v2);

	// Trigger starmate/lancemate action via communications dialog
	void setStarmateAction_JoinFormation();
	void setStarmateAction_Attack();
	void setStarmateAction_Shutdown();

	void renderCockpitInstruments(Texture* renderTargetTexture, Vehicle* vehicle);
	void setRenderSceneMatrices(double fovy, const Point3D& viewPos, const Point3D& viewCenter, const Point3D& viewUp);

	// Process weapon fire: create new shots, move shots, process impacts, render energy weapons
	void processWeaponFire(double deltaT);

	// Check when the current voice ends
	DWORD getCurrentVoiceEnd();

	// Schedule playing a voice file
	void scheduleSound(Sound* voice, DWORD startMS);
};

MechSim* getMechSim();

#endif
