//-----------------------------------------------------------
// Copyright Christian Arnault LAL-Orsay CNRS
// arnault@lal.in2p3.fr
// Modified by Grigory Rybkin
// See the complete license in cmt_license.txt "http://www.cecill.info". 
//-----------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#ifdef WIN32
#include <direct.h>
#define chdir _chdir
#define rmdir _rmdir
//#define mkdir _mkdir
#define getcwd _getcwd
#define popen _popen
#define pclose _pclose
#define S_IFDIR _S_IFDIR
#define USE_GETCWD 1
#define cwd_env_var "CD"
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <io.h>
#include <windows.h>

#define stat _stat
#define unlink _unlink
#define chmod _chmod
//#define putenv _putenv
#define WEXITSTATUS(w) (w)
#define PATH_MAX _MAX_PATH

#else
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <dirent.h>
#include <sys/utsname.h>
#include <limits.h> // define PATH_MAX
#define cwd_env_var "PWD"
#endif

#ifdef __hpux__
#define USE_GETCWD 1
#endif

#ifdef __linux__
#define USE_GETCWD 1
#endif

#ifdef USE_GETCWD
char* getwd (const char* name)
{
  // defined in <limits.h>
  char dir[PATH_MAX];
  //  char dir[256];
  if (NULL == ::getcwd (dir, sizeof (dir)))
    {
      perror ("cmt: getcwd failure");
      return NULL;
    }
  strcpy ((char*) name, dir);
  return ((char*) name);
}
#endif

#include "cmt_system.h"
#include "cmt_error.h"
#include "cmt_map.h"
#include "cmt_log.h"

//--------------------------------------------------
cmt_string CmtSystem::pwd ()
{
  char buffer[PATH_MAX] = "";
  //  char buffer[256] = "";
  //  char* ptr = 0;
  char* pwd_env = 0;

  pwd_env = ::getenv (cwd_env_var);  
  //  pwd_env = ::getenv ("PWD");  

  if (pwd_env != 0)
    {
      strncpy (buffer, pwd_env, PATH_MAX - 1);
      buffer[PATH_MAX - 1] = '\0'; // ensure termination
      //      strcpy (buffer, pwd_env);
    }
  else
    {
      if (NULL == ::getcwd (buffer, sizeof (buffer)))
	{
	  perror ("cmt: getcwd failure");
	  static const cmt_string empty;
	  return empty;
	}
    }

  //cerr << "pwd> " << buffer << endl;

  const char* t = &buffer[0];
  return ((cmt_string) t);
}

//--------------------------------------------------
static cmt_string get_absolute_path (const cmt_string& name)
{
  cmt_string path;
  if (CmtSystem::absolute_path (name))
    {
      path = name;
    }
  else
    {
      char buffer[PATH_MAX];
      if (NULL == ::getcwd (buffer, sizeof (buffer)))
	{
	  perror ("cmt: getcwd failure");
	  path = name;
	}
      else
	{
	  path += buffer;
	  path += CmtSystem::file_separator ();
	  path += name;
	}
    }
  CmtSystem::compress_path (path);
  return path;
}

//--------------------------------------------------
bool CmtSystem::cd (const cmt_string& dir)
{
  if (!CmtSystem::absolute_path (dir))
    {
      cmt_string d = CmtSystem::pwd ();
      if (0 == d.size ()) return false;
      d += file_separator ();
      d += dir;
      return (CmtSystem::cd (d));
    }

  static cmt_string s_dir;

  s_dir = dir;

  if ((s_dir.size () == 2) && (s_dir[1] == ':'))
    {
      s_dir += file_separator ();
    }

  compress_path (s_dir);

  if (::chdir (s_dir.c_str ()) == 0) 
    {
#ifndef WIN32
      if (putenv (cwd_env_var, s_dir))
	//      if (putenv ("PWD", s_dir))
	return (true);
      else
	return (false);
#else
      return (true);
#endif
    }
  else
    {
      /*
      if (errno != ENOENT
#ifndef WIN32
	  && errno != ENOTDIR
#endif
	  )
	perror ("cmt: chdir failure: " + get_absolute_path (s_dir));
      */
      return (false);
    }
  return (false);
}

//--------------------------------------------------
void CmtSystem::basename (const cmt_string& file_name, cmt_string& result)
{
  int pos = file_name.find_last_of ('/');
  if (pos == cmt_string::npos)
    {
      pos = file_name.find_last_of ('\\');
    }

  if (pos == cmt_string::npos)
    {
      result = file_name;
    }
  else
    {
      file_name.substr (pos + 1, result);
    }
}

//--------------------------------------------------
void CmtSystem::basename (const cmt_string& file_name,
                          const cmt_string& /*suffix*/,
                          cmt_string& result)
{
  basename (file_name, result);

  int pos;

  pos = result.find_last_of ('.');

  if (pos != cmt_string::npos)
    {
      result.erase (pos);
    }
}

//--------------------------------------------------
void CmtSystem::dirname (const cmt_string& file_name, cmt_string& result)
{
  int pos = file_name.find_last_of ('/');
  if (pos == cmt_string::npos)
    {
      pos = file_name.find_last_of ('\\');
    }

  if (pos == cmt_string::npos)
    {
      result = "";
    }
  else
    {
      result = file_name;
      result.erase (pos);
    }
}

//--------------------------------------------------
void CmtSystem::name (const cmt_string& file_name, cmt_string& result)
{
  int pos;

  result = file_name;

  // remove the suffix

  pos = result.find_last_of ('.');

  if (pos != cmt_string::npos)
    {
      result.erase (pos);
    }

  // remove the directory name

  pos = result.find_last_of ('/');
  if (pos == cmt_string::npos)
    {
      pos = result.find_last_of ('\\');
    }

  if (pos != cmt_string::npos)
    {
      result.erase (0, pos + 1);
    }
}

//-------------------------------------------------
void CmtSystem::get_suffix (const cmt_string& file, cmt_string& result)
{
  int pos = file.find_last_of ('.');
  int sep = file.find_last_of (file_separator ());

  if ((pos == cmt_string::npos) || (pos < sep))
    {
      result = "";
    }
  else
    {
      file.substr (pos + 1, result);
    }
}

//-------------------------------------------------
void CmtSystem::get_dot_suffix (const cmt_string& file, cmt_string& result)
{
  int pos = file.find_last_of ('.');
  int sep = file.find_last_of (file_separator ());

  if ((pos == cmt_string::npos) || (pos < sep))
    {
      result = "";
    }
  else
    {
      file.substr (pos, result);
    }
}

//--------------------------------------------------
bool CmtSystem::has_prefix (const cmt_string& name)
{
  if ((name.find ('/') == cmt_string::npos) &&
      (name.find ('\\') == cmt_string::npos))
    {
      return (false);
    }

  return (true);
}

//--------------------------------------------------
bool CmtSystem::realpath (const cmt_string& path, cmt_string& result)
{
  cmt_string here = CmtSystem::pwd ();
  if (0 == here.size ()) return false;
  if  (test_directory(path))
    {
      if (!CmtSystem::cd (path)) return false;
      result = CmtSystem::pwd ();
      if (0 == result.size ()) return false;
      if (!CmtSystem::cd (here))
	{
	  result.erase (0);
	  return false;
	}
      return true;
    }
  return false;
}

//--------------------------------------------------
bool CmtSystem::realpath_ (const cmt_string& path, cmt_string& result)
{
  char buffer[PATH_MAX + 1];
#ifndef WIN32
  if (::realpath (path, buffer))
    {
      result = buffer;
      //      cerr << "path: " << path << " result: " << result << endl;
      return true;
    }
  else
    {
      //      cerr << "path: " << path << " result: " << result << endl;
      if (errno != ENOENT
	  && errno != ENOTDIR
	  )
	{
	  perror ("cmt: realpath failure: " + get_absolute_path (path));
	}
      return false;
    }
#else
  if (::_fullpath (buffer, path, PATH_MAX + 1))
    {
      result = buffer;
      return true;
    }
  else
    {
      return false;
    }
#endif
}

/*
//--------------------------------------------------
bool CmtSystem::realpath_ (const cmt_string& path, cmt_string& result)
{
  if  (test_directory(path))
    {
      cmt_string here = CmtSystem::pwd ();
      if (0 == here.size ()) return false;
      if (!CmtSystem::cd (path)) return false;
      //
      char buffer[PATH_MAX] = "";
      //      char buffer[256] = "";
      if (NULL == ::getcwd (buffer, sizeof (buffer)))
	{
	  perror ("cmt: getcwd failure: " + get_absolute_path (path));
	  return false;
	}
      if (!CmtSystem::cd (here)) return false;
      const char* t = &buffer[0];
      result = t;
      //      cerr << "realpath_> path: " << path << " result: " << result << endl;
      //
      return true;
    }
  return false;
}
*/
//--------------------------------------------------
bool CmtSystem::absolute_path (const cmt_string& name)
{
  if (name.size () == 0) return (false);

  if ((name[0] == '/') ||
      (name[0] == '\\')) return (true);

  if (name.size () >= 2)
    {
      if (name[1] == ':')
        {
          return (true);
        }
    }
  return (false);
}

//--------------------------------------------------
bool CmtSystem::has_device (const cmt_string& name)
{
#ifdef WIN32
  if (name.size () == 0) return (false);

  if (name.size () >= 2)
    {
      if (name[1] == ':')
        {
          return (true);
        }
      else if ((name[0] == '\\') && (name[1] == '\\'))
        {
          return (true);
        }
    }
#endif

  return (false);
}

//--------------------------------------------------
cmt_string CmtSystem::current_branch ()
{
  cmt_string result;

  basename (pwd (), result);

  return (result);
}

//--------------------------------------------------
bool CmtSystem::test_directory (const cmt_string& name)
{
  struct stat file_stat;
  int status;

  status = ::stat (name.c_str (), &file_stat);

  //cerr << "status(stat) for " << name << " : " << status << " st_mode=" << file_stat.st_mode << endl;

  if (status == 0)
    {
      if ((file_stat.st_mode & S_IFDIR) == 0)
        {
          return (false);
        }
      else
        {
          return (true);
        }
    }
  else
    {
      /*
      if (errno != ENOENT
#ifndef WIN32
	  && errno != ENOTDIR
#endif
	  )
	{
	  perror ("cmt: stat failure: " + get_absolute_path (name));
	  CmtError::set (CmtError::file_access_error, get_absolute_path (name));
	}
      */
      return (false);
    }
}

//--------------------------------------------------
bool CmtSystem::test_file (const cmt_string& name)
{
  struct stat file_stat;
  int status;

  status = ::stat (name.c_str (), &file_stat);

  if (status == 0)
    {
      if ((file_stat.st_mode & S_IFDIR) == 0)
        {
          return (true);
        }
      else
        {
          return (false);
        }
    }
  else
    {
      /*
      if (errno != ENOENT
#ifndef WIN32
	  && errno != ENOTDIR
#endif
	  )
	{
	  perror ("cmt: stat failure: " + get_absolute_path (name));
	  CmtError::set (CmtError::file_access_error, get_absolute_path (name));
	}
      */
      return (false);
    }
}

//--------------------------------------------------
bool CmtSystem::compare_files (const cmt_string& name1,
                               const cmt_string& name2)
{
  struct stat file_stat1;
  struct stat file_stat2;
  int status;

  status = stat (name1.c_str (), &file_stat1);

  if (status == 0)
    {
      if ((file_stat1.st_mode & S_IFDIR) != 0)
        {
          return (false);
        }
    }
  else
    {
      if (errno != ENOENT
#ifndef WIN32
	  && errno != ENOTDIR
#endif
	  )
	{
	  perror ("cmt: stat failure: " + get_absolute_path (name1));
	  CmtError::set (CmtError::file_access_error, get_absolute_path (name1));
	}
      return (false);
    }

  status = stat (name2.c_str (), &file_stat2);

  if (status == 0)
    {
      if ((file_stat2.st_mode & S_IFDIR) != 0)
        {
          return (false);
        }
    }
  else
    {
      if (errno != ENOENT
#ifndef WIN32
	  && errno != ENOTDIR
#endif
	  )
	{
	  perror ("cmt: stat failure: " + get_absolute_path (name2));
	  CmtError::set (CmtError::file_access_error, get_absolute_path (name2));
	}
      return (false);
    }

  if (((int) file_stat1.st_size) != ((int) file_stat2.st_size))
    {
      return (false);
    }

  static cmt_string s1;
  static cmt_string s2;

  if (!s1.read (name1))
    {
      CmtError::set (CmtError::file_access_error, name1);
      return false;
    }
  if (!s2.read (name2))
    {
      CmtError::set (CmtError::file_access_error, name2);
      return false;
    }

  return ((s1 == s2));
}

//--------------------------------------------------
//
// Function use to change file timestamps
//
//--------------------------------------------------
bool CmtSystem::touch_file (const cmt_string& name)
{
  if (CmtSystem::test_file(name))
    {
      static cmt_string s;
      if (!s.read (name))
	{
	  CmtError::set (CmtError::file_access_error, name);
	  return false;
	}
      //unlink  (name);            
      FILE* f = fopen (name, "wb");
      if (f != NULL)
	{
	  s.write (f);
	  fclose (f);
	  return (true);
	}
      chmod (name.c_str(), 0777);
      /*            FILE* f = fopen (name, "a+");

      if (f != NULL)
      {
      cmt_string empty = " ";
      empty.write(f);
                 
      fclose (f);
      return true;
      }    
      */             
    }
  return false;
}
    

//--------------------------------------------------
//
// Check if the file "name1" is identical to "name2"
// if they are identical, "name1" will be simply deleted
// otherwise "name1" will be copied to "name2" and deleted afterwards
//
//--------------------------------------------------
bool CmtSystem::compare_and_update_files (const cmt_string& name1,
                                          const cmt_string& name2)
{
  struct stat file_stat1;
  struct stat file_stat2;
  static cmt_string s1;
  static cmt_string s2;
  int status;

  status = ::stat (name1.c_str (), &file_stat1);

  if (status == 0)
    {
      if ((file_stat1.st_mode & S_IFDIR) != 0)
        {
          // name1 is a directory.
          return (false);
        }
    }
  else
    {
      if (errno != ENOENT
#ifndef WIN32
	  && errno != ENOTDIR
#endif
	  )
	perror ("cmt: stat failure: " + get_absolute_path (name1));
      // name1 does not exist
      return (false);
    }

  if (!s1.read (name1))
    {
      CmtError::set (CmtError::file_access_error, name1);
      return false;
    }

  status = ::stat (name2.c_str (), &file_stat2);

  if (status == 0)
    {
      if ((file_stat2.st_mode & S_IFDIR) != 0)
        {
          // name2 is a directory
          return (false);
        }

      if (((int) file_stat1.st_size) == ((int) file_stat2.st_size))
        {
          if (!s2.read (name2))
	    {
	      CmtError::set (CmtError::file_access_error, name2);
	      return false;
	    }
          if (s1 == s2)
            {
              if (0 == ::unlink (name1))
		return (true);
	      else
		{
		  perror ("cmt: unlink failure: " + get_absolute_path (name1));
		  return (false);
		}
            }
        }
    }
  else
    {
      if (errno != ENOENT
#ifndef WIN32
	  && errno != ENOTDIR
#endif
	  )
	{
	  perror ("cmt: stat failure: " + get_absolute_path (name2));
	  return (false);
	}
      // name2 does not exist
    }

  FILE* f = fopen (name2, "wb");
  if (f != NULL)
    {
      s1.write (f);
      if (ferror (f))
	return (false);
      if (fclose (f))
	return (false);

      if (0 == ::unlink (name1))
	return (true);
      else
	{
	  perror ("cmt: unlink failure: " + get_absolute_path (name1));
	  return (false);
	}
    }
  else
    {
      //
      // keep the new file "name1" since it cannot be
      // copied to "name2"
      //
      return (false);
    }
}

//--------------------------------------------------
int CmtSystem::file_size (const cmt_string& name)
{
  struct stat file_stat;
  int status;

  status = ::stat (name.c_str (), &file_stat);

  if (status == 0)
    {
      return ((int) file_stat.st_size);
    }
  else
    {
      if (errno != ENOENT
#ifndef WIN32
	  && errno != ENOTDIR
#endif
	  )
	{
	  perror ("cmt: stat failure: " + get_absolute_path (name));
	  return -1;
	}
      return (0);
    }
}

//--------------------------------------------------
/*
char CmtSystem::file_separator ()
{
#ifdef WIN32
  return ('\\');
#else
  return ('/');
#endif
}
*/
/**
 *  Transform all / or \ characters in the text into the current file_separator
 *  Reduce all multiple file_separator into single ones.
 */
void CmtSystem::reduce_file_separators (cmt_string& text)
{
  if (file_separator () == '/')
    {
      text.replace_all ("\\", "/");
      while (text.find ("//") != cmt_string::npos)
	{
	  text.replace_all ("//", "/");
	}
    }
  else
    {
      text.replace_all ("/", "\\");
      while (text.find ("\\\\") != cmt_string::npos)
	{
	  text.replace_all ("\\\\", "\\");
	}
    }
}

//--------------------------------------------------
/*
char CmtSystem::path_separator ()
{
#ifdef WIN32
  return (';');
#else
  return (':');
#endif
}
*/
//--------------------------------------------------
 /*
char CmtSystem::command_separator ()
{
#ifdef WIN32
  return ('&');
#else
  return (';');
#endif
}
 */
/*
//--------------------------------------------------
const char * CmtSystem::cwd_variable ()
{
#ifdef WIN32
  return ("CD");
#else
  return ("PWD");
#endif
}
*/
//--------------------------------------------------
const cmt_string& CmtSystem::ev_open ()
{
#ifdef WIN32
  static const cmt_string s = "%";
#else
  static const cmt_string s = "${";
#endif

  return (s);
}

//--------------------------------------------------
const cmt_string& CmtSystem::ev_close ()
{
#ifdef WIN32
  static const cmt_string s = "%";
#else
  static const cmt_string s = "}";
#endif

  return (s);
}

//-------------------------------------------------
bool CmtSystem::create_symlink (const cmt_string& oldname,
                                const cmt_string& newname)
{
  if (-1 == ::unlink (newname)
      && errno != ENOENT
#ifndef WIN32
      && errno != ENOTDIR
#endif
      )
    {
      perror ("cmt: unlink failure: " + get_absolute_path (newname));
      return (false);
    }
  
#ifdef WIN32
  int status = 1;
#else
  int status = ::symlink (oldname.c_str (), newname.c_str ());
#endif

  if (status == 0) return (true);
  else
    {
#ifndef WIN32
      perror ("cmt: symlink failure: " + newname + "->" + oldname);
#endif
      return (false);
    }
}

//-------------------------------------------------
bool CmtSystem::remove_file (const cmt_string& name)
{
  if (::unlink (name) == 0)
    {
      return (true);
    }
  else
    {
      //      CmtMessage::error ("Cannot remove file " + name);
      //      cerr << "#CMT> Cannot remove file " << name << endl;
      if (errno != ENOENT
#ifndef WIN32
	  && errno != ENOTDIR
#endif
	  )
	{
	  perror ("cmt: unlink failure: " + get_absolute_path (name));
	  return (false);
	}
      return (true);
    }
}

//-------------------------------------------------
bool CmtSystem::remove_directory (const cmt_string& name)
{
  //cerr << "Try to remove directory " << name << endl;

  cmt_string_vector files;

  scan_dir (name, files);

  for (int i = 0; i < files.size (); i++)
    {
      cmt_string& file = files[i];

      if (test_directory (file))
        {
	  //cerr << "D=" << file << endl;
          if (!remove_directory (file)) return (false);
        }
      else
        {
	  //cerr << "F=" << file << endl;
          if (!remove_file (file)) return (false);
        }
    }

  int status = ::rmdir (name);
  if (status != 0)
    {
      perror ("cmt: rmdir failure: " + get_absolute_path (name));
      char num[32]; sprintf (num, "%d", errno);
      CmtMessage::error ("Cannot remove directory " + name + " errno=" + num);
      //      cerr << "#CMT> Cannot remove directory " << name << " errno=" << errno << endl;
      return (false);
    }

  return (true);
}

//-------------------------------------------------
bool CmtSystem::mkdir (const cmt_string& name)
{
  static cmt_string_vector path_vector;
  int i;
  static cmt_string full_path;
  char double_fs[] = "  ";

  double_fs[0] = file_separator ();
  double_fs[1] = file_separator ();

  full_path = name;

  if (file_separator () == '/')
    {
      full_path.replace_all ("\\", file_separator ());
    }
  else
    {
      full_path.replace_all ("/", file_separator ());
    }

  full_path.replace_all (double_fs, file_separator ());

  split (full_path, file_separator (), path_vector);

  full_path = "";

  if (absolute_path (name))
    {
      if (!has_device (name))
        {
          full_path = file_separator ();
        }
    }

  for (i = 0; i < path_vector.size (); i++)
    {
      const cmt_string& path = path_vector[i];

      if (i > 0) full_path += file_separator ();
      full_path += path;

      if (has_device (path)) continue;

      if (!test_directory (full_path))
        {
#ifdef WIN32
          if (::_mkdir (full_path.c_str ()) != 0)
#else
          if (::mkdir (full_path.c_str (), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ) != 0)
#endif
            {
	      // cerr << "CMT> cannot create directory " << full_path << endl;
	      perror ("cmt: mkdir failure: " + full_path);
              return (false);
            }
        }
    }

  return (true);
}

//----------------------------------------------------------
void CmtSystem::scan_dir (const cmt_string& dir_name,
                          cmt_string_vector& list)
{
  static cmt_string dir_prefix;
  static cmt_string name_prefix;

  dir_prefix = dir_name;
  if (dir_name == "") dir_prefix = ".";

  if (!test_directory (dir_prefix))
    {
      dirname (dir_prefix, dir_prefix);
      basename (dir_name, name_prefix);
    }
  else
    {
      name_prefix = "";
    }

  bool need_filter = false;

  int wild_card;

  wild_card = name_prefix.find ('*');
  if (wild_card != cmt_string::npos)
    {
      name_prefix.erase (wild_card);
    }

  if (name_prefix.size () > 0)
    {
      need_filter = true;
    }

  list.clear ();

#ifdef WIN32

  long dir;
  struct _finddata_t entry;

  static cmt_string search;

  search = dir_prefix;
  search += file_separator ();
  search += "*";

  dir = _findfirst (search.c_str (), &entry);
  if (dir > 0)
    {
      for (;;)
        {
          if ((strcmp ((char*) entry.name, ".") != 0) &&
              (strcmp ((char*) entry.name, "..") != 0) &&
              (strncmp ((char*) entry.name, ".nfs", 4) != 0))
            {
              const char* name = entry.name;

              if (!need_filter ||
                  (strncmp (name, name_prefix.c_str (), name_prefix.size ()) == 0))
                {
                  cmt_string& name_entry = list.add ();

                  name_entry = dir_prefix;
                  name_entry += file_separator ();
                  name_entry += name;
                }
            }

          int status = _findnext (dir, &entry);
          if (status != 0)
            {
              break;
            }
        }

      _findclose (dir);
    }
#else

  //cerr << "scan_dir> dir=" << dir_name << endl;

  DIR* dir = opendir (dir_prefix.c_str ());

  struct dirent* entry;

  if (dir != 0)
    {
      while ((entry = readdir (dir)) != 0)
        {
          //if (entry->d_name[0] == '.') continue;
          if (!strcmp ((char*) entry->d_name, ".")) continue;
          if (!strcmp ((char*) entry->d_name, "..")) continue;
          if (!strncmp ((char*) entry->d_name, ".nfs", 4)) continue;

          const char* name = entry->d_name;

          if (need_filter &&
              (strncmp (name, name_prefix.c_str (), name_prefix.size ()) != 0)) continue;

          //cerr << "scan_dir> name=" << name << endl;

          cmt_string& name_entry = list.add ();

          name_entry = dir_prefix;
          name_entry += file_separator ();
          name_entry += name;
        }

      closedir (dir);
    }
#endif

}

//----------------------------------------------------------
void CmtSystem::scan_dir (const cmt_string& dir_name,
                          const cmt_regexp& expression,
                          cmt_string_vector& list)
{
  static cmt_string dir_prefix;

  dir_prefix = dir_name;
  if (dir_name == "") dir_prefix = ".";

  if (!test_directory (dir_prefix))
    {
      dirname (dir_prefix, dir_prefix);
    }

  list.clear ();

#ifdef WIN32

  long dir;
  struct _finddata_t entry;

  static cmt_string search;

  search = dir_prefix;
  search += file_separator ();
  search += "*";

  dir = _findfirst (search.c_str (), &entry);
  if (dir > 0)
    {
      for (;;)
        {
          if ((entry.name[0] != '.') &&
              (strcmp ((char*) entry.name, ".") != 0) &&
              (strcmp ((char*) entry.name, "..") != 0) &&
              (strncmp ((char*) entry.name, ".nfs", 4) != 0))
            {
              const char* name = entry.name;
              
              if (expression.match (name))
                {
                  cmt_string& name_entry = list.add ();

                  name_entry = dir_prefix;
                  name_entry += file_separator ();
                  name_entry += name;
                }
            }

          int status = _findnext (dir, &entry);
          if (status != 0)
            {
              break;
            }
        }
      _findclose (dir);
    }
#else

  //cerr << "scan_dir> dir=" << dir_name << endl;

  DIR* dir = opendir (dir_prefix.c_str ());

  struct dirent* entry;

  if (dir != 0)
    {
      while ((entry = readdir (dir)) != 0)
        {
          //if (entry->d_name[0] == '.') continue;
          if (!strcmp ((char*) entry->d_name, ".")) continue;
          if (!strcmp ((char*) entry->d_name, "..")) continue;
          if (!strncmp ((char*) entry->d_name, ".nfs", 4)) continue;

          const char* name = entry->d_name;

          if (!expression.match (name)) continue;

          cmt_string& name_entry = list.add ();

          name_entry = dir_prefix;
          name_entry += file_separator ();
          name_entry += name;
        }

      closedir (dir);
    }
#endif

}

//----------------------------------------------------------
CmtSystem::cmt_string_vector& CmtSystem::scan_dir (const cmt_string& dir_name)
{
  static cmt_string_vector result;

  scan_dir (dir_name, result);

  return (result);
}

//----------------------------------------------------------
const cmt_string& CmtSystem::get_cmt_root ()
{
  static cmt_string root;

  root = "";

  const char* env = ::getenv ("CMTROOT");
  if (env != 0)
    {
      root = env;

      dirname (root, root);
      dirname (root, root);
      root.replace_all ("\"", "");
      return (root);
    }

#ifdef WIN32
  LONG status;
  HKEY key = 0;

  status = RegOpenKeyEx (HKEY_LOCAL_MACHINE, "Software\\CMT", 
                         0, KEY_READ, &key);
  if (status == ERROR_SUCCESS)
    {
      char temp[256];
      DWORD length = sizeof (temp) - 1;
      DWORD type;

      status = RegQueryValueEx (key, "root", 0, &type, (LPBYTE) temp, &length);
      if (status == ERROR_SUCCESS)
        {
          root = temp;
          return (root);
        }
    }
#endif

  return (root);
}

//----------------------------------------------------------
void CmtSystem::get_cmt_version (cmt_string& version)
{
  version = "";

  const char* env = ::getenv ("CMTROOT");
  if (env != 0)
    {
      cmt_string s = env;
      basename (s, version);
      version.replace_all ("\"", "");
    }
  else
    {
#ifdef WIN32
      LONG status;
      HKEY key = 0;

      status = RegOpenKeyEx (HKEY_LOCAL_MACHINE, "Software\\CMT", 
                             0, KEY_READ, &key);
      if (status == ERROR_SUCCESS)
        {
          char temp[256];
          DWORD length = sizeof (temp) - 1;
          DWORD type;
          
          status = RegQueryValueEx (key, "version", 0, &type, 
                                    (LPBYTE) temp, &length);
          if (status == ERROR_SUCCESS)
            {
              version = temp;
            }
        }
#endif
    }
}

//----------------------------------------------------------
cmt_string CmtSystem::get_cmt_config ()
{
  const char* env = ::getenv ("CMTCONFIG");
  if (env != 0)
    {
      return (cmt_string (env));
    }

  env = ::getenv ("CMTBIN");
  if (env != 0)
    {
      return (cmt_string (env));
    }

#ifdef WIN32
  LONG status;
  HKEY key = 0;

  status = RegOpenKeyEx (HKEY_LOCAL_MACHINE, "Software\\CMT", 
                         0, KEY_READ, &key);
  if (status == ERROR_SUCCESS)
    {
      char temp[256];
      DWORD length = sizeof (temp) - 1;
      DWORD type;

      status = RegQueryValueEx (key, "config", 0, &type, 
                                (LPBYTE) temp, &length);
      if (status == ERROR_SUCCESS)
        {
          cmt_string config (temp);
          return (config);
        }
    }

  return ("VisualC");
#endif

  return ("");

}

//----------------------------------------------------------
cmt_string CmtSystem::get_cmt_site ()
{
  const char* env = ::getenv ("CMTSITE");
  if (env != 0)
    {
      return (cmt_string (env));
    }

#ifdef WIN32
  LONG status;
  HKEY key = 0;

  status = RegOpenKeyEx (HKEY_LOCAL_MACHINE, "Software\\CMT", 
                         0, KEY_READ, &key);
  if (status == ERROR_SUCCESS)
    {
      char temp[256];
      DWORD length = sizeof (temp) - 1;
      DWORD type;

      status = RegQueryValueEx (key, "site", 0, &type, (LPBYTE) temp, &length);
      if (status == ERROR_SUCCESS)
        {
          cmt_string site (temp);
          return (site);
        }
    }
#endif

  return ("");
}

//----------------------------------------------------------
void CmtSystem::get_uname (cmt_string& uname)
{
#ifdef WIN32
  uname = "WIN32";
#else

  uname = "";
  struct utsname buf;

  if (::uname (&buf) >= 0)
    {
      uname = buf.sysname;
    }
  else
    {
      perror ("cmt: uname failure");
    }

  /*
  FILE* file;

  file = popen ("uname", "r");

  if (file != 0)
    {
      char line[1024];
      char* ptr;
      char* nl;

      line[0] = 0;
      ptr = fgets (line, sizeof (line), file);
      if (ptr != 0)
        {
          nl = strrchr (ptr, '\n');
          if (nl != 0) *nl = 0;

          uname = ptr;
        }
      pclose (file);
    }
  */
#endif
}

//----------------------------------------------------------
void CmtSystem::get_hosttype (cmt_string& hosttype)
{
  hosttype = "";

  char* ptr;

  ptr = ::getenv ("HOSTTYPE");
  if (ptr != 0)
    {
      hosttype = ptr;
    }
}

//----------------------------------------------------------
cmt_string CmtSystem::get_temporary_name ()
{
  //  cmt_string name;
  char * ptr;
  if ((ptr = ::tmpnam (NULL)) == NULL)
    {
      perror ("cmt: tmpnam failure");
      static const cmt_string empty;
      return empty;
    }
  //  name = ::tmpnam (NULL);

  return ptr;
  //  return (name);
}

//----------------------------------------------------------
cmt_string CmtSystem::get_home_package ()
{
  cmt_string name = "CMTHOME";

  return (name);
}

//----------------------------------------------------------
bool CmtSystem::is_home_package (const cmt_string& name,
                                 const cmt_string& version)
{
  if (name == "CMTHOME") return (true);

  return (false);
}

//----------------------------------------------------------
cmt_string CmtSystem::get_user_context_package ()
{
  cmt_string name = "CMTUSERCONTEXT";

  return (name);
}

//----------------------------------------------------------
bool CmtSystem::is_user_context_package (const cmt_string& name,
					 const cmt_string& version)
{
  if (name == "CMTUSERCONTEXT") return (true);

  return (false);
}

//----------------------------------------------------------
cmt_string CmtSystem::get_project_package ()
{
  cmt_string name = "PROJECT";

  return (name);
}

//----------------------------------------------------------
bool CmtSystem::is_project_package (const cmt_string& name,
                                    const cmt_string& version)
{
  if (name == "PROJECT") return (true);

  return (false);
}

//----------------------------------------------------------
bool CmtSystem::testenv (const cmt_string& name)
{
  const char* env = ::getenv (name);
  if (env == 0) return (false);
  return (true);
}

//----------------------------------------------------------
cmt_string CmtSystem::getenv (const cmt_string& name)
{
  cmt_string result;

  const char* env = ::getenv (name);
  if (env != 0)
    {
      result = env;
    }

  if (name == "CMTCONFIG")
    {
      return (get_cmt_config ());
    }

  /*
    if (name == "CMTROOT")
    {
    return (get_cmt_root ());
    }

    if (name == "CMTSITE")
    {
    return (get_cmt_site ());
    }
  */

  return (result);
}

/**
   Implementation based on a local static map of used environment
   variables to ensure a stable character string. The OS requires
   that the character string sent via a putenv is kept untouched forever
*/
bool CmtSystem::putenv (const cmt_string& name, const cmt_string& value)
{
  static cmt_map <cmt_string, cmt_string> envs;

  if (!envs.has (name))
    {
      cmt_string& v = *(new cmt_string);
      v = name;
      v += "=";
      v += value;
      envs.add (name, v);
    }
  else
    {
      cmt_string& v = *(envs.find (name));
      v = name;
      v += "=";
      v += value;
    }

  {
    const cmt_string& v = *envs.find (name);
    //cerr << "#CmtSystem::putenv> name=" << name << " &v=" << &v << endl;

#ifdef WIN32
    int status = ::_putenv ((char*) v.c_str ());
#else
    int status = ::putenv ((char*) v.c_str ());
#endif

    if (status == 0) return (true);
    else
      {
#ifdef WIN32
	CmtMessage::error ("putenv failure: " + v);
#else
	perror ("cmt: putenv failure: " + v);
#endif
	return (false);
      }
  }

}

//----------------------------------------------------------
//
// This singleton interacts with the ProjectFactory to consistently create
// the project graph.
//
// In particular a single-depth stack of the top project is maintained.
//
//----------------------------------------------------------
class CMTPathManager
{
public:
  static CMTPathManager& instance ();
  static void reset ();
  static void add_cmt_path (const cmt_string& path,
			    const cmt_string& path_source,
			    IProjectFactory& factory);

private:
  CMTPathManager () : m_project (0)
  {
  }

  void do_reset ()
  {
    m_project = 0;
  }

  void do_add_cmt_path (const cmt_string& path,
			const cmt_string& path_source,
			IProjectFactory& factory)
  {
    cmt_string npath = path;

    if (npath == "") return;

#ifdef WIN32
    if (npath.size () == 2)
      {
	if (npath[1] == ':')
	  {
	    npath += CmtSystem::file_separator ();
	  }
      }
#endif

    npath.replace_all ("\\", CmtSystem::file_separator ());
    npath.replace_all ("/", CmtSystem::file_separator ());

    if (!CmtSystem::absolute_path (npath))
      {
	cmt_string h = CmtSystem::pwd ();
	h += CmtSystem::file_separator ();
	h += npath;
	npath = h;
      }
    
    CmtSystem::compress_path (npath);

    //cerr << "adding npath=" << npath << endl;

    while (npath[npath.size ()-1] == CmtSystem::file_separator ())
      {
	npath.erase (npath.size ()-1);
      }
    
    //cerr << "adding npath=[" << npath << "]" << endl;
    
    if (npath != "")
      {
	cmt_string project_name;

	if ((path_source == "CMTUSERCONTEXT") ||
	    (path_source == "CMTHOME"))
	  {
	    project_name = path_source;
	  }

	m_project = factory.create_project (project_name, npath, path_source, 0);
	//	m_project = factory.create_project (project_name, npath, path_source, m_project);
	/*
	if ((path_source == "CMTUSERCONTEXT") ||
	    (path_source == "CMTHOME"))
	  {
	    m_project = 0;
	  }
	*/
      }
  }

  Project* m_project;
};

CMTPathManager& CMTPathManager::instance ()
{
  static CMTPathManager me;
  
  return (me);
}

void CMTPathManager::reset ()
{
  static CMTPathManager& me = instance ();
  me.do_reset ();
}

void CMTPathManager::add_cmt_path (const cmt_string& path,
				   const cmt_string& path_source,
				   IProjectFactory& factory)
{
  static CMTPathManager& me = instance ();
  me.do_add_cmt_path (path, path_source, factory);
}



//----------------------------------------------------------
static void add_cmt_paths_from_text (const cmt_string& text,
				     const cmt_string& context,
				     IProjectFactory& factory)
{
  static CmtSystem::cmt_string_vector path_vector;
  int i;

  CmtSystem::split (text, CmtSystem::path_separator (), path_vector);

  for (i = 0; i < path_vector.size (); i++)
    {
      const cmt_string& path = path_vector[i];

      CMTPathManager::add_cmt_path (path, context, factory);
    }
}

//----------------------------------------------------------
static void add_cmt_paths_from_file (const cmt_string& file_name, IProjectFactory& factory)
{
  if (!CmtSystem::test_file (file_name)) return;

  static cmt_string text;

  if (!text.read (file_name))
    {
      CmtError::set (CmtError::file_access_error, file_name);
      return;
    }

  int pos = text.find ("CMTPATH");
  if (pos == cmt_string::npos) return;
  pos += strlen ("CMTPATH");
  pos = text.find (pos, "=");
  if (pos == cmt_string::npos) return;
  pos++;

  text.erase (0, pos);

  int nl = text.find (pos, "\n");
  if (nl != cmt_string::npos) text.erase (nl);

  add_cmt_paths_from_text (text, file_name, factory);
}

//----------------------------------------------------------
//
// With this function we analyse all possible ways of
// externally entering CMTPATH items
//  + from the environment variable
//  + from .cmtrc files
//  + from registry on Windows
//  + from EV settings for CMTUSERCONTEXT and CMTHOME
// 
// Then projects are created from these settings.
//
// (The other way to enter project graph is through project files)
//----------------------------------------------------------
void CmtSystem::get_cmt_paths (IProjectFactory& factory, 
			       const cmt_string& init_text,
			       const cmt_string& cmt_user_context,
			       const cmt_string& cmt_home)
{
  CMTPathManager::reset ();

  CMTPathManager::add_cmt_path (cmt_user_context, "CMTUSERCONTEXT", factory);
  CMTPathManager::add_cmt_path (cmt_home, "CMTHOME", factory);
  /*
  if (init_text != "")
    {
      add_cmt_paths_from_text (init_text, "initialization", factory);
    }
  */

#ifdef WIN32
  LONG status;
  HKEY key = 0;

  status = RegOpenKeyEx (HKEY_CURRENT_USER, "Software\\CMT\\path",
                         0, KEY_READ, &key);
  if (status == ERROR_SUCCESS)
    {
      DWORD index = 0;
      char name[256];
      char temp[256];

      for (;;)
        {
          DWORD name_length = sizeof (name) - 1;
          DWORD length = sizeof (temp) - 1;
          DWORD type;
          status = RegEnumValue (key, index,
                                 name, &name_length, 0, &type,
                                 (LPBYTE) temp, &length);
          if ((status == ERROR_SUCCESS) ||
              (status == 234))
            {
              const cmt_string path = temp;
              CMTPathManager::add_cmt_path (path, "HKEY_CURRENT_USER", factory);
            }

          if (status == 259)
            {
              break;
            }

          index++;
        }
    }

  status = RegOpenKeyEx (HKEY_LOCAL_MACHINE, "Software\\CMT\\path",
                         0, KEY_READ, &key);
  if (status == ERROR_SUCCESS)
    {
      DWORD index = 0;
      char name[256];
      char temp[256];

      for (;;)
        {
          DWORD type;
          DWORD name_length = sizeof (name) - 1;
          DWORD length = sizeof (temp) - 1;


          status = RegEnumValue (key, index,
                                 name, &name_length, 0, &type,
                                 (LPBYTE) temp, &length);
          if (status != ERROR_NO_MORE_ITEMS)
            {
              const cmt_string path = temp;
              CMTPathManager::add_cmt_path (path, "HKEY_LOCAL_MACHINE", factory);
            }
          else
            {
              break;
            }
          index++;
        }
    }

#endif

  //-----------------------------------------
  // look for .cmtrc files :
  //  first look in ./
  //  then in "~/"
  //  then in ${CMTROOT}/mgr
  //-----------------------------------------
  cmt_string rc_name;

  add_cmt_paths_from_file (".cmtrc", factory);

  if (get_home_directory (rc_name))
    {
      rc_name += file_separator ();
      rc_name += ".cmtrc";
      add_cmt_paths_from_file (rc_name, factory);
    }

  rc_name = get_cmt_root ();
  rc_name += file_separator ();
  rc_name += "CMT";
  rc_name += file_separator ();
  cmt_string version;
  get_cmt_version (version);
  rc_name += version;
  rc_name += file_separator ();
  rc_name += "mgr";
  rc_name += file_separator ();
  rc_name += ".cmtrc";

  add_cmt_paths_from_file (rc_name, factory);

  /*
  CMTPathManager::add_cmt_path (cmt_user_context, "CMTUSERCONTEXT", factory);
  CMTPathManager::add_cmt_path (cmt_home, "CMTHOME", factory);
  */
  if (init_text != "")
    {
      add_cmt_paths_from_text (init_text, "initialization", factory);
    }
}

//----------------------------------------------------------
int CmtSystem::execute (const cmt_string& command)
{
  //cerr << "CmtSystem::execute1> [" << command << "]" << endl;
  int status = system (command.c_str ());
  if (status == -1) // failed
    return -1;
  else
    return WEXITSTATUS(status); // return status of the command
  //  return (system (command.c_str ()));
}

//----------------------------------------------------------
int CmtSystem::execute (const cmt_string& command, cmt_string& output)
{
  output = "";

  //  cerr << "CmtSystem::execute2> [" << command << "]" << endl;

  FILE* f = popen (command.c_str (), "r"); 
  
  if (f != 0) 
    { 
      char line[PATH_MAX]; 
      //      char line[256]; 
      char* ptr;

      while ((ptr = fgets (line, sizeof (line), f)) != NULL) 
        {
          output += ptr;
        } 

      int status = pclose (f);
      if (status == -1) // error reported by pclose ()
	{
	  perror ("cmt: pclose failure");
	  if (errno == ECHILD)
	    {
	      return (0);
	    }
	  return (-1);
	}
      else
	{
	  return WEXITSTATUS(status); // return status of the command
	  //return (0);
	}
    }

  return (-2);
}

//----------------------------------------------------------
bool CmtSystem::is_package_directory (const cmt_string& name)
{
  cmt_string_vector dirs;

  cmt_regexp exp ("^[a-zA-Z.][0-9]+([a-zA-Z.][0-9]+([a-zA-Z.][0-9]+)?)?");

  scan_dir (name, exp, dirs);

  cmt_string req;

  req = name;
  req += file_separator ();
  req += "cmt";
  req += file_separator ();
  req += "requirements";

  if (test_file (req)) return (true);

  if (dirs.size () == 0) 
    {
      return (false);
    }

  for (int i = 0; i < dirs.size (); i++)
    {
      const cmt_string& d = dirs[i];

      req = d;
      req += file_separator ();
      req += "mgr";
      req += file_separator ();
      req += "requirements";

      if (test_file (req)) return (true);

      req = d;
      req += file_separator ();
      req += "cmt";
      req += file_separator ();
      req += "requirements";
      
      if (test_file (req)) return (true);
    }

  return (false);
}

//----------------------------------------------------------
bool CmtSystem::is_version_directory (const cmt_string& name)
{
  int v;
  int r;
  int p;

  return (is_version_directory (name, v, r, p));
}

//----------------------------------------------------------
bool CmtSystem::is_version_directory (const cmt_string& name,
                                      int& v,
                                      int& r,
                                      int& p)
{
  if ((name == "HEAD") || (name == "head"))
    {
      v = 0;
      r = 0;
      p = 0;

      return (true);
    }

  static const cmt_string numbers = "0123456789";

  static const int id_version = 0;
  static const int id_release = 1;
  static const int id_patch   = 2;

  cmt_string buffer;

  enum 
    {
      starting,
      at_key,
      at_number
    } state;

  int id;
  int pos;
  int value;

  v = 0;
  r = 0;
  p = 0;

  //
  // version : v-field
  //         | v-field r-field
  //         | v-field r-field p-field
  //
  // v-field : field
  // r-field : field
  // p-field : field
  //
  // field   : key '*'
  //         | key number
  //
  // key     : letters
  //

  state = starting;
  id    = id_version;

  for (pos = 0; pos < name.size (); pos++)
    {
      char c = name[pos];

      if (c == '*')
        {
          // A wild card
          switch (state)
            {
            case starting:
              // cannot start with a wild card ??
	      v = -1;
	      r = -1;
	      p = -1;
              return (false);
            case at_key:
              // the numeric field is valued with a wild card
              switch (id)
                {
                case id_version:
                  v = -1;
                case id_release:
                  r = -1;
                case id_patch:
                  p = -1;
                  break;
                }
              return (true);
            case at_number:
              // question:
	      // a number followed by a wild-card is considered as:
	      //   1) a wild card on the number itself (1* comp with 1, 10, 12, 120, etc)
	      //   2) a wild card on the next fields (1* comp with 1r1, 1-12 etc)
	      //

	      //  Here we select option 1)

              sscanf (buffer.c_str (), "%d", &value);
              switch (id)
                {
                case id_version:
		  //
                  // lazy option 1 implies v = -1;
                  // strict option 1 would imply v = -value;
                  // option 2 implies v = value;
		  //

                  v = -1;
		  r = -1;
		  p = -1;
                  break;
                case id_release:
                  r = value;
		  p = -1;
                  break;
                case id_patch:
                  p = value;
                  break;
                }

              return (true);
            }
        }
      else if (numbers.find (c) == cmt_string::npos)
        {
          // A letter
          switch (state)
            {
            case starting:
              state = at_key;
              break;
            case at_key:
              // Multiple letter key (is it permitted??)
              break;
            case at_number:
              sscanf (buffer.c_str (), "%d", &value);
              switch (id)
                {
                case id_version:
                  v = value;
                  break;
                case id_release:
                  r = value;
                  break;
                case id_patch:
                  p = value;
                  break;
                }
              buffer = "";
              id++;
              state = at_key;
              break;
            }
        }
      else
        {
          // a number
          switch (state)
            {
            case starting:
              // not starting by a letter (syntax error)
              //return (false);
            case at_key:
              // the numeric field for the current id is starting now
              buffer += c;
              state = at_number;
              break;
            case at_number:
              // continuing the current numeric field
              buffer += c;
              break;
            }
        }
    }

  switch (state)
    {
    case starting:
      // Empty version string
      return (false);
    case at_key:
      // Syntax error (when only letters. Ending letters is not an error)
      if (id == id_version) return (false);
      else return (true);
    case at_number:
      sscanf (buffer.c_str (), "%d", &value);
      switch (id)
        {
        case id_version:
          v = value;
          break;
        case id_release:
          r = value;
          break;
        case id_patch:
          p = value;
          break;
        }
      id++;
      state = at_key;
      return (true);
    }

  return (false);
}

//----------------------------------------------------------
//  Split a line into words. Separators are spaces and tabs
//  Text enclosed in double quotes is one word.
//----------------------------------------------------------
void CmtSystem::split (const cmt_string& text,
                       const cmt_string& separators,
                       cmt_string_vector& strings,
		       const bool& unquote)
{
  static char* buffer = 0;
  static int allocated = 0;

  strings.clear ();

  if (text.size () == 0) return;

  /*
    We are going to work in a copy of the text, since
    \0 will be inserted right after each found word.

    Then the vector of strings is iteratively filled by each found word.
  */

  if (buffer == 0)
    {
      allocated = text.size ();
      buffer = (char*) malloc (allocated + 1);
    }
  else
    {
      if (text.size () > allocated)
        {
          allocated = text.size ();
          buffer = (char*) realloc (buffer, allocated + 1);
        }
    }

  strcpy (buffer, text.c_str ());

  /*
    Algorithm :

    We look for words separated by <separators> which may be
    o spaces (' ' or '\t')
    o other characters such as ':'

    A word is a character string not containing any separator. A substring in
    this word my be enclosed between quotes (" or ') which permits separator
    inclusion within words.
  */

  char * b = buffer;
  char * begin = b;
  char * e;
  char * pq = 0;
  char * pqm = 0;
  char q, ev;
  char * token = 0;
  bool before_q = false;
  bool after_q = false;
  bool matched = false;

//   cerr << "split: " << buffer << endl;
//   cerr << "seps: `";
//   for (int i = 0; i < separators.size (); i++)
//     cerr << separators[i];
//   cerr << "'" << endl;

  // sep...ab..."cd"ef...sep...
  while (pq = strpbrk (begin, "\"\'"))
    {
      if (begin < pq && *(pq - 1) == '\\')
	{// quote considered escaped
	  begin = pq + 1;
	  continue;
	}

      if ((b < pq && NULL == strchr (separators.c_str (), *(pq - 1))) ||
	  (b == pq && after_q))
	before_q = true;
      else
	before_q = false;

      // save quote found
      q = *pq;
      // terminate string for standard string functions
      *pq = 0;

      // parse string - up to quote - into a sequence of tokens
      if (token = strtok (b, separators.c_str ()))
	{
	  if (after_q)
	    strings.back () += token;
	  else
	    strings.add () = token;

// 	  cerr << (after_q ? "append: " : "add: ")
// 	       << "[" << token << "]{" << strings.back () << "}";

	  while (token = strtok (NULL, separators.c_str ()))
	    {
	      strings.add () = token;
	      //strings.push_back (token);
	      //cerr << "[" << token << "]{" << strings.back () << "}";
	    } // while ( token = strtok (NULL, separators.c_str ()) )
	  //cerr << endl;
	}

      if (unquote)
	{
	  b = pq + 1;
	}
      else
	{
	  // restore quote found
	  *pq = q;
	  b = pq;
	}
      begin = pq + 1;

      // look for matching quote
      matched = false;
      while (pqm = strchr (begin, q))
	{
	  // commented out because of
	  // inconsistency of quoting rules
	  // and backward compatibility
// 	  if (begin < pqm && *(pqm - 1) == '\\')
// 	    {// quote considered escaped
// 	      begin = pqm + 1;
// 	      continue;
// 	    }
	  matched = true;

	  if (*(pqm + 1) && NULL == strchr (separators.c_str (), *(pqm + 1)))
	    after_q = true;
	  else
	    after_q = false;

	  if (unquote)
	    {
	      e = pqm;
	    }
	  else
	    {
	      e = pqm + 1;
	      ev = *e;
	    }
	  // terminate string for standard string functions
	  *e = 0;
	  
	  if (before_q)
	    strings.back () += b;
	  else
	    strings.add () = b;

// 	  cerr << (before_q ? "append: " : "add: ")
// 	       << "|" << b << "|{" << strings.back () << "}" << endl;
	  
	  // restore e value
	  if (!unquote)
	    {
	      *e = ev;
	    }
	  b = pqm + 1;
	  begin = b;
	  break;
	} // while (pqm = strchr (begin, q))

      if (!matched)
	{ // unmatched quote : the rest of the line will be taken as a token
	  if (before_q)
	    strings.back () += b;
	  else
	    strings.add () = b;

	  // append quote to match
	  if (!unquote)
	    strings.back () += q;
	  
// 	  cerr << (before_q ? "append: " : "add: ")
// 	       << "|" << b << "|{" << strings.back () << "}" << endl;

	  b = buffer + strlen(buffer);
	  begin = b;
	  break;
	}

    } // while (pq = strpbrk (begin, "\"\'"))

      // parse string - up to end - into a sequence of tokens
      if (token = strtok (b, separators.c_str ()))
	{
	  if (after_q)
	    strings.back () += token;
	  else
	    strings.add () = token;

// 	  cerr << (after_q ? "append: " : "add: ")
// 	       << "<" << token << ">{" << strings.back () << "}";

	  while (token = strtok (NULL, separators.c_str ()))
	    {
	      strings.add () = token;
	      //cerr << "<" << token << ">{" << strings.back () << "}";
	    } // while ( token = strtok (NULL, separators.c_str ()) )
	  //cerr << endl;
	}
//       cerr << "strings:";
//       for (int i = 0; i < strings.size (); i++)
// 	cerr << " {" << strings[i] << "}";
//       //	cerr << " `" << strings[i] << "'";
//       cerr << endl;
}

//-------------------------------------------------------------
//  Quote separators (spaces and tabs) with double quotes,
//  double quotes with single quotes,
//  single quotes with double quotes in text.
//  Note: quotes preceded by backslash (i.e., \" or \') are NOT quoted
//  (considered escaped)
//-------------------------------------------------------------
cmt_string CmtSystem::quote (const cmt_string& text,
			     const cmt_string& separators)
{
  //cerr << "quote: `" << text << "'" << endl;
  cmt_string result;
  if (text.size () == 0) return result;

  size_t allocated = 3 * text.size (); // if EACH character of text quoted with " or '
  char* const buffer = (char*) malloc (allocated + 1);
  if (0 == buffer)
    {
      char num[32]; sprintf (num, "%lu", allocated + 1);
      cmt_string msg ("Cannot allocate ");
      msg += num;
      msg += " bytes";
      cmt_string e = errno ? ": " + cmt_string (strerror (errno)) : "";
      CmtMessage::error (msg + e);
      exit (EXIT_FAILURE);
    }
  char* b (buffer);

  //  char* const beg (buffer);
  const char* p = text.c_str ();
  const char* const text_c (p);
  //  const char* const beg_t (p);
  //  cerr << "quote: p = `" << p << "'" << endl;

  while (*p)
    //  while (*p != '\0')
    {
      size_t l_nonsep = strcspn (p, separators.c_str ());
      //cerr << "quote: l_nonsep = " << l_nonsep << " *p = '" << *p << "'" << endl;
      while (l_nonsep--)
	{
	  if (*p == '\"' &&
	      (p > text_c && *(p - 1) != '\\' || p == text_c))
	    { // quote " with '
	      *b++ = '\'';
	      *b++ = *p++;
	      *b++ = '\'';
	    }
	  else if (*p == '\'' &&
		   (p > text_c && *(p - 1) != '\\' || p == text_c))
	    { // quote ' with "
	      *b++ = '\"';
	      *b++ = *p++;
	      *b++ = '\"';
	    }
	  else
	    { // simply copy
	      *b++ = *p++;
	    }
	}
      size_t l_sep = strspn (p, separators.c_str ());
      //cerr << "quote: l_sep = " << l_sep << " *p = '" << *p << "'" << endl;
      if (l_sep)
	{ // quote separators with "
	  // place quote before all backslashes preceding separators, if any
	  char* r = b;
	  while (r > buffer && *(r - 1) == '\\')
	    r--;
	  if (r == b)
	    *b++ = '\"';
	  else
	    *r = '\"', *b++ = '\\';
	  while (l_sep--)
	    *b++ = *p++;
	  *b++ = '\"';
	}
    }
  *b = '\0';
  result = buffer;
  free (buffer);
  return result;
}

//----------------------------------------------------------
//  Mangle text so that it consists solely of
//  letters, digits, and underscores
//  the first character being not digit
//  (any other characters are replaced with character '_').
//  The out can be used as shell variable name.
//  Return the number of characters replaced with character '_'.
//----------------------------------------------------------
int CmtSystem::mangle (const cmt_string& text, cmt_string& out)
{
  int result (0);
  size_t allocated = text.size ();
  //allocated = 3000000000;
  char* const buffer = (char*) malloc (allocated + 1);
  if (0 == buffer)
    {
      char num[32]; sprintf (num, "%lu", allocated + 1);
      cmt_string msg ("Cannot allocate ");
      msg += num;
      msg += " bytes";
      cmt_string e = errno ? ": " + cmt_string (strerror (errno)) : "";
      CmtMessage::error (msg + e);
      exit (EXIT_FAILURE);
    }
  char* b (buffer);
  const char* t = text.c_str ();
  while (*t)
    {
      if (isalnum (*t) || *t == '_')
	*b++ = *t++;
      else
	{
	  *b++ = '_'; t++;
	  result++;
	}
    }
  *b = '\0';
  if (0 != allocated && isdigit (*buffer))
    {
      *buffer = '_';
      result++;
    }
  out = buffer;
  free (buffer);
  return result;
}

cmt_string CmtSystem::mangle (const cmt_string& text)
{
  cmt_string result;
  mangle (text, result);
  return result;
}

//----------------------------------------------------------
void CmtSystem::compress_path (const cmt_string& dir, cmt_string& new_dir)
{
  new_dir = dir;

  compress_path (new_dir);
}

//----------------------------------------------------------
//
//  We try to detect the aaaa/xxxx/../bbbb patterns which should be 
// equivalent to aaaa/bbbb
//  this therefore consists in removing all /xxxx/../ when 
//     xxxx is different from ".."
//     xxxx is different from "."
//     xxxx does not contain any macro reference
//
// Also replace "/.." with "/". One cannot walk down past the root.
//----------------------------------------------------------
void CmtSystem::compress_path (cmt_string& dir)
{
#ifdef WIN32
  static const char pattern[] = "\\..";
  static const char fs[] = "\\\\";
#else
  static const char pattern[] = "/..";
  static const char fs[] = "//";
#endif

  if (dir.size () == 0) return;

  //
  // We first synchronize to using file_separator() in any case.
  //

  if (file_separator () == '/')
    {
      dir.replace_all ("\\", file_separator ());
    }
  else
    {
      dir.replace_all ("/", file_separator ());
    }

  // Suppress all duplicated file separators
  dir.replace_all (fs, file_separator ());

  for (;;)
    {
      int pos0 (0);
      int pos1;
      int pos2;
      int pos3;

      //pos1 = dir.find (pattern);
      //if (pos1 == cmt_string::npos) break;

      do
	{
	  pos1 = dir.find (pos0, pattern);
	  if (pos1 == cmt_string::npos) break;
	  pos0 = pos1 + 3;
	}
      while (pos0 < dir.size () && dir[pos0] != file_separator ());

      if (pos1 == cmt_string::npos) break;

      //
      // One cannot walk down past the root: "/.." is the same as "/".
      //
#ifdef WIN32
      if (pos1 == 0)
	{
	  dir.erase (pos1, 3);
	  if (dir == "")
	    dir = file_separator ();
	  continue;
	}
      else if (pos1 == 2 && dir[1] == ':')
	{
	  dir.erase (pos1, 3);
	  if (dir.size () == 2)
	    dir += file_separator ();
	  continue;
	}
#else
      if (pos1 == 0)
	{
	  dir.erase (pos1, 3);
	  if (dir == "")
	    dir = file_separator ();
	  continue;
	}
#endif

      //
      // extract "aaaa/xxxx" from "aaaa/xxxx/../bbbb"
      //
      cmt_string p = dir.substr (0, pos1);

      cmt_string dn;
      basename (p, dn);
      if (dn == "..") break;
      if (dn == ".") break;
      if (dn == "") break;
      
      //
      // Is "aaaa/xxxx" only made of "xxxx" ?
      // 
      pos2 = p.find_last_of (file_separator ());
      
      if (pos2 == cmt_string::npos) 
	{
	  // the pattern was xxxx/../bbbb
	  //
	  // so xxxx is [0:pos1-1]
	  //
	  // erase the "xxxx/.." pattern
	  // result will be "/bbbb"
	  // so, need to process a little more
	  //
	  pos3 = p.find ("$");
	  if (pos3 == cmt_string::npos)
	    {
	      dir.erase (0, pos1 + 3);
	      if (dir.size () < 2)
		{
		  dir = ".";
		}
	      else
		{
		  dir.erase (0, 1);
		}
	    }
	  else
	    {
	      break;
	    }
	}
      else
	{
	  //    01234567890123456
	  //    aaaa/xxxx/../bbbb
	  //        2    1   3
	  //
	  // erase the "/xxxx/.." pattern
	  // result will be "aaaa/bbbb"
	  //
	  // Here xxxx is [pos2+1:pos1-1]
	  //

	  pos3 = p.find (pos2, "$");
	  if (pos3 == cmt_string::npos)
	    {
	      dir.erase (pos2, pos1 + 3 - pos2);
#ifdef WIN32
	      if (dir == "")
		{
		  dir = file_separator ();
		}
	      else if (dir.size () == 2 && dir[1] == ':')
		{
		  dir += file_separator ();
		}
#else
	      if (dir == "")
		dir = file_separator ();
#endif
	    }
	  else
	    {
	      break;
	    }
	}
    }

  //if (dir[dir.size () - 1] == file_separator ()) dir.erase (dir.size () - 1);
}

//----------------------------------------------------------
cmt_string CmtSystem::now ()
{
  cmt_string result;

  time_t ltime;
  time (&ltime);
  result = ctime (&ltime);

  result.replace_all ("\n", "");

  return (result);
}

//----------------------------------------------------------
cmt_string CmtSystem::user ()
{
#ifdef _WIN32
  cmt_string result = ::getenv ("USERNAME");
#else
  cmt_string result = ::getenv ("USER");
#endif

  return (result);
}

//----------------------------------------------------------
void CmtSystem::get_cvsroot (cmt_string& cvsroot)
{
  cvsroot = "";

  const char* env = ::getenv ("CVSROOT");
  if (env != 0)
    {
      cvsroot = env;
      return;
    }

#ifdef WIN32
  LONG status;
  HKEY key = 0;

  status = RegOpenKeyEx (HKEY_LOCAL_MACHINE, "Software\\CMT", 
                         0, KEY_READ, &key);
  if (status == ERROR_SUCCESS)
    {
      char temp[256];
      DWORD length = sizeof (temp) - 1;
      DWORD type;

      status = RegQueryValueEx (key, "CVSROOT", 0, &type, 
                                (LPBYTE) temp, &length);
      if (status == ERROR_SUCCESS)
        {
          cvsroot = temp;
          return;
        }
    }
#endif
}

//----------------------------------------------------------
bool CmtSystem::get_home_directory (cmt_string& dir)
{
  bool status = false;

#ifdef WIN32
  const char* homedrive = ::getenv ("HOMEDRIVE");
  const char* homepath = ::getenv ("HOMEPATH");

  if ((homedrive != 0) && (homepath != 0))
    {
      dir = homedrive;
      dir += homepath;
      status = true;
    }

#else
  const char* home_env = ::getenv ("HOME");
  if (home_env != 0)
    {
      dir = home_env;
      status = true;
    }
#endif

  return (status);
}

//----------------------------------------------------------
cmt_string CmtSystem::get_makefile_suffix ()
{
#ifdef WIN32
  return "nmake";
#else
  return "make";
#endif
}

//----------------------------------------------------------
void CmtSystem::close_ostream (FILE *stream, const cmt_string& name)
{
  cmt_string msg ("Cannot write");
  cmt_string n = (name != "") ? " " + name : "";
  if (stream == NULL)
    {
      CmtMessage::error (msg + n);
      exit (EXIT_FAILURE);
    }

  int prev_fail = ferror (stream);
  int fclose_fail = fclose (stream);
//   bool prev_fail = ferror (stream);
//   bool fclose_fail = fclose (stream);

  if (prev_fail || fclose_fail)
    {
      int err = fclose_fail ? errno : 0;
      cmt_string e = err ? ": " + cmt_string (strerror (err)) : "";
      CmtMessage::error (msg + n + e);
      exit (EXIT_FAILURE);
    }
}

//----------------------------------------------------------
void CmtSystem::close_stdout (void)
{
  /* 
   * The idea and, to some extent, implementation of this function
   * were borrowed from the GNU core utilities of the Free Software Foundation
   * http://www.gnu.org/software/coreutils/
   */
  close_ostream (stdout, "stdout");
}

//----------------------------------------------------------
FilePath::FilePath ()
{
  p_name = "";
  l_name = "";
  alternates.resize (0);
}

//----------------------------------------------------------
FilePath::FilePath (const FilePath& other)
{
  set (other);
}

//----------------------------------------------------------
FilePath::FilePath (const cmt_string& other)
{
  set (other);
}

//----------------------------------------------------------
FilePath::FilePath (const char* other)
{
  set (other);
}

//----------------------------------------------------------
FilePath& FilePath::operator = (const FilePath& other)
{
  FilePath& me = *this;

  me.set (other);

  return (me);
}

//----------------------------------------------------------
FilePath& FilePath::operator = (const cmt_string& other)
{
  FilePath& me = *this;

  me.set (other);

  return (me);
}

//----------------------------------------------------------
FilePath& FilePath::operator = (const char* other)
{
  FilePath& me = *this;

  me.set (other);

  return (me);
}

//----------------------------------------------------------
bool FilePath::operator == (const FilePath& other) const
{
  if (other.p_name == p_name) return (true);
  return (false);
}

//----------------------------------------------------------
bool FilePath::operator == (const cmt_string& other) const
{
  if (p_name == other) return (true);
  if (l_name == other) return (true);

  for (int i = 0; i < alternates.size (); i++)
    {
      if (alternates[i] == other) return (true);
    }

  cmt_string here = CmtSystem::pwd ();
  CmtSystem::cd (other);
  cmt_string p = CmtSystem::pwd ();
  CmtSystem::cd (here);

  if (p_name == p) return (true);

  return (false);
}

//----------------------------------------------------------
bool FilePath::operator == (const char* other) const
{
  const FilePath& me = *this;

  const cmt_string o = other;

  return ((me == o));

  return (false);
}

//----------------------------------------------------------
bool FilePath::operator != (const FilePath& other) const
{
  const FilePath& me = *this;

  return (!(me == other));
}

//----------------------------------------------------------
bool FilePath::operator != (const cmt_string& other) const
{
  const FilePath& me = *this;

  return (!(me == other));
}

//----------------------------------------------------------
bool FilePath::operator != (const char* other) const
{
  const FilePath& me = *this;

  return (!(me == other));
}

//----------------------------------------------------------
bool FilePath::cd () const
{
  CmtSystem::cd (l_name);

  return (false);
}

//----------------------------------------------------------
void FilePath::set (const FilePath& other)
{
  p_name = other.p_name;
  l_name = other.l_name;
  alternates = other.alternates;
}

//----------------------------------------------------------
void FilePath::set (const cmt_string& other)
{
  // Skip if no change.

  if (p_name == other) return;
  if (l_name == other) return;

  for (int i = 0; i < alternates.size (); i++)
    {
      if (alternates[i] == other) return;
    }

  // Something changes

  cmt_string here = CmtSystem::pwd ();
  CmtSystem::cd (other);
  cmt_string p = CmtSystem::pwd ();
  CmtSystem::cd (here);

  if (p == p_name)
    {
      // The physical name does not change => we are just adding a new logical

      if (l_name == "")
	{
	  // the logical name was not set => set it
	  l_name = other;
	}
      else
	{
	  // add a new logical name
	  cmt_string& n = alternates.add ();
	  n = other;
	}
    }
  else
    {
      // The physical names differ => we completely reset the object

      p_name = p;
      l_name = other;
      alternates.resize (0);
    }
}

//----------------------------------------------------------
void FilePath::set (const char* other)
{
  const cmt_string o = other;
  set (o);
}

//----------------------------------------------------------
const cmt_string& FilePath::name () const
{
  if (l_name != "") return (l_name);
  else return (p_name);
}

//----------------------------------------------------------
FilePath::operator const cmt_string& () const
{
  if (l_name != "") return (l_name);
  else return (p_name);
}

//----------------------------------------------------------
bool FilePath::in (const FilePath& other) const
{
  const cmt_string& o = other.name ();

  return (in (o));

  return (false);
}

//----------------------------------------------------------
bool FilePath::in (const cmt_string& other) const
{
  if (p_name.find (other) == 0) return (true);
  if (l_name.find (other) == 0) return (true);

  for (int i = 0; i < alternates.size (); i++)
    {
      const cmt_string& a = alternates[i];
      if (a.find (other) == 0) return (true);
    }

  return (false);
}

//----------------------------------------------------------
bool FilePath::in (const char* other) const
{
  const cmt_string o = other;

  return (in (o));
}
