/**********************************************************************
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 "dx5/r1_dx5_texture.hh"

#include "video/win32/dx5.hh"
#include "video/win32/dx5_error.hh"
#include "time/profile.hh"
#include "r1_res.hh"

i4_profile_class pf_dx5_install_vram("dx5 install vram texture");
i4_profile_class pf_dx5_free_vram("dx5 free vram texture");
i4_profile_class pf_dx5_select_texture("dx5 select_texture");
i4_profile_class pf_dx5_texture_load("dx5 texture Load()");

DDPIXELFORMAT dd_fmt_565;
DDPIXELFORMAT dd_fmt_1555;
DDPIXELFORMAT dd_fmt_4444;

r1_dx5_texture_class *r1_dx5_texture_class_instance=0;

LPDDSURFACEDESC first_tex_format=0;

HRESULT WINAPI texture_enum(LPDDSURFACEDESC lpddsd, LPVOID _regular_format)
{
  if ((lpddsd->ddpfPixelFormat.dwFlags & DDPF_ALPHAPIXELS)==0 &&
       lpddsd->ddpfPixelFormat.dwRGBBitCount==16)
  {
    //find the best non-alpha surface format
    i4_pixel_format *regular_format = (i4_pixel_format *)_regular_format;

    sw32 total_new_bits = lpddsd->ddpfPixelFormat.dwRBitMask +
                          lpddsd->ddpfPixelFormat.dwGBitMask +
                          lpddsd->ddpfPixelFormat.dwBBitMask;

    sw32 total_reg_bits = regular_format->red_mask   + 
                          regular_format->green_mask +
                          regular_format->blue_mask;

    if (total_new_bits > total_reg_bits)
    {
      regular_format->red_mask   = lpddsd->ddpfPixelFormat.dwRBitMask;
      regular_format->green_mask = lpddsd->ddpfPixelFormat.dwGBitMask;
      regular_format->blue_mask  = lpddsd->ddpfPixelFormat.dwBBitMask;
      regular_format->alpha_mask = 0;
      regular_format->lookup     = 0;
      regular_format->calc_shift();
      regular_format->pixel_depth = I4_16BIT;
    }
  }

  if (first_tex_format)
  {
    if (lpddsd==first_tex_format) 
      return D3DENUMRET_CANCEL;
  }
  else
    first_tex_format = lpddsd;

  return D3DENUMRET_OK;
}

r1_dx5_texture_class::r1_dx5_texture_class(const i4_pal *pal)
: r1_texture_manager_class(pal),finished_array(16,16)
{
  r1_dx5_texture_class_instance = this;
  
  tex_no_heap = 0;

  //find out how much texture memory we have and show it to anyone debugging
  DDSCAPS tcaps;
  tcaps.dwCaps = DDSCAPS_TEXTURE;  
  w32 total_free, texture_free;
  dx5_common.ddraw->GetAvailableVidMem(&tcaps, &total_free, &texture_free);  
  i4_warning("Total Display Memory Free: %d",total_free);
  i4_warning("Total Display Texture Memory Free: %d",texture_free);
    
  memset(®ular_format,0,sizeof(i4_pixel_format));

  if (r1_dx5_class_instance.d3d_device->EnumTextureFormats(texture_enum,(LPVOID)®ular_format) != D3D_OK)
    i4_error("Couldn't determine Direct3D Texture Formats");
  
  chroma_format.red_mask   = 31 << 10;
  chroma_format.green_mask = 31 << 5;
  chroma_format.blue_mask  = 31;
  chroma_format.alpha_mask = 1 << 15;
  chroma_format.lookup     = 0;  
  chroma_format.calc_shift();
  chroma_format.pixel_depth = I4_16BIT;
  
  alpha_format.red_mask   = 15 << 8;
  alpha_format.green_mask = 15 << 4;
  alpha_format.blue_mask  = 15;
  alpha_format.alpha_mask = 15 << 12;
  alpha_format.lookup     = 0;  
  alpha_format.calc_shift();
  alpha_format.pixel_depth = I4_16BIT;

  memset(&dd_fmt_565,0,sizeof(DDPIXELFORMAT));
  dd_fmt_565.dwSize = sizeof(DDPIXELFORMAT);
  dd_fmt_565.dwFlags = DDPF_RGB;
  dd_fmt_565.dwRGBBitCount = 16;
  dd_fmt_565.dwRBitMask = regular_format.red_mask;
  dd_fmt_565.dwGBitMask = regular_format.green_mask;
  dd_fmt_565.dwBBitMask = regular_format.blue_mask;

  memset(&dd_fmt_1555,0,sizeof(DDPIXELFORMAT));
  dd_fmt_1555.dwSize = sizeof(DDPIXELFORMAT);
  dd_fmt_1555.dwFlags = DDPF_RGB | DDPF_ALPHAPIXELS;
  dd_fmt_1555.dwRGBBitCount     = 16;    
  dd_fmt_1555.dwRBitMask        = 31 << 10;
  dd_fmt_1555.dwGBitMask        = 31 << 5;
  dd_fmt_1555.dwBBitMask        = 31;
  dd_fmt_1555.dwRGBAlphaBitMask = 1 << 15;

  memset(&dd_fmt_4444,0,sizeof(DDPIXELFORMAT));
  dd_fmt_4444.dwSize = sizeof(DDPIXELFORMAT);
  dd_fmt_4444.dwFlags = DDPF_RGB | DDPF_ALPHAPIXELS;
  dd_fmt_4444.dwRGBBitCount     = 16;    
  dd_fmt_4444.dwRBitMask        = 15 << 8;
  dd_fmt_4444.dwGBitMask        = 15 << 4;
  dd_fmt_4444.dwBBitMask        = 15;
  dd_fmt_4444.dwRGBAlphaBitMask = 15 << 12;

  min_texture_dimention = 0;

  max_texture_dimention = r1_max_texture_size;

  if (max_texture_dimention>256)
    max_texture_dimention=256;     // don't allow d3d to go above 256

  square_textures = r1_dx5_class_instance.needs_square_textures;

  init();
}

void r1_dx5_texture_class::init()
{  
  r1_texture_manager_class::init();
  
  tex_no_heap  = new r1_texture_no_heap_class(this,sizeof(used_node),(w32 *)&frame_count);
  
  array_lock.lock();
  array_lock.unlock();

  bytes_loaded = 0;
  textures_loaded = 0;
}

void r1_dx5_texture_class::uninit()
{  
  if (tex_no_heap)
  {
    delete tex_no_heap;
    tex_no_heap = 0;
  }

  r1_texture_manager_class::uninit();
}


#define R1_DX5_SURFACE_HOLY         1
#define R1_DX5_SURFACE_ALPHATEXTURE 2
#define R1_DX5_SURFACE_VRAM         4


IDirectDrawSurface3 *dx5_make_surface(sw32 w, sw32 h, w8 flags)
{
  DDSURFACEDESC ddsd;
  memset(&ddsd,0,sizeof(DDSURFACEDESC));
  ddsd.dwSize = sizeof(DDSURFACEDESC);  
  ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
  ddsd.dwWidth  = w;
  ddsd.dwHeight = h;

  if (flags & R1_DX5_SURFACE_VRAM)
  {
    ddsd.ddsCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_ALLOCONLOAD;
  
    if (r1_dx5_class_instance.hardware_tmapping)
      ddsd.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;
    else
      ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
  }
  else
    ddsd.ddsCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_SYSTEMMEMORY;

  if (flags & R1_DX5_SURFACE_HOLY)
    ddsd.ddpfPixelFormat = dd_fmt_1555;
  else
  if (flags & R1_DX5_SURFACE_ALPHATEXTURE)
    ddsd.ddpfPixelFormat = dd_fmt_4444;
  else
    ddsd.ddpfPixelFormat = dd_fmt_565;
  
  IDirectDrawSurface  *return_surface  = 0;
  IDirectDrawSurface3 *return_surface3 = 0;
  
  HRESULT res = dx5_common.ddraw->CreateSurface(&ddsd,&return_surface,0);

  if (!i4_dx5_check(res))
  {
#ifdef DEBUG
    i4_warning("r1_dx5_texture::dx5_make_surface failed.");
#endif
    return_surface = 0;
  }
  else
  {    
    res = return_surface->QueryInterface(IID_IDirectDrawSurface3,(void **)&return_surface3);    

    if (!i4_dx5_check(res))
    {
#ifdef DEBUG
      i4_warning("r1_dx5_texture::dx5_make_surface failed.");
#endif
      return_surface3 = 0;
    }
    
    return_surface->Release();
  }

  return return_surface3;
}

r1_dx5_texture_class::used_node *r1_dx5_texture_class::make_surfaces_for_load(
                                                     IDirectDrawSurface3 *&vram_surface3,
                                                     IDirectDrawSurface3 *&system_surface3,
                                                     r1_mip_load_info *&load_info,
                                                     sw32 actual_w, sw32 actual_h,
                                                     w8 node_alloc_flags)
{
  r1_miplevel_t *mip = load_info->dest_mip;

  sw32 need_size = actual_w*actual_h*2;

  w8 create_flags = 0;
  
  if (mip->entry->is_transparent())
    create_flags = R1_DX5_SURFACE_HOLY;
  else
  if (mip->entry->is_alphatexture())
    create_flags = R1_DX5_SURFACE_ALPHATEXTURE;
  
  system_surface3 = dx5_make_surface(actual_w, actual_h, create_flags);
    
  if (!system_surface3)
  {    
    load_info->error = R1_MIP_LOAD_NO_ROOM;    
    return 0;
  }

  create_flags |= R1_DX5_SURFACE_VRAM;

  vram_surface3 = dx5_make_surface(actual_w, actual_h, create_flags);

  used_node *new_used = 0;

  if (vram_surface3)
  {
    new_used = (used_node *)tex_no_heap->alloc(node_alloc_flags);
  }
  else  
  {
    used_node *u = (used_node *)tex_no_heap->alloc_from_used(mip->width*mip->height*2,node_alloc_flags);
    
    if (!u)
    {
      system_surface3->Release();
      system_surface3 = 0;

      load_info->error = R1_MIP_LOAD_NO_ROOM;      
      return 0;
    }
        
    vram_surface3 = dx5_make_surface(actual_w, actual_h, create_flags);
    
    if (!vram_surface3)
    {
      system_surface3->Release();
      system_surface3 = 0;
      
      tex_no_heap->missed_one(mip->width*mip->height*2);
      
      load_info->error = R1_MIP_LOAD_NO_ROOM;      
      return 0;
    }

    new_used = u;
  }
  
  if (!new_used)
  {
    i4_warning("couldn't alloc used_node");
    if (system_surface3)
      system_surface3->Release();
    if (vram_surface3)
      vram_surface3->Release();
    
    system_surface3 = 0;
    vram_surface3   = 0;

    load_info->error = R1_MIP_LOAD_NO_ROOM;    
    return 0;
  }  

  return new_used;
}


i4_bool r1_dx5_texture_class::immediate_mip_load(r1_mip_load_info *load_info)
{  
  if (textures_loaded > 0)
  {
    if (load_info->dest_mip->last_frame_used!=-1)
    {
      load_info->error = R1_MIP_LOAD_BUSY;
      return i4_F;
    }
  }

  pf_dx5_install_vram.start();

  IDirectDrawSurface3 *vram_surface   = 0;
  IDirectDrawSurface3 *system_surface = 0;

  sw32 i,j,actual_w,actual_h;
    
  r1_miplevel_t *mip = load_info->dest_mip;

  for (actual_w = 1; actual_w < mip->width;  actual_w*=2); // these need to be power of 2
  for (actual_h = 1; actual_h < mip->height; actual_h*=2);
    
  used_node *new_used = make_surfaces_for_load(vram_surface,system_surface,load_info,actual_w,actual_h);
  
  if (!new_used)
  {
    pf_dx5_install_vram.stop();
    return i4_F;
  }

  DDSURFACEDESC ddsd;
  memset(&ddsd,0,sizeof(DDSURFACEDESC));
  ddsd.dwSize = sizeof(DDSURFACEDESC);

  // Lock the surface so we can get the memory address where it is
  system_surface->Lock(0, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY | DDLOCK_NOSYSLOCK, 0);  

  w8   *texture_ptr = (w8 *)ddsd.lpSurface;
  sw32  bpl         = mip->width*2;

  i4_bool segmented_load = i4_F;
    
  if (load_info->src_file)
  {
    if (!segmented_load)
      load_info->src_file->read(texture_ptr,mip->width*mip->height*2);
    else
    {
      for (i=0; iheight; i++)
      {
        load_info->src_file->read(texture_ptr,bpl);
        texture_ptr += ddsd.lPitch;
      }
    }
  }
  else
  {
    i4_str *fn = r1_texture_id_to_filename(mip->entry->id,r1_get_decompressed_dir());    
    
    i4_file_class *fp = i4_open(*fn,I4_READ | I4_NO_BUFFER);

    delete fn;
    
    fp->seek(mip->entry->file_offsets[mip->level]+8);
    
    if (!segmented_load)
      fp->read(texture_ptr,mip->width*mip->height*2);
    else
    {
      for (i=0; iheight; i++)
      {
        fp->read(texture_ptr,bpl);
        texture_ptr += ddsd.lPitch;
      }
    }

    delete fp;
  }   

  system_surface->Unlock(0);

  mip->vram_handle = new_used;

  if (mip->last_frame_used != -1)
    mip->last_frame_used = frame_count;  

  new_used->vram_surface   = vram_surface;
  new_used->system_surface = 0;
  new_used->mip            = mip;
  new_used->async_fp       = 0;
  new_used->data           = 0;

  IDirect3DTexture2 *system_texture, *vram_texture;
  
  system_surface->QueryInterface(IID_IDirect3DTexture2,(void **)&system_texture);
  vram_surface->QueryInterface(IID_IDirect3DTexture2,(void **)&vram_texture);

  pf_dx5_texture_load.start();
  if (vram_texture->Load(system_texture) != D3D_OK)
    i4_warning("r1_dx5_texture:: Load() failed");
  pf_dx5_texture_load.stop();
  
  vram_texture->GetHandle(r1_dx5_class_instance.d3d_device, &new_used->texture_handle);
   
  vram_texture->Release();
  system_texture->Release();

  system_surface->Release();
  
  bytes_loaded += mip->width*mip->height*2;
  textures_loaded++;

  pf_dx5_install_vram.stop();
  return i4_T;
}

void dx5_async_callback(w32 count, void *context)
{
  r1_dx5_texture_class::used_node *u = (r1_dx5_texture_class::used_node *)context;

  if (u->async_fp)
  {
    delete u->async_fp;
    u->async_fp = 0;
  }

  r1_dx5_texture_class_instance->async_load_finished(u);
}

void r1_dx5_texture_class::async_load_finished(used_node *u)
{
  array_lock.lock();
  
  finished_array.add(u);
  
  array_lock.unlock();
}

i4_bool r1_dx5_texture_class::async_mip_load(r1_mip_load_info *load_info)
{  
  if (bytes_loaded > 32768 || textures_loaded > 16)
  {
    if (load_info->dest_mip->last_frame_used!=-1)
    {
      load_info->error = R1_MIP_LOAD_BUSY;
      return i4_F;
    }
  }

  pf_dx5_install_vram.start();  

  IDirectDrawSurface3 *vram_surface   = 0;
  IDirectDrawSurface3 *system_surface = 0;

  sw32 i,j,actual_w,actual_h;
    
  r1_miplevel_t *mip = load_info->dest_mip;

  //for (actual_w = 1; actual_w < mip->width;  actual_w*=2); // these need to be power of 2
  //for (actual_h = 1; actual_h < mip->height; actual_h*=2);
  
  //all textures are power of 2 now
  actual_w = mip->width;
  actual_h = mip->height;
    
  used_node *new_used = make_surfaces_for_load(vram_surface,system_surface,load_info,actual_w,actual_h,R1_TEX_NO_HEAP_DONT_LIST);
  
  if (!new_used)
  {
    pf_dx5_install_vram.stop();
    return i4_F;
  }

  new_used->mip      = mip;
  new_used->data     = (w8 *)i4_malloc(mip->width*mip->height*2,"dx5 async load alloc");
  new_used->async_fp = 0;  
  
  new_used->vram_surface   = vram_surface;
  new_used->system_surface = system_surface;    
  new_used->texture_handle = 0;

  mip->vram_handle  = 0;
  mip->flags       |= R1_MIPLEVEL_IS_LOADING;

  i4_bool async_worked = i4_F;
  
  if (load_info->src_file)
  {
    async_worked = load_info->src_file->async_read(new_used->data,
                                                   mip->width*mip->height*2,
                                                   dx5_async_callback,
                                                   new_used);
  }
  else
  {
    i4_str *fn = r1_texture_id_to_filename(mip->entry->id,r1_get_decompressed_dir());    
    
    i4_file_class *fp = i4_open(*fn,I4_READ | I4_NO_BUFFER);

    delete fn;
    
    new_used->async_fp = fp;

    fp->seek(mip->entry->file_offsets[mip->level]+8);
    
    async_worked = fp->async_read(new_used->data,
                                  mip->width*mip->height*2,
                                  dx5_async_callback,
                                  new_used);
  }
  
  if (!async_worked)
  {
    mip->flags &= (~R1_MIPLEVEL_IS_LOADING);
    free_mip(new_used);    
    
    load_info->error = R1_MIP_LOAD_BUSY;    
    pf_dx5_install_vram.stop();
    return i4_F;
  }  
  
  bytes_loaded += mip->width*mip->height*2;
  textures_loaded++;

  pf_dx5_install_vram.stop();
  return i4_T;
}

void r1_dx5_texture_class::free_mip(void *vram_handle)
{
  pf_dx5_free_vram.start();
  used_node *u = (used_node *)vram_handle;

  if (u->system_surface)
  {
    u->system_surface->Release();
    u->system_surface = 0;
  }
    
  if (u->vram_surface)
  {
    u->vram_surface->Release();
    u->vram_surface = 0;
  }
    
  if (u->async_fp)
  {
    delete u->async_fp;
    u->async_fp = 0;
  }
  
  if (u->data)
  {
    i4_free(u->data);
    u->data = 0;
  }    

  tex_no_heap->free((r1_tex_no_heap_used_node *)u);
  
  pf_dx5_free_vram.stop();
}

void r1_dx5_texture_class::select_texture(r1_local_texture_handle_type handle,
                                          float &smul, float &tmul)
{
  pf_dx5_select_texture.start();

  used_node *u = (used_node *)handle;
  
  if (r1_dx5_class_instance.d3d_device->SetRenderState(D3DRENDERSTATE_TEXTUREHANDLE,
                                                   u->texture_handle) != D3D_OK)
  {
    i4_warning("select_texture failed");
  }

  smul = 1.0;//n->smul;
  tmul = 1.0;//n->tmul; 

  pf_dx5_select_texture.stop();
}

r1_miplevel_t *r1_dx5_texture_class::get_texture(r1_texture_handle handle,
                                                 w32 frame_counter,
                                                 sw32 desired_width,
                                                 sw32 &w, sw32 &h)
{
  r1_miplevel_t *mip = r1_texture_manager_class::get_texture(handle,frame_counter,desired_width,w,h);
  
  used_node *u = (used_node *)mip->vram_handle;  

  tex_no_heap->update_usage((r1_tex_no_heap_used_node *)u);
  
  return mip;
}

void r1_dx5_texture_class::next_frame()
{
  r1_texture_manager_class::next_frame();
  
  sw32 i,j;
  array_lock.lock();

  for (i=0; imip->vram_handle  = u;
    u->mip->flags       &= (~R1_MIPLEVEL_IS_LOADING);
    
    if (u->mip->last_frame_used != -1)
      u->mip->last_frame_used  = frame_count;
    
    //this adds it into the list of used nodes
    tex_no_heap->update_usage((r1_tex_no_heap_used_node *)u);
                                    
    DDSURFACEDESC ddsd;
    memset(&ddsd,0,sizeof(DDSURFACEDESC));
    ddsd.dwSize = sizeof(DDSURFACEDESC);

    u->system_surface->Lock(0, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY | DDLOCK_NOSYSLOCK, 0);    
    
    sw32 bpl = u->mip->width*2;

    w8 *texture_ptr = (w8 *)ddsd.lpSurface;

    if (0)//ddsd.lPitch > bpl)
    {      
      for (j=0; jmip->height; j++)
      {
        memcpy(texture_ptr,u->data,bpl);
        texture_ptr += ddsd.lPitch;
      }
    }
    else
      memcpy(texture_ptr,u->data,u->mip->width*u->mip->height*2);    

    u->system_surface->Unlock(0);

    i4_free(u->data);
    u->data = 0;    

    IDirect3DTexture2  *system_texture, *vram_texture;        

    u->system_surface->QueryInterface(IID_IDirect3DTexture2,(void **)&system_texture);
    u->vram_surface->QueryInterface(IID_IDirect3DTexture2,(void **)&vram_texture);

    pf_dx5_texture_load.start();
    if (vram_texture->Load(system_texture) != D3D_OK)
      i4_warning("d3d:load texture failed");
    pf_dx5_texture_load.stop();

    vram_texture->GetHandle(r1_dx5_class_instance.d3d_device, &u->texture_handle);

    vram_texture->Release();
    system_texture->Release();    
    
    u->system_surface->Release();
    u->system_surface = 0;    
  }
  finished_array.clear();

  array_lock.unlock();
  
  if (tex_no_heap->needs_cleanup)
  {
    tex_no_heap->free_really_old();
  }

  bytes_loaded = 0;
  textures_loaded = 0;
}