
// ##################################################
// ##       vesmirna telesa a jine objekty         ##
// ##                                              ##
// ##################################################

#pragma once
#ifndef SPACE_H
#define SPACE_H

#include "common.h"

#include <set>
#include <cmath>
#include <list>

#include "cache_list.h"
#include "independent_constants.h"
#include "mathfunc.h"
#include "spaceindex.h"
#include "physics.h"

enum spaceObjectType
{
    SOT_STAR   = 1,
    SOT_PLANET = 2,
    SOT_MOON   = 4,
    SOT_BASE   = 8
};

// --------------------------------

class spaceObject : public dataListObjectClass
{
protected:
    dtIdentifier parentObject_;             /* rodicovsky objekt, 0 = cely vesmir */
public:
    typedef std :: set<dtIdentifier> :: const_iterator dtIdentifierConstIterator;

    virtual vector_dist_t                getPosition(void) const            { return vector_dist_t(0, 0); }
    virtual vector_dist_t                getAbsolutePosition(void) const    { return vector_dist_t(0, 0); }

            dtIdentifier                 getStarSystem(void);
            dtIdentifier                 getParentSpaceObject(void)  const  { return parentObject_;               } 
            spaceObject*                 getParentSpaceObjectInstance(void);

    virtual spaceObjectType              getType(void) const = 0;
    virtual void                         getAimedTargets(std :: list<dtIdentifier>& ids) = 0;
    virtual const std :: string          getName(void) const                { return "??? unknown";               }

	        void                         setParent(dtIdentifier PID) { this->parentObject_ = PID; };

	virtual void          DB_Load       (  unsigned long ID                );
	virtual void          DB_Save       (  unsigned long ID                );
	virtual void          DB_Insert     (  unsigned long ID                );

    virtual void                         getWholeTreeIds(std :: set<dtIdentifier>& ids) const; 

    virtual ~spaceObject() {};
};

// --------------------------------

class baseClass;
class spaceFortressObject : public spaceObject
{
protected:
    dtIdentifier idFortress_;       /* id fortressu */
public:
    spaceFortressObject()                                   {                           };
    spaceFortressObject(dtIdentifier fortress)              { idFortress_ = fortress;   };
    spaceObjectType getType(void)                     const { return SOT_BASE;          }; 
    dtIdentifier    getFortressID(void)               const { return idFortress_;       };      

	virtual void          DB_Load       (  unsigned long ID                ) sealed;
	virtual void          DB_Save       (  unsigned long ID                ) sealed;
	virtual void          DB_Insert     (  unsigned long ID                ) sealed;

    virtual vector_dist_t                getAbsolutePosition(void) const; 
    virtual void                         getAimedTargets(std :: list<dtIdentifier>& ids);

    virtual ~spaceFortressObject() {};

    static dtIdentifier   createWithFortress( const dtIdentifier parentSpaceObject, baseClass* base);
};

// --------------------------------

class spaceRegularObject : public spaceObject
{
protected:
    vector_dist_t position_;

    dist_t areaRadius_;             /* polomer oblasti, u hvezdy polomer hvezdne soustavy */
    dist_t objectRadius_;           /* polomer vlastniho objektu, u hvezdy polomer hvezdy */
    weight_t objectWeight_;         /* vaha hvezdy */


    /* parametry rotace objektu okolo hlavni hvezdy */
    dist_t distanceFromParentObject_; /* vzdalenost od objektu, okolo ktereho tento objekt obiha */
    double basicRotationAngle_;       /* zakladni uhel rotace v radianech */
    double angularVelocity_;          /* uhlova rychlost nanodegrees per second */

    std :: set<dtIdentifier> subObjects_;  /* podobjekty tohoto objektu */
public:
    vector_dist_t   getPosition(void) const;          /* pozice relativne vuci oblasti rodicovskeho objektu */      
    vector_dist_t   getAbsolutePosition(void) const;  /* pozice absolutne ve vesmiru */
    dist_t          getAreaRadius(void)               const { return areaRadius_;                                                 } 
    dist_t          getObjectRadius(void)             const { return objectRadius_;                                               } 
    double          getSquaredAreaRadius(void)        const { return areaRadius_*areaRadius_;                                     } 
    dist_t          getSquaredObjectRadius(void)      const { return objectRadius_*objectRadius_;                                 } 
    dist_t          getOrbitingSpeed(dist_t d)        const { return computeOrbitalSpeed(d, objectWeight_); } /* m/s */
    dist_t          getOrbitingSpeed(vector_dist_t p) const { return (getOrbitingSpeed(sqrt((double)(p - getPosition()).squaredNorm()))); } 
    weight_t        getWeight(void)                   const { return objectWeight_;                                               } 
    dist_t          getAngularVelocity(void)          const { return angularVelocity_;                                            } /* nanodegrees per second */
    dist_t          getDistanceFromParentObject(void) const { return distanceFromParentObject_;                                   }
    dist_t          getAngle(void) const; /* nanodegrees per second */
    virtual void    getWholeTreeIds(std :: set<dtIdentifier>& ids) const; 
    virtual void    getAimedTargets(std :: list<dtIdentifier>& ids);

	void            setAreaRadius        (dist_t       radius    ) { this->areaRadius_               = radius;     };
	void            setObjectRadius      (dist_t       radius    ) { this->objectRadius_             = radius;     };
	void            setWeight            (weight_t     weight    ) { this->objectWeight_             = weight;     };
	void            setDistanceFromParent(dist_t       distance  ) { this->distanceFromParentObject_ = distance;   };
	void            setBasicRotationAngle(double       basicAngle) { this->basicRotationAngle_       = basicAngle; };
	void            setAngularVelocity   (double       velocity  ) { this->angularVelocity_          = velocity;   };
	void            addChild             (dtIdentifier CHID      ) { this->subObjects_.insert(CHID);               };
    
    dtIdentifierConstIterator subObjectsBegin(void) const       { return subObjects_.begin();                   }
    dtIdentifierConstIterator subObjectsEnd(void) const         { return subObjects_.end();                     }
    unsigned long             subObjectCount(void)              { return (unsigned long) subObjects_.size();    }
    void                      subObjectAdd(dtIdentifier soId)   { subObjects_.insert(soId);                     }  
    void                      subObjectRem(dtIdentifier soId)   { subObjects_.erase(soId);                      }

    bool getNearestCrossingPoint(const vector_dist_t& course, const vector_dist_t& basicpt, vector_dist_t& rslt);

	virtual void          DB_Load       (  unsigned long ID                );
	virtual void          DB_Save       (  unsigned long ID                );
	virtual void          DB_Insert     (  unsigned long ID                );
    virtual ~spaceRegularObject() {};
};

// --------------------------------

enum spaceObjectSunType
{
    WHITE_DWARF = 1,
    STANDARD = 2
};

// --------------------------------

class spaceSunObject : public spaceRegularObject
{
private:
    spaceObjectSunType sunType_;
public:
    spaceSunObject()                            {                   };
    spaceSunObject(vector_dist_t pos)           { position_ = pos;  };
    spaceObjectType getType(void)  const        { return SOT_STAR;  } 
    spaceObjectSunType getSunType(void) const   { return sunType_;  }

	void setStarToBeWhiteDwarf() { this->sunType_ = WHITE_DWARF; };
	void setStarToBeRegular()    { this->sunType_ = STANDARD;    }

    virtual const std :: string          getName(void) const;

	virtual void          DB_Load       (  unsigned long ID                ) sealed;
	virtual void          DB_Save       (  unsigned long ID                ) sealed;
	virtual void          DB_Insert     (  unsigned long ID                ) sealed;
    virtual ~spaceSunObject() {};
};

// --------------------------------

enum spaceObjectPlanetType
{
    TERESTRIAL = 1,
    GAS_TITAN = 2
};

// --------------------------------

#define PLANET_MAX_NUMBER_OF_TERRAINS 7
#define PLANET_TEXTURE_FROZEN         0
#define PLANET_TEXTURE_ALIEN          1
#define PLANET_TEXTURE_CRYSTAL        2
#define PLANET_TEXTURE_DESERT         3
#define PLANET_TEXTURE_GRASS          4
#define PLANET_TEXTURE_MOUNTAINS      5
#define PLANET_TEXTURE_VOLCANO        6
#define DEEP_ICE_TEMPERATURE          -222
#define VOLCANO_DAY_TEMPERATURE       222
#define VOLCANO_NIGHT_TEMPERATURE     100
#define ALIEN_EDGE_OF_ILMENITE        (double)0.2
#define GRASS_EDGE_OF_HEWZRC          (double)0.2
#define MOUNTAIN_EDGE_OF_ILM          (double)0.4

// --------------------------------

class spacePlanetObject : public spaceRegularObject
{
#define SOURCES_MODIFIER             (double)10000
#define THE_BEST_SOURCE              (double)40000
#define ZRC_HEW_SEA_MAX_LIMIT        (double)7500
#define ILM_TERRA_MAX_LIMIT          (double)7500
#define ILM_GAS_MAX_LIMIT            (double)5000
#define WHOLE_SEA_CHANCE             (double)0.04
#define MAX_TEMPERATURE              600
#define MIN_TEMPERATURE              -250
#define MAX_HOSTILE_TEMPERATURE_DIFF (double)600
#define MAX_TEMPERATURE_FOR_SEA      99
#define MIN_TEMPERATURE_FOR_SEA      0
private:
    spaceObjectType         type_; /* planety planet jsou mesice */

	// delka roku a dne je jen pro predstavu, herne to nema vliv
    unsigned long long      yearPeriod_;       // kolik sekund ma rok
    unsigned long long      dayPeriod_;        // kolik sekund ma den
	// na typu planety zavisi stavba zakladen, muze byt i velikost, pokud ji sem nedame, klient to podle toho bude zobrazovat
    spaceObjectPlanetType   planetType_;       // plynny obr, nebo teresticka planeta
	// na teplotach zavisi stavba zakladen, mozna casem i energie do stitu zakladen a tak dale
    long                    dayTemperature_;   // °C, max cca 400, zavisi na vzdalenosti od hvezdy
    long                    nightTemperature_; // °C, min -250, zavisi na atmosfere planety
	// na morich zavisi tezba, ale take nejak udavaji podobu planety
    long                    sea_;              // 0 az 10,000; 0 je sucha planeta, 10,000 je jen more
	// pri 0 morich je i HeWS = 0, do 7k5 mori tvori hodnota mori maximum pro HeWS, pak uz muze byt maximum i vyssi (pochopitelne muze byt i malo HeWS pro morskou planetu)
    long                    heavyWaterSource_; // N >= 0; N / 10,000 je modifikator tezby, takze 10,000 je plna tezba, 20,000 je dvojnasobna tezba
	// pro zirkonove krystaly plati priblizne to, co pro tezkou vodu, i ty more
    long                    zirconiumSource_;  // N >= 0; N / 10,000 je modifikator tezby, takze 10,000 je plna tezba, 20,000 je dvojnasobna tezba
	// pro 10,000 more je 0 IlmS, 10,000 - more tvori maximum pro IlmS do hodnoty 7k5 mori, pro mene mori uz je to jedno a maximum muze byt vyssi (opet maximum nemusi zdaleka odpovidat prumeru)
    long                    ilmeniteSource_;   // N >= 0; N / 10,000 je modifikator tezby, takze 10,000 je plna tezba, 20,000 je dvojnasobna tezba
public:
    spaceObjectType         getType(void)  const            { return type_;             } 
    unsigned long long      getYearPeriod(void) const       { return yearPeriod_;       }
    unsigned long long      getDayPeriod(void) const        { return dayPeriod_;        }
    spaceObjectPlanetType   getPlanetType(void) const       { return planetType_;       }
    long                    getDayTemperature(void) const   { return dayTemperature_;   }
    long                    getNightTemperature(void) const { return nightTemperature_; }
    long                    getSea(void) const              { return sea_;              }
    long                    getHeavyWaterSource(void) const { return heavyWaterSource_; }
    long                    getZirconiumSource(void) const  { return zirconiumSource_;  }
    long                    getIlmeniteSource(void) const   { return ilmeniteSource_;   }
    long                    getTexture(void) const;

	double                  getHeavyWaterModifier()               { return (double)heavyWaterSource_ / SOURCES_MODIFIER;                                        };
	double                  getZirconiumCrystalModifier()         { return (double)zirconiumSource_  / SOURCES_MODIFIER;                                        };
	double                  getIlmeniteModifier()                 { return (double)ilmeniteSource_   / SOURCES_MODIFIER;                                        };
	double                  getHeavyWaterQuarryingPercent()       { return ((double)heavyWaterSource_ - SOURCES_MODIFIER) / SOURCES_MODIFIER * HUNDRED_PERCENT; };
	double                  getZirconiumCrystalQuarryingPercent() { return ((double)zirconiumSource_  - SOURCES_MODIFIER) / SOURCES_MODIFIER * HUNDRED_PERCENT; };
	double                  getIlmeniteQuarryingPercent()         { return ((double)ilmeniteSource_   - SOURCES_MODIFIER) / SOURCES_MODIFIER * HUNDRED_PERCENT; };

	double                  getHostilityModifierByPlanetType()        { if (TERESTRIAL == this->getPlanetType()) return 0; else return 0.75;                                                                                                               };
	double                  getHostilityModifierByPlanetTemperature() { double tempDiff = max((double)0, (double)this->dayTemperature_ - (double)this->nightTemperature_); return max((double)0, min((double)1, tempDiff / MAX_HOSTILE_TEMPERATURE_DIFF)); };

	void                    setPlanetToBeAPlanet()                         { this->type_             = SOT_PLANET;   };
	void                    setPlanetToBeAMoon()                           { this->type_             = SOT_MOON;     };
	void                    setPlanetToBeTerestrial()                      { this->planetType_       = TERESTRIAL;   };
	void                    setPlanetToBeGassTitan()                       { this->planetType_       = GAS_TITAN;    };
	void                    setYearPeriod(unsigned long long periodInSecs) { this->yearPeriod_       = periodInSecs; };
	void                    setDayPeriod(unsigned long long periodInSecs)  { this->dayPeriod_        = periodInSecs; };
	void                    setDayTemperature(long temperature)            { this->dayTemperature_   = temperature;  };
	void                    initPlanetByTypeAndDayTemperature();
	void                    initStartingPlanet();

    virtual const std :: string          getName(void) const;

	virtual void          DB_Load       (  unsigned long ID                ) sealed;
	virtual void          DB_Save       (  unsigned long ID                ) sealed;
	virtual void          DB_Insert     (  unsigned long ID                ) sealed;

    virtual ~spacePlanetObject() {};
};

// --------------------------------

dtIdentifier spaceObjectLeastCommonParent(dtIdentifier v1, dtIdentifier v2);
dtIdentifier spaceObjectLeastCommonParent(spaceObject* v1, spaceObject* v2);
dtIdentifier spaceObjectGetStarSystem(dtIdentifier spaceObj);

// --------------------------------

void getShipsAndBasesInArea(dtIdentifier area, std :: list<dtIdentifier>& ships, 
                            std :: list<dtIdentifier>& bases, bool searchSubareas = false);

// --------------------------------

// ################################
// ################################

class starPosGenerator
{
public:
    virtual vector_dist_t generatePoint(void) const abstract;
	virtual dist_t getSurface() = 0;
	virtual dist_t getLength()  = 0;
};

// ################################

class squareGenerator : public starPosGenerator
{
private:
    dist_t xmin_, xmax_, ymin_, ymax_;
public:
    squareGenerator(const dist_t xmin, const dist_t xmax, const dist_t ymin, const dist_t ymax);
    virtual vector_dist_t generatePoint(void) const;

	dist_t getSurface() { return (this->xmax_ - this->xmin_) * (this->ymax_ - this->ymin_);       };
	dist_t getLength()  { return ((this->xmax_ - this->ymax_) + (this->ymax_ - this->ymin_)) / 2; };
};

// ################################

class circleGenerator : public starPosGenerator
{
private:
    vector_dist_t base_;
    dist_t radius_;
public:
    circleGenerator(const vector_dist_t base, const dist_t radius);
    virtual vector_dist_t generatePoint(void) const;

	dist_t getSurface() { return PI * pow(this->radius_, (double)2); };
	dist_t getLength()  { return 2 * this->radius_;                  };
};

// ################################

class spaceGenerator
{
#define MIN_STARS_IN_SPACE                 8
#define STARTING_PLANET_ORDER              3 // je to treti planeta v databazi, vse viz nize
#define STAR_WEIGHT_MIN                    ((double)1.59128 * pow((double)10, (double)30))
#define STAR_WEIGHT_MAX                    ((double)1.27329 * pow((double)10, (double)32))
#define SUN_WEIGHT                         ((double)1.98951 * pow((double)10, (double)30))
#define STAR_DENSITY_MIN                   (double)1000
#define STAR_DENSITY_MAX                   (double)4000
#define AREA_RADIUS_TO_CENTER_WEIGHT_IN_KM ((dist_t)0.001 / pow((double)10, (double)16))
#define WHITE_DWARF_CHANCE                 (double)0.01
#define WHITE_DWARF_WEIGHT_MIN             ((weight_t)0.95 * WHITE_DWARF_WEIGHT_MAX)
#define WHITE_DWARF_WEIGHT_MAX             ((weight_t)(pow((double)MAXIMAL_IMPULSE_DRIVE_SPEED, (double)2) * (MAGNETICAL_COLLECTORS_OF_PLASMA_RANGE - ROUNDING_ERROR_PROTECTION) * KILO / physicsConstants<double>::kappa()))
#define WHITE_DWARF_RADIUS_MIN             ((dist_t)0.8 * (double)WHITE_DWARF_RADIUS_MAX)
#define WHITE_DWARF_RADIUS_MAX             ((dist_t)MAGNETICAL_COLLECTORS_OF_PLASMA_RANGE / (double)2)
#define ROUNDING_ERROR_PROTECTION          (double)1
#define HABITABLE_SYSTEM_CHANCE            (double)0.9
#define PLANETS_IN_HABITABLE_SYSTEM_MIN    1
#define PLANETS_IN_STARTING_SYSTEM         12
#define PLANETS_IN_HABITABLE_SYSTEM_MAX    16
#define FIRST_GASS_TITAN_CHANCE            (double)0.08
#define PLANET_MIN_WEIGHT                  ((double)1.2500 * pow((double)10, (double)22))
#define PLANET_MED_WEIGHT                  ((double)5.9736 * pow((double)10, (double)24))
#define PLANET_MAX_WEIGHT                  ((double)1.8990 * pow((double)10, (double)27))
#define SMALL_PLANET_CHANCE                (double)0.25
#define PLANET_MAX_WEIGHT_TO_STAR_WEIGHT   (PLANET_MAX_WEIGHT / SUN_WEIGHT);
#define TERESTIC_MIN_DENSITY               (double)4540
#define TERESTIC_MAX_DENSITY               (double)6460
#define GAS_TITAN_MIN_DENSITY              (double)512
#define GAS_TITAN_MAX_DENSITY              (double)2048
#define SOLAR_SYSTEM_MAX_RADIUS            (double)8859564408
#define SOLAR_SYSTEM_MED_RADIUS            (double)4498252900
#define SOLAR_SYSTEM_MIN_RADIUS            (double)1426725413
#define SOLAR_SYSTEM_INNER_RADIUS          (double)28954588
#define SPACE_OBJECT_DISTANCE_SPACING      (double)1.1
#define MERCURY_LOOP1                      0
#define MERCURY_WEIGHT                     ((double)3.3020 * pow((double)10, (double)23))
#define MERCURY_CIRCLE                     (double)57909176
#define VENUS_LOOP1                        1
#define VENUS_WEIGHT                       ((double)4.8685 * pow((double)10, (double)24))
#define VENUS_CIRCLE                       (double)108208926
#define EARTH_LOOP1                        2
#define EARTH_WEIGHT                       ((double)5.9736 * pow((double)10, (double)24))
#define EARTH_CIRCLE                       (double)149597887
#define MIN_DAY_IN_SECONDS                 16384
#define MAX_GAS_DAY_IN_SECONDS             65536
#define MAX_DAY_IN_SECONDS                 33554432
#define MIN_STARS_DISTANCE_IN_KM           (2 * SPACE_OBJECT_DISTANCE_SPACING * STAR_WEIGHT_MAX * AREA_RADIUS_TO_CENTER_WEIGHT_IN_KM)
private:
	static unsigned long lengthToStarCount(dist_t length);
public:
    static void addStars(unsigned long reindexLimit /* doporucena odmocnina z howMany plus poctu vsech existujcich hvezd */,
		                 unsigned long howMany, 
						 starPosGenerator & generator);
    static void initializeSolarSystem(spaceSunObject * star, bool & isWhiteDwarfInSpace);

	static void makeCircleSpace(dist_t radius);
	static void makeSquareSpace(dist_t side  );
	static void createNewSmallSpace();
	static void createSpace(double stars);
};

// ################################

#endif