
// polar_shader_state.cpp - GLSL shader global state for polar stroking

#include <assert.h>
#include <string.h>
#include <limits.h> // for INT_MAX

#include <GL/glew.h>

#include "countof.h"

#include "StrokeTessellatorSettings.hpp"
#include "polar_shader_state.hpp"
#include "global_polar_settings.hpp"  // for stroke_tessellator_settings

#include "read_file.hpp"  // for read_binary_file
#include "getElapsedTime.h"
#include "verbose.h"

float arc_length_offset = 0;

static bool includes_populated = false;

// Static helper based on GLUT's glutExtensionSupported
static int isExtensionSupported(const char *extension)
{
    static const GLubyte *extensions = NULL;
    const GLubyte *start;
    const GLubyte *where, *terminator;

    // Extension names should not have spaces.
    where = (GLubyte *) strchr(extension, ' ');
    if (where || *extension == '\0') {
        return 0;
    }

    if (!extensions) {
        extensions = glGetString(GL_EXTENSIONS);
    }
    // It takes a bit of care to be fool-proof about parsing the
    // OpenGL extensions string.  Don't be fooled by sub-strings,
    // etc.
    start = extensions;
    for (;;) {
        // If your application crashes in the strstr routine below,
        // you are probably calling glutExtensionSupported without
        // having a current window.  Calling glGetString without
        // a current OpenGL context has unpredictable results.
        // Please fix your program.
        where = (const GLubyte *) strstr((const char *) start, extension);
        if (!where) {
            break;
        }
        terminator = where + strlen(extension);
        if (where == start || *(where - 1) == ' ') {
            if (*terminator == ' ' || *terminator == '\0') {
                return 1;
            }
        }
        start = terminator;
    }
    return 0;
}

StrokerContext::StrokerContext()
    : composite_mode(COMPOSITE_BLEND_ONCE)
    , shading_mode(CONSTANT_COLOR)
    , stroking_space(OBJECT_SPACE)
    , arc_length_offset(0)
    , arc_length_texgen_scale(1)
    , draw_inside_of_round_joins(0)
{
    for (size_t i=0; i<countof(current_color); i++) {
        current_color[i] = 1;
    }
    for (size_t i=0; i<countof(viewport_scale); i++) {
        viewport_scale[i] = 0;
    }
}

void StrokerContext::setCompositeMode(CompositeMode mode)
{
    composite_mode = mode;
}

const char* StrokerContext::getCompositeModeString() const
{
    static const char *compositingMode[4] = {
        "off",                     // COMPOSITE_OFF
        "allow double blending",   // COMPOSITE_BLEND
        "blend once & only once",  // COMPOSITE_BLEND_ONCE
        "blend last",              // COMPOSITE_BLEND_ONCE_LAST
    };
    assert(size_t(composite_mode) < countof(compositingMode));
    return compositingMode[size_t(composite_mode)];
}

void StrokerContext::setCurrentColor(const GLfloat* color)
{
    current_color[0] = color[0];
    current_color[1] = color[1];
    current_color[2] = color[2];
    current_color[3] = color[3];
}

void StrokerContext::setCurrentColorState(const GLfloat* color)
{
    shading_mode = CONSTANT_COLOR;
    setCurrentColor(color);
}

void StrokerContext::setCurrentOpacity(float opacity)
{
    current_color[0] = opacity;
    current_color[1] = opacity;
    current_color[2] = opacity;
    current_color[3] = opacity;
}

void StrokerContext::setArcLengthOffset(float offset)
{
    arc_length_offset = offset;
}

void StrokerContext::setArcLengthTexGenScale(float scale)
{
    arc_length_texgen_scale = scale;
}

void StrokerContext::setShadingMode(ShadingMode mode)
{
    shading_mode = mode;
}

const char* StrokerContext::getShadingModeString() const
{
    return shadingModeString(shading_mode);
}

bool StrokerContext::usesArcLengthExtension() const
{
    return StrokeConfigTypes::usesArcLengthExtension(shading_mode);
}

// For linear & radial gradients
// coords == 1 for linear gradient
// coords == 2 for center radial gradient
// coords == 3 for focal gradient
void StrokerContext::setGradientState(ShadingMode mode, int coords, const GLfloat coeffs[], float opacity)
{
    shading_mode = mode;
    for (int i=0; i<coords; i++) {
        gradient_coeff[i][0] = coeffs[i*3+0];
        gradient_coeff[i][1] = coeffs[i*3+1];
        gradient_coeff[i][2] = coeffs[i*3+2];
    }
    current_color[3] = opacity;  // Just use alpha component of current color.
}

void StrokerContext::prepViewportScale(int window_width, int window_height)
{
    viewport_scale[0] = 0.5f*window_width;
    viewport_scale[1] = 0.5f*window_height;
    viewport_scale[2] = 1.0f/viewport_scale[0];
    viewport_scale[3] = 1.0f/viewport_scale[1];
}

void StrokerContext::updateTrivialCulling()
{
#if 0
    updateTrivialCulling(stroke_tessellator_settings.TrivialCulling());
#endif
}

void StrokerContext::updateInsideRoundJoinMode()
{
#if 0
    updateInsideRoundJoinMode(stroke_tessellator_settings.InsideRoundJoinMode());
#endif
}

#ifdef _WIN32
#define snprintf _snprintf  // Workaround Win32's deprecated POSIX name nonsense.
#endif

// Reset underlying tessellation shaders to force lazy recompilation.
void StrokerContext::resetTessellation()
{
    includes_populated = false;
}

void initPolarTessellation()
{
    // The polar stroking tessellation shader expects six control
    // points per patch.
    //
    // For a cubic Bezier segment, the first 4 control points are
    // the cubic Bezier's control points.
    //
    // The 5th control point is the first control point after the
    // initial control point that is distinct from (non-equal to)
    // the initial control point.
    //
    // The 6th control point is the first control point working
    // in reverse order from the terminal control point that is
    // distinct from the terminal control point.
    const int verticesPerPatch = 6;
    glPatchParameteri(GL_PATCH_VERTICES, verticesPerPatch);
}

bool StrokeConfigTypes::shadingModeNeedsArcLength(ShadingMode shading_mode)
{
    switch (shading_mode) {
    default:
        assert(!"unknown shading mode");
        // Fallthrough...
    case CONSTANT_COLOR:
    case LINEAR_GRADIENT:
    case CENTER_RADIAL_GRADIENT:
    case FOCAL_RADIAL_GRADIENT:
    case IMAGE_GRADIENT:
        return false;
    case ARC_LENGTH_TEXTURE_2D:
    case ARC_LENGTH_TEXTURE_2D_EARLY_FRAGMENT_TEST:
        return true;
    }
}

bool StrokeConfigTypes::shadingModeNeedsMemoryBarrier(ShadingMode shading_mode)
{
    // We need a memory barrier between the "stencil" and "cover" step
    // so the segment lengths are flushed to memory.
    return shadingModeNeedsArcLength(shading_mode);
}

const char* StrokeConfigTypes::strokingSpaceString(StrokingSpace storking_space)
{
    switch (storking_space) {
    case OBJECT_SPACE:
        return "ObjectSpace";
    case WINDOW_SPACE:
        return "WindowSpace";
    default:
        assert(!"invalid stroking_space");
        // Fallthrough...
    case EYE_SPACE:
        return "EyeSpace";
    }
}

const char* StrokeConfigTypes::shadingModeString(ShadingMode shading_mode)
{
    switch (shading_mode) {
    case CONSTANT_COLOR:
        return "constantColor";
    case LINEAR_GRADIENT:
        return "linearGradient";
    case CENTER_RADIAL_GRADIENT:
        return "centerGradient";
    case FOCAL_RADIAL_GRADIENT:
        return "focalGradient";
    case IMAGE_GRADIENT:
        return "imageGradient";
    case ARC_LENGTH_TEXTURE_2D:
        return "friezeGradient";
    case ARC_LENGTH_TEXTURE_2D_EARLY_FRAGMENT_TEST:
        return "friezeGradientDiscard";
    default:
        assert(!"unknown shading mode");
        return "none";
    }
}

bool StrokeConfigTypes::usesArcLengthExtension(ShadingMode shading_mode)
{
    switch (shading_mode) {
    case ARC_LENGTH_TEXTURE_2D:
    case ARC_LENGTH_TEXTURE_2D_EARLY_FRAGMENT_TEST:
        return true;
    default:
        return false;
    }
}

StrokerContext g_stroker_context;
