/* file.C: open/close files recognising certain extensions. */

#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdarg.h>

#include "file.H"
#include "error.H"
#include "Assoc.H"
#include "CmpString.H"

namespace xrml {

PoolImpl(file)

/* opens a file with given name and fopen() open_mode ("w" or "r" e.g.). Returns the
 * FILE *, or NULL if opening the file was not succesful. Returns in ispipe whether
 * or not the file has been opened through a pipe. File extensions
 * .Z, .gz, .bz and .bz2 are recognised and lead to piped input/output with the
 * proper compress/uncompress commands. Also if the first character of the file name is
 * equal to '|', '<' or '>', a pipe is opened. */
static bool ispipename(const char *filename)
{
  return filename[0]=='|' || filename[0]=='<' || filename[0]=='>';
}

static FILE *OpenFile(const char *filename, const char *open_mode, int *ispipe, const char *ext)
{
  FILE *fp = (FILE *)NULL;

  if ((*open_mode != 'r' && *open_mode != 'w' && *open_mode != 'a')) {
    Error("OpenFile", "Invalid fopen() mode '%s'\n",
	  open_mode ? open_mode : "(null)");
    return fp;
  }

  if (filename[0] != '\0' && filename[strlen(filename)-1] != '/') {
    char *cmd = new char[strlen(filename)+20];
    if (ispipename(filename)) {
      sprintf(cmd, "%s", filename+1);
      *ispipe = TRUE;
    } else {
      *ispipe = FALSE;
      if (!ext) {
	ext = strrchr(filename, '.');
	if (ext) ext++; // skip '.'
      }
      if (ext && strcmp(ext, "gz")==0) {
	if (*open_mode == 'r')
	  sprintf(cmd, "gunzip < %s", filename);
	else
	  sprintf(cmd, "gzip > %s", filename);
	*ispipe = TRUE;
      } else if (ext && strcmp(ext, "Z")==0) {
	if (*open_mode == 'r')
	  sprintf(cmd, "uncompress < %s", filename);
	else
	  sprintf(cmd, "compress > %s", filename);
	*ispipe = TRUE;
      } else if (ext && strcmp(ext, "bz")==0) {
	if (*open_mode == 'r')
	  sprintf(cmd, "bunzip < %s", filename);
	else
	  sprintf(cmd, "bzip > %s", filename);
	*ispipe = TRUE;
      } else if (ext && strcmp(ext, "bz2")==0) {
	if (*open_mode == 'r')
	  sprintf(cmd, "bunzip2 < %s", filename);
	else
	  sprintf(cmd, "bzip2 > %s", filename);
	*ispipe = TRUE;
      }
    }

    if (*ispipe) {
      fp = popen(cmd, open_mode);
    } else {
      fp = fopen(filename, open_mode);
    }

    delete cmd;
    return fp;
  }

  return (FILE *)NULL;
}

/* closes the file taking into account whether or not it is a pipe */
static void CloseFile(FILE *fp, const int ispipe)
{
  if (fp) {
    /* close the file */
    if (ispipe)
      pclose(fp);
    else
      fclose(fp);
  }
}

FILE *file::open(const char *fname, const char *mode, const char *ext)
{
  if (!fname || fname[0]=='\0') {
    Error("file::open", "no file name");
    error = true;
    return (FILE*)NULL;
  }
  if (name != fname) {
    if (name) delete name;
    name = strdup(fname);
  }

  Info(NULL, "Opening %s '%s'", ispipename(name) ? "pipe to/from" : "local file",  name);
  error = false;
  if (!url) url = strdup(name);

  open_mode = (char*)mode;

  int i_pipe;
  fp = OpenFile(name, open_mode, &i_pipe, ext);
  ispipe = i_pipe ? true : false;

  if (!fp) {
    error = true;
    Error(NULL, "Can't open %s '%s'", ispipename(name) ? "pipe to/from" : "local file",  name);
  }
  return fp;
}

void file::make_url_target(const char *url_arg, const char *base_url_arg)
{
  if (!url_arg) {
    url = base_url = target = 0;
    return;
  }

  if (base_url) {    	// we already have a base url
    if (base_url_arg && base_url != base_url_arg) {     // replace it
      delete base_url;
      base_url = strdup(base_url_arg);
    } else {						// keep it
    }
  } else if (base_url_arg) {
    base_url = strdup(base_url_arg);			// copy it
  }

  if (url && url != url_arg) delete url;
  if (!strchr(url_arg, ':') || ispipename(url_arg)) {
    // no protocol specified
    if (!base_url || url_arg[0]=='/' || ispipename(url_arg)) {
      // no parent url or url_arg is a piped command
      url = new char[strlen(url_arg)+1];
      sprintf(url, "%s", url_arg);
    } else {
      // parent url: combine base_url with url_arg.
      url = new char[strlen(url_arg)+strlen(base_url)+1];
      sprintf(url, "%s", base_url);
      char *slash = strrchr(url, '/'); // trailing slash
      sprintf(slash ? slash+1 : url, "%s", url_arg);
    }
  } else {
    // protocol specified -> interpret the url as an absolute URL.
    // TODO: this is probably not correct. Check with relative URL specs.
    url = new char[strlen(url_arg)+1];
    sprintf(url, "%s", url_arg);
  }
  
  // separate target
  char *sep = strrchr(url, '#');
  if (sep) {
    *sep = '\0';
    target = sep+1;
  } else
    target = (char*)0;
}

#ifndef WGET_COMMAND
/* first argument is temporary file name, second argument is absolute URL */
#define WGET_COMMAND "wget -nv -O%s %s"
#endif /*WGET_COMMAND*/

// retrieves url to temporary file with given name, without 
// inspecting the cache.
static int retrieve_url_no_cache(const char *url, const char *name)
{
  char *cmd = new char[20+strlen(url)+strlen(name)];
  sprintf(cmd, WGET_COMMAND, name, url);
  int rc = system(cmd);
  delete cmd;
  return rc;
}

class url_cache: public assoc<comparable_string, char*> {
public:
  void print(void)
  {
    for (int i=0; i<size; i++) {
      cerr << i << " : " << key(i) << " -> " << value(i) << "\n";
    }
  }

  char *lookup(const char *url) 
  {
    int index = (*this)(comparable_string((char*)url));
    if (index >= 0) {	// cached
      return value(index);
    }
    return 0;
  }

  ~url_cache() {
    for (int i=0; i<size; i++) {
      Info(NULL, "Deleting temporary file '%s'", value(i));
      unlink(value(i));
    }
  }
};

static char* retrieve_url(const char *url)
{
  static url_cache cache;
  // temporary file cache: associates absolute URL with temporary file name

  Info(NULL, "Retrieving %s ...", url);

  // inspect cache
  char *name = cache.lookup(url);
  if (name) {
    Info(NULL, "Cached -> %s", name);
    return strdup(name);
  }

  // create temporary file name
  name = new char[20];
  sprintf(name, "/tmp/XXXXXX");
  name = mktemp(name);

  // retrieve the url using wget to the temporary file
  int rc = retrieve_url_no_cache(url, name);

  if (rc == 0) {	// add to cache
    cache.add(strdup(url), strdup(name));
    return name;
  }

  delete name;
  return (char*)0;
}

FILE *file::openFromURL(const char *url_arg, const char *parent_url)
{
  make_url_target(url_arg, parent_url);

  if (!url) {
    Error("file::openFromURL", "no url");
    error = true;
    return fp = (FILE*)0;
  }

  if (!strchr(url, ':') || ispipename(url)) {
    // no protocol specified -> open local file
    return open(url, "r");
  }

  if (strncmp(url, "file:", 5) == 0) {
    // file protocol -> local filename after "file:"
    return open(url+5, "r");
  }

  // other protocol (http:, ftp:, ...) download URL into temporary file
  name = retrieve_url(url);
  if (!name) {
    // url invalid or doesn't exist or network down or
    // wget command not installed or whatever ...
    error = true;
    return fp = (FILE*)0;
  }

  // check if temporary file really exists (cached file
  // may have been deleted for instance)
  struct stat buf;
  if (stat(name, &buf) != 0) {
    Info(NULL, "Temporary file %s disappeared mysteriously. Retrieving %s again ...", name, url);
    // try to retrieve it again
    int rc = retrieve_url_no_cache(url, name);
    if (rc != 0) {	// failed
      error = true;
      return fp = (FILE*)0;
    }
  }

  // open the temporary file
  istmpfile = true;
  char *ext = strrchr(url, '.');
  if (ext) ext++; 	// skip '.'
  return open(name, "r", ext);
}

FILE *file::open(const char *filename, char *mode, FILE *filep, const bool pipe)
{
  name = strdup(filename);
  url = strdup(filename);
  open_mode = mode;
  fp = filep;
  ispipe = pipe;
  return fp;
}

void file::close(void)
{
  CloseFile(fp, ispipe);
  fp = NULL;
}

file::~file()
{
  close();
  if (name) delete name;
  if (url) delete url;
  if (base_url) delete base_url;
  // don't delete target: target points into already deleted 'url' buffer
}

int file::printf(const char* format, ...)
{
  va_list pvar;
  int n;
  va_start(pvar, format);
  n = vfprintf(fp, format, pvar);
  va_end(pvar);
  return n;
}

} // namespace xrml

