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

#include "hairline.hpp"

#include "PathContainer.hpp"

#include <Cg/max.hpp>
#include <Cg/min.hpp>

#include "polar/BakeIndexedStrokedPath.hpp"

#include "common/verbose.h"

GLenum current_stroke_policy = GL_SVG_POLICY_NV;

void PathContainer::process(bool include_caps, bool include_joins)
{
    PathStyle default_style;  // for dashing state

    default_style.dash_offset = current_dash_offset;
    // Scale the dash array by the current stroke width.
    const float stroke_width = current_paint.StrokeWidth();
    default_style.dash_array = current_dash_pattern;
    for (size_t i=0; i<default_style.dash_array.size(); i++) {
        default_style.dash_array[i] *= stroke_width;
    }

    setDashState(default_style);

    BakeIndexedStrokedPath baker(default_style, include_caps, include_joins);
    baker.setVerbose(!!verbose);
    baker.setNativeQuadratics(native_quadratics);
    catmull_rom_extra_points.clear();
    baker.setCatmullRomExtraPointsVector(&catmull_rom_extra_points);
    baker.processSegments(path);
    tess_path.load(baker);

    indexed_path_state.swap(baker);
}
void PathContainer::process()
{
    process(true, true);
}
void PathContainer::toggleShowMe()
{
    show_me = !show_me;
}

static bool load_from_string = false; // false means use PathPtr's SVG path string loader

GLenum capEnum(Paint::CapEnum cap)
{
    const int cap_ndx = cap;
    static const GLenum capList[4] = {
        GL_FLAT,
        GL_ROUND_NV,
        GL_TRIANGULAR_NV,
        GL_SQUARE_NV
    };
    return capList[cap_ndx];
}

void PathContainer::setParameters(const Paint& paint)
{
    glPathParameterfNV(nvpr_path, GL_PATH_STROKE_WIDTH_NV, paint.StrokeWidth());
    glPathParameteriNV(nvpr_path, GL_PATH_INITIAL_END_CAP_NV, paint.GLInitialEndCap());
    glPathParameteriNV(nvpr_path, GL_PATH_TERMINAL_END_CAP_NV, paint.GLTerminalEndCap());
    glPathParameteriNV(nvpr_path, GL_PATH_INITIAL_DASH_CAP_NV, paint.GLInitialDashCap());
    glPathParameteriNV(nvpr_path, GL_PATH_TERMINAL_DASH_CAP_NV, paint.GLTerminalDashCap());
    glPathParameteriNV(nvpr_path, GL_PATH_JOIN_STYLE_NV, currentGLJoinStyle());
    glPathParameterfNV(nvpr_path, GL_PATH_MITER_LIMIT_NV, paint.MiterLimit());
    // Improve fidelity of cubic and conic strokes from default (0.2) to tigher bound.
    glPathParameterfNV(nvpr_path, GL_PATH_STROKE_BOUND_NV, 0.03f);
    glPathParameteriNV(nvpr_path, GL_PATH_STROKE_POLICY_NV, current_stroke_policy);
}

void PathContainer::setDashState(PathStyle default_style)
{
    std::vector<float>& dash_array = default_style.dash_array;

    if (dash_array.size() > 0) {
        glPathDashArrayNV(nvpr_path, GLsizei(dash_array.size()), &dash_array[0]);
    } else {
        glPathDashArrayNV(nvpr_path, 0, NULL);
    }
    glPathParameterfNV(nvpr_path, GL_PATH_DASH_OFFSET_NV, current_dash_offset);
}

void PathContainer::specifyNVprPath(GLuint nvpr_path, const vector<char> &cmds, const vector<float> &coords)
{
    PathStyle style;
    path = PathPtr(new Path(style, cmds, coords));
    glPathCommandsNV(nvpr_path,
        path->getGLCmdSize(), path->getGLCmds(),
        path->getGLCoordSize(), GL_FLOAT, path->getGLCoords());
}

void PathContainer::updateQuadraticSpline(size_t count, const float3 points[])
{
    const int control_points_per_quadratic_command = 2;
    assert(count >= 1+control_points_per_quadratic_command);  // 1+ for MoveTo
    assert((count - 1) % control_points_per_quadratic_command == 0);

    vector<char> cmds;
    cmds.push_back('M');  // MoveTo command
    for (size_t i=1; i<count; i+=control_points_per_quadratic_command) {
        cmds.push_back('Q');  // QuadraticCurveTo command
    }

    vector<float> coords;
    for (size_t i=0; i<count; i++) {
        coords.push_back(points[i].x);
        coords.push_back(points[i].y);
        assert(points[i].z == 1);
    }

    specifyNVprPath(nvpr_path, cmds, coords);
    process();
}

void PathContainer::updateQuadraticSpline(const vector<float3> points)
{
    updateQuadraticSpline(points.size(), &points[0]);
}

void PathContainer::updateConicSpline(size_t count, const float3 points[])
{
    const int control_points_per_quadratic_command = 2;
    assert(count >= 1+control_points_per_quadratic_command);  // 1+ for MoveTo
    assert((count - 1) % control_points_per_quadratic_command == 0);

    vector<char> cmds;
    cmds.push_back('M');  // MoveTo command
    for (size_t i=1; i<count; i+=control_points_per_quadratic_command) {
        cmds.push_back('W');  // ConicCurveTo command
    }

    vector<float> coords;
    // MoveTo coordinates
    coords.push_back(points[0].x);
    coords.push_back(points[0].y);
    assert(points[0].z == 1);
    for (size_t i=1; i<count; i+=2) {
        coords.push_back(points[i+0].x);
        coords.push_back(points[i+0].y);
        const float w = points[i+0].z;  // really a homogeneous W component

        coords.push_back(points[i+1].x);
        coords.push_back(points[i+1].y);
        assert(points[i+1].z == 1);

        coords.push_back(w);
    }

    specifyNVprPath(nvpr_path, cmds, coords);
    process();
}

void PathContainer::updateConicSpline(const vector<float3> points)
{
    updateConicSpline(points.size(), &points[0]);
}

void PathContainer::updateLineSpline(size_t count, const float3 points[])
{
    const int control_points_per_line_command = 1;
    assert(count >= control_points_per_line_command+1);
    assert((count - 1) % control_points_per_line_command == 0);

    vector<char> cmds;
    cmds.push_back('M');  // MoveTo command
    for (size_t i=1; i<count; i+=control_points_per_line_command) {
        cmds.push_back('L');  // LineTo command
    }

    vector<float> coords;
    for (size_t i=0; i<count; i++) {
        coords.push_back(points[i].x);
        coords.push_back(points[i].y);
        assert(points[i].z == 1);
    }

    specifyNVprPath(nvpr_path, cmds, coords);
    process();
}

void PathContainer::updateLineSpline(const vector<float3> points)
{
    updateLineSpline(points.size(), &points[0]);
}

void PathContainer::updateCubicSpline(size_t count, const float3 points[])
{
    const int control_points_per_cubic_command = 3;
    assert(count >= 1+control_points_per_cubic_command);  // 1+ for MoveTo
    assert((count - 1) % control_points_per_cubic_command == 0);

    vector<char> cmds;
    cmds.push_back('M');  // MoveTo command
    for (size_t i=1; i<count; i+=control_points_per_cubic_command) {
        cmds.push_back('C');  // CubicCurveTo command
    }

    vector<float> coords;
    for (size_t i=0; i<count; i++) {
        coords.push_back(points[i].x);
        coords.push_back(points[i].y);
        assert(points[i].z == 1);
    }

    specifyNVprPath(nvpr_path, cmds, coords);
    process();
}

void PathContainer::updateCubicSpline(const vector<float3> points)
{
    updateCubicSpline(points.size(), &points[0]);
}

void PathContainer::loadCubicSpline(size_t count, const float3 points[])
{
    nvpr_path = glGenPathsNV(1);

    updateCubicSpline(count, points);
    setParameters(current_paint);
}

void PathContainer::loadCubicSpline(const vector<float3> points)
{
    loadCubicSpline(points.size(), &points[0]);
}

void PathContainer::updateCatmullRomLoop(size_t count, const float3 points[])
{
    const int control_points_per_spline_command = 1;
    assert(count >= 1+control_points_per_spline_command);  // 1+ for MoveTo

    // XXX hack until NV_path_rendering driver supports Catmull-Rom splines
    // Draw loop as line segment loop
    vector<char> cmds;
    cmds.push_back('M');  // MoveTo command
    for (size_t i=1; i<count; i+=control_points_per_spline_command) {
        cmds.push_back('L');  // LineTo command
    }
    cmds.push_back('z');  // ClosePath command

    vector<float> coords;
    for (size_t i=0; i<count; i++) {
        coords.push_back(points[i].x);
        coords.push_back(points[i].y);
        assert(points[i].z == 1);
    }

    specifyNVprPath(nvpr_path, cmds, coords);

    cmds.clear();
    cmds.push_back(GL_BEGIN_CLOSED_SPLINE_TO_NV);
    for (size_t i=1; i<count-1; i+=control_points_per_spline_command) {
        cmds.push_back(GL_SPLINE_TO_NV);
    }
    cmds.push_back(GL_END_CLOSED_SPLINE_TO_NV);

    PathStyle style;
    path = PathPtr(new Path(style, cmds, coords));
    process();
}

void PathContainer::updateCatmullRomLoop(const vector<float3> points)
{
    updateCatmullRomLoop(points.size(), &points[0]);
}

void PathContainer::loadCatmullRomLoop(size_t count, const float3 points[])
{
    nvpr_path = glGenPathsNV(1);

    updateCatmullRomLoop(count, points);
    setParameters(current_paint);
}

void PathContainer::loadCatmullRomLoop(const vector<float3> points)
{
    loadCatmullRomLoop(points.size(), &points[0]);
}

void PathContainer::updateCatmullRomSpline(size_t count, const float3 points[])
{
    const int control_points_per_spline_command = 1;
    assert(count >= 4);

    // XXX hack until NV_path_rendering driver supports Catmull-Rom splines
    // Draw loop as line segment loop
    vector<char> cmds;
    cmds.push_back('M');  // MoveTo command
    cmds.push_back('M');  // MoveTo command
    for (size_t i=2; i<count-1; i+=control_points_per_spline_command) {
        cmds.push_back('L');  // LineTo command
    }
    cmds.push_back('M');  // MoveTo command

    vector<float> coords;
    for (size_t i=0; i<count; i++) {
        coords.push_back(points[i].x);
        coords.push_back(points[i].y);
        assert(points[i].z == 1);
    }

    specifyNVprPath(nvpr_path, cmds, coords);

    cmds.clear();
    cmds.push_back(GL_PRIME_SPLINE_TO_NV);  // PrimeSpline command
    cmds.push_back('M');  // MoveTo command
    for (size_t i=2; i<count-1; i+=control_points_per_spline_command) {
        cmds.push_back(GL_SPLINE_TO_NV);  // LineTo command
    }
    cmds.push_back(GL_PRIME_SPLINE_TO_NV);  // ClosePath commandq

    PathStyle style;
    path = PathPtr(new Path(style, cmds, coords));
    process();
}

void PathContainer::updateCatmullRomSpline(const vector<float3> points)
{
    updateCatmullRomSpline(points.size(), &points[0]);
}

void PathContainer::loadCatmullRomSpline(size_t count, const float3 points[])
{
    nvpr_path = glGenPathsNV(1);

    updateCatmullRomSpline(count, points);
    setParameters(current_paint);
}

void PathContainer::loadCatmullRomSpline(const vector<float3> points)
{
    loadCatmullRomSpline(points.size(), &points[0]);
}

void PathContainer::loadQuadraticSpline(size_t count, const float3 points[])
{
    nvpr_path = glGenPathsNV(1);

    updateQuadraticSpline(count, points);
    setParameters(current_paint);
}

void PathContainer::loadQuadraticSpline(const vector<float3> points)
{
    loadQuadraticSpline(points.size(), &points[0]);
}

void PathContainer::loadConicSpline(size_t count, const float3 points[])
{
    nvpr_path = glGenPathsNV(1);

    updateConicSpline(count, points);
    setParameters(current_paint);
}

void PathContainer::loadConicSpline(const vector<float3> points)
{
    loadConicSpline(points.size(), &points[0]);
}

void PathContainer::loadLineSpline(size_t count, const float3 points[])
{
    nvpr_path = glGenPathsNV(1);

    updateLineSpline(count, points);
    setParameters(current_paint);
}

void PathContainer::loadLineSpline(const vector<float3> points)
{
    loadLineSpline(points.size(), &points[0]);
}

void PathContainer::load(size_t count, const char *path_string[])
{
    size_t total_len = 0;
    for (size_t i=0; i<count; i++) {
        total_len += strlen(path_string[i]);
    }
    char *combo_path = (char*)malloc(total_len+1);
    if (combo_path) {
        combo_path[0] = '\0';
        for (size_t i=0; i<count; i++) {
            strncat(combo_path, path_string[i], total_len);
        }
        if (verbose) {
            printf("combo_path=\n%s\n", combo_path);
        }

        nvpr_path = glGenPathsNV(1);

        path = PathPtr(new Path(combo_path));

        if (verbose) {
            printf("path has:\n %d commands\n %d cooordinates\n",
                path->getGLCmdSize(), path->getGLCoordSize());
        }

        if (load_from_string) {
            glPathStringNV(nvpr_path, GL_PATH_FORMAT_SVG_NV, GLsizei(strlen(combo_path)), combo_path);
        } else {
            glPathCommandsNV(nvpr_path,
                path->getGLCmdSize(), path->getGLCmds(),
                path->getGLCoordSize(), GL_FLOAT, path->getGLCoords());
        }
        assert(glIsPathNV(nvpr_path));
        setParameters(current_paint);

        free(combo_path);

        process();
    }
}

void PathContainer::drawStrokeTessellated(GLSLProgram *program)
{
    if (show_me) {
        programColor(program);
        tess_path.draw();
    }
}
