#ifndef D_PATH_H
#define D_PATH_H

#include "common.h"
#include "d/d_bzs_types.h"
#include "m/m_vec.h"

/**
 * A dPath_c describes a parametric 3D curve in a stage.
 * This base path class provides read-only evaluation of the curve.
 *
 * A path consists of a number of points that this curve passes through.
 * Paths can have two ends, or wrap around, connecting the end point with the start point again.
 * The curve can either linearly connect the points, or it can be implemented as a spline
 * (splines are used for certain actors where smooth movement is necessary, e.g. Sandship).
 *
 * A curve point (or the segment between that point and the next) is identified by an integer point index [0; n-1], and
 * each segment itself is parametrized by a float parameter in [0.0; 1.0]. We call this parameter "time", though since
 * no requirements are imposed on the distance between points, there is no guarantee of constant speed across the curve.
 * ActorOnRail_Ext (todo rename) provides calculations that allow actors to move along the curve with constant speed.
 */
class dPath_c {
private:
    const PATH *mpPathPtr;
    s32 mRoomIndex;
    bool mPathSubtype;

    /** Must only be called for spline paths. Extracts the relevant path points and control points for the given
     * segment. */
    bool extractControlPoints(
        s32 pointIndex, mVec3_c &currPos, mVec3_c &currControl2, mVec3_c &nextControl1, mVec3_c &nextPos
    ) const;

    /**
     * Check if the given path segment is a linear segment - even splines can have linear segments!
     * This can speed up some calculations.
     */
    bool isLinearSegment(s32 pointIndex) const;

    void set(const PATH *path, s32 roomIndex, bool subtype);

    const PNT *getPathPoint(s32 pointIndex) const;
    const BPNT *getPathBpoint(s32 pointIndex) const;

public:
    dPath_c();

    virtual ~dPath_c();

    bool isWrapping() const {
        return mpPathPtr->flags & PATH_FLAG_WRAP_AROUND;
    }

    bool isLinear() const {
        return (mpPathPtr->flags & PATH_FLAG_SPLINE) == 0;
    }

    s32 getNumPoints() const {
        return mpPathPtr->pointCount;
    }

    s32 getLastPointIdx() const {
        s32 pnt = getNumPoints() - 1;
        return pnt >= 0 ? pnt : 0;
    }

    bool initWithPathId(s32 pathId, s32 roomId, bool pathSubtype);
    bool initWithPathIndex(s32 pathIndex, s32 roomId, bool pathSubtype);

    void clear();

    /** Get a given point that the curve passes through. */
    const Vec *getPoint(s32 pointIndex) const;
    /** Evaluates the curve position for the given segment and time. */
    void getPoint(s32 pointIndex, f64 time, mVec3_c &out) const;
    /** Extract point param - TODO what is this used for? */
    u8 getPointParam(s32 pointIndex, s32 param) const;

    /** Return the index of this PATH in the current room. */
    u16 getMyPathIndex(s32 roomId) const;
    /** Apparently paths can be linked somehow. */
    bool getNextPath(dPath_c *next) const;

    /**
     * Approximate the time for the given distance along the segment.
     * The return value is clamped to [0.0, 1.0]. If the distance exceeds
     * the length of this segment, write the excess to the provided pointer.
     */
    f32 getSegmentTime(s32 pointIndex, f32 segmentDistance, f32 stepSize, f32 *excess);

    /**
     * Approximates the distance already traveled on the given segment for the given time.
     * Calling this with time == 1.0f returns the segment length.
     * Higher resolution -> higher accuracy.
     */
    f32 getDistanceMovedOnSegment(s32 pointIndex, s32 resolution, f32 time) const;
    /** Calculates the normalized direction of an actor moving along the segment for the given time. */
    bool getDirection(s32 segmentIndex, f32 time, mVec3_c &result) const;
    /** Calculates the speed (scalar) of an actor moving along the segment at the given time. */
    bool getSpeed(s32 segmentIndex, f32 time, f32 *result) const;
    /** Calculates the velocity (vector) of an actor moving along the segment at the given time. */
    bool getVelocity(s32 segmentIndex, f32 time, mVec3_c &result) const;
};

/** A stateful "path driver" that moves along a given path with a given speed. */
class ActorOnRail_Ext {
public:
    ActorOnRail_Ext();
    ~ActorOnRail_Ext();

    f32 getRemainingDistanceOnSegment() const;
    s32 getClosestXZPoint(const mVec3_c &) const;
    void setSegment(s32 segmentIndex, f32 segmentTime);
    bool
    init(s32 pathIndex, s32 roomId, u32 flags, s32 segmentIndex, bool pathSubtype, f32 segmentTime, f32 speed, f32 unk);
    s32 execute();

    s32 getSegmentIndex() const {
        return mSegmentIndex;
    }

    f32 getSegmentTime() const {
        return mSegmentTime;
    }

    const mVec3_c &getPosition() const {
        return mPosition;
    }

    void setSpeed(f32 speed) {
        mSpeed = speed;
    }

    f32 getSpeed() const {
        return mSpeed;
    }

    void getDirection(mVec3_c &result) {
        mPath.getDirection(mSegmentIndex, mSegmentTime, result);
    }

    const mVec3_c *getPoint(s32 idx) const {
        return reinterpret_cast<const mVec3_c *>(mPath.getPoint(idx));
    }

    bool checkFlag(u32 flags) const {
        return (mFlags & flags) != 0;
    }

    void unsetFlag(u32 flags) {
        mFlags &= ~flags;
    }

    void setFlag(u32 flags) {
        mFlags |= flags;
    }

    const dPath_c &getPath() const {
        return mPath;
    }
    s32 getNextPointIndex2() const;

private:
    s32 getNextPointIndex(s32 point) const;
    s32 getNextPointIndex() const;

    /* 0x00 */ dPath_c mPath;
    /* 0x10 */ s32 mSegmentIndex;
    /* 0x14 */ f32 mSegmentTime;
    /* 0x18 */ f32 mSpeed;
    /* 0x1C */ u32 mFlags;
    /* 0x20 */ f32 field_0x20;
    /* 0x24 */ f32 mSegmentDistance;
    /* 0x28 */ mVec3_c mPosition;
};

class ActorOnRail3 {
    /* 0x00 */ dPath_c mPath;
    /* 0x10 */ u16 mSomePntIdx;
    /* 0x14 */ u32 mFlags;

public:
    ActorOnRail3() : mSomePntIdx(0), mFlags(0) {}
};

#endif
