
// PathContainer.hpp - path container interface for "hairline" stroking algorithm testbed

#ifndef __PathContainer_hpp__
#define __PathContainer_hpp__

#include "sg/path.hpp"
#include "polar/gl/GLPathStrokeState.hpp"
#include "common/gl_program.hpp"  // for GLSLProgram
#include "Paint.hpp"  // for Paint

#include "color_space.hpp"
#include "sRGB_math.h"

#define GL_PATH_STROKE_POLICY_NV                            0x9375
#define GL_SVG_POLICY_NV                                    0x9376
#define GL_PDF_POLICY_NV                                    0x9377
#define GL_XPS_POLICY_NV                                    0x9378

class PathContainer {
    PathPtr path;
    GLuint nvpr_path;
    GLPathStrokeStateExt tess_path;
    float3 fill_color;
    float3 stroke_color;
    float fill_opacity;
    float stroke_opacity;
    bool show_me;
    std::vector<float2> catmull_rom_extra_points;

    void specifyNVprPath(GLuint nvpr_path, const vector<char> &cmds, const vector<float> &coords);

public:
    IndexedStrokedPath indexed_path_state;  // XXX code this be private?

    PathContainer()
        : nvpr_path(0)
        , fill_color(1,1,1)
        , stroke_color(1,1,1)
        , fill_opacity(1)
        , stroke_opacity(1)
        , show_me(true)
    {}

    void setParameters(const Paint& paint);
    void setDashState(PathStyle default_style);

    void updateLineSpline(size_t count, const float3 points[]);
    void updateLineSpline(const vector<float3> points);
    void loadLineSpline(size_t count, const float3 points[]);
    void loadLineSpline(const vector<float3> points);

    void updateQuadraticSpline(size_t count, const float3 points[]);
    void updateQuadraticSpline(const vector<float3> points);
    void loadQuadraticSpline(size_t count, const float3 points[]);
    void loadQuadraticSpline(const vector<float3> points);

    void updateConicSpline(size_t count, const float3 points[]);
    void updateConicSpline(const vector<float3> points);
    void loadConicSpline(size_t count, const float3 points[]);
    void loadConicSpline(const vector<float3> points);

    void updateCubicSpline(size_t count, const float3 points[]);
    void updateCubicSpline(const vector<float3> points);
    void loadCubicSpline(size_t count, const float3 points[]);
    void loadCubicSpline(const vector<float3> points);

    void updateCatmullRomLoop(size_t count, const float3 points[]);
    void updateCatmullRomLoop(const vector<float3> points);
    void loadCatmullRomLoop(size_t count, const float3 points[]);
    void loadCatmullRomLoop(const vector<float3> points);

    void updateCatmullRomSpline(size_t count, const float3 points[]);
    void updateCatmullRomSpline(const vector<float3> points);
    void loadCatmullRomSpline(size_t count, const float3 points[]);
    void loadCatmullRomSpline(const vector<float3> points);

    void load(size_t count, const char *path_string[]);
    void process(bool include_caps, bool include_joins);
    void process();
    void setColor(float3 color) {
        fill_color = color;
        stroke_color = color;
    }
    void setColor(float r, float g, float b) {
        fill_color = float3(r,g,b);
        stroke_color = float3(r,g,b);
    }
    void setFillColor(float r, float g, float b) {
        fill_color = float3(r,g,b);
    }
    void setStrokeColor(float r, float g, float b) {
        stroke_color = float3(r,g,b);
    }
    void setOpacity(float v) {
        fill_opacity = v;
        stroke_opacity = v;
    }
    void setStrokeOpacity(float v) {
        stroke_opacity = v;
    }
    void setFillOpacity(float v) {
        fill_opacity = v;
    }
    void sendFillColor() const {
        if (color_space == UNCORRECTED) {
            float r = convertLinearColorComponentToSRGBf(fill_opacity*fill_color[0]);
            float g = convertLinearColorComponentToSRGBf(fill_opacity*fill_color[1]);
            float b = convertLinearColorComponentToSRGBf(fill_opacity*fill_color[2]);
            glColor4f(r,g,b,fill_opacity);
        } else {
            glColor4f(fill_opacity*fill_color[0],fill_opacity*fill_color[1],fill_opacity*fill_color[2],fill_opacity);
        }
    }
    void sendStrokeColor() const {
        if (color_space == UNCORRECTED) {
            float r = convertLinearColorComponentToSRGBf(stroke_opacity*stroke_color[0]);
            float g = convertLinearColorComponentToSRGBf(stroke_opacity*stroke_color[1]);
            float b = convertLinearColorComponentToSRGBf(stroke_opacity*stroke_color[2]);
            glColor4f(r,g,b,stroke_opacity);
        } else {
            glColor4f(stroke_opacity*stroke_color[0],stroke_opacity*stroke_color[1],stroke_opacity*stroke_color[2],stroke_opacity);
        }
    }
    void programColor(GLSLProgram *program) const {

        if (color_space == UNCORRECTED) {
            float r = convertLinearColorComponentToSRGBf(stroke_opacity*stroke_color[0]);
            float g = convertLinearColorComponentToSRGBf(stroke_opacity*stroke_color[1]);
            float b = convertLinearColorComponentToSRGBf(stroke_opacity*stroke_color[2]);
            program->set4f("stroke_color",
                r,g,b,stroke_opacity);
        } else {
            program->set4f("stroke_color",
                stroke_opacity*stroke_color[0],stroke_opacity*stroke_color[1],stroke_opacity*stroke_color[2],stroke_opacity);
        }
    }
    void drawStrokeTessellated(GLSLProgram *program);
    void drawOnePatch(GLSLProgram *program, int ndx) const {
        if (show_me) {
            tess_path.drawPatch(ndx);
        }
    }
    void drawControlPoints() const {
        if (show_me) {
            tess_path.drawControlPoints();
        }
    }
    void drawStrokeNVPR() const {
        if (show_me) {
            sendStrokeColor();
            glStencilThenCoverStrokePathNV(nvpr_path, 1, ~0, GL_CONVEX_HULL_NV);
        }
    }
    void drawFillNVPR() const {
        if (show_me) {
            sendFillColor();
            glStencilThenCoverFillPathNV(nvpr_path, GL_COUNT_UP_NV, ~0, GL_CONVEX_HULL_NV);
        }
    }
    void configureStrokeWidth(float stroke_width) {
        glPathParameterfNV(nvpr_path, GL_PATH_STROKE_WIDTH_NV, stroke_width);
    }
    void configureJoinStyle(GLenum join_style) {
        glPathParameteriNV(nvpr_path, GL_PATH_JOIN_STYLE_NV, join_style);
    }
    void configureMiterLimit(float miter_limit) {
        glPathParameterfNV(nvpr_path, GL_PATH_MITER_LIMIT_NV, miter_limit);
    }
    void configureStrokePolicy(GLenum stroke_policy) {
        glPathParameteriNV(nvpr_path, GL_PATH_STROKE_POLICY_NV, stroke_policy);
    }
    void toggleShowMe();
    int numPatches() const {
        return tess_path.numPatches();
    }

    float3 StrokeColor() const {
        return stroke_color;
    }
    float StrokeOpacity() const {
        return stroke_opacity;
    }
    float3 FillColor() const {
        return fill_color;
    }
    float FillOpacity() const {
        return fill_opacity;
    }
    const GLPathStrokeState& TessPath() const {
        return tess_path;
    }
    const GLPathStrokeStateExt& TessPathExt() const {
        return tess_path;
    }
    bool ShowMe() const {
        return show_me;
    }
    const std::vector<float2> CatmullRomExtraPoints() const {
        return catmull_rom_extra_points;
    }
};

extern GLenum current_stroke_policy;

#endif // __PathContainer_hpp__
