/**********************************************************************
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 "tex_heap.hh"
#include "tex_no_heap.hh"

i4_profile_class pf_tex_heap_alloc("tex_heap::alloc()");
i4_profile_class pf_tex_heap_free("tex_heap::free()");
i4_profile_class pf_tex_heap_cleanup("tex_heap::cleanup()");

i4_profile_class pf_tex_no_heap_alloc("tex_heap::alloc()");
i4_profile_class pf_tex_no_heap_free("tex_heap::free()");
i4_profile_class pf_tex_no_heap_cleanup("tex_heap::cleanup()");


r1_texture_heap_class::~r1_texture_heap_class()
{
  //kill the free list
  r1_tex_heap_free_node *next_free,*last_free;

  next_free = first_free;
  while (next_free)
  {
    last_free = next_free;
    next_free = next_free->next;
      
    if (last_free->mip)
    {
      last_free->mip->vram_handle = 0;
    }      
    free_node_alloc->free(last_free);
  }
  first_free = 0;

    //kill the used list
  r1_tex_heap_used_node *next_used,*last_used;
  
  next_used = first_used;
  while (next_used)
  {
    last_used = next_used;
    next_used = next_used->next;
      
    if (last_used->node->mip->vram_handle)
    {
      last_used->node->mip->vram_handle = 0;
    }
    free_node_alloc->free(last_used->node);
    used_node_alloc->free(last_used);
  }
  first_used = 0;

  oldest_used = 0;
  //both lists are cleared. kill the allocaters

  delete free_node_alloc;
  free_node_alloc=0;

  delete used_node_alloc;
  used_node_alloc=0;
}

r1_texture_heap_class::r1_texture_heap_class(w32 heap_size, w32 heap_start, w32 free_node_size, w32 used_node_size, sw32 *frame_count)
{
  num_ram_misses  = 0;
  needs_cleanup   = i4_F;

  free_node_alloc = 0;
  used_node_alloc = 0;

  frame_count_ptr = frame_count;
   
  if (!free_node_alloc)
  {
    free_node_alloc   = new i4_linear_allocator(free_node_size, 2048, 1024, "texture heap free nodes");
    first_free        = (r1_tex_heap_free_node *)free_node_alloc->alloc();
    first_free->size  = heap_size;
    first_free->start = heap_start;
    first_free->next  = 0;
    first_free->mip   = 0;
  }

  if (!used_node_alloc)
  {
    used_node_alloc  = new i4_linear_allocator(used_node_size, 2048, 1024, "texture heap used nodes");
    first_used       = 0;
    oldest_used      = 0;
  }
}


void r1_texture_heap_class::free_really_old()
{
  pf_tex_heap_cleanup.start();    

  w32 total_freed = 0;    
    
  r1_tex_heap_used_node *u = oldest_used;
  r1_tex_heap_used_node *last;
    
  while (u && total_freed < max_fail_size)
  {      
    sw32 lfu = u->node->mip->last_frame_used;
    sw32 age = *frame_count_ptr - lfu;

    last = u->last;

    if ((lfu != -1) && (age > 10))
    {        
      total_freed += u->node->size;        

      free(u);
    }
      
    u = last;
  }        

//   if (total_freed < max_fail_size)      
//     i4_warning("Texture cleanup: freed %d bytes (not enough)",total_freed);
//   else
//     i4_warning("Texture cleanup: freed %d bytes (successful)",total_freed);
    
  max_fail_size = 0;
  needs_cleanup = i4_F;

  pf_tex_heap_cleanup.stop();
}


  
r1_tex_heap_used_node *r1_texture_heap_class::alloc(w32 need_size, w8 flags)
{
  pf_tex_heap_alloc.start();

  //ok. we know how much memory we need, now try to find a free area of memory
  r1_tex_heap_free_node *f    = first_free;
  r1_tex_heap_free_node *last = 0;
  
  while (f && f->sizenext;
  }

  r1_tex_heap_used_node *new_used = 0;

  if (!f)
  {
    //no memory large enough. dig through and see if there is a used chunk that we can free
    r1_tex_heap_used_node *u = oldest_used;
      
    while (u)
    {
      if (u->node->size >= need_size)
      {          
        r1_miplevel_t *m = u->node->mip;
        if (m->last_frame_used!=-1 && m->last_frame_used < (*frame_count_ptr - 2))
        {
          if (m->flags & R1_MIPLEVEL_IS_LOADING)
          {
            i4_warning("loading miplevel is in used list. bad.");              
          }
          break;
        }
      }      
      u = u->last;
    }
    
    //couldnt find a used one that we could free
    if (!u)
    {
      num_ram_misses++;
        
      if (need_size > max_fail_size)
        max_fail_size = need_size;

      if ((num_ram_misses & 15) == 0)
      {
        //cleanup old textures every 16 misses
        needs_cleanup = i4_T;
      }

      pf_tex_heap_alloc.stop();
      return 0;
    }
      
    new_used = u;
      
    f = new_used->node;

    //kill the old mip's reference to it
    f->mip->vram_handle = 0;

    if (f->size > need_size)
    {
      //need to merge unused space back into free list        
       
      merge_into_free_list(f->start + need_size, f->size - need_size);
        
      f->size = need_size;
    }

    if (flags & R1_TEX_HEAP_DONT_LIST)
    {
      if (new_used==first_used)
        first_used = new_used->next;        
      if (new_used==oldest_used)
        oldest_used = new_used->last;

      //this flag is set for asynchronously loaded textures
      //we dont even want this node to be listed. it will be added
      //when the async load is finished
      if (new_used->next) new_used->next->last = new_used->last;
      if (new_used->last) new_used->last->next = new_used->next;                

      new_used->next=0;
      new_used->last=0;
    }
  }
  else
  {
    // remove the this node from the free list      
    if (!last)
      first_free = f->next;
    else
      last->next = f->next;

    //if we dont match the size exactly, need to create a new free node to fill in the space
    if (f->size != need_size)
    {
      r1_tex_heap_free_node *new_free = (r1_tex_heap_free_node *)free_node_alloc->alloc();
      
      if (!new_free)
      {
        i4_warning("alloc of tex_heap free_node failed");
        pf_tex_heap_alloc.stop();
        return 0;
      }

      new_free->size  = f->size  - need_size;
      new_free->start = f->start + need_size;
      new_free->mip=0;
   
      if (last)
        last->next = new_free;
      else
        first_free = new_free;
  

      new_free->next = f->next;
      f->size = need_size;
    }
      
    new_used = (r1_tex_heap_used_node *)used_node_alloc->alloc();
    
    //f is the node we'll use
    //add into the used_list    
    
    if (!new_used)
    {
      i4_warning("alloc of tex_heap used_node failed");
      pf_tex_heap_alloc.stop();
      return 0;        
    }

    new_used->node = f;
      
    if (flags & R1_TEX_HEAP_DONT_LIST)
    {
      new_used->next=0;
      new_used->last=0;
    }
    else
    {
      new_used->next = first_used;  
      new_used->last = 0;
    
      if (first_used)
        first_used->last = new_used;          

      first_used = new_used;
    }
  }
    
  pf_tex_heap_alloc.stop();
  return new_used;
}

void r1_texture_heap_class::merge_into_free_list(w32 _start, w32 _size)
{    
  r1_tex_heap_free_node *next, *last;

  // find proper spot to add into free list
  next = first_free; last=0;
  for (; next && next->start < _start;)
  {
    last = next;
    next = next->next;
  }

  // can we combine with the last node?
  if (last && last->start+last->size == _start)
  {
    last->size += _size;
      
    // does this also combine with the next node?
    if (next && (last->start + last->size == next->start) )
    {
      last->size += next->size;
      last->next =  next->next;
  
      free_node_alloc->free(next);
    }        
  }
  else
    if (next && (_start + _size == next->start) )  // does it combine with the next block?
    {
      next->start = _start;
      next->size += _size;                  
    }
    else
      if (last) // put after the last proper node
      {
        r1_tex_heap_free_node *f = (r1_tex_heap_free_node *)free_node_alloc->alloc();

        f->size  = _size;
        f->start = _start;
        f->next  = last->next; //(last->next should be NULL)
        f->mip   = 0;

        last->next = f;
      }
      else // put it at the begining of the list
      {
        r1_tex_heap_free_node *f = (r1_tex_heap_free_node *)free_node_alloc->alloc();
      
        f->size  = _size;
        f->start = _start;
        f->next  = first_free;
        f->mip   = 0;
        first_free = f;      
      }    
}

void r1_texture_heap_class::update_usage(r1_tex_heap_used_node *u)
{
  if (u->node->mip->last_frame_used==-1)
    return ;
    

  u->node->mip->last_frame_used = *frame_count_ptr;
      
  if (u != first_used)
  {
    //put this node at the front of the used list
    //has the effect of sorting all used nodes according to
    //their age (most recently used are at the
    //front of the list)

    if (u==oldest_used)
      oldest_used = u->last;

    if (u->next) u->next->last = u->last;    
    if (u->last) u->last->next = u->next;
  
    u->last = 0;
    u->next = first_used;
      
    if (first_used)
      first_used->last = u;

    first_used = u;
  }
    
  //update the oldest used pointer
  if (!oldest_used)
    oldest_used = u;
  else
  {
    if ((u->node->mip->last_frame_used != -1) &&
        (u->node->mip->last_frame_used < oldest_used->node->mip->last_frame_used))
    {
      oldest_used = u;    
    }
  }
}

void r1_texture_heap_class::free(r1_tex_heap_used_node *u)
{    
  if (!u || !u->node)
  {
    i4_warning("tex_heap_class free : handle is 0");
    return;
  }
      
  if (u->node->mip->flags & R1_MIPLEVEL_IS_LOADING)
  {
    i4_warning("free called on a mip that was still loading");
  }

  pf_tex_heap_free.start();

  r1_tex_heap_free_node *f = u->node, *next, *last;
    
  //give its r1_miplevel_t an invalid vram handle
  f->mip->vram_handle = 0;
  
  //process these cases 1st
  if (u==first_used)    
    first_used = u->next;    
    
  if (u==oldest_used)    
    oldest_used = u->last;

  //pull it from the used_list    
  if (u->next) u->next->last = u->last;
  if (u->last) u->last->next = u->next;          

  used_node_alloc->free(u);
  //end of pulling from the used list
  
  // find proper spot to add into free list
  next = first_free; last=0;
  for (; next && next->startstart;)
  {
    last=next;
    next=next->next;
  }

  // can we combine with the last node?
  if (last && last->start+last->size==f->start)
  {
    last->size += f->size;
      
    // does this also combine with the next node?
    if (next && (last->start + last->size == next->start) )
    {
      last->size += next->size;
      last->next =  next->next;
  
      free_node_alloc->free(next);
    }
  
    free_node_alloc->free(f);
  }
  else
    if (next && (f->start + f->size == next->start) )  // does it combine with the next block?
    {
      next->start = f->start;
      next->size += f->size;   
      
      free_node_alloc->free(f);      
    }
    else
      if (last)              // put after the last proper node
      {
        f->next = last->next;
        last->next = f;
      }
      else                       // put it at the begining of the list
      {
        f->next = first_free;
        first_free = f;      
      }
    
  pf_tex_heap_free.stop();
}