/**********************************************************************
This file is part of Crack dot Com's free source code release of Golgotha.
for information about compiling & licensing issues visit this URL
 If that doesn't help, contact Jonathan Clark at 
  golgotha_source@usa.net (Subject should have "GOLG" in it) 
***********************************************************************/

#include "arch.hh"
#include "math/num_type.hh"
#include "time/profile.hh"
#include "software/r1_software.hh"
#include "software/r1_software_globals.hh"
#include "software/mappers.hh"
#include "software/span_buffer.hh"
#include "software/inline_fpu.hh"
#include "software/amd3d/amd3d.h"

//declarations of the arrays
span_tri_info global_tri_list[MAX_TRIS];
span_entry    global_span_list[MAX_SPANS];
span_edge     global_edges[MAX_NUM_EDGES];
span_edge     *new_edges[MAX_VERTICAL_RESOLUTION];
span_edge     *remove_edges[MAX_VERTICAL_RESOLUTION];

int num_global_tris  = 0;
int num_global_spans = 1; //span #0 is a sentinel, unused
int num_global_edges = 0;

//"background" edges (so there are no holes. or if there are, you can
//make them funky colors or somethin)
span_tri_info tri_stack;
span_edge active_list_head;
span_edge active_list_tail;

span_edge *compare_here;
span_edge *temp_edge,*t1;

inline void sort_in_new_edges(span_edge *first)
{
  compare_here = &active_list_head;

  while (first)
  {    
    while (first->x > compare_here->next_active->x)
    {      
      compare_here = compare_here->next_active;      
    }        
    
    //store first->next. remember, ->next and ->last_active are a union
    // (to save space)

    temp_edge = first->next;

    first->last_active = compare_here;
    first->next_active = compare_here->next_active;

    compare_here->next_active->last_active = first;
    compare_here->next_active = first;    
    
    compare_here = first;
    
    #ifdef DEBUG
	if (!first->next_active)
	{
	sw32 a=0;
	}
    #endif

	//first = first->next,see comment above
    first = temp_edge;
  }
}

span_entry *next_new_span;
span_tri_info *cur_stack;
span_tri_info *temp_stack;
span_tri_info *e_tri;
i4_bool everything_transparent;
sw32 span_buff_x,span_buff_y,span_length;
w16  *cur_scanline;

span_edge **new_edge_list = new_edges;
span_edge **remove_edge_list = remove_edges;

float fx_fy[2];

#define fx (fx_fy[0])
#define fy (fx_fy[1])

float e_ooz;
float ooz_compare;

i4_bool on_top;

inline void intel_depth_at_pixel(tri_gradients *grads, float *result)
{
#ifdef USE_ASM
  _asm
  {
    mov edi,dword ptr [grads]
    mov esi,dword ptr [result]

    fld dword ptr [fx_fy]
    fld dword ptr [fx_fy+4]
    fmul qword ptr [edi]tri_gradients.doozdy
    fxch st(1)
    fmul qword ptr [edi]tri_gradients.doozdx
    fxch st(1)
    fadd qword ptr [edi]tri_gradients.oozat00
    fxch st(1)
    faddp st(1),st(0)
    
    fstp dword ptr [esi]
  }
#else
  *result = grads->oozat00 + fx*grads->doozdx + fy*grads->doozdy;
#endif
}

i4_bool intel_build_triangle_span_lists()
{
  register span_edge *e;

  new_edge_list    = new_edges;
  remove_edge_list = remove_edges;
  cur_scanline     = r1_software_render_buffer_ptr;
  next_new_span    = &global_span_list[num_global_spans];

  for (span_buff_y = 0; span_buff_y < r1_software_render_buffer_height;
       span_buff_y++, cur_scanline += r1_software_render_buffer_wpl)
  {
    if (*new_edge_list)
    {
      sort_in_new_edges(*new_edge_list);
    }
    new_edge_list++;
        
    tri_stack.has_leader         = 0;
    tri_stack.cur_span_start_x   = 0;
    tri_stack.cur_span_start_ooz = -99999.f;
    tri_stack.next_stack = tri_stack.last_stack = &tri_stack;
    
    fy = (float)span_buff_y;

    //go through the x sorted active spans for this scanline
    for (e=active_list_head.next_active; e != &active_list_tail; e=e->next_active)
    {      
      //round to nearest x
      span_buff_x  = (e->x+0xFFFF) >> 16;
      
      fx = ((float)span_buff_x);// * oo65536);

      e_tri = e->tri_1;

      if (e->flags & LEADING_1)
      {         
        if ((e_tri->has_leader++)==0)
        {
          //e_ooz = e_tri->grads.oozat00 + (fx*e_tri->grads.doozdx) + (fy*e_tri->grads.doozdy);
          intel_depth_at_pixel(&e_tri->grads, &e_ooz);

          //leading edge. stack manipulation stuff
          cur_stack = tri_stack.next_stack;
          
          everything_transparent = i4_T;
          on_top                 = i4_T;
          
          //go down the stack until we find where this guy will go.          
          while (cur_stack != &tri_stack)
          {
            //ooz_compare = e_ooz - (cur_stack->grads.oozat00     +
                                   //cur_stack->grads.doozdx * fx +
                                   //cur_stack->grads.doozdy * fy);

            intel_depth_at_pixel(&cur_stack->grads, &ooz_compare);
            ooz_compare = e_ooz - ooz_compare;

            if ( ( (*(int *)&ooz_compare) & (1<<31) ) == 0) //if its positive
              break;
            
            //if (ooz_compare == 0)
            if ((*(int *)&ooz_compare) == 0) //its zero
            {
              //edges are of equal depth. compare the gradient.
              if (e_tri->grads.doozdx > cur_stack->grads.doozdx)
                break;
            }
            
            on_top = i4_F;

            //this will tell us if everything on top of (in front of) this surface is see-thru-able
            if (cur_stack->type < SPAN_TRI_SEE_THRU)
              everything_transparent = i4_F;
            
            cur_stack = cur_stack->next_stack;                                                            
          }
          
          e_tri->next_stack = cur_stack;
          e_tri->last_stack = cur_stack->last_stack;          
  
          cur_stack->last_stack->next_stack = e_tri;
          cur_stack->last_stack             = e_tri;
  
          e_tri->cur_span_start_x = span_buff_x;
          *(int *)&e_tri->cur_span_start_ooz = *(int *)&e_ooz; //for some reason the compiler did this with fld / fst

          //if this is a solid span, and everything above it is transparent, gotta draw whatever is below it
          //if this is a solid span, and its on top, gotta draw whatever is below it
          
          if (e_tri->typenext_stack;

            while (cur_stack != &tri_stack)
            {
              //we've gotta create spans for everything below this in the stack.
              span_length = span_buff_x - cur_stack->cur_span_start_x;
              if (span_length>0)
              {
                //make a span
                next_new_span->s.x            = cur_stack->cur_span_start_x;
                next_new_span->s.y            = span_buff_y;
                next_new_span->s.width        = span_length;
                next_new_span->s.ooz          = cur_stack->cur_span_start_ooz;
                next_new_span->s.scanline_ptr = cur_scanline;
                
                //add to its triangles list of spans
                next_new_span->s.next_tri_span = cur_stack->span_list_head;
                cur_stack->span_list_head = num_global_spans;
                
                num_global_spans++;
                next_new_span++;
                if (num_global_spans>=MAX_SPANS) return i4_F;
              }        
              if (cur_stack->typenext_stack;            
            }
          }
        }
      }
      else //is a trailer
      if ((e_tri->has_leader--)==1)
      {
        //trailing edge
        //is this the end of a span for the tri currently on top of the stack?
        if (tri_stack.next_stack==e_tri)
        {
          cur_stack = e_tri;
          
          span_length = span_buff_x - cur_stack->cur_span_start_x;
          if (span_length>0)
          {
            //make a span            
            next_new_span->s.x            = cur_stack->cur_span_start_x;
            next_new_span->s.y            = span_buff_y;
            next_new_span->s.width        = span_length;
            next_new_span->s.ooz          = cur_stack->cur_span_start_ooz;
            next_new_span->s.scanline_ptr = cur_scanline;
                
            //add to its triangles list of spans
            next_new_span->s.next_tri_span = cur_stack->span_list_head;
            cur_stack->span_list_head = num_global_spans;                
                
            num_global_spans++;
            next_new_span++;
            if (num_global_spans>=MAX_SPANS) return i4_F;
          }
                    
          //setup the start of the next span on the stack
          //IF this one wasnt transparent. otherwise,
          //we want the span start info for that span to be
          //maintained (unaltered)
          if (e_tri->typenext_stack;
              
              if (cur_stack==&tri_stack)
                break;
              
              cur_stack->cur_span_start_x = span_buff_x;

              //calculate the ooz value for this span (needed for depth comparisons as well as actually
              //writing out the span)

              intel_depth_at_pixel(&cur_stack->grads, &cur_stack->cur_span_start_ooz);
              // = cur_stack->grads.oozat00 + (cur_stack->grads.doozdy * fy) + (cur_stack->grads.doozdx * fx);

            } while (cur_stack->type > SPAN_TRI_SEE_THRU);
          }
        }
        else
        {
          //this span ends below whats on top of the stack.
          //but if everything above this span is transparent, we need to draw it
          temp_stack = e_tri->last_stack;
          
          while (temp_stack->type>SPAN_TRI_SEE_THRU)
            temp_stack = temp_stack->last_stack;
          
          //did we make it past everything in the stack?
          if (temp_stack==&tri_stack)
          {
            //if so, that means everything above us is transparent and this span
            //will be seen, so we gotta draw it
          
            span_length = span_buff_x - e_tri->cur_span_start_x;
            if (span_length>0)
            {                          
              //make a span
              next_new_span->s.x            = e_tri->cur_span_start_x;
              next_new_span->s.y            = span_buff_y;
              next_new_span->s.width        = span_length;
              next_new_span->s.ooz          = e_tri->cur_span_start_ooz;
              next_new_span->s.scanline_ptr = cur_scanline;
                
              //add to its triangles list of spans
              next_new_span->s.next_tri_span = e_tri->span_list_head;
              e_tri->span_list_head = num_global_spans;
                
              num_global_spans++;
              next_new_span++;
              if (num_global_spans>=MAX_SPANS) return i4_F;
            }

            //setup the start of the next span in
            //IF this one wasnt transparent. otherwise,
            //we want the span start info for that span to be
            //maintained (unaltered)
            temp_stack = e_tri;

            if (e_tri->typenext_stack;
                if (temp_stack == &tri_stack)
                  break;
                
                temp_stack->cur_span_start_x = span_buff_x;

                //calculate the ooz value for this span (needed for depth comparisons as well as actually
                //writing out the span)            
                //temp_stack->cur_span_start_ooz = temp_stack->grads.oozat00 +
                //                                (temp_stack->grads.doozdy * fy) +
                //                                (temp_stack->grads.doozdx * fx);

                intel_depth_at_pixel(&temp_stack->grads, &temp_stack->cur_span_start_ooz);

              } while (temp_stack->type > SPAN_TRI_SEE_THRU);
            }
          }                    
        }
                
        e_tri->last_stack->next_stack = e_tri->next_stack;
        e_tri->next_stack->last_stack = e_tri->last_stack;                
      }      
    }
        
    //done with this scanline.
    //dont forget that IF we want to draw the background, we need to make one more span going from
    //tri_stack.cur_span_start_x to the rightmost pixel on the screen (if that area is "open")

    //remove the ones that need removing
    temp_edge = *remove_edge_list;
    while (temp_edge)
    {      
      temp_edge->last_active->next_active = temp_edge->next_active;
      temp_edge->next_active->last_active = temp_edge->last_active;

      temp_edge = temp_edge->next_remove;
    }
    remove_edge_list++;

    //step the active edges
    temp_edge = active_list_head.next_active;
    while (temp_edge != &active_list_tail)
    {
      temp_edge->x += temp_edge->dxdy;
      
      //make sure the edges remain sorted            
      compare_here = temp_edge;
      if (temp_edge->x < compare_here->last_active->x)
      { 
        t1 = temp_edge->next_active;

        while (temp_edge->x < compare_here->last_active->x)
          compare_here = compare_here->last_active;
     
        //get temp_edge out of its current spot
        temp_edge->last_active->next_active = temp_edge->next_active;
        temp_edge->next_active->last_active = temp_edge->last_active;

        //insert it between compare_here and its predecessor      
        compare_here->last_active->next_active = temp_edge;
        
        temp_edge->last_active = compare_here->last_active;
        compare_here->last_active = temp_edge;

        temp_edge->next_active = compare_here;
        
        temp_edge = t1;
      }
      else
      {
        temp_edge = temp_edge->next_active;
      }
    }    
  }  
  return i4_T;
}

void span_draw_solid_color(span_tri_info *tri)
{  
  span_entry *s = &global_span_list[tri->span_list_head];
  w16 *start_pixel;

  solid_blend_span left;  
  
  left.color = tri->color;

  while (s!=global_span_list)
  {    
    cur_scanline_texture_func(s->s.scanline_ptr,(s->s.x<<1),&left,s->s.width);

    s = &global_span_list[s->s.next_tri_span];
  }
}

void init_span_buffer()
{
  memset(remove_edges,0,sizeof(span_edge *) * MAX_VERTICAL_RESOLUTION);
  memset(new_edges,0,sizeof(span_edge *) * MAX_VERTICAL_RESOLUTION);
  num_global_spans = 1; //span #0 is a sentinel, unused
  num_global_tris  = 0;
  num_global_edges = 0;
  
  //setup "background" (leaves no holes)  
  active_list_head.x           = (sw32)(-2048) << 16; //large negative fixed number
  active_list_head.dxdy        = 0;
  active_list_head.flags       = LEADING_1;
  active_list_head.tri_1       = &tri_stack;

  active_list_tail.x           = (sw32)(2048) << 16; //large fixed number
  active_list_tail.dxdy        = 0;
  active_list_tail.flags       = 0;
  active_list_tail.tri_1       = &tri_stack;

  tri_stack.next_stack = tri_stack.last_stack = &tri_stack;
  tri_stack.grads.doozdx = 0;
  tri_stack.grads.doozdy = 0;
  tri_stack.grads.oozat00 = -999999.f;
  
  tri_stack.color = 0x55555555;

  tri_stack.type = SPAN_TRI_SOLID_FILL;
}

i4_profile_class build_span_lists("build span lists");
i4_profile_class pf_software_add_start_edge("software::add_start_edge");

sw32 shared_edges = 0;
sw32 total_edges  = 0;

void clear_spans()
{
  memset(remove_edges,0,sizeof(span_edge *) * MAX_VERTICAL_RESOLUTION);
  memset(new_edges,0,sizeof(span_edge *) * MAX_VERTICAL_RESOLUTION);
  
  num_global_spans = 1; //span #0 is a sentinel, unused
  num_global_tris  = 0;
  num_global_edges = 0;
  
  //i4_warning("total edges: %3d shared edges: %3d",total_edges,shared_edges);
  total_edges  = 0;
  shared_edges = 0;
}

void flush_spans() //set all pointers in the scanline list to 0
{
  //take it out of single precision
  if (!num_global_tris) return;

  sw32 i;
  span_tri_info *t;
  
  //reset these pointers incase they got messed with
  active_list_head.next_active = &active_list_tail;
  active_list_head.last_active = 0;
  
  active_list_tail.next_active = 0;
  active_list_tail.last_active = &active_list_head;
  
  build_span_lists.start();
  
  //single precision truncate mode from here on out
  start_trunc();
  start_single();

  if (!cur_build_span_lists_function || !cur_build_span_lists_function())
  {
    //if the function isnt defined or didnt succeed, issue an error
    i4_warning("software::build triangle span lists failed");    
  }
  
  build_span_lists.stop();

  //draw all the solid ones
  for (i=0,t=global_tri_list; ispan_list_head) continue;            
    if (t->type > SPAN_TRI_SEE_THRU) continue;
    
    cur_scanline_texture_func = texture_scanline_functions[t->type];
    (span_draw_functions[t->type])(t);    
  }    
  
  
  //draw all the transparent ones
  for (i=0,t=global_tri_list; ispan_list_head) continue;
    if (t->type < SPAN_TRI_SEE_THRU) continue;        
    
    cur_scanline_texture_func = texture_scanline_functions[t->type];
    (span_draw_functions[t->type])(t);        
  }
  
#ifdef USE_AMD3D  
  if (do_amd3d)
    _asm femms  //end the mmx usage for all the tmappers
#endif

  stop_single();
  stop_trunc();   
}