#include "../include/zf.h"

/* see zf_animation_system.c for comments! ... shudder*/
CLcolour fog_colour;

typedef struct Renderer Renderer;

struct Renderer
{
    void* data;

    ZfSmartPointer* smart_pointer;

    ZfRender* render;
};

static ZfSmartPointer renderer_smart_pointer;

static GList* opaque_list;
static GList* translucent_list;

static bool   tool_mode;            /* if true, all objects are drawn in tool mode */

static bool
renderer_is_valid(const Renderer* renderer)
{
    return renderer->smart_pointer->is_valid(renderer->data);
}

static void
renderer_reference(Renderer* renderer)
{
    renderer->smart_pointer->reference(renderer->data);
}

static void
renderer_release(Renderer* renderer)
{
    renderer->smart_pointer->release(renderer->data);

    g_free(renderer);
}

static void
render(Renderer* renderer)
{
    renderer->render(renderer->data);
}

void
zf_render_system_init(void)
{
    opaque_list = 0;
    translucent_list = 0;

    renderer_smart_pointer.is_valid = (ZfIsValid*) renderer_is_valid;
    renderer_smart_pointer.reference = (ZfReference*) renderer_reference;
    renderer_smart_pointer.release = (ZfRelease*) renderer_release;

#if 1
    
    fog_colour.r = 0.0f;
    fog_colour.g = 0.0f;
    fog_colour.b = 0.1f;
    fog_colour.a = 1.0f;
    
    glFogfv(GL_FOG_COLOR, (GLfloat*)&fog_colour);
    glFogf(GL_FOG_START, 50.5f);
    glFogf(GL_FOG_END, 200.5f);
    glFogi(GL_FOG_MODE, GL_LINEAR);

#endif

}

void
zf_render_system_close(void)
{
    zf_list_destroy(opaque_list, &renderer_smart_pointer);
    zf_list_destroy(translucent_list, &renderer_smart_pointer);
}

/* nick - special function to get around water reflection drawing over
   translucent objects (since they aren't in depth buffer) */
void
zf_render_system_step_translucent(void)
{
    glPushAttrib(GL_ALL_ATTRIB_BITS);

    if (tool_mode)
	glDisable(GL_FOG);
    else
	glEnable(GL_FOG);
    
    glEnable(GL_ALPHA_TEST);

    glAlphaFunc(GL_NOTEQUAL, 1.0);
    glEnable(GL_BLEND);
    glDepthMask(GL_FALSE);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    zf_list_foreach_valid(translucent_list,
			  &renderer_smart_pointer, (ZfRender*) render);

    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    glDisable(GL_FOG); 

    glPopAttrib();

    /* invalids - who needs them? (not the render system... anymore) */
    translucent_list = zf_list_remove_invalids(translucent_list,
					       &renderer_smart_pointer);
}

/*!
  \todo A light source is hacked in to this function!
*/
void
zf_render_system_step_opaque(void)
{
    glPushAttrib(GL_ALL_ATTRIB_BITS); /* very conservative!!! */

    glClearColor(fog_colour.r, fog_colour.g, fog_colour.b, fog_colour.a);
    
    if (tool_mode)
	glDisable(GL_FOG);
    else
	glEnable(GL_FOG);

    /*!
      haXor! add a light, later on use functions to add lights to
      render system
    */
    {
	static CLlight* light = 0;
	
	if (!light)
	    light = cluSetLightSubtle(clNewLight());

	light->position[0] = -0.5f;
	light->position[1] = 1.0f;
	light->position[2] = 0.5f;
	light->position[3] = 0;

	clLoadLight(light);
    }

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    glEnable(GL_ALPHA_TEST);

    glAlphaFunc(GL_EQUAL, 1.0);
    glEnable(GL_DEPTH_TEST);
    zf_list_foreach_valid(opaque_list,
			  &renderer_smart_pointer, (ZfRender*) render);

    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    glDisable(GL_FOG); 

    glPopAttrib();

    /* invalids - who needs them? (not the render system... anymore) */
    opaque_list = zf_list_remove_invalids(opaque_list,
					  &renderer_smart_pointer);
}

/*!
  \todo A light source is hacked in to this function!

  \todo fog enabled/disabled each frame - yuck
*/
void
zf_render_system_step(void)
{
    glPushAttrib(GL_ALL_ATTRIB_BITS); /* very conservative!!! */

    glClearColor(fog_colour.r, fog_colour.g, fog_colour.b, fog_colour.a);
    
    if (tool_mode)
    {
	glDisable(GL_FOG);
    }
    else
    {
	glEnable(GL_FOG);
    }

#if 1
    /*!
      haXor! add a light, later on use functions to add lights to
      render system
    */
    {
	static CLlight* light = 0;
	
	if (!light)
	    light = cluSetLightSubtle(clNewLight());

	light->position[0] = -0.5f;
	light->position[1] = 1.0f;
	light->position[2] = 0.5f;
	light->position[3] = 0;

	clLoadLight(light);
    }

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
#endif

    glEnable(GL_ALPHA_TEST);
    /*
      printf("in render start2\n");
      fflush(stdout);
    */

    glAlphaFunc(GL_EQUAL, 1.0);
    glEnable(GL_DEPTH_TEST);
    zf_list_foreach_valid(opaque_list,
			  &renderer_smart_pointer, (ZfRender*) render);

    /*
      printf("in render start3\n");
      fflush(stdout);
    */

    glAlphaFunc(GL_NOTEQUAL, 1.0);
    glEnable(GL_BLEND);
    glDepthMask(GL_FALSE);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    zf_list_foreach_valid(translucent_list,
			  &renderer_smart_pointer, (ZfRender*) render);

    /*
      printf("in render start4\n");
      fflush(stdout);
    */

    glPopAttrib();

    /* invalids - who needs them? (not the render system... anymore) */
    opaque_list = zf_list_remove_invalids(opaque_list,
					  &renderer_smart_pointer);
    translucent_list = zf_list_remove_invalids(translucent_list,
					       &renderer_smart_pointer);

    /*
      printf("in render start5\n");
      fflush(stdout);
    */
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    glDisable(GL_FOG); 
}

void
zf_render_system_add_opaque(void* data,
			    ZfSmartPointer* smart_pointer,
			    ZfRender* render)
{
    Renderer* renderer;

    renderer = g_new(Renderer, 1);
    
    renderer->data = data;
    renderer->smart_pointer = smart_pointer;
    renderer->render = render;

    opaque_list = zf_list_add_data(opaque_list,
				   renderer,
				   &renderer_smart_pointer);
}

void
zf_render_system_add_translucent(void* data,
				 ZfSmartPointer* smart_pointer,
				 ZfRender* render)
{
    Renderer* renderer;

    renderer = g_new(Renderer, 1);
    
    renderer->data = data;
    renderer->smart_pointer = smart_pointer;
    renderer->render = render;

    translucent_list = zf_list_add_data(translucent_list,
					renderer,
					&renderer_smart_pointer);
}

/*
  render system

  set alpha test to discard non-opaque
  render opaque

  set alpha test to discard non-translucent
  enable blending
  render translucent
  disable blending

  want callbacks for

   in frustum
   distance hint

  want data for

   opaque/translucent (if both, add twice with different render funcs)
   texture/not textured (all textured?)

  want to be able to add

   lights... or just loadables?... probably lights
   renderables
*/

void
zf_render_system_print(void)
{
    GList* li;
    Renderer* renderer;    
    unsigned int count = 0;

    printf("%s()\n", __FUNCTION__);
    printf("opaque list has %u elements\n",
	   g_list_length(opaque_list));

    printf("translucent list has %u elements\n",
	   g_list_length(translucent_list));
}

unsigned int
zf_render_system_get_renderable_count(void)
{
    return g_list_length(opaque_list) + g_list_length(translucent_list);
}

void
zf_render_system_set_tool_mode(bool is_tool)
{
    tool_mode = is_tool;
}

bool
zf_render_system_get_tool_mode(void)
{
    return tool_mode;
}
