#pragma once

#include "dk_shared.h"
#include "dk_gce_point.h"

//moves one parameter from a start to an end with a start and end velocity over a specified length of time.
class CCubicSpline {
  public:
    CCubicSpline();
    
    //computes the spline parameters.
    void Init(float p0, float d0, float p1, float d1, float time);
    void Init(CCubicSpline &other);

    //this command can only be run in the dll
    void Save(FILE *file);
    //can be run from the exe only
    void Load(FILE *file);

  protected:
    //the cubic parameters for x, y, and z.
    float a, b, c, d;

  public:
    float Value(float time);
    float Dir(float time);
};

//moves a point from a start to an end.
class CPointSpline {
  public:
    CPointSpline();

    void Init(point &p0, point &d0, point &p1, point &d1, float time);
    void Init(CPointSpline *other);

    //this command can only be run in the dll
    void Save(FILE *file);
    //can be run from the exe only
    void Load(FILE *file);

  protected:
    float time;

    //splines for x y and z.
    CCubicSpline x, y, z;

  public:
    void Pos(float time, point &pos);
    void Dir(float time, point &dir);

    //computes the length of the spline from time 0 to time.
    float Len(float time);
};

typedef enum {
    EV_DEFAULT,
    EV_ZERO,
} end_vel;

//stores a string of splines all connected one after another.
class CSplineSequence {
  public:
    CSplineSequence();
    ~CSplineSequence();

    void operator=(const CSplineSequence &other);

    void Init(const point &start, const point &dir);

    //wipes out all data.
    void Reset();

    //this command can only be run in the dll
    void Save(FILE *file);
    //can be run from the exe only
    void Load(FILE *file, int version);

  protected:
    //true if this is a position only spline sequence.
    bool position_only;

    //the number of points in the sequence.
    int num_points;

    //the time we take for each segment.
    float *times;

    //our positions.
    point *points;
    //our velocity direction.
    point *velocity_norm;
    //our velocity magnitueds.
    float *velocity;

    //our directions.
    point *directions;
    //the velocity vectors of the direction.
    point *dir_vel_norm;
    //the magnitude of the direction velocity
    float *dir_vel;

    //the splines that connect the points.
    CPointSpline **pos_splines;
    //the splines that interpolate the directions.
    CPointSpline **dir_splines;

    //the velocity flags we use for beginning and end of the sequence.
    end_vel velocity_start, velocity_end;

    //computes all the splines.
    void ComputeSplines(point *points, point *vel_norms, float *velocity, CPointSpline **splines, bool wrap);

    //the total time of the sequence.
    float total_time;

    //these values used to speed up the computation of segment number given a time.
    int cache_segment;
    float cache_time;

    //computes the total time of the sequence.
    void ComputeTime();

    //
    //private utility functions.
    //
    //worker function used by MakeCurveEntities
    void MakeCurveEntity(float time_start, float time_end, int segment_num, entity_t *reference_entity, entity_t *(*entity_alloc)());

  public:
    //
    //member access
    //
    int NumPoints() const;
    point &Point(int index) const;
    point &Angle(int index) const;
    end_vel VelStart() const;
    end_vel VelEnd() const;
    float Time(int segment) const;
    float Velocity(int pnt) const;
    float TotalTime() const;
    void Velocity(int pnt, point &vel) const;


    //
    //functions for modifying the sequence.
    //

    //adds a point to the sequence.  time is the time it takes to get from the previous point to this point.
    void Add(const point &loc, const point &dir, float time);
    //sets the time/angle/position for the given segment
    void Time(int segment, float time);
    void Angle(int index, const point &angle);
    void Pos(int index, const point &pos);
    //sets the start and end velocity.
    void VelStart(end_vel vel);
    void VelEnd(end_vel vel);

    //
    //utility functions
    //

    //returns the segment that is active at the given time.
    int Segment(float &time);

    //
    //playback fuctions.
    //
    //gets the position on the spline at the given time.
    void Pos(float time, point &pos);
    void Dir(float time, point &dir);

    //gets the derivative of pos, normalized.
    void Vel(float time, point &vel);

    //
    //functions for drawing a sequence with lasers.
    //
    //Initializes the position splines with the given array of positions and velocities.
    void InitPositions(int num_positions, const vec3_t *positions, const vec3_t *velocities);

    //Makes as many entities as are needed to represent the sequence position splines.
    //Uses the given function for allocating entities.  reference_entity is used
    //to initialize data memebers in the entity that are not actually needed by the function
    //to position the laser entities.  max_angle (in degrees) affects the smoothness of the curve by specifying
    //the maximum allowed angle between adjacent entities.  max_length specifies the maximum length of
    //any entity
    int MakeCurveEntities(entity_t *reference_entity, entity_t *(*entity_alloc)(), float max_angle, float max_length);
};

//various utility functions that can be used when constructing splines.

//returns the smooth direction a spline should take when passing through loc from before, towards after.
void SplineDirection(point &before, point &loc, point &after, point &dir, bool wrap);
void SplineStartDir(point &loc, point &after, point &after_dir, point &dir, bool wrap);
void SplineEndDir(point &before, point &loc, point &before_dir, point &dir, bool wrap);

//returns the reflection of a point through a line.  Line should be normalized.
void PointReflection(point &pnt, point &line, point &reflect);

//sets wrapped_end to an equivalent angle_end
void ComputeEndAngleWrap(point &angle_start, point &angle_end, point &wrapped_end);

