// ################################
// ## Fyzikalni vzorce a vztahy, ##
// ## prevody jednotek a podobne ##
// ################################

#include "physics.h"

#include <cmath>
#include <limits>

#include "error_handling.h"

#ifdef _DEBUG
#ifndef DEBUG_NEW
#define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__)
#define new DEBUG_NEW
#endif
#endif

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

unsigned long long forceToStopMovingObject(unsigned long long weight, unsigned long long speed, unsigned long long time)
{
	if (0 < time)
	{
		return weight / time * speed;
	}
	else
	{
		errLogicalError("Zero time for stopping object.");
		return 0;
	}
}

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

unsigned long long consumptionToStopMovingObject(unsigned long long jetForce, unsigned long jetConsumption, unsigned long long weight, unsigned long long speed)
{
	if (0 < jetForce)
	{
		return (unsigned long long)ceill((long double)speed * (long double)jetConsumption / (long double)jetForce * (long double)weight);
	}
	else
	{
		errLogicalError("Stopping engine has no force.");
		return 0;
	}
}

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

double convertDatetimeToSeconds(const dv_time:: datetime dt)
{
    return dt / dv_time:: datetime :: second();
}

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

dv_time:: datetime convertSecondsToDatetime(const double seconds)
{
    return seconds * dv_time:: datetime :: second();
}

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

vector_dist_t updatePosition(vector_dist_t oldPos /* (km, km) */, vector_dbl_t course /* normed vector */,
                             double speed /* m/sec */, double acceleration /* m/sec */,
                             dv_time:: datetime deltaTime /* regular time */)
{
    const long double dseconds      = convertDatetimeToSeconds(deltaTime);
    const long double dspeed        = unitsConversion<long double> :: mPerSecondToKmPerSecond((long double)speed);
    const long double dacceleration = unitsConversion<long double> :: mPerSecondToKmPerSecond((long double)acceleration);
    const long double medspeed      = dspeed + dacceleration * dseconds * 0.5L;
    const long double maxspeed      = unitsConversion<long double> :: mPerSecondToKmPerSecond((long double)MAXIMAL_IMPULSE_DRIVE_SPEED);
    const long double realspeed     = std :: min(medspeed, maxspeed);
    const vector_dbl_t deltaPos     = course * realspeed * dseconds;
    return oldPos + convert_vec<dist_t, double>(deltaPos);
}

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

vector_dist_t updateWarpPosition(vector_dist_t oldPos /* (km, km) */, vector_dbl_t course /* normed vector */,
                             double speed /* m/sec */,
                             dv_time:: datetime deltaTime /* regular time */)
{
    const long double dseconds      = convertDatetimeToSeconds(deltaTime);
    const long double realspeed     = unitsConversion<long double> :: mPerSecondToKmPerSecond((long double)speed);
    const vector_dist_t deltaPos     = course * realspeed * dseconds;
    return oldPos + deltaPos;
}

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

double updateSpeed(double speed /* m/sec */, double acceleration /* m/sec */, 
                               dv_time:: datetime deltaTime)
{
    const long double dseconds      = convertDatetimeToSeconds(deltaTime);
    const long double dacceleration = (long double)acceleration;  
    return std :: min<long double>(speed + (double)(dacceleration * dseconds), (long double)MAXIMAL_IMPULSE_DRIVE_SPEED);
}

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

dv_time:: datetime computeTimeForTravelDistance(double distance /* km */, double speed /* m/s */)
{
    const long double ddistance = (long double) distance;
    const long double dspeed    = unitsConversion<long double> :: mPerSecondToKmPerSecond((long double) speed);
    if (dspeed == 0) 
    {
        errLogicalError("Speed [%u] is zero, cannot compute travel time", (unsigned long long)speed);
        return 0.0;
    }
    else
    {
        return convertSecondsToDatetime(ddistance / dspeed);
    }
}

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

dv_time:: datetime computeTimeForTravelDistance(double distance /* km */, double speed /* m/s */, double acceleration /* m/s^2 */)
{
    if (acceleration == 0)
    {
        return computeTimeForTravelDistance(distance, speed);
    }
    else
    {
        const long double ddistance         = (long double) distance;
        const long double dspeed            = unitsConversion<long double> :: mPerSecondToKmPerSecond((long double) speed);
        const long double dmaxspeed         = unitsConversion<long double> :: mPerSecondToKmPerSecond((long double)MAXIMAL_IMPULSE_DRIVE_SPEED);
        const long double dacceleration     = unitsConversion<long double> :: mPerSecondToKmPerSecond((long double) acceleration);

        /* spocitame, za jak dlouho dosahneme rychlosti svetla */
        const long double t0                = (dmaxspeed - dspeed) / dacceleration; /* seconds */
        const long double vmean             = (2.0L * dspeed + dacceleration * t0) / 2.0L;
        const long double s0                = t0 * vmean;

        if (s0 >= ddistance)
        {
            /* a/2t^2 + vt - s = 0 */
            const long double discriminant = dspeed*dspeed + 2 * ddistance * dacceleration;
            const long double t1 = (- dspeed - sqrt(discriminant)) / dacceleration;
            const long double t2 = (- dspeed + sqrt(discriminant)) / dacceleration;

            return convertSecondsToDatetime(std :: max(t1, t2));
        }
        else
        {
            /* zrychlime a pak letime konstantni rychlosti */
            return (convertSecondsToDatetime(t0) + computeTimeForTravelDistance(ddistance - s0, dmaxspeed));
        }
    }
}

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

unsigned long long computeNormalizedNanodegrees(unsigned long long ndg /* nanodegrees */) /* nanodegrees */
{
    return ndg % (360 * unitsConversion<unsigned long long> :: degreesToNanoDegrees(1));
}

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

vector_dist_t updatePositionOrbiting(vector_dist_t base /* (km, km) */, dist_t distance /* km */,
                                     double angle /* nanodegrees */)
{
    const double normalizedAngle = (double)computeNormalizedNanodegrees((unsigned long long)angle);
    const long double dangle = unitsConversion<long double> :: degreesToRadians(unitsConversion<long double> :: nanoDegreesToDegrees((long double)normalizedAngle));

    return base + vector_dist_t(((double)distance * cos(dangle)), ((double)distance * sin(dangle)));
}

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

vector_dist_t updatePositionOrbiting(vector_dist_t base /* (km, km) */, double distance /* km */,
                                     double basicangle /* nanodegrees */, 
                                     double angularSpeed /* nanodegrees/s*/, dv_time:: datetime dt) /* (km, km) */
{
    const double angle = basicangle + ((double)angularSpeed * convertDatetimeToSeconds(dt)); 
    return updatePositionOrbiting(base, distance, angle);
}

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

dist_t computeOrbitalSpeed(dist_t distance /* km */, double weight /* kg */)
{
    return (dist_t)(sqrt(physicsConstants<long double> :: kappa() * weight / unitsConversion<long double> :: KmTom((long double)distance)));
}

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

dist_t computeAngularVelocity(dist_t distance /* km */, double weight /* kg */) /* nanodegrees/s */
{
    return (dist_t)unitsConversion<long double> :: degreesToNanoDegrees((sqrt(physicsConstants<long double> :: kappa() * weight / (long double)distance)) / (2.0L * PI * (long double)distance));
}

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

dist_t computeGravityAcceleration(dist_t distance /* km */, double weight /* kg */) /* m/s^2 */
{
    const long double ddistance = unitsConversion<long double> :: KmTom((long double)distance);
    return (dist_t)(physicsConstants<long double> :: kappa() * weight / (ddistance * ddistance));
}

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

vector_dbl_t  computeComposedVectors(vector_dbl_t v1, vector_dbl_t v2, double norm1, double norm2)
{
    return v1.normedVector() * norm1 + v2.normedVector() * norm2;
}

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

double computeTravelledDistance(double speed /* m/s */, dv_time:: datetime dt) /* km */
{
    return (double)unitsConversion<long double> :: mToKm((double)speed * convertDatetimeToSeconds(dt));
}

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

double computeBasicAngle(vector_dist_t position /* (km,km) */) /* nanodegrees */
{
    position            = position.normedVector();
    long double alpha   = convert_vec<double, dist_t>(position) * vector_dbl_t(1.0, 0.0);
    long double radians = acos(alpha);

    if (position.y < 0)
        radians = 2.0L * PI - radians;

    return (double)unitsConversion<long double> :: degreesToNanoDegrees(unitsConversion<long double> :: radiansToDegrees(radians));
}

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

double computeAcceleration(double force /* G kg.m/s^2 */, double weight /* kg */)
{
    return unitsConversion<double> :: GNewtonsToNewtons(force) / weight;
}

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

double computeForceFromAcceleration(double gacceleration /* m/s^2 */, double weight /* kg */) /* GN */
{
    double force = unitsConversion<double> :: NewtonsToGNewtons(gacceleration * weight);
    if (force == 0)
        force = 1;

    return force;
}

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

double computeGravitationCircleRadius(double weight /* kg */, double speed /* m/s */) /* km */
{
    if (speed == 0)
        return numeric_limits<double> :: infinity();
    else
        return (physicsConstants<double> :: kappa() * weight / speed) / speed;
}

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

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