/********************************************************************** <BR>
  This file is part of Crack dot Com's free source code release of
  Golgotha. <a href="http://www.crack.com/golgotha_release"> <BR> for
  information about compiling & licensing issues visit this URL</a> 
  <PRE> If that doesn't help, contact Jonathan Clark at 
  golgotha_source@usa.net (Subject should have "GOLG" in it) 
***********************************************************************/

//{{{ File & File Manager Classes
//
//$Id: file.cc,v 1.48 1998/07/15 21:16:39 jc Exp $

#include "memory/malloc.hh"
#include "memory/array.hh"

#include "error/error.hh"

#include "time/time.hh"
#include "time/profile.hh"

#include "string/string.hh"

#include "threads/threads.hh"

#include "device/device.hh"
#include "device/kernel.hh"

#include "main/win_main.hh"

#include "window/style.hh"
#include "window/window.hh"

#include "file/file.hh"
#include "file/file_man.hh"
#include "file/async.hh"
#include "file/buf_file.hh"
#include "file/ram_file_man.hh"
#include "file/ram_file.hh"
#include "file/sub_section.hh"
#include "file/get_filename.hh"

#include <windows.h>
#include <stdio.h>
#include <memory.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

#include <fcntl.h>
#include <io.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <direct.h>





static i4_profile_class pf_tell("File tell");
static i4_profile_class pf_open("File Open");
static i4_profile_class pf_sopen("System file Open");
static i4_profile_class pf_close("File Close");
static i4_profile_class pf_read("File Read");
static i4_profile_class pf_con_buf("::buffered file");


i4_bool i4_file_class::async_write(const void *buffer, w32 size, 
                                   async_callback call,
                                   void *context)
{
  (*call)(write(buffer, size), context);

  return i4_T;
}


i4_bool i4_file_class::async_read (void *buffer, w32 size, 
                                   async_callback call,
                                   void *context)
{
  (*call)(read(buffer, size), context);
  return i4_T;
}


class file_string : public i4_str
{
public:
  file_string(w16 size) : i4_str(size) {}

  char *buffer() { return (char *)ptr; }
  void set_len(w16 _len) { len = _len; }
};


i4_str* i4_file_class::read_str(w32 len)
{
  file_string *str = new file_string((w16)len);   
  len = read(str->buffer(),len);
  str->set_len((w16)len);

  return str;
}

i4_str* i4_file_class::read_counted_str()
{
  w16 len = read_16();
  return read_str(len);
}


class file_write_string : public i4_const_str
{
public:
  char *buffer() { return (char *)ptr; }
};


w32 i4_file_class::write_str(const i4_const_str &str)
{
  file_write_string *s = (file_write_string *)&str;

  return write(s->buffer(), s->length());
}

w32 i4_file_class::write_counted_str(const i4_const_str &str)
{
  w32 count;

  count = write_16((w16)str.length());
  if (str.length())      
    return (count + write_str(str));
  else
    return count;
}



static inline int fmt_char(char c)
{
  if ((c>='a' && c<='z') || (c>='A' && c<='Z'))
    return 1;
  return 0;
}

// same as fprintf, but with the addition %S is a i4_const_str *
int i4_file_class::printf(char *fmt, ...)
{
  w32 start=tell();

  va_list ap;
  va_start(ap, fmt);

  while (*fmt)
  {
    if (*fmt=='%')
    {
      char *fmt_end=fmt;
      while (!fmt_char(*fmt_end) && *fmt_end) fmt_end++;
      char f[10], out[500]; 
      memcpy(f, fmt, fmt_end-fmt+1);
      f[fmt_end-fmt+1]=0;
      out[0]=0;

      switch (*fmt_end)
      {
        case 's' : 
        {
          char *str=va_arg(ap,char *);
          write(str,strlen(str));
        } break;          
        case 'S' : 
        {
          i4_const_str *s=va_arg(ap,i4_const_str *); 
          write_str(*s);
        } break;
        case 'd' :
        case 'i' :
        case 'x' :
        case 'X' :
        case 'c' :
        case 'o' :
        {
          ::sprintf(out,f,va_arg(ap,int));
          write(out,strlen(out));
        } break;

        case 'f' :
        case 'g' :
          ::sprintf(out,f,va_arg(ap,double));
          write(out,strlen(out));
          break;

        default :
          ::sprintf(out,f,va_arg(ap,void *));
          write(out,strlen(out));
          break;
      }
      fmt=fmt_end;
      if (*fmt)
        fmt++;
    }
    else
    {
      write_8(*fmt);
      fmt++;
    }
  }
  va_end(ap);

  return tell()-start;
}




int i4_file_class::write_format(char *format, ...)
{
  char *f=format;
  va_list ap;
  va_start(ap, format);

  int start=tell();
  while (*f)
  {
    switch (*f)
    {
      case '1' : write_8(*(va_arg(ap,     w8 *))); break;
      case '2' : write_16(*(va_arg(ap,    w16 *))); break;
      case '4' : write_32(*(va_arg(ap,     w32 *))); break;
      case 'f' : write_float(*(va_arg(ap,  float *))); break;
      case 'S' : write_counted_str(  *(va_arg(ap, i4_const_str *))); break;
    }
    f++;
  }
  va_end(ap);

  return tell()-start;
}


// format is same as write_format, returns number of fields written
int i4_file_class::read_format(char *format, ...)
{
  char *f=format;
  va_list ap;
  va_start(ap, format);

  int start=tell();
  while (*f)
  {
    switch (*f)
    {
      case '1' : *(va_arg(ap,   w8 *))=read_8(); break;
      case '2' : *(va_arg(ap,  w16 *))=read_16(); break;
      case '4' : *(va_arg(ap,  w32 *))=read_32(); break;
      case 'f' : *(va_arg(ap, float*))=read_float(); break;
      case 'S' : *(va_arg(ap, i4_str **))=read_counted_str(); break;
    }
    f++;
  }
  va_end(ap);

  return tell()-start;
}


// returns NULL if unable to open file
i4_file_class *i4_open(const i4_const_str &name, w32 flags)
{
  for (i4_file_manager_class *m=i4_file_manager_class::first; m; m=m->next)
  {    
    i4_file_class *fp=m->open(name,flags);
    if (fp) 
      return fp;
  }

  return 0;
}

// return i4_F on failure
i4_bool i4_unlink(const i4_const_str &name)
{
  for (i4_file_manager_class *m=i4_file_manager_class::first; m; m=m->next)
    if (m->unlink(name))
      return i4_T;
  return i4_F;
}


// returns i4_F if file does not exsist
i4_bool i4_get_status(const i4_const_str &filename, 
                      i4_file_status_struct &return_stat)
{
  for (i4_file_manager_class *m=i4_file_manager_class::first; m; m=m->next)
    if (m->get_status(filename, return_stat))
      return i4_T;

  return i4_F;
}

// return i4_F on failure
i4_bool i4_mkdir(const i4_const_str &name)
{
  for (i4_file_manager_class *m=i4_file_manager_class::first; m; m=m->next)
    if (m->mkdir(name))
      return i4_T;

  return i4_F;
}

// returns i4_F if path is bad (tfiles and tdirs will be 0 as well)
// you are responsible for deleting both the array of strings and each string in the array
// file_status is a pointer to an array of file_status's that will be created, you
// must free these as well.  file_status may be 0 (default), in which case no array is created

i4_bool i4_get_directory(const i4_const_str &path, 
                         i4_directory_struct &dir_struct,
                         i4_bool get_status,
                         i4_status_class *status)
{
  for (i4_file_manager_class *m=i4_file_manager_class::first; m; m=m->next)
    if (m->get_directory(path, dir_struct, get_status, status)) 
      return i4_T;

  return i4_F;
}

// returns i4_F if path cannot be split
i4_bool i4_split_path(const i4_const_str &name, i4_filename_struct &fname_struct)
{
  for (i4_file_manager_class *m=i4_file_manager_class::first; m; m=m->next)
    if (m->split_path(name, fname_struct))
      return i4_T;

  return i4_F;
}

// return 0 if full path cannot be determined
i4_str *i4_full_path(const i4_const_str &relative_name)
{
  for (i4_file_manager_class *m=i4_file_manager_class::first; m; m=m->next)
  {
    i4_str *s=m->full_path(relative_name);
    if (s) 
        return s;
  }

  return 0;
}


i4_file_manager_class *i4_file_manager_class::first=0;

void i4_add_file_manager(i4_file_manager_class *fman, i4_bool add_front)
{
  if (add_front)
  {
    fman->next=i4_file_manager_class::first;
    i4_file_manager_class::first=fman;
  }
  else
  {
    i4_file_manager_class *last=0, *p=i4_file_manager_class::first;
    while (p)
    {
      last=p;
      p=p->next;
    }

    if (!last) 
      i4_add_file_manager(fman, i4_T);
    else 
    {
      last->next=fman;
      fman->next=0;
    }
  }
}

void i4_remove_file_manger(i4_file_manager_class *fman)
{
  i4_file_manager_class *last=0, *p=i4_file_manager_class::first;
  while (p && p!=fman)
  {
    last=p;
    p=p->next;
  }

  if (p!=fman)
    i4_error("unable to find file manager");
  else if (last)
    last->next=fman->next;
  else
    i4_file_manager_class::first=fman->next;   
}




i4_bool i4_file_manager_class::split_path(const i4_const_str &name, i4_filename_struct &fn)
{
  char buf[512];
  i4_os_string(name, buf, 512);


  char *p=buf, *last_slash=0, *last_dot=0, *q;

  while (*p)
  {
    if (*p=='/' || *p=='\\')
      last_slash=p;
    else if (*p=='.')
      last_dot=p;
    p++;
  }

 
  if (last_dot)
  {
    q=fn.extension;    
    for (p=last_dot+1; *p; )
      *(q++)=*(p++);
    *q=0;
  }
  else last_dot=p;
      
  
  if (last_slash)
  {
    q=fn.path;
    for (p=buf; p!=last_slash; )
      *(q++)=*(p++);
    *q=0;
    last_slash++;
  }
  else last_slash=buf;


  q=fn.filename;
  for (p=last_slash; p!=last_dot;)
    *(q++)=*(p++);
  *q=0;


  return i4_T;
}



i4_directory_struct::~i4_directory_struct()
{
  int i;

  for (i=0; i<(int)tfiles; i++)
    delete files[i];

  if (files) 
    i4_free(files);

  for (i=0; i<(int)tdirs; i++)
    delete dirs[i];

  if (dirs)
    i4_free(dirs);


  if (file_status)
    i4_free(file_status);

}



static char convert_slash(char c)
{
  if (c=='\\') return '/';
  else return c;
}

i4_str *i4_relative_path(const i4_const_str &path)
{
  i4_str *full_path=i4_full_path(path);
  i4_str *full_current=i4_full_path(i4_const_str("."));
  
  char full[512], current[512], ret[512];

  i4_os_string(*full_path, full, 512);
  i4_os_string(*full_current, current, 512);

  delete full_path;
  delete full_current;


  // files are on different drives  
  if (full[1]==':' && current[1]==':' && full[0]!=current[0])
    return new i4_str(full);

  // one files is on a network drive and one is local
  if ((full[1]==':' && current[1]!=':') ||
      (full[1]!=':' && current[1]==':'))
    return new i4_str(full);


  int start=0;
  while (convert_slash(full[start])==convert_slash(current[start]))
    start++;


  char *c=current+start;
  if (*c==0)
    return new i4_str(full+start+1);

  strcpy(ret,"../");
  while (*c)
  {
    if (convert_slash(*c)=='/')
      strcpy(ret+strlen(ret),"../");

    c++;
  }

  strcpy(ret+strlen(ret), full+start);
  return new i4_str(ret);
}

// ASYNC.CPP
/********************************************************************** <BR>
  This file is part of Crack dot Com's free source code release of
  Golgotha. <a href="http://www.crack.com/golgotha_release"> <BR> for
  information about compiling & licensing issues visit this URL</a> 
  <PRE> If that doesn't help, contact Jonathan Clark at 
  golgotha_source@usa.net (Subject should have "GOLG" in it) 
***********************************************************************/


i4_async_reader::i4_async_reader(char *name) : sig(name)  
{ 
  emulation=i4_F; 
  stop=i4_F;
}

void i4_async_reader_thread_start(void *arg)
{
  ((i4_async_reader *)arg)->PRIVATE_thread();
}

void i4_async_reader::init()
{
  if (i4_threads_supported())
  {
    stop=i4_T;
    i4_add_thread(i4_async_reader_thread_start, STACK_SIZE, this);
  }
}

void i4_async_reader::uninit()
{
  if (i4_threads_supported())
  {
    while (stop==i4_T)       // wait to thread is read to be stopped
      i4_thread_yield();
  
    stop=i4_T;
    sig.signal();
    while (stop)
      i4_thread_yield();
  }
}

i4_bool i4_async_reader::start_read(int fd, void *buffer, w32 size, 
                                    i4_file_class::async_callback call,
                                    void *context)
{
  if (!i4_threads_supported())
    i4_error("threads not supported");

  que_lock.lock();

  read_request r(fd, buffer, size, call, context);
  if (!request_que.que(r))
  {
    que_lock.unlock();
    return i4_F;
  }
  que_lock.unlock();

  sig.signal();
  return i4_T;
}


void i4_async_reader::emulate_speeds(read_request &r)
{    
  if (emulation) 
  {
    i4_time_class now, start;
    while (start.milli_diff(now) < (sw32)r.size*1000/(1000*1024) + 20*1000)
    {
      i4_thread_yield();
      now.get();
    }
  }
}


void i4_async_reader::PRIVATE_thread()
{
  read_request r;
  sw32 amount;

  do
  {
    while (request_que.empty())   // if no more request to satisfy, wait for main process signal
    {
      stop=i4_F;
      sig.wait_signal();
    }

    if (!stop)
    {
      que_lock.lock();

      if (request_que.deque(r))
      {
        que_lock.unlock();
        emulate_speeds(r);

        amount = read(r.fd, r.buffer, r.size);          

        r.callback(amount, r.context);
      }
      else que_lock.unlock();
    }

  } while (!stop);
  stop=i4_F;
}
// buf_file
/********************************************************************** <BR>
  This file is part of Crack dot Com's free source code release of
  Golgotha. <a href="http://www.crack.com/golgotha_release"> <BR> for
  information about compiling & licensing issues visit this URL</a> 
  <PRE> If that doesn't help, contact Jonathan Clark at 
  golgotha_source@usa.net (Subject should have "GOLG" in it) 
***********************************************************************/




w32 i4_buffered_file_class::read(void *buffer, w32 size)
{
  w32 total_read=0;
  while (size)
  {
    if (offset>=buf_start && offset<buf_end)
    {
      w32 copy_size;
      if (buf_end-offset<size)
        copy_size=buf_end-offset;
      else copy_size=size;
      memcpy(buffer,((w8 *)buf)+offset-buf_start,copy_size);
      size-=copy_size;
      buffer=(void *)(((w8 *)buffer)+copy_size);
      offset+=copy_size;
      total_read+=copy_size;
    } else if (offset==buf_end)      // sequentially read more into the buffer
    {
      buf_start=offset;
      buf_end=offset+from->read(buf,buf_size);
      if (buf_end==buf_start)
        return total_read;
    } else                          // need to seek from file to a new spot
    {
      from->seek(offset);
      buf_start=buf_end=offset;
    }
  }
  return total_read;
}


w32 i4_buffered_file_class::write(const void *buffer, w32 size)
{
  w32 total_write=0;

  while (size)
  {      
    write_file=i4_T;
    if (offset>=buf_start && offset<=buf_end)
    {
      w32 copy_size;
      if (offset+size<buf_start+buf_size)
        copy_size=size;
      else
        copy_size=buf_start+buf_size-offset;

      memcpy(((w8 *)buf)+offset-buf_start,buffer,copy_size);

      size-=copy_size;
      buffer=(void *)(((w8 *)buffer)+copy_size);
      offset+=copy_size;
      total_write+=copy_size;

      if (offset>buf_end)
      {
        buf_end=offset;
        if (buf_end-buf_start==buf_size)
        {
          from->write(buf, buf_end-buf_start);
          buf_start=buf_end;
        }
      }

    }
    else if (buf_end!=buf_start)      // flush the buffer
    {
      from->write(buf, buf_end-buf_start);
      buf_start=buf_end;
    } else
    {
      from->seek(offset);
      buf_start=buf_end=offset;
    }
  }

  return total_write;
}


w32 i4_buffered_file_class::seek (w32 offset)
{
  i4_buffered_file_class::offset=offset;
  return offset;
}


w32 i4_buffered_file_class::size ()
{
  return from->size();
}


w32 i4_buffered_file_class::tell ()
{
  return offset;
}


i4_buffered_file_class::~i4_buffered_file_class()
{
  if (write_file && buf_start!=buf_end)
    from->write(buf, buf_end-buf_start);

  delete from;
  i4_free(buf);
}


i4_buffered_file_class::i4_buffered_file_class(i4_file_class *from, 
                                               w32 buffer_size,
                                               w32 current_offset)
  : from(from), buf_size(buffer_size), offset(current_offset)
{
  write_file=i4_F;
  buf=I4_MALLOC(buf_size,"file buffer");
  buf_start=buf_end=0;
}

struct callback_context
{
  i4_bool in_use;
  w32 prev_read;
  void *prev_context;
  i4_file_class::async_callback prev_callback;
  i4_buffered_file_class *bfile;
} ;


// Maximum number async reads going on at the same time
enum { MAX_ASYNC_READS = 4 };
static callback_context contexts[MAX_ASYNC_READS];
static w32 t_callbacks_used=0;

void i4_async_buf_read_callback(w32 count, void *context)
{
  callback_context *c=(callback_context *)context;
  c->bfile->offset+=count;

  i4_file_class::async_callback call=c->prev_callback;
  count += c->prev_read;
  void *ctext=c->prev_context;
  c->in_use=i4_F;

  t_callbacks_used--;
  
  call(count, ctext);
}



i4_bool i4_buffered_file_class::async_read (void *buffer, w32 size, 
                                            async_callback call,
                                            void *context)
{
  if (!(offset>=buf_start && offset<buf_end))
  {
    from->seek(offset);
    buf_start=buf_end=0;
  }


  if (t_callbacks_used>=MAX_ASYNC_READS)
    return i4_file_class::async_read(buffer, size, call, context);
  else
  {    
    w32 avail_size;

    if (offset>=buf_start && offset<buf_end)    
      avail_size=buf_end-offset;
    else
      avail_size=0;

    if (avail_size < size)
    {
      callback_context *c=0;
      for (w32 i=0; !c && i<MAX_ASYNC_READS; i++)
        if (!contexts[i].in_use)
        {
          c=contexts+i;
          c->in_use=i4_T;
        }

      if (c==0)
        i4_error("didn't find a free context");

      t_callbacks_used++;

      if (avail_size)
        c->prev_read=read(buffer,avail_size);
      else
        c->prev_read=0;

      c->prev_context=context;
      c->prev_callback=call;
      c->bfile=this;
      return from->async_read((w8 *)buffer + avail_size, size-avail_size, 
                              i4_async_buf_read_callback, c);
    }
    else
    {
      call(read(buffer,avail_size), context);
      return i4_T;
    }

  }
}


//{{{ Emacs Locals
// Local Variables:
// folded-file: t
// End:
//}}}

// RAM_FILE_MAN.CPP
/********************************************************************** <BR>
  This file is part of Crack dot Com's free source code release of
  Golgotha. <a href="http://www.crack.com/golgotha_release"> <BR> for
  information about compiling & licensing issues visit this URL</a> 
  <PRE> If that doesn't help, contact Jonathan Clark at 
  golgotha_source@usa.net (Subject should have "GOLG" in it) 
***********************************************************************/


i4_openable_ram_file_info *i4_flist=0;

class i4_ram_file_manager_class : public i4_file_manager_class
{  
public: 

  virtual i4_file_class *open(const i4_const_str &name, w32 flags)
  {
    if (flags==I4_READ)
    {
      char buf[256];
      i4_os_string(name, buf, 256);

      for (i4_openable_ram_file_info *f=i4_flist; f; f=f->next)
        if (strcmp(buf, f->filename)==0)
          return new i4_ram_file_class(f->data, f->data_size);         
    }

    return 0;
  }

  i4_ram_file_manager_class()
  {
    i4_add_file_manager(this, i4_F);
  }

  ~i4_ram_file_manager_class()
  {
    i4_remove_file_manger(this);
  }
} i4_ram_file_manager_instance;

i4_openable_ram_file_info::i4_openable_ram_file_info(char *filename, void *data, w32 data_size)
  : filename(filename), data(data), data_size(data_size)
{ 
  next=i4_flist;
  i4_flist=this;
}
// SUB_SECTION.CPP
/********************************************************************** <BR>
  This file is part of Crack dot Com's free source code release of
  Golgotha. <a href="http://www.crack.com/golgotha_release"> <BR> for
  information about compiling & licensing issues visit this URL</a> 
  <PRE> If that doesn't help, contact Jonathan Clark at 
  golgotha_source@usa.net (Subject should have "GOLG" in it) 
***********************************************************************/



w32 i4_sub_section_file::read (void *buffer, w32 rsize)
{
  if( foffset-fstart+rsize > (w32)fsize )  // don't allow reads past the sub section
    rsize=fstart+fsize-foffset;

  int rs=fp->read(buffer, rsize);
  foffset+=rs;
  return rs;
}

w32 i4_sub_section_file::write(const void *buffer, w32 size)
{
  i4_error("don't write here");
  return 0;
}


w32 i4_sub_section_file::seek(w32 offset)
{
  if( offset >= (w32)fsize )
    offset=fsize-1;
  
  foffset=fstart+offset;
  fp->seek(foffset);
  return foffset;
}

w32 i4_sub_section_file::size()
{
  return fsize;
}

w32 i4_sub_section_file::tell()
{
  return foffset-fstart;
}


i4_sub_section_file::i4_sub_section_file(i4_file_class *_fp, int start, int size)
{
  fp=_fp;
  if (!fp)
    i4_error("no file");

  fp->seek(start);
  fsize=size;
  fstart=start;
  foffset=start;
}

i4_sub_section_file::~i4_sub_section_file()
{
  if (fp)
    delete fp;
}


i4_bool i4_sub_section_file::async_read (void *buffer, w32 size, 
                                         async_callback call,
                                         void *context)
{
  if (foffset-fstart+size> (w32)fsize)  // don't allow reads past the sub section
    size=fstart+fsize-foffset;


  foffset+=size;  
  return fp->async_read(buffer, size, call, context);
}
// WIN32\WIN_FILE.CPP

/********************************************************************** <BR>
  This file is part of Crack dot Com's free source code release of
  Golgotha. <a href="http://www.crack.com/golgotha_release"> <BR> for
  information about compiling & licensing issues visit this URL</a> 
  <PRE> If that doesn't help, contact Jonathan Clark at 
  golgotha_source@usa.net (Subject should have "GOLG" in it) 
***********************************************************************/


i4_profile_class pf_win32_seek("Win32File::Seek");

i4_profile_class pf_win32_read("Win32File::Read");
i4_profile_class pf_win32_write("Win32File::Write");

class i4_win32_async_reader : public i4_async_reader
{
public:
  i4_win32_async_reader(char *name) : i4_async_reader(name) {}
  virtual w32 read(sw32 fd, void *buffer, w32 count) 
  { 
    w32 res = _read(fd,buffer,count);
    return res;
  }
} i4_win32_async_instance("hd_async_reader_2");


////////////////////////////////////////////////////////////////////////
//
//  Normal Win32 File Class
//

class i4_win32_file_class : public i4_file_class
{
protected:
  w32 fd;
public:

  i4_win32_file_class(w32 fd) : fd(fd) {}

  virtual w32 read (void *buffer, w32 size)
  {
    pf_win32_read.start();
    w32 res = _read(fd,buffer,size);
    pf_win32_read.stop();
    return res;
  }

  virtual w32 write(const void *buffer, w32 size)
  {
    pf_win32_write.start();
    w32 res = _write(fd,buffer,size);
    pf_win32_write.stop();
    return res;
  }

  virtual w32 seek (w32 offset)
  {  
    pf_win32_seek.start();
    w32 res = lseek(fd, offset, SEEK_SET);
    pf_win32_seek.stop();
    return res;
  }

  virtual w32 size ()                       
  { 
    w32 len = _filelength(fd);

    /*
    w32 cur = lseek(fd,0,SEEK_CUR);
      ]  len = lseek(fd,0,SEEK_END);
    lseek(fd,cur,SEEK_SET);
    */
    return len;
  }

  virtual w32 tell ()
  {
    return _tell(fd);
  }

  ~i4_win32_file_class()
  { 
    _close(fd); 
  }


  // returns i4_F if an immediate error occured
  virtual i4_bool async_read (void *buffer, w32 size, 
                              async_callback call,
                              void *context=0)
  {
    if (i4_threads_supported())
      return i4_win32_async_instance.start_read(fd, buffer, size, call, context);
    else
      call(read(buffer,size),context);
    return i4_T;
  }

};


////////////////////////////////////////////////////////////////////////
//
//  File Manager Methods
//










// see file/file.hh for a description of what each of these functions do
class i4_win32_file_manager_class : public i4_file_manager_class
{
public:   
  virtual i4_file_class *open(const i4_const_str &name, w32 flags)
  {
    sw32 f=0;
    i4_bool no_buffer=i4_F;
    
    flags &= ~I4_SUPPORT_ASYNC;     // don't have to do anything special for these
    
    if (flags & I4_NO_BUFFER)
    {
      flags=(flags & (~I4_NO_BUFFER));
      no_buffer=i4_T;
    }

    f=O_BINARY;       // open all files in binary mode
    char sbuf[256];

    switch (flags)
    {
      case I4_READ: 
        f|=O_RDONLY;
        break;

      case I4_WRITE: 
        f |= O_WRONLY | O_CREAT;
        _unlink(i4_os_string(name,sbuf,sizeof(sbuf)));
        break;

      case I4_WRITE|I4_READ: 
        f |= O_RDWR | O_CREAT;
        _unlink(i4_os_string(name,sbuf,sizeof(sbuf)));
        break;

      case I4_APPEND:
      case I4_WRITE|I4_APPEND:
        f |= O_WRONLY|O_CREAT|O_APPEND;
        break;

      default: 
        i4_warning("i4_file_class::Bad open flags!");
        return NULL;     
    }

    int fd;


    fd=::_open(i4_os_string(name,sbuf,sizeof(sbuf)),f,_S_IREAD | _S_IWRITE);

    if (fd<0)
    {
      i4_warning("i4_file_class::open failed for %s\n",i4_os_string(name,sbuf,sizeof(sbuf)));
      return NULL;
    }


    i4_file_class *ret_fp;
    if (!no_buffer)
      ret_fp=new i4_buffered_file_class(new i4_win32_file_class(fd));
    else
      ret_fp=new i4_win32_file_class(fd);

    return ret_fp;
  }



  virtual i4_bool unlink(const i4_const_str &name)
  {
    char buf[256];
    return _unlink(i4_os_string(name,buf,sizeof(buf)))==0;
  }


  virtual i4_bool mkdir(const i4_const_str &name)
  {  
    char buf[256];
    return ::_mkdir(i4_os_string(name,buf,sizeof(buf)))==0;
  }

  i4_bool get_status(const i4_const_str &filename, i4_file_status_struct &return_stat)
  {
    i4_bool error=i4_F;
    struct _stat times;
    char buf[256];

    return_stat.flags=0;
    if (_stat(i4_os_string(filename,buf,sizeof(buf)),&times)==0)      
    {
      return_stat.last_modified=times.st_mtime;
      return_stat.last_accessed=times.st_atime;
      return_stat.created=times.st_ctime;

      if (times.st_mode &  _S_IFDIR)
        return_stat.flags=I4_FILE_STATUS_DIRECTORY;     
    }
    else 
      error=i4_T;

    return (i4_bool)(!error);
  }

  virtual i4_bool get_directory(const i4_const_str &path, 
                                i4_directory_struct &dir_struct,
                                i4_bool get_status,
                                i4_status_class *status)

  {
    _finddata_t fdat;
    char os_str[255],buf[256];
    sprintf(os_str,"%s/*.*",i4_os_string(path,buf,sizeof(buf)));

    long handle=_findfirst(os_str,&fdat),done;
    if (handle==-1)
      return i4_F;
  
    i4_array<i4_file_status_struct> stats(64,64);

    do
    {
      if (fdat.attrib & _A_SUBDIR)
      {
        dir_struct.tdirs++;
        dir_struct.dirs=(i4_str **)i4_realloc(dir_struct.dirs,
                                              sizeof(i4_str *)*dir_struct.tdirs,"dir list");
        dir_struct.dirs[dir_struct.tdirs-1]=new i4_str(i4_const_str(fdat.name));
      }
      else
      {
        i4_file_status_struct *s=stats.add();
        s->last_accessed=fdat.time_access;
        s->last_modified=fdat.time_write;
        s->created=fdat.time_create;

        dir_struct.tfiles++;
        dir_struct.files=(i4_str **)i4_realloc(dir_struct.files,
                                               sizeof(i4_str *)*dir_struct.tfiles,"dir list");
        dir_struct.files[dir_struct.tfiles-1]=new i4_str(i4_const_str(fdat.name));
      }

      done=_findnext(handle, &fdat);
    } while (done!=-1);


    if (get_status)
    {    
      if (dir_struct.tfiles)
      {
        i4_file_status_struct *sa;
        sa=(i4_file_status_struct *)I4_MALLOC(sizeof(i4_file_status_struct)*dir_struct.tfiles,"");
        for (int j=0; j< (int)dir_struct.tfiles; j++)
          sa[j]=stats[j];

        dir_struct.file_status=sa;
      }
    }

    return i4_T;
  }

  virtual i4_str *full_path(const i4_const_str &relative_name)
  {
    char buf[256], buf2[256];
    ::_fullpath(i4_os_string(relative_name, buf, 256), buf2, 256);
    return new i4_str(i4_const_str(buf2));
  }

  i4_win32_file_manager_class()
  {
    i4_add_file_manager(this, i4_F);    
  }

  ~i4_win32_file_manager_class()
  {
    i4_remove_file_manger(this);
  }

} i4_win32_file_man;


i4_bool i4_rmdir(const i4_const_str &path)
{
  char p[256];
  i4_os_string(path, p, 256);
  if (_rmdir(p)==0)
    return i4_T;
  else return i4_F;
}

i4_bool i4_chdir(const i4_const_str &path)
{
  char p[256];
  i4_os_string(path, p, 256);
  if (_chdir(p)==0)
    return i4_T;
  else return i4_F;
}

// WIN32\WIN_OPEN.CPP
/********************************************************************** <BR>
  This file is part of Crack dot Com's free source code release of
  Golgotha. <a href="http://www.crack.com/golgotha_release"> <BR> for
  information about compiling & licensing issues visit this URL</a> 
  <PRE> If that doesn't help, contact Jonathan Clark at 
  golgotha_source@usa.net (Subject should have "GOLG" in it) 
***********************************************************************/


class open_string : public i4_str
{
public:
  open_string(char *fname)
    : i4_str(strlen(fname))
  {
    len=strlen(fname);
    memcpy(ptr, fname, len);
  }
};


long FAR PASCAL WindowProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );



UINT APIENTRY win32_dialog_hook( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
  switch( message )
  {
    case WM_LBUTTONUP :

    case WM_RBUTTONUP :

    case WM_MBUTTONUP :
    case WM_ACTIVATEAPP :


    case WM_SYSKEYUP :
    case WM_KEYUP :
      WindowProc(hWnd, message, wParam, lParam);
      break;
  }

  return FALSE;
}


void i4_create_file_open_dialog(i4_graphical_style_class *style,
                                const i4_const_str &title_name,
                                const i4_const_str &start_dir,
                                const i4_const_str &file_mask,
                                const i4_const_str &mask_name,
                                i4_event_handler_class *tell_who,
                                w32 ok_id, w32 cancel_id)
{
  OPENFILENAME ofn;

  char mname[256], m[256], tname[256], idir[256], fname[256], curdir[256];

  ShowCursor(TRUE);

  _getcwd(curdir,256);

  i4_os_string(mask_name, mname, sizeof(mname));
  i4_os_string(file_mask, m, sizeof(m));
  i4_os_string(title_name, tname, sizeof(tname));
  i4_os_string(start_dir, idir, sizeof(idir));


  char *af="All files\0*.*\0\0";

  int i=strlen(mname)+1;
  int l=strlen(m) + 1;
  memcpy(mname + i, m, l);
  i+=l;
  l=15;
  memcpy(mname + i, m, l);

  fname[0]=0;

  ofn.lStructSize = sizeof(OPENFILENAME);
  ofn.hwndOwner = i4_win32_window_handle;
  ofn.hInstance = i4_win32_instance;
  ofn.lpstrCustomFilter = 0;
  ofn.nMaxCustFilter = 0;
  ofn.nFilterIndex = 0;
  ofn.lpstrFile = fname;
  ofn.nMaxFile = 256;

  ofn.nMaxFileTitle = 256;
  ofn.lpstrFileTitle = tname;

  ofn.lpstrInitialDir = idir;
  ofn.nFileOffset = 0;
  ofn.nFileExtension = 0;
  ofn.lCustData = 0L;
  ofn.lpfnHook = NULL;
  ofn.lpTemplateName = NULL;

  ofn.lpfnHook = win32_dialog_hook;


  ofn.lpstrFilter = mname;
  ofn.lpstrDefExt = "level";
  ofn.lpstrTitle = tname;
  ofn.Flags = OFN_HIDEREADONLY | OFN_ENABLEHOOK | OFN_EXPLORER  ;

  WindowProc(i4_win32_window_handle, WM_LBUTTONUP, 0,0);
  WindowProc(i4_win32_window_handle, WM_RBUTTONUP, 0,0);
  WindowProc(i4_win32_window_handle, WM_MBUTTONUP, 0,0);



  if (GetOpenFileName(&ofn)) 
  {	
    i4_file_open_message_class o(ok_id, new open_string(ofn.lpstrFile));
    i4_kernel.send_event(tell_who, &o);
  }
  else
  {
    i4_user_message_event_class o(cancel_id);
    i4_kernel.send_event(tell_who, &o);
  }

  _chdir(curdir);

  ShowCursor(FALSE);
}



void i4_create_file_save_dialog(i4_graphical_style_class *style,
                                const i4_const_str &default_name,
                                const i4_const_str &title_name,
                                const i4_const_str &start_dir,
                                const i4_const_str &file_mask,
                                const i4_const_str &mask_name,
                                i4_event_handler_class *tell_who,
                                w32 ok_id, w32 cancel_id)
{
  OPENFILENAME ofn;

  char mname[256], m[256], tname[256], idir[256], fname[256], curdir[256];

  _getcwd(curdir,256);

  i4_os_string(mask_name, mname, sizeof(mname));
  i4_os_string(file_mask, m, sizeof(m));
  i4_os_string(title_name, tname, sizeof(tname));
  i4_os_string(start_dir, idir, sizeof(idir));


  char *af="All files\0*.*\0\0";

  int i=strlen(mname)+1;
  int l=strlen(m) + 1;
  memcpy(mname + i, m, l);
  i+=l;
  l=15;
  memcpy(mname + i, m, l);

  fname[0]=0;

  ofn.lStructSize = sizeof(OPENFILENAME);
  ofn.hwndOwner = i4_win32_window_handle;
  ofn.hInstance = i4_win32_instance;
  ofn.lpstrCustomFilter = 0;
  ofn.nMaxCustFilter = 0;
  ofn.nFilterIndex = 0;
  ofn.lpstrFile = fname;
  ofn.nMaxFile = 256;

  ofn.nMaxFileTitle = 256;
  ofn.lpstrFileTitle = tname;

  ofn.lpstrInitialDir = idir;
  ofn.nFileOffset = 0;
  ofn.nFileExtension = 0;
  ofn.lCustData = 0L;
  ofn.lpfnHook = NULL;
  ofn.lpTemplateName = NULL;

  ofn.lpfnHook = win32_dialog_hook;

  ofn.lpstrFilter = mname;
  ofn.lpstrDefExt = m;
  while (*ofn.lpstrDefExt && *ofn.lpstrDefExt!='.')
    ofn.lpstrDefExt++;

  ofn.lpstrTitle = tname;
  ofn.Flags = OFN_HIDEREADONLY | OFN_ENABLEHOOK | OFN_EXPLORER ;

  WindowProc(i4_win32_window_handle, WM_LBUTTONUP, 0,0);
  WindowProc(i4_win32_window_handle, WM_RBUTTONUP, 0,0);
  WindowProc(i4_win32_window_handle, WM_MBUTTONUP, 0,0);


  if (GetSaveFileName(&ofn)) 
  {	
    i4_file_open_message_class o(ok_id, new open_string(ofn.lpstrFile));
    i4_kernel.send_event(tell_who, &o);
  }
  else
  {
    i4_user_message_event_class o(cancel_id);
    i4_kernel.send_event(tell_who, &o);
  }

  _chdir(curdir);
}
