//-----------------------------------------------------------
// Copyright Christian Arnault LAL-Orsay CNRS
// arnault@lal.in2p3.fr
// 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

#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <io.h>
#include <windows.h>

#define stat _stat

#else
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <dirent.h>
#endif

#ifdef __hpux__
#define USE_GETCWD 1
#endif

#ifdef __linux__
#define USE_GETCWD 1
#endif

#ifdef USE_GETCWD
char* getwd (const char* name)
{
  char dir[256];
  getcwd (dir, sizeof (dir));
  strcpy ((char*) name, dir);
  return ((char*) name);
}
#endif

#include "cmt_system.h"
#include "cmt_error.h"

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

#ifdef USE_PWD
  pwd_env = ::getenv ("PWD");  
#endif

  if (pwd_env != 0)
    {
      strcpy (buffer, pwd_env);
    }
  else
    {
      ptr = getcwd (buffer, sizeof (buffer));
    }

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

//--------------------------------------------------
bool CmtSystem::cd (const cmt_string& dir)
{
  static cmt_string new_dir;

  if ((dir.size () == 2) && (dir[1] == ':'))
    {
      new_dir = dir;
      new_dir += file_separator ();
      if (chdir (new_dir.c_str ()) == 0) 
	{
#ifdef USE_PWD
	  new_dir = "PWD=";
	  new_dir += dir;
	  new_dir += file_separator ();
	  putenv (new_dir);
#endif

	  return (true);
	}
      return (false);
    }
  else
    {
      if (chdir (dir.c_str ()) == 0) 
	{
#ifdef USE_PWD
	  new_dir = "PWD=";
	  new_dir += dir;
	  putenv (new_dir);
#endif

	  return (true);
	}
      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::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
    {
      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
    {
      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
    {
      return (false);
    }

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

  if (status == 0)
    {
      if ((file_stat2.st_mode & S_IFDIR) != 0)
        {
          return (false);
        }
    }
  else
    {
      return (false);
    }

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

  static cmt_string s1;
  static cmt_string s2;

  s1.read (name1);
  s2.read (name2);

  return ((s1 == s2));
}

//--------------------------------------------------
//
// 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
    {
      // name1 does not exist
      return (false);
    }

  s1.read (name1);

  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))
        {
          s2.read (name2);
          if (s1 == s2)
            {
              unlink (name1);
              return (true);
            }
        }
    }

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

      unlink (name1);

      return (true);
    }
  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
    {
      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 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)
{
  ::unlink (newname.c_str ());

#ifdef WIN32
  int status = 1;
#else
  int status = ::symlink (oldname.c_str (), newname.c_str ());
#endif

  if (status == 0) return (true);
  return (false);
}

//-------------------------------------------------
bool CmtSystem::remove_file (const cmt_string& name)
{
  if (::unlink (name) != 0)
    {
      cerr << "#CMT> Cannot remove file " << name << endl;
      return (false);
    }

  return (true);
}

//-------------------------------------------------
bool CmtSystem::remove_directory (const cmt_string& name)
{
  //cout << "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))
        {
          if (!remove_directory (file)) return (false);
        }
      else
        {
          if (!remove_file (file)) return (false);
        }
    }
 
  int status = ::rmdir (name);
  if (status != 0)
    {
      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)
            {
                // cerr << "CMT> cannot create directory " << full_path << endl;
              return (false);
            }
#else
          if (::mkdir (full_path.c_str (), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ) != 0)
            {
                // cerr << "CMT> cannot create directory " << full_path << endl;
              return (false);
            }
#endif
        }
    }

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

  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

  //cout << "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;

          //cout << "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

  //cout << "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 = "";

  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;

  name = ::tmpnam (NULL);

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

//----------------------------------------------------------
bool CmtSystem::putenv (const cmt_string& name_value)
{
  int status = ::putenv ((char*) name_value.c_str ());

  if (status == 0) return (true);
  else 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 != "")
      {
	if (!CmtSystem::test_directory (npath))
	  {
	    CmtError::set (CmtError::path_not_found, npath);
	    return;
	  }
	
	m_project = factory.create_project (npath, path_source, m_project);
      }
  }

  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;

  text.read (file_name);

  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 ();

  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;

  const cmt_string env = CmtSystem::getenv ("CMTPATH");

  if (env != "")
    {
      static cmt_string_vector path_vector;
      int i;

      split (env, path_separator (), path_vector);

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

          CMTPathManager::add_cmt_path (path, "${CMTPATH}", factory);
        }
    }

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

  // Suppress the definition of a CMTPATH entry for CMT itself
  //CMTPathManager::add_cmt_path (get_cmt_root (), "default path", factory);

  CMTPathManager::add_cmt_path (cmt_user_context, "CMTUSERCONTEXT", factory);
  CMTPathManager::add_cmt_path (cmt_home, "CMTHOME", factory);
}

//----------------------------------------------------------
int CmtSystem::execute (const cmt_string& command)
{
  //cout << "CmtSystem::execute1> [" << command << "]" << endl;

  return (system (command.c_str ()));
}

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

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

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

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

      return (0);
    }

  return (1);
}

//----------------------------------------------------------
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)
{
  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)
{
  static char* buffer = 0;
  static int allocated = 0;

  bool finished = false;

  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* current_word = buffer;

  while (*current_word != 0)
    {
      size_t prefix_length;
      size_t word_length;

      /*
        while ((*current_word == ' ') ||
        (*current_word == '\t'))
        {
        current_word++;
        }
      */

      // first skip all starting separators.

      prefix_length = strspn (current_word, separators.c_str ());
      if (prefix_length > 0)
        {
          // Move to the first non-separator character

          current_word += prefix_length;
        }

      /*
        Parse the next word.

        It may contain enclosures in quote characters or not.
        Quotes must be identical on both sides of each enclosure.
      */

      char* running_char = current_word;

      word_length = 0;

      for (;;)
        {
          size_t unquoted_length;
          size_t separator_offset;

	  for (int p = 0;;)
	    {
	      unquoted_length = strcspn (running_char + p, "\"\'") + p;
	      if ((unquoted_length > 0) && (running_char[unquoted_length-1] == '\\'))
		{
		  p = unquoted_length + 1;
		}
	      else
		{
		  break;
		}
	    }

          separator_offset = strcspn (running_char, separators.c_str ());

          if (separator_offset <= unquoted_length)
            {
              // no quote in this word -> we are finished for this one.
              running_char += separator_offset;
              break;
            }

          // We have found a quoted enclosure. Move to it.

          running_char += unquoted_length;

          char quote = running_char[0];

          // Remove it.
          {
            char* p = running_char;
            while (p[1] != 0)
              {
                *p = p[1];
                p++;
              }
            *p = 0;
          }

          // Look for the next occurence of this quote.
          {
            char* p = strchr (running_char, quote);
            if (p == 0)
              {
                // Unmatched quote : the rest of the line will be taken as a word...
                running_char += strlen (running_char);
                finished = true;
                break;
              }
            else
              {
                running_char = p;
              }
          }

          // Now we remove the ending quote from the word
          // (by shifting all remaining characters by one place to the left)

          {
            char* p = running_char;
            while (p[1] != 0)
              {
                *p = p[1];
                p++;
              }
            *p = 0;
          }
        }

      word_length = running_char - current_word;

      if (current_word[word_length] == 0)
        {
          finished = true;
        }
      else
        {
          current_word[word_length] = 0;
        }

      /*
        if ((t[0] == '"') ||
        (t[0] == '\'') ||
        (t[0] == ':'))
        {
        char* quote;

        t++;
        quote = strchr (t, sep);
        if (quote != 0) *quote = 0;
        else finished = true;
        }
        else
        {
        int offset;

        offset = strcspn (t, " \t:");
        if ((offset < 0) || (t[offset] == 0)) finished = true;
        if (!finished)
        {
        space = t + offset;
        *space = 0;
        }
        }
      */

      // Store the current word into the vector of strings

      {
        cmt_string& s = strings.add ();
        s = current_word;
      }

      if (finished) break;

      // Move to the next possible word.
      current_word += word_length + 1;
    }
}

//----------------------------------------------------------
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
//
//----------------------------------------------------------
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 pos1;
      int pos2;
      int pos3;

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

        //
        // 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/..
	  //
	  // so xxxx is [0:pos1-1]
	  //
	  pos3 = p.find ("$");
	  if (pos3 == cmt_string::npos)
	    {
	      dir.erase (0, pos1 + 3);
	    }
	  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);
	    }
	  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);
}

