/**********************************************************************
 *
 * fileutil.cpp -- 
 * Copyright (C) 1999  The New Zealand Digital Library Project
 *
 * A component of the Greenstone digital library software
 * from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *********************************************************************/

#include "fileutil.h"

#if defined(GSDL_USE_OBJECTSPACE)
#  include <ospace\std\iostream>
#  include <ospace\std\fstream>
#elif defined(GSDL_USE_IOS_H)
#  include <iostream.h>
#  include <fstream.h>
#else
#  include <iostream>
#  include <fstream>
#endif


// returns the proper concatenation of directory + directory/filename
text_t filename_cat (text_t path1, text_t path2) {
  text_t::iterator here;
  text_t::iterator begin;
  text_t::iterator end;

  // make sure there is just one slash, of the correct type,
  // at the end of path1 (unless path1 is an empty string).
  if (!path1.empty()) {
    // remove all trailing slashes
    here = path1.end();
    --here;
    begin = path1.begin();
    while (here != begin && (*here == '/' || *here == '\\')) {
      --here;
    }
    ++here;
    path1.erase(here,path1.end());
    
    // add one final slash
#ifdef __WIN32__
    path1 += "\\";
#else
    path1 += "/";
#endif 
  }

  // remove all slashes from the start of path2
  here = path2.begin();
  end = path2.end();
  while (here != end && (*here == '/' || *here == '\\')) {
    ++here;
  }
  path2.erase (path2.begin(), here);
  
  text_t fullpath = path1 + path2;
  
  // make sure all the right slashes are used
  here = fullpath.begin();
  end = fullpath.end();
  while (here != end) {
#ifdef __WIN32__
    if (*here == '/') *here = '\\';
#else
    if (*here == '\\') *here = '/';
#endif
    ++here ;
  }
  return fullpath;
}

text_t filename_cat (text_t path1, text_t path2, text_t path3) {
  return filename_cat(filename_cat(path1,path2),path3);
}

text_t filename_cat (text_t path1, text_t path2, text_t path3, text_t path4) {
  return filename_cat(filename_cat(filename_cat(path1,path2),path3),path4);
}

text_t filename_cat (text_t path1, text_t path2, text_t path3, text_t path4, 
		     text_t path5) {
  return filename_cat(filename_cat(filename_cat(filename_cat(path1,path2),path3),
				   path4),path5);
}

text_t filename_cat (text_t path1, text_t path2, text_t path3, text_t path4, 
		     text_t path5, text_t path6) {
  return filename_cat(filename_cat(path1,path2,path3,path4,path5),path6);
}



// returns the proper concatenation of the two paths, as in
// /usr/bin:/usr/local/bin (for Unix) and c:\usr\bin;c:\usr\local\bin (for windows)

text_t pathname_cat (text_t path1, text_t path2) {
  text_t::iterator here;
  text_t::iterator begin;
  text_t::iterator end;

  // make sure there are no slashes at the end of path1

  if (!path1.empty()) {
    // remove all trailing slashes
    here = path1.end();
    --here;
    begin = path1.begin();
    while (here != begin && (*here == '/' || *here == '\\')) {
      --here;
    }
    ++here;
    path1.erase(here,path1.end());
    
    // add one final slash
#ifdef __WIN32__
    path1 += ";";
#else
    path1 += ":";
#endif 
  }

  // make sure there are no slashes at the end of path2
  here = path2.end();
  --here;
  begin = path2.begin();
  while (here != begin && (*here == '/' || *here == '\\')) {
    --here;
  }
  ++here;
  path2.erase (here, path2.end());
  
  text_t fullpath = path1 + path2;
  
  // make sure all the right slashes are used
  here = fullpath.begin();
  end = fullpath.end();
  while (here != end) {
#ifdef __WIN32__
    if (*here == '/') *here = '\\';
#else
    if (*here == '\\') *here = '/';
#endif
    ++here ;
  }
  return fullpath;
}

text_t pathname_cat (text_t path1, text_t path2, text_t path3) {
  return pathname_cat(pathname_cat(path1,path2),path3);
}

text_t pathname_cat (text_t path1, text_t path2, text_t path3, text_t path4) {
  return pathname_cat(pathname_cat(pathname_cat(path1,path2),path3),path4);
}

text_t pathname_cat (text_t path1, text_t path2, text_t path3, text_t path4, 
		     text_t path5) {
  return pathname_cat(pathname_cat(pathname_cat(pathname_cat(path1,path2),path3),
				   path4),path5);
}

text_t pathname_cat (text_t path1, text_t path2, text_t path3, text_t path4, 
		     text_t path5, text_t path6) {
  return pathname_cat(pathname_cat(path1,path2,path3,path4,path5),path6);
}





// returns true if filename can be opened
bool file_exists (const text_t &filename) {
  char *cstr = filename.getcstr();
#ifdef GSDL_USE_IOS_H
  ifstream filestream (cstr, ios::in | ios::nocreate);
#else
  ifstream filestream (cstr, ios::in);
#endif
  delete []cstr;
  
  if (filestream) {
    // file exists
    filestream.close ();
    return true;
  }
  
  // file does not exist
  return false;
}

// returns true if filename can be opened
bool file_writable (const text_t &filename) {
  char *cstr = filename.getcstr();
#ifdef GSDL_USE_IOS_H
  ifstream filestream (cstr, ios::out | ios::nocreate);
#else
  ifstream filestream (cstr, ios::out);
#endif
  delete []cstr;
  
  if (filestream) {
    // file exists
    filestream.close ();
    return true;
  }
  
  // file does not exist
  return false;
}

#if defined(__WIN32__) && !defined(__GNUC__)

#include <windows.h>

bool directory_exists (const text_t &dirname) {
  
  WIN32_FIND_DATA FileData;  
  HANDLE hSearch; 

  text_t dir = dirname + "\\*";
  char *dirpath = dir.getcstr();
  
  hSearch = FindFirstFile(dirpath, &FileData);
  if (hSearch == INVALID_HANDLE_VALUE) {
    delete []dirpath;
    return false;
  }

  FindClose (hSearch);
  delete []dirpath;
  return true;
}

bool read_dir (const text_t &dirname, text_tset &filelist) {
  
  WIN32_FIND_DATA FileData;  
  HANDLE hSearch; 
  
  text_t dir = dirname + "\\*";
  char *dirpath = dir.getcstr();
  
  hSearch = FindFirstFile(dirpath, &FileData); 
  if (hSearch == INVALID_HANDLE_VALUE) {
    delete []dirpath;
    return false;
  }
  
  text_t filename = FileData.cFileName;
  if (filename != "." && filename != ".." && filename != "CVS" && filename != ".svn")
    filelist.insert (filename); 
  
  while (FindNextFile(hSearch, &FileData)) {
    filename = FileData.cFileName;
    if (filename == "." || filename == ".." || filename == "CVS" || filename == ".svn")
      continue;
    filelist.insert (filename); 
  }
  
  FindClose(hSearch);
  delete []dirpath;
  
  return true;
}

bool read_dir (const text_t &dirname, text_tarray &filelist) {
  
  WIN32_FIND_DATA FileData;  
  HANDLE hSearch; 
  
  text_t dir = dirname + "\\*";
  char *dirpath = dir.getcstr();
  
  hSearch = FindFirstFile(dirpath, &FileData); 
  if (hSearch == INVALID_HANDLE_VALUE) {
    delete []dirpath;
    return false;
  }
  
  text_t filename = FileData.cFileName;
  if (filename != "." && filename != ".." && filename != "CVS" && filename != ".svn")
    filelist.push_back (filename); 
  
  while (FindNextFile(hSearch, &FileData)) {
    filename = FileData.cFileName;
    if (filename == "." || filename == ".." || filename == "CVS" || filename == ".svn")
      continue;
    filelist.push_back (filename); 
  }
  
  FindClose(hSearch);
  delete []dirpath;

  return true;
}

#else

#include <dirent.h>

bool directory_exists (const text_t &dirname) {
  
  char *tmp = dirname.getcstr();
  DIR *dirin = opendir (tmp);
  delete []tmp;
  
  if (dirin == NULL) return false;
  closedir (dirin);
  return true;
}

bool read_dir (const text_t &dirname, text_tset &filelist) {
  
  char *tmp = dirname.getcstr();
  DIR *dirin = opendir (tmp);
  delete []tmp;
  
  if (dirin == NULL) return false;
  
  dirent *dirp; 
  
  text_t filename;
  while ((dirp = readdir (dirin)) != NULL) {
    filename = dirp->d_name;
    if (filename == "." || filename == ".." || filename == "CVS" || filename == ".svn")
      continue;
    filelist.insert (filename);
  }
  closedir (dirin);
  return true;
}

bool read_dir (const text_t &dirname, text_tarray &filelist) {
  
  char *tmp = dirname.getcstr();
  DIR *dirin = opendir (tmp);
  delete []tmp;
  
  if (dirin == NULL) return false;
  
  dirent *dirp; 
  
  text_t filename;
  while ((dirp = readdir (dirin)) != NULL) {
    filename = dirp->d_name;
    if (filename == "." || filename == ".." || filename == "CVS" || filename == ".svn")
      continue;
    filelist.push_back (filename);
  }
  closedir (dirin);
  return true;
}

#endif

// returns true if things look like they happened ok
bool file_copy (const text_t &fromfile, const text_t &tofile) {
  
  char *fromfilec = fromfile.getcstr();
  char *tofilec = tofile.getcstr();
  bool fail = false;

#ifdef _MSC_VER
  // Use this block of code only for compilation under Windows 
  // with MS Visual Studio.  Copilation under Windows with a compiler
  // like minGW needs to go to the 'else' block

  if (CopyFile (fromfilec, tofilec, FALSE) == 0) fail = true;

#else

  ifstream from (fromfilec);
  if (!from) {
    fail = true;
  } else {
    ofstream to (tofilec);
    if (!to) {
      fail = true;
      from.close();
    } else {
      from >> to.rdbuf();
      from.close();
      to.close();
    }
  }

#endif

  delete []fromfilec;
  delete []tofilec;
  if (fail) return false;
  return true;
}

// This is a fairly quick and nasty attempt at doing a "tail" on a file
// (i.e. returning the last numlines lines of filename).  It has one
// important limitation in that it expects lines to be linelength
// characters long on average.  This of course makes it possible that it
// won't return numlines lines if there are some lines that are longer than
// linelength.  It also makes it fairly inefficient as it always reads in
// numlines*linelength characters when it most often doesn't need them all.
// For current needs it's fine though.
// -- Pass a linelength of 0 to use the default linelength (256 chars).
// -- The maximum value of linelength is MAXLINELENGTH
text_t file_tail (const text_t &filename, int numlines, int linelength) {

#define MAXLINELENGTH 2048

  if (numlines < 1) numlines = 1;
  if (linelength < 1) linelength = 256;
  if (linelength > MAXLINELENGTH) linelength = MAXLINELENGTH;

  streampos numchars = linelength*numlines;

  text_tarray lines;
  text_t ret;

  char *filenamec = filename.getcstr();
  char linec[MAXLINELENGTH];
  ifstream file_in (filenamec);
  delete []filenamec;
  if (file_in) {
    file_in.seekg (0, ios::end);
    streampos file_length = file_in.tellg();
    if (file_length < numchars) numchars = file_length;
    file_in.seekg (-numchars, ios::end);

    while (!file_in.eof()) {
      file_in.getline (linec, linelength);
      ret.setcstr(linec);
      text_t::const_iterator here = ret.begin();
      text_t::const_iterator end = ret.end();
      // make sure line has content
      while (here != end) {
	if (*here != '\n' && *here != ' ') {
	  lines.push_back (ret);
	  break;
	}
	++here;
      }
    }
    file_in.close();
  }

  ret.clear();
  int numlinesgot = lines.size();
  int sindex = 0;
  if (numlinesgot > numlines) sindex = numlinesgot - numlines;
  for (int i = sindex; i < numlinesgot; ++i) {
    ret += lines[i] + "\n";
  }

  return ret;
}

#if defined(__WIN32__)

#include <direct.h>
// returns true if directory was created successfully
bool mk_dir (const text_t &dirname) {
  char *dirnamec = dirname.getcstr();
  int rv = _mkdir (dirnamec);
  delete []dirnamec;
  if (rv == 0) return true;
  return false;
}

#else

#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
// returns true if directory was created successfully
bool mk_dir (const text_t &dirname) {
  mode_t mode = 0777;
  char *dirnamec = dirname.getcstr();
  int rv = mkdir (dirnamec, mode);
  delete []dirnamec;
  if (rv == 0) return true;
  return false;
}

#endif

// read in file from filename and load into content
bool read_file (const text_t &filename, text_t &content) {

  content.clear();

  char *filenamec = filename.getcstr();
#ifdef GSDL_USE_IOS_H
  ifstream file_in (filenamec, ios::in | ios::nocreate);
#else
  ifstream file_in (filenamec, ios::in);
#endif
  delete []filenamec;

  if (file_in) {
    char c;
    file_in.get(c);
    while (!file_in.eof ()) {
      // Casting c to an unsigned char is vital when reading non-ASCII documents
      content.push_back((unsigned char) c);
      file_in.get(c);
    }
    file_in.close();
  } else {
    return false;
  }
  return true;
}
