
/* path.hpp - path rendering class. */

/* Requires the OpenGL Utility Toolkit (GLUT) and Cg runtime (version
   2.0 or higher). */

#ifndef __path_hpp__
#define __path_hpp__

#if defined(_MSC_VER) && (_MSC_VER >= 1020)
# pragma once
#endif

#include <vector>
#include <string>

#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/lexical_cast.hpp>

#include <stdio.h>    // for printf and NULL
#include <stdlib.h>   // for exit
#include <math.h>     // for sin and cos
//#include "util/gl_headers.h"

#include <Cg/double.hpp>
#include <Cg/vector/xyzw.hpp>
#include <Cg/vector.hpp>
#include <Cg/matrix.hpp>
#include <Cg/max.hpp>
#include <Cg/min.hpp>

#include <GL/glew.h>

#include "path_stats.hpp"
#include "sg/PathSegmentProcessor.hpp"

#if defined(_MSC_VER)
# pragma warning(disable: 4355) /* 'this' : used in base member initializer list */
#endif

// Grumble, Microsoft's <windef.h> (and probably other headers) define these as macros
#undef min
#undef max

using namespace Cg;
using std::string;

// Use Boost shared pointer template names.
using boost::shared_ptr;
using boost::enable_shared_from_this;
using boost::weak_ptr;
using boost::lexical_cast;

using std::vector;

struct Command {
    Command() { }
    Command(char _cmd, int _ndx) : cmd(_cmd), ndx(_ndx) { }

    char cmd;
    int ndx;
};

typedef shared_ptr<struct Path> PathPtr;
typedef shared_ptr<struct Node> NodePtr;

//#include "ActiveControlPoint.hpp"
#include "sg/PathStyle.hpp"

//typedef RendererState<Path> PathRendererState;
//typedef shared_ptr<PathRendererState> PathRendererStatePtr;

class ShadersGL;

typedef unsigned int VersionNumber;

class PathVersion {
private:
    VersionNumber version;

public:
    PathVersion()
        : version(1)
    {}
    unsigned int get() const {
        return version;
    }
    void bump() {
        version++;
        if (version == 0) {
            // Wrapping versions should never allow zero since zero is always an invalid version.
            // XXX wrapping could cause validation errors once in a blue moon.
            version = 1;
        }
    }
};

// Command values for spline support
// XXX not in shipping NV_path_rendering
#define GL_PRIME_SPLINE_TO_NV                               0x1C
#define GL_RELATIVE_PRIME_SPLINE_TO_NV                      0x1D
#define GL_SPLINE_TO_NV                                     0x1E
#define GL_RELATIVE_SPLINE_TO_NV                            0x1F
#define GL_BEGIN_CLOSED_SPLINE_TO_NV                        0x20
#define GL_RELATIVE_BEGIN_CLOSED_SPLINE_TO_NV               0x21
#define GL_END_CLOSED_SPLINE_TO_NV                          0x22

struct Path : enable_shared_from_this<Path> /*, HasRendererState<Path>*/ {
private:
    bool has_logical_bbox;
    float4 logical_bbox;

    bool valid_actual_bbox;
    mutable float4 actual_bbox;

    PathVersion version;

    // Path data
    vector<char> cmd;
    vector<float> coord;

public:
    friend class OptimizeGroups;
    PathStyle style;

    Path(const PathStyle &style);
    Path(const PathStyle &style, const char *string);
    Path(const PathStyle &style, const vector<char> &cmds, const vector<float> &coords);
    Path(const char *string);
    Path(const vector<char> &cmds, const vector<float> &coords);

    void invalidate();

    Path & operator = (const Path &src) {
        if (this != &src) {
            assert(!"rethink");

            this->cmd = src.cmd;
            this->coord = src.coord;
            this->style = src.style;

            this->has_logical_bbox = src.has_logical_bbox;
            this->logical_bbox = src.logical_bbox;

            this->valid_actual_bbox = src.valid_actual_bbox;
            this->actual_bbox = src.actual_bbox;

#if 0
            // Empty the renderer_state array
            this->renderer_states = vector<RendererStatePtr>();
#endif
        }
        return *this;
    }

    inline const float &getCoord(size_t ndx) const {
        return coord[ndx];
    }
    inline void setCoord(size_t ndx, float2 v) {
        coord[ndx+0] = v.x;
        coord[ndx+1] = v.y;
        valid_actual_bbox = false;
    }
    inline void setCoord(size_t ndx, float1 v) {
        coord[ndx+0] = v;
        valid_actual_bbox = false;
    }
    inline size_t getCmdSize() const {
        return cmd.size();
    }
    inline size_t getCoordSize() const {
        return coord.size();
    }
    inline GLsizei getGLCmdSize() const {
        return GLsizei(cmd.size());
    }
    inline GLsizei getGLCoordSize() const {
        return GLsizei(coord.size());
    }

    inline const vector<char>& getCmds() const {
        return cmd;
    }
    inline const vector<float>& getCoords() const {
        return coord;
    }
    inline const GLubyte* getGLCmds() const {
        return reinterpret_cast<const GLubyte*>(&cmd[0]);
    }
    inline const GLfloat* getGLCoords() const {
        return &coord[0];
    }
    inline VersionNumber getVersion() const {
        return version.get();
    }

    bool isEmpty() const;
    bool allowDilation() const {
        return style.allow_dilation;
    }

    string convert_to_svg_path(const float4x4 &transform);

    void drawControlPoints();
    void drawControlPoints(ShadersGL*);
    void drawReferencePoints();
    void drawReferencePoints(ShadersGL*);

#if 0
    void testForControlPointHit(ActiveControlPoint &hit,
                                float2 &p,
                                size_t coord_index,
                                float2 &relative_to_coord,
                                ActiveControlPoint::CoordUsage coord_usage,
                                bool &hit_path);
    void findNearerControlPoint(ActiveControlPoint &hit);
#endif

    int countSegments();

    float4 getActualFillBounds();   // computes bounding box for filled region of path
    float4 getDilatedFillBounds(bool include_stroking);  // dilates actual fill bounds by stroke width, if stroking
    float4 getBounds();             // substitute logical bounds, if specified, for dilated fill bounds

    void setLogicalBounds(const float4 &bbox);
    void unsetLogicalBounds();

    // Iterate over all the path's segments, determining the
    // segment data, and calling the appropriate segment processor
    // virtual function for the segment type (moveto, lineto, etc.).
    void processSegments(PathSegmentProcessor &processor);

    bool sameStyle(const PathPtr other_path) const {
        return style == other_path->style;
    }

    void unionBounds(const PathPtr other_path) {
        if (has_logical_bbox) {
            if (other_path->has_logical_bbox) {
                logical_bbox.xy = min(logical_bbox.xy, other_path->logical_bbox.xy);
                logical_bbox.zw = max(logical_bbox.zw, other_path->logical_bbox.zw);
            }
        } else {
            has_logical_bbox = other_path->has_logical_bbox;
            logical_bbox = other_path->logical_bbox;
        }
        if (valid_actual_bbox) {
            if (other_path->valid_actual_bbox) {
                actual_bbox.xy = min(actual_bbox.xy, other_path->actual_bbox.xy);
                actual_bbox.zw = max(actual_bbox.zw, other_path->actual_bbox.zw);
            }
        } else {
            valid_actual_bbox = other_path->valid_actual_bbox;
            actual_bbox = other_path->actual_bbox;
        }
    }
};

class PathVersionValidation {
private:
    VersionNumber version;
public:
    PathVersionValidation()
        : version(0)
    {}
    bool same(const Path *path) const {
        if (version == path->getVersion()) {
            return true;
        } else {
            return false;
        }
    }
    bool match(const Path *path) {
        VersionNumber path_version = path->getVersion();
        if (version == path_version) {
            return true;
        } else {
            version = path_version;
            return false;
        }
    }
};

// path_util.cpp
bool pointCompare(const float2 &a, const float2 &b);
bool pointEqual(const float2 &a, const float2 &b);
float3x3 getPlaneEqs(const float2 b[4], const double4x3 &M);
template <typename Tvector>
double solve_curve_segment_intersection(const Tvector *b);
template <typename Tvector>
double solve_curve_segment_intersection(const Tvector &A,
                                        const Tvector &B,
                                        const Tvector &C,
                                        const Tvector &D);
extern double solve_curve_segment_intersection(const double2 *);
#ifndef NDEBUG
template <typename Tvector>
double no_curve_segment_intersection(const Tvector &A,
                                     const Tvector &B,
                                     const Tvector &C,
                                     const Tvector &D);
#endif

#endif // __path_hpp__
