/**********************************************************************
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 "/usr/include/sys/stat.h"
#include 
#include 
#include 
#include 
#include 


#include "error/error.hh"
#include "file/file.hh"
#include "file/buf_file.hh"
#include "file/file_man.hh"
#include "status/status.hh"
#include "threads/threads.hh"
#include "string/string.hh"
#include "memory/malloc.hh"
#include "file/async.hh"

i4_bool i4_allow_async_reads=i4_T;
//#define EMULATE_CD_SPEEDS 1
//#define EMULATE_SLOW_HARDDRIVE 1


extern "C" {
// libpthreads doesn't have this defined (it's not thread-safe)
extern char *realpath __P ((__const char *__name, char *__resolved));
           };

i4_critical_section_class realpath_lock;

enum { S_IRWUGO=(S_IRUSR|S_IRGRP|S_IROTH| S_IWUSR|S_IWGRP|S_IWOTH) };
enum { S_ALL   =(S_IRUSR|S_IRGRP|S_IROTH| S_IWUSR|S_IWGRP|S_IWOTH | S_IXUSR|S_IXGRP|S_IXOTH) };

class i4_unix_async_reader : public i4_async_reader
{
public:
  i4_unix_async_reader(char *name) : i4_async_reader(name) {}
  virtual w32 read(sw32 fd, void *buffer, w32 count) 
  { 
    return ::read(fd,buffer,count);
  }
} i4_unix_async_instance("hd_async_reader");




char *i4_linux_filename_filter(const i4_const_str &name, char *obuf, int olen)
{
  char buf[256], *s, *d;
  i4_os_string(name, buf, 256);
  int changed=0;

  if (olen) olen--;     // save space for null

  for (s=buf, d=obuf; olen && *s; )
  {
    if (*s=='\\')
    {
      *d='/';
      changed=1;
    }
    else if (s[0]=='c' && s[1]==':' && s[2]=='/')
    {
      *d='/';
      s+=2;
      changed=1;
    }
    else if (s[0]=='x' && s[1]==':')
    {
      d[0]='/';
      d[1]='u';
      d++;
      olen--;
      s++;
      changed=1;
    }
    else *d=*s;

    s++;
    d++;
    olen--;
  }
  *d=0;
  
  return obuf;
}



////////////////////////////////////////////////////////////////////////
//
//  Normal UNIX File Class
//

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

  i4_normal_file_class(w32 fd) : fd(fd) {}


  virtual i4_bool async_read (void *buffer, w32 size, async_callback call, void *context)
  {
    if (i4_threads_supported())
      return i4_unix_async_instance.start_read(fd, buffer, size, call, context);
    else 
      (*call)(read(buffer, size), context);
    return i4_T;
  }

  virtual w32 read (void *buffer, w32 size) 
  {
    int ret=::read(fd,buffer,size);
    if (ret<0) ret=0;
    return ret;
  }
  
  virtual w32 write(const void *buffer, w32 size) 
  { 
    w32 ret=::write(fd,buffer,size); 
    return ret;
  }
  
  virtual w32 seek (w32 offset)
  { 
    w32 ret=lseek(fd,offset,SEEK_SET); 
    return ret;
  }
  
  virtual w32 size ()                       
  { 
    w32 cur=lseek(fd,0,SEEK_CUR);
    w32 len=lseek(fd,0,SEEK_END);
    lseek(fd,cur,SEEK_SET);
    return len;
  }
  
  virtual w32 tell ()                       
  { 
    w32 ret=lseek(fd,0,SEEK_CUR); 
    return ret;
  }
  
  ~i4_normal_file_class()
  { 
    close(fd); 
  }
};



class i4_linux_file_manager_class : public i4_file_manager_class
{

public:
  virtual i4_file_class *open(const i4_const_str &name, w32 flags=I4_READ)
  {
    char buf[256];
    i4_file_class *fp=0;

    sw32 f=0;
    i4_bool no_buffer=i4_F;

    flags &= ~(I4_SUPPORT_ASYNC);
    
    if (flags & I4_NO_BUFFER)
    {
      flags=(flags & (~I4_NO_BUFFER));
      no_buffer=i4_T;
    }


    switch (flags)
    {
      case I4_READ | I4_APPEND :
      case I4_READ | I4_WRITE | I4_APPEND :
        f |= O_RDWR | O_CREAT | O_APPEND;
        break;
        
      case I4_READ: 
        f|=O_RDONLY;
        break;

      case I4_WRITE: 
        f |= O_WRONLY | O_CREAT;
        ::unlink(i4_linux_filename_filter(name, buf, sizeof(buf)));
        break;

      case I4_WRITE|I4_READ: 
        f |= O_RDWR | O_CREAT;
        ::unlink(i4_linux_filename_filter(name, buf, sizeof(buf)));
        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;


    if (flags & I4_WRITE)
      fd=::open(i4_linux_filename_filter(name, buf, sizeof(buf)),f,S_IRWUGO);
    else
      fd=::open(i4_linux_filename_filter(name, buf, sizeof(buf)),f);

    if (fd<0)
      return 0;

    if (!no_buffer)
      fp=new i4_buffered_file_class(new i4_normal_file_class(fd));
    else
      fp=new i4_normal_file_class(fd);

    return fp;
    
  }

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

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

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

    return_stat.flags=0;

    if (stat(i4_linux_filename_filter(filename,buf,sizeof(buf)),×)==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)
  {
    char buf[256];
    struct dirent *de;

    DIR *d=opendir(i4_linux_filename_filter(path,buf,sizeof(buf)));
    if (!d) return i4_F;

    i4_str **tlist=NULL;
    sw32 t=0;
    char curdir[200];
    getcwd(curdir,200);
    if (chdir(i4_linux_filename_filter(path,buf,sizeof(buf)))!=0)
      return i4_F;

    do
    {
      de=readdir(d);

      if (status) 
        status->update(0.5);

      if (de && de->d_name[0])
      {
        t++;
        tlist=(i4_str **)i4_realloc(tlist,sizeof(i4_str *)*t,"tmp file list");
        tlist[t-1]=new i4_str(de->d_name);
      }
    } while (de);
    closedir(d);

    i4_file_status_struct *sa=0;
    
    if (t)
      sa=(i4_file_status_struct *)i4_malloc(sizeof (i4_file_status_struct) * t, "stat array");
   
    for (int i=0;iupdate(i/(float)t);


      struct stat s;
      stat(i4_linux_filename_filter(*tlist[i],buf,sizeof(buf)), &s);
                                                                                   
      
      if (S_ISDIR(s.st_mode))
      {
        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]=tlist[i];
      } else
      {
        int on=dir_struct.tfiles++;
        
        dir_struct.files=(i4_str **)i4_realloc(dir_struct.files,
                                               sizeof(i4_str *)*dir_struct.tfiles,"dir list");
        dir_struct.files[on]=tlist[i];

        sa[on].last_accessed=s.st_atime;
        sa[on].last_modified=s.st_mtime;
        sa[on].created=s.st_ctime;
      }
    }
    
    if (t)
      i4_free(tlist);

    if (get_status)
      dir_struct.file_status=sa;
    else if (sa)
      i4_free(sa);

    
    chdir(curdir);
    return i4_T;
  }

  virtual i4_str *full_path(const i4_const_str &relative_name)
  {
    char buf[256], buf2[256];
    realpath_lock.lock();
    ::realpath(i4_linux_filename_filter(relative_name, buf, 256), buf2);
    realpath_lock.unlock();
    return new i4_str(i4_const_str(buf2));
  }


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

  ~i4_linux_file_manager_class()
  {
    i4_remove_file_manger(this);
  }

} i4_linux_file_manager;



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;
}