
// menus.cpp - menu implementation for "hairline" stroking algorithm testbed

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <GL/glew.h>
#ifdef __APPLE__
# include <GLUT/glut.h>
#else
# include <GL/glut.h>
# ifdef _WIN32
#  include <windows.h>  // for wglGetProcAddress
# else
#  include <GL/glx.h>  // for glXGetProcAddress
# endif
#endif

#include <vector>

#include <Cg/double.hpp>
#include <Cg/vector/xyzw.hpp>
#include <Cg/matrix/1based.hpp>
#include <Cg/matrix/rows.hpp>
#include <Cg/vector.hpp>
#include <Cg/matrix.hpp>

using namespace Cg;

#include "countof.h"

#include "PathContainer.hpp"

#include "CurveConfig.hpp"

#include "quality_menu.hpp"

#include "hairline.hpp"
#include "menus.hpp"
#include "keyinput.hpp"
#include "hodograph.hpp"
#include "load_paths.hpp"
#include "simple_path.hpp"
#include "decoration.hpp"
#include "textures.hpp"
//#include "polar/tangent_change_map.hpp"
#include "polar/global_polar_settings.hpp"  // for stroke_tessellator_settings
#include "polar/polar_shader_state.hpp"
#include "showfps.h"  // for invalidateFPS

#ifdef _WIN32
#define snprintf _snprintf
#endif

#if SUPPORT_POLAR_VISUALIZE
static void setExtraSteps(int extra_steps)
{
    assert(extra_steps >= 0);
    printf("extra steps = %d\n", extra_steps);
    stroke_tessellator_settings.setExtraSteps(extra_steps);
    postAllWindowsRedisplay();
}

static int createExtraStepsMenu()
{
    int menu = glutCreateMenu(setExtraSteps);
    for (int i=0; i<=5; i++) {
        char buffer[100];
        snprintf(buffer, sizeof(buffer), "%d steps", i);
        glutAddMenuEntry(buffer, i);
    }
    return menu;
}
#endif

static void menu(int item)
{
    if (item < 0) {
        special(-item, 0, 0);  // bogus (x,y) location
    } else {
        keyboard((unsigned char)item, 0, 0);  // bogus (x,y) location
    }
}

static void spl_menu(int item)
{
    keyboard(char(item), 0,0);  // bogus (x,y) location
}

void config_menu(int item)
{
    curveType = item;
    curveTypeSelect = true;
    postAllWindowsRedisplay();
}

static const int SHOW_STATUS_TEXT = 0xCCC;
static const int HIDE_STATUS_TEXT = 0xDDD;
static const int HIDE_FRAMES_PER_SECOND = 0xEEE;

static void decorationMenu(int item)
{
    if (item == SHOW_STATUS_TEXT) {
        show_status_text = true;
    } else if (item == HIDE_STATUS_TEXT) {
        show_status_text = false;
    } else if (item == HIDE_FRAMES_PER_SECOND) {
        disableFPS();
    } else {
        // Is just 1 bit set in item?
        if ((item & (item-1)) == 0 && item != 0) {
            // Yes, toggle that bit
            decoration ^= item;
        } else {
            // No, set the mask
            decoration = item;
        }
    }
    postAllWindowsRedisplay();
}

static int createDecorationMenu()
{
    const int menu = glutCreateMenu(decorationMenu);
    glutAddMenuEntry("Show all decorations", ~0);
    glutAddMenuEntry("Show stroke decorations", DRAW_GENERATING_LINES|DRAW_GENERATING_POINTS|DRAW_GENERATING_NORMALS);
    glutAddMenuEntry("Show hull decorations", DRAW_CONTROL_POINTS|DRAW_HULL_LINES);
    glutAddMenuEntry("Hide all decorations", 0);
    glutAddMenuEntry("Toggle generating lines (white)", DRAW_GENERATING_LINES);
    glutAddMenuEntry("Toggle generating points (red)", DRAW_GENERATING_POINTS);
    glutAddMenuEntry("Toggle generating normals (gray ribs)", DRAW_GENERATING_NORMALS);
    glutAddMenuEntry("Toggle control points (green/yellow)", DRAW_CONTROL_POINTS);
    glutAddMenuEntry("Toggle spline points (red/blue)", DRAW_SPLINE_POINTS);
    glutAddMenuEntry("Toggle hull lines (pink)", DRAW_HULL_LINES);
    glutAddMenuEntry("Show path list control point decorations", DRAW_PATH_LIST_CONTROL_POINTS);
    glutAddMenuEntry("Show status text", SHOW_STATUS_TEXT);
    glutAddMenuEntry("Hide status text", HIDE_STATUS_TEXT);
    glutAddMenuEntry("Hide frames per second", HIDE_FRAMES_PER_SECOND);
    return menu;
}

static void setJoinStyle(Paint::JoinEnum ndx)
{
    current_paint.setJoinStyle(ndx);
    updatePathListJoinStyle(currentGLJoinStyle());
    postAllWindowsRedisplay();
}

static void joinStyleMenu(int item)
{
    setJoinStyle(Paint::JoinEnum(item));
}

static int createJoinStyleMenu()
{
    int menu = glutCreateMenu(joinStyleMenu);
    for (int i=0; i<int(joinStyleConfigCount()); i++) {
        glutAddMenuEntry(joinStyleConfig[i].name, i);
    }
    return menu;
}

static void capMenu(int item)
{
    int placement = (item >> 8) & 0xFF;
    int type = item & 0xFF;
    Paint::CapEnum cap = Paint::CapEnum(type);
    int mask = 0xFF << (8*placement);

    switch (placement) {
    case 0:
        current_paint.setInitialEndCap(cap);
        break;
    case 1:
        current_paint.setTerminalEndCap(cap);
        break;
    case 2:
        current_paint.setInitialDashCap(cap);
        break;
    default:
        assert(!"bad placement");
        // Fallthrough...
    case 3:
        current_paint.setTerminalDashCap(cap);
        break;
    }

    GLuint cap_placement_to_type = ((type << (8*placement)) & mask);
    printf("cap_placement_to_type = 0x%x\n", cap_placement_to_type);
    printf("  initial       = %d\n", (cap_placement_to_type >> 0) & 0xff);
    printf("  terminal      = %d\n", (cap_placement_to_type >> 8) & 0xff);
    printf("  initial dash  = %d\n", (cap_placement_to_type >> 16) & 0xff);
    printf("  terminal dash = %d\n", (cap_placement_to_type >> 24) & 0xff);
    simple.setParameters(current_paint);
    postAllWindowsRedisplay();
}

static int createCapMenu(int placement)
{
    int menu = glutCreateMenu(capMenu);
    glutAddMenuEntry("None",       0 | (placement << 8));
    glutAddMenuEntry("Round",      1 | (placement << 8));
    glutAddMenuEntry("Triangular", 2 | (placement << 8));
    glutAddMenuEntry("Square",     3 | (placement << 8));
    return menu;
}

struct DashPattern {
    int count;
    float pattern[6];
};

static const DashPattern dash_pattern[] = {
    { 2, {1, 1} },
    { 2, {2, 2} },
    { 2, {3, 2} },
    { 3, {3, 2, 3} },
    { 4, {4, 4, 3, 3} },
    { 2, {3, 4, 3} },
    { 2, {6, 2} },
    { 6, {1.5f, 3, 1.6f, 3, 1.7f, 3} },
    { 2, {0, 3} },
    { 2, {0.1f, 3} },
    { 4, {0, 2, 1, 4} },
    { 4, {0, 2, 0, 2.5f} },
};

vector<float> current_dash_pattern;
float current_dash_offset = 0;

static void dashPatternMenu(int item)
{
    current_dash_pattern.clear();
    if (item >= 0) {
        for (int j=0; j<dash_pattern[item].count; j++) {
            current_dash_pattern.push_back(dash_pattern[item].pattern[j]);
        }
    }
    reprocessPaths(true,true);
    invalidateFPS();
    postAllWindowsRedisplay();
}

static void buildPatternString(char buffer[], const DashPattern& pattern)
{
    strcpy(buffer, "[ ");
    for (int j=0; j<pattern.count; j++) {
        if (j > 0) {
            strcat(buffer, ",");
        }
        char number[32];
        const float dash_element = pattern.pattern[j];
        sprintf(number, "%g", dash_element);
        strcat(buffer, number);
    }
    strcat(buffer, " ]");
}

static int createDashPatternMenu()
{
    int menu = glutCreateMenu(dashPatternMenu);
    glutAddMenuEntry("No dash pattern", -1);
    for (int i=0; i<icountof(dash_pattern); i++) {
        char buffer[200];
        buildPatternString(buffer, dash_pattern[i]);
        glutAddMenuEntry(buffer, i);
    }
    return menu
;
}

static void multiCapMenu(int item)
{
    switch (item) {
    case 0:
        current_paint.forceNoCaps();
        break;
    case 1:
        current_paint.setInitialEndCap(Paint::SQUARE_CAP);
        current_paint.setTerminalEndCap(Paint::TRIANGULAR_CAP);
        current_paint.setInitialDashCap(Paint::NO_CAP);
        current_paint.setTerminalDashCap(Paint::TRIANGULAR_CAP);
        break;
    case 2:
        current_paint.setCapsIdentical(Paint::ROUND_CAP);
        break;
    case 3:
        current_paint.setCapsIdentical(Paint::SQUARE_CAP);
        break;
    case 4:
        current_paint.setCapsIdentical(Paint::TRIANGULAR_CAP);
        break;
    }
    simple.setParameters(current_paint);
    postAllWindowsRedisplay();
}

void disableCaps()
{
    current_paint.forceNoCaps();
    simple.setParameters(current_paint);
    postAllWindowsRedisplay();
}

static int createCapMenus()
{
    int dash_menu[4];

    for (int i=0; i<icountof(dash_menu); i++) {
        dash_menu[i] = createCapMenu(i);
    }
    int menu = glutCreateMenu(multiCapMenu);
    static const char* label[4] = {
        "Initial cap...",
        "Terminal cap...",
        "Initial dash cap...",
        "Terminal dash cap..."
    };
    for (size_t i=0; i<countof(dash_menu); i++) {
        glutAddSubMenu(label[i], dash_menu[i]);
    }
    glutAddMenuEntry("[Ctrl-C] No caps", 0);
    glutAddMenuEntry("Oriented caps", 1);
    glutAddMenuEntry("Round caps", 2);
    glutAddMenuEntry("Square caps", 3);
    glutAddMenuEntry("Triangular caps", 4);
    return menu;
}

struct ShadingModeConfig {
    const char *name;
    StrokeConfigTypes::ShadingMode mode;
};

static const ShadingModeConfig shadingModeConfig[] = {
    { "Constant color",                                 StrokeConfigTypes::CONSTANT_COLOR },
    { "Linear gradient",                                StrokeConfigTypes::LINEAR_GRADIENT },
    { "Center Radial gradient",                         StrokeConfigTypes::CENTER_RADIAL_GRADIENT },
    { "Focal Radial gradient",                          StrokeConfigTypes::FOCAL_RADIAL_GRADIENT },
    { "Image gradient",                                 StrokeConfigTypes::IMAGE_GRADIENT },
    { "Arc length texture 2D",                          StrokeConfigTypes::ARC_LENGTH_TEXTURE_2D },
    { "Arc length texture 2D with early fragment test", StrokeConfigTypes::ARC_LENGTH_TEXTURE_2D_EARLY_FRAGMENT_TEST },
};

StrokeConfigTypes::ShadingMode getShadingMode()
{
    return g_stroker_context.getShadingMode();
}

void setShadingMode(StrokeConfigTypes::ShadingMode mode)
{
    g_stroker_context.setShadingMode(mode);
    invalidateFPS();
    postMainWindowRedisplay();
}

void cycleShadingMode()
{
    StrokeConfigTypes::ShadingMode shading_mode = g_stroker_context.getShadingMode();
    shading_mode = StrokeConfigTypes::ShadingMode(int(shading_mode+1) % StrokeConfigTypes::NUM_SHADING_MODES);
    printf("shading_mode = %s\n", shadingModeConfig[shading_mode].name);
    g_stroker_context.setShadingMode(shading_mode);
    invalidateFPS();
}

void cycleCompositeMode()
{
    StrokeConfigTypes::CompositeMode composite_mode = g_stroker_context.getCompositeMode();
    composite_mode = StrokeConfigTypes::CompositeMode((int(composite_mode)+1)%StrokeConfigTypes::NUM_COMPOSITE_MODES);
    g_stroker_context.setCompositeMode(composite_mode);
    printf("compositing = %s\n", g_stroker_context.getCompositeModeString());
    invalidateFPS();
}

size_t shadingModeConfigCount()
{
    return countof(shadingModeConfig);
}

static void shadingModeMenu(int item)
{
    setShadingMode(StrokeConfigTypes::ShadingMode(item));
    invalidateFPS();
}

int createShaderModeMenu()
{
    int menu = glutCreateMenu(shadingModeMenu);
    for (int i=0; i<int(shadingModeConfigCount()); i++) {
        glutAddMenuEntry(shadingModeConfig[i].name, i);
    }
    return menu;
}

static const char* curveConfigSubMenuName(char spline_type)
{
    switch (spline_type) {
    case 'C':
        return "Cubic segments...";
    case 'Q':
        return "Quadratic segments...";
    case 'W':
        return "Conic segments...";
    case 'L':
        return "Line segments...";
    case 'S':
        return "Open spline...";
    case 'O':
        return "Closed spline...";
    case 'G':
        return "SVG graphic...";
    default:
        assert(!"unknown spline type");
        return "Unknown...";
    }
}
static int createCurveConfigMenu()
{
    int submenus[128];

    for (size_t i=0; i<countof(submenus); i++) {
        submenus[i] = -1;
    }
    for (int i=0; i<num_configs; i++) {
        const int spline_type = config_list[i].spline_type;
        if (submenus[spline_type] == -1) {
            submenus[spline_type] = glutCreateMenu(config_menu);
        } else {
            glutSetMenu(submenus[spline_type]);
        }
        glutAddMenuEntry(config_list[i].description, i);
    }
    const int config_menu_id = glutCreateMenu(config_menu);
    for (int i=0; i<icountof(submenus); i++) {
        if (submenus[i] > -1) {
            const char* menu_name = curveConfigSubMenuName(i);
            glutAddSubMenu(menu_name, submenus[i]);
        }
    }
    return config_menu_id;
}

static const struct StrokePolicy {
    const char* name;
    GLenum token;
} stroke_policy[] = {
    { "None policy", GL_NONE },
    { "SVG policy", GL_SVG_POLICY_NV },
    { "PDF policy", GL_PDF_POLICY_NV },
    { "XPS policy", GL_XPS_POLICY_NV },
};

void setStrokePolicy(int item)
{
    assert(item < icountof(stroke_policy));
    printf("stroke policy = %s\n", stroke_policy[item].name);
    current_stroke_policy = stroke_policy[item].token;

    simple.configureStrokePolicy(current_stroke_policy);
}

static int createStrokPolicyMenu()
{
    int menu = glutCreateMenu(setStrokePolicy);
    for (size_t i=0; i<countof(stroke_policy); i++) {
        glutAddMenuEntry(stroke_policy[i].name, int(i));
    }
    return menu;
}

void createMenu()
{
    const int join_style_menu_id = createJoinStyleMenu();
    const int config_menu_id = createCurveConfigMenu();
    const int decoration_menu_id = createDecorationMenu();
    const int quality_menu_id = createQualitySettingMenu(capabilities.hasFramebufferMixedSamples());
    const int cap_menu_id = createCapMenus();
    const int shading_mode_id = createShaderModeMenu();
    const int textures_id = createTextureMenu();
#if SUPPORT_POLAR_VISUALIZE
    const int extra_steps_id = createExtraStepsMenu();
#endif
    const int dash_pattern_id = createDashPatternMenu();
    const int stroke_policy_id = createStrokPolicyMenu();

    glutCreateMenu(menu);
    glutAddSubMenu("Curve configurations...", config_menu_id);
    glutAddSubMenu("Decorations...", decoration_menu_id);
    glutAddSubMenu("Quality...", quality_menu_id);
    glutAddSubMenu("Join style...", join_style_menu_id);
    glutAddSubMenu("Capping...", cap_menu_id);
    glutAddSubMenu("Dash pattern...", dash_pattern_id);
    glutAddSubMenu("Stroke policy...", stroke_policy_id);
    glutAddSubMenu("Shading mode...", shading_mode_id);
    glutAddSubMenu("Textures...", textures_id);

#if SUPPORT_POLAR_VISUALIZE
    glutAddSubMenu("Extra steps...", extra_steps_id);
#endif

    glutAddMenuEntry("[,] Forward curve configuration", ',');
    glutAddMenuEntry("[.] Backward curve configuration", '.');

    glutAddMenuEntry("[s] Toggle polar versus uniform stroking", 's');
    glutAddMenuEntry("[y] Cycle interpolation mode", 'y');
    glutAddMenuEntry("[p] Toggle between NVPR/default mode", 'p');
    glutAddMenuEntry("[P] Cycle textures", 'P');
    glutAddMenuEntry("[n] Toggle the Hodograph window", 'n');
    glutAddMenuEntry("[m] Toggle the Parametric graph window", 'm');
    glutAddMenuEntry("[F] Toggle filling", 'F');
    glutAddMenuEntry("[a] Increase q or number of segments", 'a');
    glutAddMenuEntry("[z] Decrease q or number if segments", 'z');
    glutAddMenuEntry("[o] Toggle outline/fill of stroking", 'o');
    glutAddMenuEntry("[i] Toggle conic support", 'i');
    glutAddMenuEntry("[O] Toggle view conservative rasterization", 'O');
    glutAddMenuEntry("[J] Cycle join styles", 'J');
    glutAddMenuEntry("[b] Cycle background color", 'b');
    glutAddMenuEntry("[B] Cycle simple path color", 'B');
    glutAddMenuEntry("[Insert] Toggle sRGB correct vs uncorrected color", -GLUT_KEY_INSERT);
    glutAddMenuEntry("[Home] Restore view", -GLUT_KEY_HOME);
    glutAddMenuEntry("[j] Toggle show joins", 'j');
    glutAddMenuEntry("[w] Stroke in window space", 'w');
    glutAddMenuEntry("[u] Toggle eye space for stroking", 'u');
    glutAddMenuEntry("[[] Decrease miter limit", '[');
    glutAddMenuEntry("[]] Increase miter limit", ']');
    glutAddMenuEntry("[(] Decrease dash offset", '(');
    glutAddMenuEntry("[)] Increase dash offset", ')');
    glutAddMenuEntry("[c] Cycle compositing mode", 'c');
    glutAddMenuEntry("[+] Increase stroke width", '=');
    glutAddMenuEntry("[-] Decrease stroke width", '-');
    glutAddMenuEntry("[ ] Toggle animation", ' ');
    glutAddMenuEntry("[W] Toggle minimum window stroke width clamp", 'W');
    glutAddMenuEntry("[<] Half minimum window stroke width", '<');
    glutAddMenuEntry("[>] Double minimum window stroke width", '>');
    glutAddMenuEntry("[0] Reset model transform to affine", '0');
    glutAddMenuEntry("[r] Reset view", 'r');
    glutAddMenuEntry("[v] Toggle vsync", 'v');
    glutAddMenuEntry("[f] Toggle showing frame rate", 'F');
    glutAddMenuEntry("[V] Debugging mode", 'V');
    glutAddMenuEntry("[T] Reinitialize shaders", 'T');
    glutAddMenuEntry("[L] Cycle shading mode", 'L');
    glutAddMenuEntry("[Ctrl-J] Cycle inside round join mode", 10);
    glutAddMenuEntry("[Esc] Quit", '\027');
    glutAttachMenu(GLUT_RIGHT_BUTTON);
}
