/*
 * Copyright (c) 1997 - 2001 Hansj÷rg Malthaner
 *
 * This file is part of the Simutrans project under the artistic licence.
 * (see licence.txt)
 */

#ifndef boden_grund_h
#define boden_grund_h


#include "../halthandle_t.h"
#include "../simimg.h"
#include "../simcolor.h"
#include "../simdepot.h"
#include "../dataobj/koord3d.h"
#include "../dataobj/dingliste.h"
#include "wege/weg.h"


class spieler_t;
class depot_t;
class karte_t;
class grund_info_t;
class cbuffer_t;


/* A map from ding_t subtypes to their enum equivalent
 * Used by grund_t::find<T>()
 */
class aircraft_t;
class baum_t;
class bruecke_t;
class crossing_t;
class field_t;
class fussgaenger_t;
class gebaeude_t;
class groundobj_t;
class label_t;
class leitung_t;
class pillar_t;
class pumpe_t;
class roadsign_t;
class senke_t;
class signal_t;
class stadtauto_t;
class automobil_t;
class tunnel_t;
class wayobj_t;
class zeiger_t;

template<typename T> struct map_ding {};
template<> struct map_ding<aircraft_t>    { static const ding_t::typ code = ding_t::aircraft;    };
template<> struct map_ding<baum_t>        { static const ding_t::typ code = ding_t::baum;        };
template<> struct map_ding<bruecke_t>     { static const ding_t::typ code = ding_t::bruecke;     };
template<> struct map_ding<crossing_t>    { static const ding_t::typ code = ding_t::crossing;    };
template<> struct map_ding<field_t>       { static const ding_t::typ code = ding_t::field;       };
template<> struct map_ding<fussgaenger_t> { static const ding_t::typ code = ding_t::fussgaenger; };
template<> struct map_ding<gebaeude_t>    { static const ding_t::typ code = ding_t::gebaeude;    };
template<> struct map_ding<groundobj_t>   { static const ding_t::typ code = ding_t::groundobj;   };
template<> struct map_ding<label_t>       { static const ding_t::typ code = ding_t::label;       };
template<> struct map_ding<leitung_t>     { static const ding_t::typ code = ding_t::leitung;     };
template<> struct map_ding<pillar_t>      { static const ding_t::typ code = ding_t::pillar;      };
template<> struct map_ding<pumpe_t>       { static const ding_t::typ code = ding_t::pumpe;       };
template<> struct map_ding<roadsign_t>    { static const ding_t::typ code = ding_t::roadsign;    };
template<> struct map_ding<senke_t>       { static const ding_t::typ code = ding_t::senke;       };
template<> struct map_ding<signal_t>      { static const ding_t::typ code = ding_t::signal;      };
template<> struct map_ding<stadtauto_t>   { static const ding_t::typ code = ding_t::verkehr;     };
template<> struct map_ding<automobil_t>   { static const ding_t::typ code = ding_t::automobil;   };
template<> struct map_ding<tunnel_t>      { static const ding_t::typ code = ding_t::tunnel;      };
template<> struct map_ding<wayobj_t>      { static const ding_t::typ code = ding_t::wayobj;      };
template<> struct map_ding<zeiger_t>      { static const ding_t::typ code = ding_t::zeiger;      };



/**
 * <p>Abstrakte Basisklasse für Untergründe in Simutrans.</p>
 *
 * <p>Von der Klasse grund_t sind alle Untergruende (Land, Wasser, Strassen ...)
 * in simu abgeleitet. Jedes Planquadrat hat einen Untergrund.</p>
 *
 * <p>Der Boden hat Eigenschaften, die abgefragt werden koennen
 * ist_natur(), ist_wasser(), hat_wegtyp(), ist_bruecke().
 * In dieser Basisklasse sind alle Eigenschaften false, sie werden erst
 * in den Subklassen redefiniert.</p>
 *
 * @author Hj. Malthaner
 */
class grund_t
{
public:
	/**
	 * Flag-Werte für das neuzeichnen geänderter Untergründe
	 * @author Hj. Malthaner
	 */
	enum flag_values {
		keine_flags=0,
		dirty=1, // was changed => redraw full
		is_kartenboden=2,
		has_text=4,
		marked = 8,  // will have a frame
		draw_as_ding = 16, // is a slope etc => draw as one
		is_halt_flag = 32,	// is a part of a halt
		has_way1 = 64,
		has_way2 = 128
	};

	// just to calculate the offset for skipping the ways ...
	static uint8 offsets[4];

public:
	/** true, when showing a grid
	 * @author prissi
	 */
	static bool show_grid;

	/* underground modes */
	/* @author Dwachs    */
	enum _underground_modes {
		ugm_none = 0,	// normal view
		ugm_all  = 1,   // everything underground visible, grid for grounds
		ugm_level= 2	// overground things visible if their height  <= underground_level
						// underground things visible if their height == underground_level
	};
	static uint8 underground_mode;
	static sint8 underground_level;

protected:
	/**
	 * Zusammenfassung des Ding-Container als Objekt
	 * @author V. Meyer
	 */
	dingliste_t dinge;

	/**
	 * Koordinate in der Karte.
	 * @author Hj. Malthaner
	 */
	koord3d pos;

	/**
	 * Flags für das neuzeichnen geänderter Untergründe
	 * @author Hj. Malthaner
	 */
	uint8 flags;

	/**
	 * 0..100: slopenr, (bild_nr%100), normal ground
	 * (bild_nr/100)%17 left slope
	 * (bild_nr/1700) right slope
	 * @author Hj. Malthaner
	 */
	image_id bild_nr;

	/* image of the walls */
	sint8 back_bild_nr;

	// slope (now saved locally), because different grounds need differen slopes
	uint8 slope;

	/**
	 * Description;
	 *      Checks whether there is a way connection from this to gr in dv
	 *      direction of the given waytype.
	 *
	 * Return:
	 *      false, if ground is NULL (this case is explitly allowed).
	 *      true, if waytype_t is invalid_wt (this case is explitly allowed)
	 *      Check result otherwise
	 *
	 * Notice:
	 *      helper function for "get_neighbour"
	 *      Currently private, but it may be possible to make it public, if
	 *      neeeded.
	 *      Was previously a part of the simposition module.
	 *
	 * @author: Volker Meyer
	 * @date: 21.05.2003
	 */
	bool is_connected(const grund_t *gr, waytype_t wegtyp, koord dv) const;

public:
	/**
	 * setzt die Bildnr. des anzuzeigenden Bodens
	 * @author Hj. Malthaner
	 */
	inline void set_bild(image_id n)
	{
		bild_nr = n;
		set_flag(dirty);
	}


protected:
	/**
	* Pointer to the world of this ground. Static to conserve space.
	* Change to instance variable once more than one world is available.
	* @author Hj. Malthaner
	*/
	static karte_t *welt;

	// calculates the slope image and sets the draw_as_ding flag correctly
	void calc_back_bild(const sint8 hgt,const sint8 slope_this);

	// this is the real image calculation, called for the actual ground image
	virtual void calc_bild_internal() = 0;

public:
	enum typ {grund, boden, wasser, fundament, tunnelboden, brueckenboden, monorailboden };

	grund_t(karte_t *welt, loadsave_t *file);
	grund_t(karte_t *welt, koord3d pos);

	virtual ~grund_t();

	/**
	 * Toggle ground grid display (now only a flag)
	 */
	static void toggle_grid() { grund_t::show_grid = !grund_t::show_grid; }

	/**
	 * Sets the undergroundmode & level
	 */
	static void set_underground_mode(const uint8 ugm, const sint8 level);

	karte_t *get_welt() const {return welt;}

	/**
	* Setzt Flags für das neuzeichnen geänderter Untergründe
	* @author Hj. Malthaner
	*/
	inline void set_flag(enum flag_values flag) {flags |= flag;}

	inline void clear_flag(enum flag_values flag) {flags &= ~flag;}
	inline bool get_flag(enum flag_values flag) const {return (flags & flag) != 0;}

	void entferne_grund_info() const;

	/**
	* start a new month (and toggle the seasons)
	* @author prissi
	*/
	void check_season(const long month) { calc_bild_internal(); dinge.check_season(month); }

	/**
	 * Dient zur Neuberechnung des Bildes, wenn sich die Umgebung
	 * oder die Lage (Hang) des grundes geaendert hat.
	 * @author Hj. Malthaner
	 */
	void calc_bild();

	/**
	* Gibt die Nummer des Bildes des Untergrundes zurueck.
	* @return Die Nummer des Bildes des Untergrundes.
	* @author Hj. Malthaner
	*/
	inline image_id get_bild() const {return bild_nr;}

	/**
	* Returns the number of an eventual foundation
	* @author prissi
	*/
	image_id get_back_bild(int leftback) const;
	virtual void clear_back_bild() {back_bild_nr=0;}

	/**
	* Gibt den Namen des Untergrundes zurueck.
	* @return Den Namen des Untergrundes.
	* @author Hj. Malthaner
	*/
	virtual const char* get_name() const = 0;

	/**
	* Gibt den Typ des Untergrundes zurueck.
	* @return Der Typ des Untergrundes.
	* @author Hj. Malthaner
	*/
	virtual enum grund_t::typ get_typ() const {return grund;}

	/**
	* Gibt eine Beschreibung des Untergrundes (informell) zurueck.
	* @return Einen Beschreibungstext zum Untergrund.
	* @author Hj. Malthaner
	*/
	const char* get_text() const;

	/**
	* @return NULL
	* @author Hj. Malthaner
	*/
	virtual void info(cbuffer_t & buf) const;

	/**
	* Auffforderung, ein Infofenster zu oeffnen.
	* Oeffnet standardmaessig kein Infofenster.
	* @author Hj. Malthaner
	*/
	bool zeige_info();

	/**
	* Gibt die Farbe des Beschreibungstexthintergrundes zuurck
	* @return die Farbe des Beschreibungstexthintergrundes.
	* @author Hj. Malthaner
	*/
	PLAYER_COLOR_VAL text_farbe() const;

	/**
	 * Sets the label text (by copying it)
	 * @param text the new text (NULL will remove any label text)
	 */
	void set_text(const char* text);

	virtual bool ist_natur() const {return false;}
	virtual bool ist_wasser() const {return false;}

	/**
	* This is called very often, it must be inlined and therefore
	* cannot be virtual - subclasses must set the flags appropriately!
	* @author Hj. Malthaner
	*/
	inline bool ist_bruecke() const {return get_typ()==brueckenboden;}

	/**
	* true if tunnelboden (hence true also for tunnel mouths)
	* check for visibility in is_visible()
	*/
	inline bool ist_tunnel() const {
		return ( (get_typ()==tunnelboden) );
	}

	/**
	* gives true for grounds inside tunnel (not tunnel mouths)
	* check for visibility in is_visible()
	*/
	inline bool ist_im_tunnel() const {
		return ( get_typ()==tunnelboden && (!ist_karten_boden())) ;
	}

	/* this will be stored locally, since it is called many, many times */
	inline uint8 ist_karten_boden() const {return (flags&is_kartenboden);}
	void set_kartenboden(bool tf) {if(tf) {flags|=is_kartenboden;} else {flags&=~is_kartenboden;} }

	/**
	* returns powerline here
	* @author Kieron Green
	*/
	leitung_t *get_leitung() const { return (leitung_t *) dinge.get_leitung(); }

	/**
	* Laedt oder speichert die Daten des Untergrundes in eine Datei.
	* @param file Zeiger auf die Datei in die gespeichert werden soll.
	* @author Hj. Malthaner
	*/
	virtual void rdwr(loadsave_t *file);

	// map rotation
	virtual void rotate90();

	/**
	* Gibt die 3d-Koordinaten des Planquadrates zurueck, zu dem der
	* Untergrund gehoert.
	* @return Die Position des Grundes in der 3d-Welt
	* @author Hj. Malthaner
	*/
	inline const koord3d& get_pos() const { return pos; }

	inline void set_pos(koord3d newpos) { pos = newpos;}

	// slope are now maintained locally
	hang_t::typ get_grund_hang() const { return (hang_t::typ)slope; }
	void set_grund_hang(hang_t::typ sl) { slope = sl; }

	/**
	 * Manche Böden können zu Haltestellen gehören.
	 * @author Hj. Malthaner
	 */
	void set_halt(halthandle_t halt);

	/**
	 * Ermittelt, ob dieser Boden zu einer Haltestelle gehört.
	 * @return NULL wenn keine Haltestelle, sonst Zeiger auf Haltestelle
	 * @author Hj. Malthaner
	 */
	halthandle_t get_halt() const;
	bool is_halt() const { return flags & is_halt_flag; }

	inline sint8 get_hoehe() const {return pos.z;}

	void set_hoehe(int h) { pos.z = h;}

	// Helper functions for underground modes
	//
	// returns the height for the use in underground-mode,
	// heights above underground_level are cutted
	inline sint8 get_disp_height() const {
		return (underground_mode & ugm_level )
			? (pos.z > underground_level ? underground_level : pos.z)
			: pos.z ;
	}

	// returns slope
	// if tile is not visible, 'flat' is returned
	// special care has to be taken of tunnel mouths
	inline hang_t::typ get_disp_slope() const {
		return (  (underground_mode & ugm_level)  &&  (pos.z > underground_level  ||  (get_typ()==tunnelboden  &&  ist_karten_boden()  &&  pos.z == underground_level))
							? (hang_t::typ)hang_t::flach
							: get_grund_hang() );

		/*switch(underground_mode) {// long version of the return statement above
			case ugm_none: return(get_grund_hang());
			case ugm_all:  return(get_grund_hang()); // get_typ()==tunnelboden && !ist_karten? hang_t::flach : get_grund_hang());
			case ugm_level:return((pos.z > underground_level || (get_typ()==tunnelboden && ist_karten_boden() && pos.z == underground_level))
							? hang_t::flach
							: get_grund_hang());
		}*/
	}

	inline bool is_visible() const {
		if(get_typ()==tunnelboden) {
			switch(underground_mode) {
				case ugm_none: return ist_karten_boden();
				case ugm_all:  return true;
				case ugm_level:return  pos.z == underground_level  ||  (ist_karten_boden()  &&  pos.z <= underground_level);
			}
		}
		else {
			switch(underground_mode) {
				case ugm_none: return true;
				case ugm_all:  return false;
				case ugm_level:return pos.z <= underground_level;
			}
		}
		return(false);
	}

	// the same as above but specialized for kartenboden
	inline bool is_karten_boden_visible() const {
		switch(underground_mode) {
			case ugm_none: return true;
			case ugm_all:  return get_typ()==tunnelboden;
			case ugm_level:return pos.z <= underground_level;
		}
		return(false);
	}

	/**
	* Zeichnet Bodenbild des Grundes
	* @author Hj. Malthaner
	*/
	void display_boden(const sint16 xpos, sint16 ypos) const;

	/* displays everything that is on a tile;
	 * @param is_global set to true, if this is called during the whole screen update
	 */
	void display_dinge(const sint16 xpos, sint16 ypos, const bool called_from_simview) const;

	/* overlayer with signs, good levels and station coverage
	 * resets the dirty flag
	 * @author kierongreen
	 */
	void display_overlay(sint16 xpos, sint16 ypos);

	inline ding_t *first_obj() const { return dinge.bei(offsets[flags/has_way1]); }
	ding_t *suche_obj(ding_t::typ typ) const { return dinge.suche(typ,0); }
	ding_t *obj_remove_top() { return dinge.remove_last(); }

	template<typename T> T* find(uint start = 0) const { return static_cast<T*>(dinge.suche(map_ding<T>::code, start)); }

	uint8  obj_add(ding_t *obj) { return dinge.add(obj); }
	uint8 obj_remove(const ding_t* obj) { return dinge.remove(obj); }
	bool obj_loesche_alle(spieler_t *sp) { return dinge.loesche_alle(sp,offsets[flags/has_way1]); }
	bool obj_ist_da(const ding_t* obj) const { return dinge.ist_da(obj); }
	ding_t * obj_bei(uint8 n) const { return dinge.bei(n); }
	uint8  obj_count() const { return dinge.get_top()-offsets[flags/has_way1]; }
	uint8 get_top() const {return dinge.get_top();}

	// moves all object from the old to the new grund_t
	void take_obj_from( grund_t *gr);

	/**
	* @returns NULL wenn OK, oder Meldung, warum nicht
	* @author Hj. Malthaner
	*/
	const char * kann_alle_obj_entfernen(const spieler_t *sp) const { return dinge.kann_alle_entfernen(sp,offsets[flags/has_way1]); }

	/**
	* Interface zur Bauen und abfragen von Gebaeuden
	* =============================================
	*/

	/**
	* Falls es hier ein Depot gibt, dieses zurueckliefern
	* @author Volker Meyer
	*/
	depot_t *get_depot() const { return dynamic_cast<depot_t *>(first_obj()); }

	/*
	* Interface zur Abfrage der Wege
	* ==============================
	* Jeder Boden hat bis zu 2. Special fuer Wasser: ohne Weg-Objekt werden
	* alle ribis vom weg_t::wassert als gesetzt zurueckgeliefert.
	*/

	/**
	* The only way to get the typ of a way on a tile
	* @author Hj. Malthaner
	*/
	weg_t *get_weg_nr(int i) const { return (flags&(has_way1<<i)) ? static_cast<weg_t *>(obj_bei(i)) : NULL; }

	/**
	* Inline da sehr oft aufgerufen.
	* Sucht einen Weg vom typ 'typ' auf diesem Untergrund.
	* @author Hj. Malthaner
	*/
	weg_t *get_weg(waytype_t typ) const {
		if(flags&has_way1) {
			weg_t *w=(weg_t *)obj_bei(0);
			if(w->get_waytype()==typ) {
				return w;
			}
		}
		if(flags&has_way2) {
			weg_t *w=(weg_t *)obj_bei(1);
			if(w->get_waytype()==typ) {
				return w;
			}
		}
		return NULL;
	}

	uint8 has_two_ways() const { return flags&has_way2; }

	bool hat_weg(waytype_t typ) const { return get_weg(typ)!=NULL; }

	/**
	* Returns the system type s_type of a way of type typ at this location
	* Currently only needed for tramways or other different types of rails
	*
	* @author DarioK
	* @see get_weg
	*/
	uint8 get_styp(waytype_t typ) const
	{
		weg_t *weg = get_weg(typ);
		return (weg) ? weg->get_besch()->get_styp() : 0;
	}

	/**
	* Ermittelt die Richtungsbits furr den weg vom Typ 'typ'.
	* Liefert 0 wenn kein weg des Typs vorhanden ist. Ein Weg kann ggf.
	* auch 0 als Richtungsbits liefern, deshalb kann die Anwesenheit eines
	* Wegs nicht hierurber, sondern mit get_weg(), ermittelt werden.
	* Also beware of water, which always allows all directions ...thus virtual
	* @author Hj. Malthaner
	*/
	virtual ribi_t::ribi get_weg_ribi(waytype_t typ) const;

	/**
	* Ermittelt die Richtungsbits furr den weg vom Typ 'typ' unmaskiert.
	* Dies wird beim Bauen ben÷tigt. Furr die Routenfindung werden die
	* maskierten ribis benutzt.
	* @author Hj. Malthaner/V. Meyer
	*
	*/
	virtual ribi_t::ribi get_weg_ribi_unmasked(waytype_t typ) const;

	/**
	* checks a ways on this ground tile and returns the highest speedlimit.
	* only used for the minimap
	* @author hsiegeln
	*/
	int get_max_speed() const;

	/**
	* only used for bridges, which start at a slope
	* @author V. Meyer
	*/
	virtual sint8 get_weg_yoff() const { return 0; }

	/**
	* Hat der Boden mindestens ein weg_t-Objekt? Liefert false für Wasser!
	* @author V. Meyer
	*/
	inline bool hat_wege() const { return (flags&(has_way1|has_way2))!=0;}

	/**
	* Kreuzen sich hier 2 verschiedene Wege?
	* Strassenbahnschienen duerfen nicht als Kreuzung erkannt werden!
	* @author V. Meyer, dariok
	*/
	inline bool ist_uebergang() const { return (flags&has_way2)!=0  &&  ((weg_t *)dinge.bei(1))->get_besch()->get_styp()!=7; }

	/**
	* returns the vehcile of a convoi (if there)
	* @author V. Meyer
	*/
	ding_t *get_convoi_vehicle() const { return dinge.get_convoi_vehicle(); }

	virtual hang_t::typ get_weg_hang() const { return get_grund_hang(); }

	/*
	 * Search a matching wayobj
	 */
	wayobj_t *get_wayobj( waytype_t wt ) const;

	/**
	* Interface zur Bauen der Wege
	* =============================
	*/

	/**
	 * Bauhilfsfunktion - ein neuer weg wird mit den vorgegebenen ribis
	 * eingetragen und der Grund dem Erbauer zugeordnet.
	 *
	 * @param weg	    der neue Weg
	 * @param ribi	    die neuen ribis
	 * @param sp	    Spieler, dem der Boden zugeordnet wird
	 *
	 * @author V. Meyer
	 */
	sint64 neuen_weg_bauen(weg_t *weg, ribi_t::ribi ribi, spieler_t *sp);

	/**
	 * Bauhilfsfunktion - die ribis eines vorhandenen weges werden erweitert
	 *
	 * @return bool	    true, falls weg vorhanden
	 * @param wegtyp	    um welchen wegtyp geht es
	 * @param ribi	    die neuen ribis
	 *
	 * @author V. Meyer
	 */
	bool weg_erweitern(waytype_t wegtyp, ribi_t::ribi ribi);

	/**
	 * Bauhilfsfunktion - einen Weg entfernen
	 *
	 * @param wegtyp	    um welchen wegtyp geht es
	 * @param ribi_rem  sollen die ribis der nachbar zururckgesetzt werden?
	 *
	 * @author V. Meyer
	 */
	sint32 weg_entfernen(waytype_t wegtyp, bool ribi_rem);

	/**
	 * Description;
	 *      Look for an adjacent way in the given direction. Think of an object
	 *      that needs the given waytyp for movement. The object is current at
	 *      the ground "this". It wants to move in "dir". The routine checks if
	 *      this is possible and returns the destination ground.
	 *      Tunnels and bridges are entered and left correctly. This requires
	 *      some complex checks, since we have three types of level changes
	 *      (tunnel entries, bridge ramps and horizontal bridge start).
	 *
	 * Notice:
	 *      Uses two helper functions "is_connected()" and "get_vmove()"
	 *      Was previously a part of the simposition module.
	 *
	 * Parameters:
	 *      If dir is not (-1,0), (1,0), (0,-1) or (0, 1), the function fails
	 *      If wegtyp is set to invalid_wt, no way checking is performed
	 *
	 * In case of success:
	 *      "to" ist set to the ground found
	 *      true is returned
	 * In case of failure:
	 *      "to" ist not touched
	 *      false is returned
	 *
	 * @author: Volker Meyer
	 * @date: 21.05.2003
	 */
	bool get_neighbour(grund_t *&to, waytype_t type, koord dir) const;

	/**
	 * Description;
	 *      Check, whether it is possible that a way goes up or down in dv
	 *      direction. The result depends of the ground type (i.e tunnel entries)
	 *      and the "hang_typ" of the ground.
	 *
	 *      Returns 1, if it is possible to go up
	 *      Returns -1, if it is possible to go down
	 *      Returns 0 otherwise.
	 *
	 * Notice:
	 *      helper function for "get_neighbour"
	 *      Was previously a part of the simposition module.
	 *
	 * @author: Volker Meyer
	 * @date: 21.05.2003
	 */
	sint16 get_vmove(koord dir) const;

	/* removes everything from a tile, including a halt but i.e. leave a
	 * powerline ond other stuff
	 * @author prissi
	 */
	bool remove_everything_from_way(spieler_t *sp,waytype_t wt,ribi_t::ribi ribi_rem);

	void* operator new(size_t s);
	void  operator delete(void* p, size_t s);

};


#endif
