#include "winbase.h"
#include "winreg.h"
#include "wine/winbase16.h"
#include <stdlib.h>
#include <stdio.h>

typedef struct
{
    const GLubyte *name;
    int enabled;
} extension_properties;

/*
 * The extensions that are "on" are based on a GeForce4 4400 Ti with
 * nVidia binary driver v61.11
 */
extension_properties extension_def[] =
{
   {"GL_3DFX_multisample", 0},
   {"GL_3DFX_tbuffer", 0},
   {"GL_3DFX_texture_compression_FXT1", 0},
   {"GL_APPLE_client_storage", 0},
   {"GL_APPLE_element_array", 0},
   {"GL_APPLE_fence", 0},
   {"GL_APPLE_specular_vector", 0},
   {"GL_APPLE_transform_hint", 0},
   {"GL_APPLE_vertex_array_object", 0},
   {"GL_APPLE_vertex_array_range", 0},
   {"GL_APPLE_ycbcr_422", 0},
   {"GL_ARB_depth_texture", 1},
   {"GL_ARB_draw_buffers", 0},
   {"GL_ARB_fragment_program", 0},
   {"GL_ARB_fragment_program_shadow", 0},
   {"GL_ARB_fragment_shader", 0},
   {"GL_ARB_imaging", 1},
   {"GL_ARB_matrix_palette", 0},
   {"GL_ARB_multisample", 1},
   {"GL_ARB_multitexture", 1},
   {"GL_ARB_occlusion_query", 1},
   {"GL_ARB_point_parameters", 1},
   {"GL_ARB_point_sprite", 1},
   {"GL_ARB_shader_objects", 0},
   {"GL_ARB_shader_objects", 1},
   {"GL_ARB_shading_language_100", 1},
   {"GL_ARB_shadow", 1},
   {"GL_ARB_shadow_ambient", 0},
   {"GL_ARB_texture_border_clamp", 1},
   {"GL_ARB_texture_compression", 1},
   {"GL_ARB_texture_cube_map", 1},
   {"GL_ARB_texture_env_add", 1},
   {"GL_ARB_texture_env_combine", 1},
   {"GL_ARB_texture_env_crossbar", 0},
   {"GL_ARB_texture_env_dot3", 1},
   {"GL_ARB_texture_mirrored_repeat", 1},
   {"GL_ARB_texture_non_power_of_two", 0},
   {"GL_ARB_texture_rectangle", 0},
   {"GL_ARB_transpose_matrix", 1},
   {"GL_ARB_vertex_blend", 0},
   {"GL_ARB_vertex_buffer_object", 1},
   {"GL_ARB_vertex_program", 1},
   {"GL_ARB_vertex_shader", 1},
   {"GL_ARB_window_pos", 1},
   {"GL_ATI_draw_buffers", 0},
   {"GL_ATI_element_array", 0},
   {"GL_ATI_envmap_bumpmap", 0},
   {"GL_ATI_fragment_shader", 0},
   {"GL_ATI_map_object_buffer", 0},
   {"GL_ATI_pixel_format_float", 0},
   {"GL_ATI_pn_triangles", 0},
   {"GL_ATI_separate_stencil", 0},
   {"GL_ATI_text_fragment_shader", 0},
   {"GL_ATI_texture_env_combine3", 0},
   {"GL_ATI_texture_float", 0},
   {"GL_ATI_texture_mirror_once", 0},
   {"GL_ATI_vertex_array_object", 0},
   {"GL_ATI_vertex_attrib_array_object", 0},
   {"GL_ATI_vertex_streams", 0},
   {"GL_EXT_422_pixels", 0},
   {"GL_EXT_abgr", 1},
   {"GL_EXT_bgra", 1},
   {"GL_EXT_blend_color", 1},
   {"GL_EXT_blend_equation_separate", 0},
   {"GL_EXT_blend_func_separate", 0},
   {"GL_EXT_blend_logic_op", 0},
   {"GL_EXT_blend_minmax", 1},
   {"GL_EXT_blend_subtract", 1},
   {"GL_EXT_Cg_shader", 0},
   {"GL_EXT_clip_volume_hint", 0},
   {"GL_EXT_cmyka", 0},
   {"GL_EXT_color_subtable", 0},
   {"GL_EXT_compiled_vertex_array", 1},
   {"GL_EXT_convolution", 0},
   {"GL_EXT_coordinate_frame", 0},
   {"GL_EXT_copy_texture", 0},
   {"GL_EXT_cull_vertex", 0},
   {"GL_EXT_depth_bounds_test", 0},
   {"GL_EXT_draw_range_elements", 1},
   {"GL_EXT_fog_coord", 1},
   {"GL_EXT_fragment_lighting", 0},
   {"GL_EXT_histogram", 0},
   {"GL_EXT_index_array_formats", 0},
   {"GL_EXT_index_func", 0},
   {"GL_EXT_index_material", 0},
   {"GL_EXT_index_texture", 0},
   {"GL_EXT_light_texture", 0},
   {"GL_EXT_misc_attribute", 0},
   {"GL_EXT_multi_draw_arrays", 1},
   {"GL_EXT_packed_pixels", 1},
   {"GL_EXT_paletted_texture", 1},
   {"GL_EXT_pixel_buffer_object", 1},
   {"GL_EXT_pixel_transform", 0},
   {"GL_EXT_pixel_transform_color_table", 0},
   {"GL_EXT_point_parameters", 1},
   {"GL_EXT_polygon_offset", 0},
   {"GL_EXT_rescale_normal", 1},
   {"GL_EXT_scene_marker", 0},
   {"GL_EXT_secondary_color", 1},
   {"GL_EXT_separate_specular_color", 1},
   {"GL_EXT_shadow_funcs", 1},
   {"GL_EXT_shared_texture_palette", 1},
   {"GL_EXT_stencil_two_side", 0},
   {"GL_EXT_stencil_wrap", 1},
   {"GL_EXT_subtexture", 0},
   {"GL_EXT_texture", 0},
   {"GL_EXT_texture3D", 1},
   {"GL_EXT_texture_compression_dxt1", 0},
   {"GL_EXT_texture_compression_s3tc", 1},
   {"GL_EXT_texture_cube_map", 1},
   {"GL_EXT_texture_edge_clamp", 1},
   {"GL_EXT_texture_env", 0},
   {"GL_EXT_texture_env_add", 1},
   {"GL_EXT_texture_env_combine", 1},
   {"GL_EXT_texture_env_dot3", 1},
   {"GL_EXT_texture_filter_anisotropic", 1},
   {"GL_EXT_texture_lod", 1},
   {"GL_EXT_texture_lod_bias", 1},
   {"GL_EXT_texture_mirror_clamp", 0},
   {"GL_EXT_texture_object", 1},
   {"GL_EXT_texture_perturb_normal", 0},
   {"GL_EXT_vertex_array", 1},
   {"GL_EXT_vertex_shader", 0},
   {"GL_EXT_vertex_weighting", 0},
   {"GL_HP_convolution_border_modes", 0},
   {"GL_HP_image_transform", 0},
   {"GL_HP_occlusion_test", 1},
   {"GL_HP_texture_lighting", 0},
   {"GL_IBM_cull_vertex", 0},
   {"GL_IBM_multimode_draw_arrays", 0},
   {"GL_IBM_rasterpos_clip", 1},
   {"GL_IBM_static_data", 0},
   {"GL_IBM_texture_mirrored_repeat", 1},
   {"GL_IBM_vertex_array_lists", 0},
   {"GL_INGR_color_clamp", 0},
   {"GL_INGR_interlace_read", 0},
   {"GL_INTEL_parallel_arrays", 0},
   {"GL_INTEL_texture_scissor", 0},
   {"GL_KTX_buffer_region", 1},
   {"GL_MESA_pack_invert", 0},
   {"GL_MESA_resize_buffers", 0},
   {"GL_MESA_window_pos", 0},
   {"GL_MESA_ycbcr_texture", 0},
   {"GL_NV_blend_square", 1},
   {"GL_NV_copy_depth_to_color", 1},
   {"GL_NV_depth_clamp", 1},
   {"GL_NV_evaluators", 0},
   {"GL_NV_fence", 1},
   {"GL_NV_float_buffer", 0},
   {"GL_NV_fog_distance", 1},
   {"GL_NV_fragment_program", 0},
   {"GL_NV_fragment_program2", 0},
   {"GL_NV_fragment_program_option", 0},
   {"GL_NV_half_float", 0},
   {"GL_NV_light_max_exponent", 1},
   {"GL_NV_multisample_filter_hint", 1},
   {"GL_NV_occlusion_query", 1},
   {"GL_NV_packed_depth_stencil", 1},
   {"GL_NV_pixel_data_range", 1},
   {"GL_NV_point_sprite", 1},
   {"GL_NV_primitive_restart", 0},
   {"GL_NV_register_combiners", 1},
   {"GL_NV_register_combiners2", 1},
   {"GL_NV_texgen_emboss", 0},
   {"GL_NV_texgen_reflection", 1},
   {"GL_NV_texture_compression_vtc", 1},
   {"GL_NV_texture_env_combine4", 1},
   {"GL_NV_texture_expand_normal", 0},
   {"GL_NV_texture_rectangle", 1},
   {"GL_NV_texture_shader", 1},
   {"GL_NV_texture_shader2", 1},
   {"GL_NV_texture_shader3", 1},
   {"GL_NV_vertex_array_range", 1},
   {"GL_NV_vertex_array_range2", 1},
   {"GL_NV_vertex_program", 1},
   {"GL_NV_vertex_program1_1", 1},
   {"GL_NV_vertex_program2", 0},
   {"GL_NV_vertex_program2_option", 0},
   {"GL_NV_vertex_program3", 0},
   {"GL_OES_byte_coordinates", 0},
   {"GL_OES_compressed_paletted_texture", 0},
   {"GL_OES_fixed_point", 0},
   {"GL_OES_query_matrix", 0},
   {"GL_OES_read_format", 0},
   {"GL_OES_single_precision", 0},
   {"GL_OML_interlace", 0},
   {"GL_OML_resample", 0},
   {"GL_OML_subsample", 0},
   {"GL_PGI_misc_hints", 0},
   {"GL_PGI_vertex_hints", 0},
   {"GL_REND_screen_coordinates", 0},
   {"GL_S3_s3tc", 1},
   {"GL_SGI_color_matrix", 0},
   {"GL_SGI_color_table", 0},
   {"GL_SGIS_detail_texture", 0},
   {"GL_SGIS_fog_function", 0},
   {"GL_SGIS_generate_mipmap", 1},
   {"GL_SGIS_multisample", 0},
   {"GL_SGIS_multitexture", 1},
   {"GL_SGIS_pixel_texture", 0},
   {"GL_SGIS_sharpen_texture", 0},
   {"GL_SGIS_texture4D", 0},
   {"GL_SGIS_texture_border_clamp", 0},
   {"GL_SGIS_texture_color_mask", 0},
   {"GL_SGIS_texture_edge_clamp", 0},
   {"GL_SGIS_texture_filter4", 0},
   {"GL_SGIS_texture_lod", 1},
   {"GL_SGIS_texture_select", 0},
   {"GL_SGI_texture_color_table", 0},
   {"GL_SGIX_async", 0},
   {"GL_SGIX_async_histogram", 0},
   {"GL_SGIX_async_pixel", 0},
   {"GL_SGIX_blend_alpha_minmax", 0},
   {"GL_SGIX_clipmap", 0},
   {"GL_SGIX_depth_texture", 1},
   {"GL_SGIX_flush_raster", 0},
   {"GL_SGIX_fog_offset", 0},
   {"GL_SGIX_framezoom", 0},
   {"GL_SGIX_instruments", 0},
   {"GL_SGIX_interlace", 0},
   {"GL_SGIX_ir_instrument1", 0},
   {"GL_SGIX_list_priority", 0},
   {"GL_SGIX_pixel_texture", 0},
   {"GL_SGIX_pixel_texture_bits", 0},
   {"GL_SGIX_reference_plane", 0},
   {"GL_SGIX_resample", 0},
   {"GL_SGIX_shadow", 1},
   {"GL_SGIX_shadow_ambient", 0},
   {"GL_SGIX_sprite", 0},
   {"GL_SGIX_tag_sample_buffer", 0},
   {"GL_SGIX_texture_add_env", 0},
   {"GL_SGIX_texture_coordinate_clamp", 0},
   {"GL_SGIX_texture_lod_bias", 0},
   {"GL_SGIX_texture_multi_buffer", 0},
   {"GL_SGIX_texture_range", 0},
   {"GL_SGIX_texture_scale_bias", 0},
   {"GL_SGIX_vertex_preclip", 0},
   {"GL_SGIX_ycrcb", 0},
   {"GL_SUN_convolution_border_modes", 0},
   {"GL_SUN_global_alpha", 0},
   {"GL_SUN_mesh_array", 0},
   {"GL_SUN_slice_accum", 1},
   {"GL_SUN_triangle_list", 0},
   {"GL_SUN_vertex", 0},
   {"GL_SUNX_constant_data", 0},
   {"GL_WIN_phong_shading", 0},
   {"GL_WIN_specular_fog", 0},
};

#define extnum sizeof(extension_def) / sizeof(extension_properties)

#define IS_OPTION_TRUE(ch) \
    ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1')

/* config options */
int use_fixed_glextensions_buffer = 0;         /* default to no */
GLubyte *fixed_glextensions_buffer = NULL;     /* the extension string we report when above option is enabled */


 /***********************************************************************
 * get_config_key
 *
 * Get a config key from either the app-specific or the default config
 */
inline static DWORD get_config_key( HKEY defkey, HKEY appkey, const char *name,
                                    char *buffer, DWORD size )
{
    if (appkey && !RegQueryValueExA( appkey, name, 0, NULL, buffer, &size )) return 0;
    return RegQueryValueExA( defkey, name, 0, NULL, buffer, &size );
}

/*
 * Take a comma-separated list of extensions and update the internal list
 * by enabling (+) or disabling (-) a given extension. A sample list might
 * look like: +GL_ARB_imaging,-GL_ARB_depth_texture,-GL_SUN_slice_accum
 */
static void parse_extensions(GLubyte *e)
{
    GLubyte *extensions = e;
    GLubyte *extension = extensions;
    int found, x;
    int active;

    while (extensions && (strlen(extensions)) )
    {
        while((*extension) && (isspace(*extension)))
            ++extension;

        switch(*extension++)
        {
            case '+':
                active = 1;
                break;
            case '-':
                active = 0;
                break;

            default:
                {
                ERR("bad extension string %s\n", extension-1);
                return;
                };
        };

        if( (extensions = (GLubyte *)strchr(extension, ',')) )
            *extensions++ = '\0';

        found = 0;

        for(x = 0; x < extnum; x++)
        {
            if(!strcasecmp(extension, extension_def[x].name))
            {
                extension_def[x].enabled = active;
                found = 1;
                break;
            }
        }

        if (!found) {
            ERR("unknown extension %s\n", extension);
        }

        if (extensions)
            extension = extensions;
    }

    return;
}

/*
 * Returns 1 if the option FixedGLExtensionBuffer is set in the [opengl]
 * section. If true then the list extensions_def[] can be run through to
 * come up with a list of enabled OpenGL extensions.
 */
static int load_options(void) {
    char buffer[MAX_PATH+16] = "";
    HKEY hkey, appkey = 0;
    int result = 0;

    if (RegCreateKeyExA( HKEY_LOCAL_MACHINE, "Software\\Wine\\Wine\\Config\\opengl", 0, NULL, REG_OPTION_VOLATILE, KEY_ALL_ACCESS, NULL, &hkey, NULL ))
    {
        ERR("Cannot create config registry key\n" );
        return 0;
    }

    /* open the app-specific key */
    if (GetModuleFileName16( GetCurrentTask(), buffer, MAX_PATH ) ||
        GetModuleFileNameA( 0, buffer, MAX_PATH ))
    {
        HKEY tmpkey;
        char *p, *appname = buffer;
        if ((p = strrchr( appname, '/' ))) appname = p + 1;
        if ((p = strrchr( appname, '\\' ))) appname = p + 1;
        strcat( appname, "\\opengl" );
        if (!RegOpenKeyA( HKEY_LOCAL_MACHINE, "Software\\Wine\\Wine\\Config\\AppDefaults", &tmpkey ))
        {
            if (RegOpenKeyA( tmpkey, appname, &appkey )) appkey = 0;
            RegCloseKey( tmpkey );
        }
    }

    if (!get_config_key( hkey, appkey, "FixedGLExtensionBuffer", buffer, sizeof(buffer) ))
        result = IS_OPTION_TRUE(buffer[0]);
    TRACE("FixedGLExtensionBuffer=(%d)\n", result);

    if (result) {
        if (!get_config_key( hkey, appkey, "GLExtensionBuffer", buffer, sizeof(buffer) ))
            parse_extensions(buffer);
    }

    if (appkey) RegCloseKey( appkey );
    RegCloseKey( hkey );

    return result;
}

/* 
 * sanity check the enabled extensions against what is really 
 * supported by hardware, so that we don't report support for 
 * features that really don't exist
 */
void validate_extensions() 
{
    int i, found;
    const GLubyte * real_extensions = glGetString(GL_EXTENSIONS);
    GLubyte *e, *tmp;
    int len;

    TRACE("the real OpenGL extension string is:\n"); 
    if( TRACE_ON(opengl) ) {
      fprintf(stderr, "%s\n", real_extensions);
    }
      
    for( i=0; i < extnum; i++ ) {
      /* validate the enabled extensions against the real glstring,
       * taking care, since some strings may be substrings of others */
      found = 0;
      if( extension_def[i].enabled ) {
        e = (GLubyte *)real_extensions;
        len = strlen(extension_def[i].name);
        while( (tmp = strstr(e, extension_def[i].name)) != NULL ) {
          if( tmp[len] == '\0' || tmp[len] == ' ' ) {
            found = 1;
            break;
          }
          e = tmp+len;
        }
        if( !found ) {
          TRACE("extension %s not supported by hardware, disabling\n", extension_def[i].name);
          extension_def[i].enabled = 0;
        }
      }
    }
}

/* 
 * create the string the corresponds to the list of enabled extensions
 */
GLubyte * create_extension_string()
{
    int i;
    GLubyte * string;
    int size = 4096;

    /* Yes this memory will "leak" */
    string = (GLubyte *)malloc(4096);

    string[0] = '\0';

    /* The extension string is a space-delimited list */
    for (i=0; i < extnum; i++) {
	/* if the extension is enabled and actually supported by hardware, we report it */
	if (extension_def[i].enabled) {
	    if ((strlen(string) + strlen(extension_def[i].name) + 1) > size)
	    {
		size += 4096;
		string = (GLubyte *)realloc(string, size);
	    }
	    strcat(string, extension_def[i].name);
	    strcat(string, " ");
	}
    }
    /* leave the trailing space (the Nvidia 61.11 drivers seem to have one) */

    return string;
}


/* return 1 if we are overriding the gl extension string
 * the string is stored in fixed_glextensions_buffer 
 * otherwise return 0
 */
static int check_extension_override() 
{
    static int init_options = 1;

    if( init_options ) {
        /* we only want to load the options and create the string once */
	use_fixed_glextensions_buffer = load_options();
	if( use_fixed_glextensions_buffer ) 
	{
	  /* only allow extensions supported by hardware */
	  validate_extensions();
	  /* create our internal hardcode extension string */
	  fixed_glextensions_buffer = create_extension_string();
	  
	  TRACE("overriding OpenGL extension string with:\n"); 
	  if(TRACE_ON(opengl)) {
	    fprintf(stderr, "%s\n", fixed_glextensions_buffer);
	  }
	}
	init_options = 0;
    }

    return use_fixed_glextensions_buffer;
}

/*
 * Do some special work when trying to return the GL_EXTENSIONS string.
 */
static const GLubyte * glGetString_override( GLenum name ) {
    if (name == GL_EXTENSIONS && check_extension_override()) {
        return fixed_glextensions_buffer;
    } else {
        return glGetString( name );
    }
}
