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

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

#include "cmt.h"
#include "cmt_cvs.h"
#include "cmt_awk.h"

/**

    Grep : perform a grep like operation onto a cmt_string

    o All lines of the input string are collected when they contain
      the specified pattern.
    o The input string and the selector pattern are specified in
      the constructor:

         Grep (input_string, pattern)

    o All selected lines are accumulated (appended) into the internal
      variable m_result . 'space' is the separator.

    o The accumulator is retrieved by the result () method.

 */
class Grep : public Awk
{
public:

  void begin ();
  void filter (const cmt_string& line);
  const cmt_string& result () const;

private:
  cmt_string m_result;
};

/**

     Cut : perform a cut-like operation :

     o collect the <field>'th field of every line into the m_result 
       internal variable

     o the field number is given in the constructor and starts at zero.

     o selected fields are accumulated with a space as separator.

 */
class Cut : public Awk
{
public:
  Cut (int field);
  void begin ();
  void filter (const cmt_string& line);
  const cmt_string& result () const;

private:
  cmt_string m_result;
  int m_field;
};

/**

      History : maintains the history of checkout packages during a 
                recursive checkout, so as to avoid double checkouts.

 */
class History
{
public:
  static History& instance ();
  void clear ();
  void install (const cmt_string& line);
  bool is_installed (const cmt_string& line);

private:
  History ();

  cmt_string m_installed;
};

/**

     RecursivePass1 : simply validate use statements in a requirements file
                      and echo those that really need to be handled.

 */
class RecursivePass1 : public Awk
{
public:

  void begin ();
  void filter (const cmt_string& line);
  const cmt_string& result () const;

private:
  cmt_string m_result;
  bool m_first;
};

/**

     RecursivePass2 : after filtering the use statements really perform the
                      checkouts.

 */
class CvsImplementation;
class RecursivePass2 : public Awk
{
public:
  RecursivePass2 (CvsImplementation& cvs);
  void begin ();
  void filter (const cmt_string& line);

private:
  CvsImplementation& m_cvs;
};

/**

   Internal implementation of CVS to CMT operations.
   The Cvs class only provides abstract interface.

 */
class CvsImplementation
{
public:

  CvsImplementation ()
  {
    m_recursive = false;
    m_head = false;
    m_cmtcvstest = false;
    m_verbose = false;
    m_simulation = false;

    m_home_dir = "";
    m_checkout_dir = "";
    m_offset = "";
    m_branch_suffix = "";

    m_last_module = "";
    m_last_cvs_infos = "";
    error_info = "";
    tags_top_info = "";
    tags_info = "";
    cvsversions_top_info = "";
    cvsversions_info = "";
    branches_info = "";
    subpackages_info = "";
  }

  void filter_list (cmt_string& text, const cmt_regexp& exp)
      {
        CmtSystem::cmt_string_vector list;

        CmtSystem::split (text, " ", list);

        int i;

        text = "";

        for (i = 0; i < list.size (); i++)
          {
            const cmt_string& s = list[i];
            if (exp.match (s))
              {
                if (i > 0) text += " ";
                text += s;
              }
          }
      }

    //
    // This method exploits the hook installed into the loginfo script.
    // A communication is setup with a dummy CVS module named .cmtcvsinfos/<module>
    //
    //  At import time, the contents of the file will be used to parameterize
    // the script named cmt_buildcvsinfos2.sh (referenced in the loginfo script)
    //
    //  This script performs a scan in the CVS repository for currently 4 types of
    // information :
    //
    //   all top symbolic tags installed for the module
    //   all symbolic tags installed for the module
    //   all branches available below this module
    //   all subpackages installed below the module.
    //
    //    In principle, only modules corresponding to true CMT packages are considered.
    //      o tags are obtained from the requirements file
    //      o branches are sub-directories which are not themselves packages
    //      o subpackages are sub-directories which are CMT packages
    //        (a subdirectory is always either a branch or a subpackage)
    //
  void show_cvs_infos (const cmt_string& module)
      {
        if (module == "")
          {
            cout << "#CMT> cmt cvs needs a module name" << endl;
            return;
          }

	if (module == m_last_module)
	  {
	    if (m_verbose)
	      {
		cout << "#CMT> cvs infos for module " << module << " already there" << endl;
	      }
	  }
	else
	  {
	    m_last_module = module;
        
	    cmt_string home_dir = CmtSystem::pwd ();

	    //
	    // Activities related with .cmtcvsinfos will occur in a temporary directory
	    //
	    cmt_string tmp_dir = CmtSystem::getenv ("TMPDIR");
	    if (tmp_dir == "")
	      {
		tmp_dir = CmtSystem::file_separator ();
		tmp_dir += "tmp";
	      }
	    
	    if (!CmtSystem::cd (tmp_dir))
	      {
		tmp_dir = home_dir;
	      }
	    
	    tmp_dir += CmtSystem::file_separator ();
	    tmp_dir += "cmtcvs";
	    {
	      cmt_string temp = CmtSystem::get_temporary_name ();
	      CmtSystem::basename (temp, temp);
	      tmp_dir += temp;
	    }
        
	    if (!CmtSystem::test_directory (tmp_dir))
	      {
		if (!CmtSystem::mkdir (tmp_dir))
		  {
		    cout << "#CMT> Cannot create the temporary directory [" 
			 << tmp_dir << "]" << endl;
		    return;
		  }
	      }
	    
	    //trap "rm -rf ${tmp_dir}" 0 1 2 15
	    
	    if (!CmtSystem::cd (tmp_dir))
	      {
		cout << "#CMT> Cannot move to the temporary directory " << tmp_dir << endl;

		if (m_verbose)
		  {
		    cout << "#CMT> now removing tmp_dir " << tmp_dir << " home=" << home_dir << endl;
		  }
		
		CmtSystem::remove_directory (tmp_dir);

		return;
	      }
	    
	    if (m_verbose)
	      {
		cout << "#CMT> cvs infos are now obtained from the temporary directory " << CmtSystem::pwd () << endl;  
	      }
	    
	    /*
	      #
	      # The script associated to such entries is supposed to :
	      #  1) extract the set of <infos> from the ${module}/cmt/requirements file
	      #  2) build an output of the form :
	      #      <infos>=info1 info2 info3 ...
	      #
	      # Currently this script can be found in
	      #
	      # ${CMTROOT}/cmt/cmt_buildcvsinfos2.sh
	      # %CMTROOT%/cmt/cmt_buildcvsinfos.py
	      #
	    */
	    
	    if (!CmtSystem::test_directory (".cmtcvsinfos"))
	      {
		CmtSystem::mkdir (".cmtcvsinfos");
	      }
	    
	    CmtSystem::cd (".cmtcvsinfos");
	    
	    cmt_string cvsroot;
	    
	    CmtSystem::get_cvsroot (cvsroot);
	    
	    cmt_string command;
	    
	    command = "cvs";
	    if (cvsroot != "") 
	      {
		command += " -d ";
		command += cvsroot;
	      }
	    command += " -Q import -m cmt ";
	    if (m_cmtcvstest)
	      {
		command += ".cmtcvsinfos/cmtcvstest";
	      }
	    else
	      {
		command += ".cmtcvsinfos";
	      }
	    command += "/";
	    command += module;
	    command += " CMT v1";
	    
	    if (m_verbose || m_simulation)
	      {
		cout << "#CMT> Executing [" << command << "]" << endl;
	      }

	    m_last_cvs_infos = "";
	    CmtSystem::execute (command, m_last_cvs_infos);

	    if (m_verbose)
	      {
		cout << "#CMT> now removing tmp_dir " << tmp_dir << " home=" << home_dir << endl;
	      }
	    
	    CmtSystem::remove_directory (tmp_dir);

	    CmtSystem::cd (home_dir);
	  }

        /**
           Now retrieve all info fields :

             error=
             tags_top=
             tags=
             branches=
             subpackages=

         */

        Grep grep;

        grep.run (m_last_cvs_infos, "error=");

        if (grep.result () != "")
          {
            error_info = grep.result ();
            error_info.replace ("error=", "");
          }
        else
          {
            error_info = "";
          }

        grep.run (m_last_cvs_infos, "tags_top=");

        if (grep.result () != "")
          {
            tags_top_info = grep.result ();
            tags_top_info.replace ("tags_top=", "");
          }
        else
          {
            tags_top_info = "";
          }

        grep.run (m_last_cvs_infos, "tags=");

        if (grep.result () != "")
          {
            tags_info = grep.result ();
            tags_info.replace ("tags=", "");
          }
        else
          {
            tags_info = "";
          }

        grep.run (m_last_cvs_infos, "cvsversions_top=");

        if (grep.result () != "")
          {
            cvsversions_top_info = grep.result ();
            cvsversions_top_info.replace ("cvsversions_top=", "");
          }
        else
          {
            cvsversions_top_info = "";
          }

        grep.run (m_last_cvs_infos, "cvsversions=");

        if (grep.result () != "")
          {
            cvsversions_info = grep.result ();
            cvsversions_info.replace ("cvsversions=", "");
          }
        else
          {
            cvsversions_info = "";
          }

        cmt_string tag_filter = CmtSystem::getenv ("CMTCVSTAGFILTER");

        if (tag_filter != "")
          {
            cmt_string package;
            CmtSystem::basename (module, package);
            
            cmt_string pattern = "<package>";
            
            tag_filter.replace_all (pattern, package);
            
            cmt_regexp exp (tag_filter);
            
            cmt_string text;
            
            filter_list (tags_top_info, exp);
            filter_list (tags_info, exp);
            filter_list (cvsversions_top_info, exp);
            filter_list (cvsversions_info, exp);
          }

        if (m_cmtcvstest)
          {
            cout << "## tags_top_info=" << tags_top_info << endl;
            cout << "## tags_info=" << tags_info << endl;
            cout << "## cvsversions_top_info=" << cvsversions_top_info << endl;
            cout << "## cvsversions_info=" << cvsversions_info << endl;
          }

        grep.run (m_last_cvs_infos, "branches=");

        if (grep.result () != "")
          {
            branches_info = grep.result ();
            branches_info.replace ("branches=", "");
          }
        else
          {
            branches_info = "";
          }

        grep.run (m_last_cvs_infos, "subpackages=");

        if (grep.result () != "")
          {
            subpackages_info = grep.result ();
            subpackages_info.replace ("subpackages=", "");
          }
        else
          {
            subpackages_info = "";
          }
      }

  void show_cvs_infos (const cmt_string& offset,
		       const cmt_string& module)
  {
    cmt_string full_name;

    if (offset != "") 
      {
	full_name = offset;
	full_name += "/";
	while (full_name.find ("//") != cmt_string::npos)
	  {
	    full_name.replace_all ("//", "/");
	  }
      }

    full_name += module;

    show_cvs_infos (full_name);
  }

    //
    // Resolve all possible "aaa/bbb/../ccc/ddd" patterns into "aaa/ccc/ddd"
    //
  void filter_dir (cmt_string& d)
      {
        while (true)
          {
            int pos = d.find ("/../");
            if (pos == cmt_string::npos) break;
            
            int slash = d.find ("/");
            if (slash < pos)
              {
                  //
                  // xxxxx/yyy/../zzzz -> xxxxx/zzzz
                  // 01234567890123456
                  //       1234567
                  //  pos   = 9
                  //  slash = 5
                  //  length = 9+3-5
                  //
                d.erase (slash + 1, pos + 3 - slash);
              }
            else
              {
                  //
                  // yyy/../zzzz -> zzzz
                  // 01234567890
                  // 1234567
                  //  pos   = 3
                  //  length = 3+1+3
                  //
                d.erase (0, pos + 1 + 3);
              }
          }
      }

  /**
     From a space-separated list of version tags, try to find one tag
     matching a given regular expression.

       o The first matching tag is returned into 'version'
       o Success is returned as function value.

   */
  bool match_version_request (const cmt_string& text, 
                              const cmt_regexp& version_exp,
                              cmt_string& version)
      {
        CmtSystem::cmt_string_vector vs;
      
        CmtSystem::split (text, " \t", vs);

        version = "";

        for (int i = 0; i < vs.size (); i++)
          {
            const cmt_string& vv = vs[i];
        
            if (version_exp.match (vv))
              {
                version = vv;
                return (true);
              }
          }

        return (false);
      }

  bool get_version (const cmt_string& prefix,
                    const cmt_string& package,
                    const cmt_string& version_request,
                    cmt_string& module,
                    cmt_string& version,
                    bool& at_head)
      {
        Grep grep;
        cmt_string topversions;
        cmt_string versions;
        cmt_string requested_version = version_request;
    
        at_head = false;
            
        module = "";

        if (prefix != "")
          {
            module = prefix;
            module += "/"; // This is for CVS only thus we don't use the real separator.
	    while (module.find ("//") != cmt_string::npos)
	      {
		module.replace_all ("//", "/");
	      }
          }

        module += package;

          /**
           *   Try to figure out what is the effective version tag available
           *   for the requested version expressions (which may contain
           *   wild card)
           *
           *     the requested version may either be in the top tags (ie those
           *     corresponding to the same most recent CVS version number before
           *     the HEAD) or not. The returned at_head flag will show this.
           *
           *     then the requested package may either be located in the CVS repository
           *     under the prefix or not, the returned module will contain the effective
           *     location where the requested version has been found.
           */

	if (m_verbose)
	  {
	    cout << "#CMT> requesting cvs infos onto module " << module << endl;
	  }

        show_cvs_infos (module);

        if (error_info != "")
          {
            versions = "";
            cout << "#CMT> Package " << package << " not found in ${CVSROOT}" << endl;
            return (false);
          }

        versions = tags_top_info;

        cmt_string v = version_request;

        if (version_request.find ("*") != cmt_string::npos)
          {
            v.replace_all ("*", ".*");
          }
        else
          {
            v += "$";
          }

        if (m_cmtcvstest) cout << "##    (version expression is " << v << ")" << endl;

        cmt_regexp version_exp (v);
    
        if (!match_version_request (versions, version_exp, version))
          {
            if (m_cmtcvstest) cout << "##    (no match in " << versions << ")" << endl;

              // We try on non-top versions

            versions = tags_info;

            if (!match_version_request (versions, version_exp, version))
              {
                if (m_cmtcvstest) cout << "##    (no match in " << versions << ")" << endl;

                version = requested_version;
                int pos = 0;
                if ((pos = version.find ("*")) != cmt_string::npos)
                  {
                      //
                      //  There was a wild card but the expression does not match
                      // any of the existing tags in CVS.
                      //  Things will be retreived from HEAD but we have to build
                      // a reasonable version tag from the wild card expression.
                      //  If the letter before the * was a digit, then simply remove
                      // the * (v5* -> v5) otherwise add a zero (v5r* -> v5r0)
                      //
                    if (pos > 0)
                      {
                        char letter = version[pos-1];

                        static const cmt_string digits = "0123456789";

                        if (digits.find (letter) == cmt_string::npos)
                          {
                              // "v5r*" -> "v5r0"
                            version.replace ("*", "0");
                          }
                        else
                          {
                              // "v5*" -> "v5"
                            version.replace ("*", "");
                          }
                      }
                    else
                      {
                          // The expression was simply "*" !!!
                        version = "v0";
                      }
                  }
                at_head = true;
              }
            else
              {
                if (m_cmtcvstest) cout << "##    (match in non head " << versions << ")" << endl;

                at_head = false;
              }
          }
        else
          {
            if (m_cmtcvstest) cout << "##    (match in head " << versions << ")" << endl;

            at_head = true;
          }
    
          /**
           *   Here we have at least one version matching the requested expression.
           */
    
        return (true);
      }

  cmt_string build_version_directory (const cmt_string& prefix,
                                      const cmt_string& package,
                                      const cmt_string& version)
      {
        cmt_string dir = m_home_dir;

        if (m_checkout_dir != "")
          {
            dir += CmtSystem::file_separator ();
            dir += m_checkout_dir;
          }

        dir += CmtSystem::file_separator ();
        dir += prefix;
        dir += CmtSystem::file_separator ();
        dir += package;

        if (Cmt::get_current_structuring_style () == with_version_directory)
          {
            dir += CmtSystem::file_separator ();
            dir += version;
          }

	CmtSystem::reduce_file_separators (dir);

        return (dir);
      }

  bool really_checkout_package (const cmt_string& prefix,
                                const cmt_string& package,
                                const cmt_string& version,
                                const cmt_string& module,
                                const cmt_string& basedir,
                                bool at_head)
      {
        cmt_string dir = basedir;
        cmt_string out;

        cout << "# ================= working on package " << package 
             << " version " << version;

        if (at_head) cout << " (At head) ";

        {
          cmt_string full_prefix;

          full_prefix = m_offset;
          full_prefix += prefix;

          cmt_string echo_ppath;
        
          if (prefix != "")
            {
              echo_ppath = " path ";
              echo_ppath += prefix;
            }

          cout << echo_ppath << endl;
        }

        CmtSystem::dirname (dir, dir);

	//if (Cmt::get_current_structuring_style () == with_version_directory)
	{
	  if (m_simulation)
	    {
	      cout << "#CMT> Would mkdir " << dir << endl;
	    }
	  else
	    {
	      if (m_verbose)
		{
		  cout << "#CMT> About to mkdir " << dir << endl;
		}
	      
	      if (!CmtSystem::mkdir (dir))
		{
		  cout << "# Error creating the base directory :" << dir << endl;
		  cout << "#---------------------------------------------------------" << endl;
		  return (false);
		}
	      
	      CmtSystem::cd (dir);
	      
	      /*
	      if (m_verbose)
		{
		  cout << "#CMT> pwd.1=" << CmtSystem::pwd () << endl;
		}
	      */
	    }
	}
	
        cout << "  # get top files " << endl;
            
        cmt_string command = "cvs -Q co -P -l ";
        if (!at_head)
          {
            command += "-r ";
            command += version;
          }

        if (Cmt::get_current_structuring_style () == with_version_directory)
          {
            command += " -d ";
            command += version;
          }
        else
          {
            command += " -d ";
            command += package;
          }

        command += " ";
        command += module;
                
        if (m_cmtcvstest)
          {
            cmt_string cvsroot;
            
            CmtSystem::get_cvsroot (cvsroot);
            
            cout << "## cvsroot=" << cvsroot << " command[" << command << "]" << endl;
          }
        
	int status = 0;
	int retry = 0;

	for (;;)
	  {
	    if (m_verbose || m_simulation)
	      {
		cout << "#CMT> Executing [" << command << "]" << endl;
	      }

	    if (!m_simulation)
	      {
		//status = CmtSystem::execute (command, out);
		status = CmtSystem::execute (command);
	      }
        
	    if (status != 0)
	      {
		retry++;

		cout << "# Error getting package CMT contents: status=" << status << endl;
		cout << "#---------------------------------------------------------" << endl;

		if (retry > 5) exit(0);

	      }
	    else
	      {
		break;
	      }
	  }


          {
	    if (Cmt::get_current_structuring_style () == with_version_directory)
	      {
		if (m_simulation)
		  {
		    cout << "#CMT> Would mkdir " << version << endl;
		  }
		else
		  {
		    if (m_verbose)
		      {
			cout << "#CMT> About to mkdir " << version << endl;
		      }
		    if (!CmtSystem::cd (version))
		      {
			CmtSystem::mkdir (version);
			if (!CmtSystem::cd (version))
			  {
			    cout << "# Error creating the version directory :" << version << endl;
			    cout << "#---------------------------------------------------------" << endl;
			    return (false);
			  }
		      }
		    
		    /*
		    if (m_verbose)
		      {
			cout << "#CMT> pwd.2=" << CmtSystem::pwd () << endl;
		      }
		    */
		  }
		
		dir += CmtSystem::file_separator ();
		dir += version;
	      }
	    else
	      {
		if (m_simulation)
		  {
		    cout << "#CMT> will mkdir " << package << endl;
		  }
		else
		  {
		    if (!CmtSystem::cd (package))
		      {
			if (m_verbose)
			  {
			    cout << "#CMT> About to mkdir " << package << endl;
			  }
			CmtSystem::mkdir (package);
			if (!CmtSystem::cd (package))
			  {
			    cout << "# Error creating the package directory :" << package << endl;
			    cout << "#---------------------------------------------------------" << endl;
			    return (false);
			  }
		      }
		    
		    /*
		    if (m_verbose)
		      {
			cout << "#CMT> pwd.3=" << CmtSystem::pwd () << endl;
		      }
		    */
		  }
		
		dir += CmtSystem::file_separator ();
		dir += package;
	      }
	  }
                
        cmt_string entries_file_name;
        cmt_string text;
        
        cmt_string branches = CmtSystem::getenv ("CMTCVSBRANCHES");
        
        if (branches == "")
          {
            branches = branches_info;
          }
        
        CmtSystem::cmt_string_vector branch_vector;
        
        CmtSystem::split (branches, " \t", branch_vector);
        
        int i;
        
        cout << "  # get branches " << branches << endl;
        
        //command = "(";
        command = "";

        entries_file_name = "CVS";
        entries_file_name += CmtSystem::file_separator ();
        entries_file_name += "Entries";
        
        if (!text.read (entries_file_name))
          {
              // This happens when there were no top files
          }
        
        for (i = 0; i < branch_vector.size (); i++)
          {
            cmt_string& branch = branch_vector[i];

	    if (i > 0)
	      {
		command += CmtSystem::command_separator ();
	      }
            
              //command += "cvs -Q co -P ";
            command += "cvs -Q co ";
            
//            if (branch != "cmt")
//              {
            if (!at_head)
              {
                command += "-r ";
                command += version;
              }
//              }
            
            command += " -d ";
            command += branch;
            command += " ";
            command += module;
            command += "/";    // CVS uses the '/' notation on all platforms!!
            command += branch;
            //command += "\n";
            
            text += "D/";
            text += branch;
            text += "////\n";
          }

	//command += "; echo cmtcvsstatus=$?) 2>&1 ";

        if (m_cmtcvstest)
          {
            cmt_string cvsroot;
            
            CmtSystem::get_cvsroot (cvsroot);
            
            cout << " cvsroot=" << cvsroot << " command[" << command << "]" << endl;
          }
        
	status = 0;
	retry = 0;

	for (;;)
	  {
	    if (m_verbose || m_simulation)
	      {
		cout << "#CMT> Executing [" << command << "]" << endl;
	      }

	    if (!m_simulation)
	      {
		//status = CmtSystem::execute (command, out);
		status = CmtSystem::execute (command);
	      }
        
	    if (status != 0)
	      {
		retry++;

		cout << "# Error getting package contents: status=" << status << endl;
		cout << "#---------------------------------------------------------" << endl;

		if (retry > 5) exit(0);

	      }
	    else
	      {
		break;
	      }
	  }

          {
            if (!CmtSystem::test_directory ("CVS"))
              {
                  /**
                   * The CVS repository had not been created (this is generally
                   * due to the lack of top files)
                   */
                
                if (m_simulation)
		  {
		    cout << "#CMT> Would create the CVS directory" << endl;
		  }
		else
		  {
		    if (m_verbose)
		      {
			cout << "#CMT> About to mkdir " << "CVS" << endl;
		      }
		    CmtSystem::mkdir ("CVS");
		  }

                cmt_string s;
                
                  // Let's create first the CVS/Root file.
                
                CmtSystem::get_cvsroot (s);
                s += "\n";
                
                cmt_string f;
                
                f = "CVS";
                f += CmtSystem::file_separator ();
                f += "Root";
                
		if (m_simulation)
		  {
		    cout << "#CMT> Would fill in the CVS/Root file with " << endl;
		    cout << s << endl;
		  }
		else
		  {
		    if (m_verbose)
		      {
			cout << "#CMT> Fill in the CVS/Root file with " << endl;
			cout << s << endl;
		      }
		    s.write (f);
		  }
                
                  // Now we create the CVS/Repository file
                
                f = "CVS";
                f += CmtSystem::file_separator ();
                f += "Repository";
                
                CmtSystem::get_cvsroot (s);
                if (s[0] == ':')
                  {
                    int pos = s.find (1, ":");
                    s.erase (0, pos+1);
                    pos = s.find (0, ":");
                    s.erase (0, pos+1);
                  }
                s += "/";
                s += module;
                s += "\n";
                
		if (m_simulation)
		  {
		    cout << "#CMT> Would fill in the CVS/Repository file with " << endl;
		    cout << s << endl;
		  }
		else
		  {
		    if (m_verbose)
		      {
			cout << "#CMT> Fill in the CVS/Repository file with " << endl;
			cout << s << endl;
		      }
		    s.write (f);
		  }
              }
            
	    if (m_simulation)
	      {
		cout << "#CMT> Would write the top CVS/Entries file with " << endl;
		cout << text << endl;
	      }
	    else
	      {
		// Now the CVS/Entries is ready to be created.
		if (m_verbose)
		  {
		    cout << "#CMT> Fill in the top CVS/Entries file with " << endl;
		    cout << text << endl;
		  }
		text.write (entries_file_name);
	      }
	  }

        return (true);
      }

  cmt_string find_matching_version (const cmt_string& expression)
      {
        cmt_string result;

          //
          // Here expression takes the form
          //   <some path>/<expression with wild-card>
          //

        cmt_string dir;
        CmtSystem::dirname (expression, dir);
        dir += CmtSystem::file_separator ();

        cmt_string version;
        CmtSystem::basename (expression, version);

        if (version.find ("*") == cmt_string::npos)
          {
              // there is no wildcarding here. A simple test is enough.
            if (CmtSystem::test_directory (expression))
              {
                if (m_cmtcvstest) cout << "## Found direct match with " << version << endl;
                result = version;
              }
            else
              {
                if (m_cmtcvstest) cout << "## Explicit version " << version 
                                       << " has no direct match" << endl;
              }
          }
        else
          {
            version.replace ("*", ".*");

            cmt_regexp exp (version);

            CmtSystem::cmt_string_vector list;

            if (m_cmtcvstest) cout << "## Trying scan_dir dir=" << dir
                                   << " exp=" << version << endl;

            CmtSystem::scan_dir (dir, exp, list);

            if (list.size () > 0)
              {
                result = list[0];

                if (m_cmtcvstest) cout << "## At least one version is matching " << version 
                                       << "(" << list.size () << " matches) " << result << endl;

                CmtSystem::basename (result, result);
              }
            else
              {
                if (m_cmtcvstest) cout << "## There is no version matching " << version << endl;
              }
          }

        return (result);
      }
  
  void checkout_package (const cmt_string& prefix,
                         const cmt_string& package,
                         const cmt_string& specified_version)
      {
	if (m_verbose)
	  {
	    cout << "#CMT> checkout_package> prefix=" << prefix
		 << " package=" << package
		 << " specified_version=" << specified_version
		 << endl;
	  }

        cmt_string version = specified_version;
	cmt_string empty;
	cmt_string full_prefix;

	full_prefix = m_offset;
	full_prefix += prefix;

        cmt_string echo_ppath;
        
        if (prefix != "")
          {
            echo_ppath = " path ";
            echo_ppath += prefix;
          }
        
        if (version == "")
          {
            cout << "# ================= No version specified for package " << package << endl;
            return;
          }

          //
          //  First make an attempt to locate the specified version of
          //  this package "as-it-is" in the work area.
          //   Since 'version' may contain wild-card, it's likely that
          //  we should not simply use CmtSystem::test_directory but
          //  use the wild-card search.
          //

        cmt_string dir;
        
        dir = build_version_directory (prefix, package, version);
              
        if (m_cmtcvstest) 
          cout << "## (testing dir= " << dir << ")" << endl;

	bool recursive = m_recursive;

          /*
        if (m_cmtcvstest)
          {
            cmt_string v = find_matching_version (dir);

            cout << "---> v=" << v << endl;
          }
          */

        cmt_string effective_version = find_matching_version (dir);

        if (effective_version != "")
          {
            version = effective_version;

            dir = build_version_directory (prefix, package, version);

            cout << "# ================= Package " << package 
                 << " version " << version << echo_ppath 
                 << " already installed in " << dir << endl;

	    recursive = false;
          }
        else
          {
            bool at_head = false;
            cmt_string module;

              //
              // get_version attempts to find the most appropriate version
              // tag matching the specification FROM the repository. However,
              // we should take into account situations where some versions have
              // already been checked out, in which case they might be sufficient
              // (or preferred?)
              //

            if (version.find ("*") != cmt_string::npos)
              {
                cout << "# ================= Package " << package 
                     << " version " << version << echo_ppath 
                     << " has wild cards and will not be considered." << endl;
                return;
              }

            if (!get_version (full_prefix, package, version, 
                              module, version, at_head))
              {
                return;
              }

              //cout << " full_prefix=[" << full_prefix << "] module=[" << module << "]" << endl;

            if (m_cmtcvstest) cout << "## after get_version at_head=" << at_head << " m_head=" << m_head << endl;

            if (m_head)
              {
                m_head = false;

                at_head = true;
              }
            else
              {
                at_head = false;
              }

              //
              // Make a second try after having selected a version from all the
              // available versions compatible with the specified version
              //

            dir = build_version_directory (prefix, package, version);

            if (CmtSystem::test_directory (dir))
              {
                cout << "# ================= Package " << package 
                     << " version " << version << echo_ppath 
                     << " already installed." << endl;

                recursive = false;
              }
            else
              {
                  //
                  // Now we can say that we have to perform the real checkout.
                  // 

                if (!really_checkout_package (prefix, package, version, module, dir, at_head))
                  {
                    cout << "# bad return from really_checkout_package" << endl;
                    return;
                  }
              }
          }

          //
          //  Now reach the newly checked out package.
          //

	if (m_simulation)
	  {
	    cout << "#CMT> Package directory not really created " << dir << endl;
	  }
	else
	  {
	    if (!CmtSystem::cd (dir))
	      {
		cout << "#CMT> Package directory not created " << dir << endl;
	      }
	    

	    /*
	    if (m_verbose)
	      {
		cout << "#CMT> pwd.4=" << CmtSystem::pwd () << endl;
	      }
	    */

	    // Check if it is a true CMT package.
	    
	    cmt_string file_name;
	    
	    file_name = "cmt";
	    file_name += CmtSystem::file_separator ();
	    file_name += "requirements";
	    
	    if (CmtSystem::test_file (file_name))
	      {
		dir += CmtSystem::file_separator ();
		dir += "cmt";
		CmtSystem::cd ("cmt");
		
		/*
		if (m_verbose)
		  {
		    cout << "#CMT> pwd.5=" << CmtSystem::pwd () << endl;
		  }
		*/

		if (Cmt::get_current_structuring_style () == without_version_directory)
		  {
		    cmt_string text = version;
		    text += "\n";
		    text.write ("version.cmt");
		  }
	      }
	    else
	      {
		file_name = "mgr";
		file_name += CmtSystem::file_separator ();
		file_name += "requirements";
		
		if (CmtSystem::test_file (file_name))
		  {
		    dir += CmtSystem::file_separator ();
		    dir += "mgr";
		    CmtSystem::cd ("mgr");

		    /*
		    if (m_verbose)
		      {
			cout << "#CMT> pwd.6=" << CmtSystem::pwd () << endl;
		      }
		    */
		  }
		else
		  {
		    cout << "# " << package << " not a CMT package" << endl;
		    return;
		  }
	      }

	    //cout << "#   (recursive is " << recursive << ")" << endl;

	    if (recursive)
	      {
		checkout_from_requirements ("requirements");
	      }
	  }
      }

  /**
   *   We provide a path to a requirements file. From it we read the use 
   *  statements, and we try to checkout the corresponding packages.
   */
  void checkout_from_requirements (const cmt_string& requirements_path)
  {
    static cmt_regexp expression ("^[ \t]*use[ \t]");

    cmt_string text;

    text.read (requirements_path);

    RecursivePass1 p1;
    p1.run (text, expression);

    RecursivePass2 p2 (*this);
    p2.run (p1.result ());
  }

  void tags (const CmtSystem::cmt_string_vector& arguments)
      {
        if (arguments.size () < 1)
          {
            help ();
            return;
          }
        
        if (CmtSystem::getenv ("CVSROOT") == "")
          {
            cout << "# Please set CVSROOT first !" << endl;
            return;
          }
              
        if (CmtSystem::getenv ("CMTCVSTEST") != "")
          {
            m_cmtcvstest = true;
          }
        else
          {
            m_cmtcvstest = false;
          }

        m_offset = CmtSystem::getenv ("CMTCVSOFFSET");
        if (m_offset != "") 
          {
            m_offset += "/";
            m_offset.replace_all ("//", "/");
          }

        bool all = false;
        
        for (int arg = 0; arg < arguments.size (); arg++)
          {
            const cmt_string& option = arguments[arg];

            if (option == "-all")
              {
                all = true;
              }
            else
              {
                show_cvs_infos (CmtSystem::getenv ("CMTCVSOFFSET"), option);

                if (error_info != "")
                  {
                    cout << error_info << endl;
                  }
                else
                  {
                    cmt_string tags;

                    if (all)
                      {
                        tags = cvsversions_top_info;
                        tags += " ";
                        tags += cvsversions_info;
                      }
                    else
                      {
                        tags = tags_top_info;
                        tags += " ";
                        tags += tags_info;
                      }

                    CmtSystem::cmt_string_vector v;

                    CmtSystem::split (tags, " \t", v);
                    for (int i = 0; i < v.size (); i++)
                      {
                        const cmt_string& s = v[i];
                        cout << s << endl;
                      }
                  }
              }
          }
      }

  void branches (const cmt_string& module)
      {
        cmt_string out;

        show_cvs_infos (CmtSystem::getenv ("CMTCVSOFFSET"), module);

        if (error_info != "")
          {
            cout << error_info << endl;
          }
        else
          {
            cout << branches_info << endl;
          }
      }

  void subpackages (const cmt_string& module)
      {
        cmt_string out;

        show_cvs_infos (CmtSystem::getenv ("CMTCVSOFFSET"), module);

        if (error_info != "")
          {
            cout << error_info << endl;
          }
        else
          {
            cout << subpackages_info << endl;
          }
      }

  void help ()
      {
        cout << "> cd <some work area>" << endl;
        cout << "> cmt checkout [modifier ...] <package>" << endl;
        cout << "" << endl;
        cout << "   modifier :" << endl;
        cout << "   -l        Do not process used packages (default)." << endl;
        cout << "   -R        Process used packages recursively." << endl;
        cout << "   -r rev    Check out version tag. (is sticky)" << endl;
        cout << "   -d dir    Check out into dir instead of module name." << endl;
        cout << "   -o offset Offset in the CVS repository" << endl;
        cout << "   -requirements <requirements file path>  Check out packages referenced in this requirements file" << endl;
        cout << "   -n        simulation mode on" << endl;
        cout << "   -v        verbose mode on" << endl;
        cout << "   --help    print this help" << endl;
        cout << "" << endl;
      }

  void do_checkout (const cmt_string& module, const cmt_string& version_tag)
  {
    //CMTPATH=${CMTPATH}:${m_home_dir}; export CMTPATH

    History& h = History::instance ();

    h.clear ();

    if (module == "")
      {
	if (m_verbose)
	  {
	    cout << "#CMT> Missing module name" << endl;
	  }
	return;
      }

    cmt_string prefix;
    cmt_string package;
    cmt_string version;
    
    if (version_tag == "")
      {
        Cut cut (0);
        
        cmt_string m;
        m = m_offset;
        m += module;
        
        show_cvs_infos (m);
        
        if (error_info != "")
          {
            cout << error_info << endl;
            return;
          }

        if (tags_top_info != "") version = tags_top_info;
        else version = tags_info;
        
          //if (CmtSystem::testenv ("CMTTESTAWK")) cout << "version=" << version << endl;

        cut.run (version);
        
        version = cut.result ();

          //if (CmtSystem::testenv ("CMTTESTAWK")) cout << "version=" << version << endl;
      }
    else
      {
        version = version_tag;
      }
    
    CmtSystem::dirname (module, prefix);
    CmtSystem::basename (module, package);
    
    cmt_string top_dir;
        
    top_dir = m_home_dir;
    top_dir += CmtSystem::file_separator ();
    top_dir += m_checkout_dir;
    top_dir += CmtSystem::file_separator ();
    top_dir += prefix;
    top_dir += CmtSystem::file_separator ();
    top_dir += package;
    top_dir += CmtSystem::file_separator ();
    top_dir += version;

    CmtSystem::reduce_file_separators (top_dir);

    if (m_verbose)
      {
	cout << "#CMT> about to checkout package " << package << " version " << version << " into " << top_dir << endl;
      }
    
    checkout_package (prefix, package, version);

    /*
    if (m_verbose)
      {
	cout << "#CMT> after checkout_package pwd.7=" << CmtSystem::pwd () << " top_dir=" << top_dir << endl;
      }
    */

    if (m_simulation) return;

    if (!CmtSystem::cd (top_dir)) return;

    /*
    if (m_verbose)
      {
	cout << "#CMT> pwd.8=" << CmtSystem::pwd () << endl;
      }
    */
    
    cmt_string file_name;
    
    file_name = "cmt";
    file_name += CmtSystem::file_separator ();
    file_name += "requirements";
    
    if (CmtSystem::test_file (file_name))
      {
        top_dir += CmtSystem::file_separator ();
        top_dir += "cmt";
        CmtSystem::cd ("cmt");

	/*
	if (m_verbose)
	  {
	    cout << "#CMT> pwd.9=" << CmtSystem::pwd () << endl;
	  }
	*/
      }
    else
      {
        file_name = "mgr";
        file_name += CmtSystem::file_separator ();
        file_name += "requirements";
        
        if (CmtSystem::test_file (file_name))
          {
            top_dir += CmtSystem::file_separator ();
            top_dir += "mgr";
            CmtSystem::cd ("mgr");

	    /*
	    if (m_verbose)
	      {
		cout << "#CMT> pwd.10=" << CmtSystem::pwd () << endl;
	      }
	    */
          }
        else
          {
            cout << "# " << package << " was not properly checked out and is missing its cmt/requirements file" << endl;
            return;
          }
      }
    
    if (m_verbose)
      {
	cout << "#CMT> package " << package << " has been checked out" << endl;
      }

    if (m_recursive)
      {
	if (m_verbose || m_simulation)
	  {
	    cout << "#CMT> Executing [" << "cmt -quiet broadcast cmt -quiet config" << "]" << endl;
	  }
        
	if (!m_simulation)
	  {
	    CmtSystem::execute ("cmt -quiet broadcast cmt -quiet config");
	  }
      }
    else
      {
	if (m_verbose || m_simulation)
	  {
	    cout << "#CMT> Executing [" << "cmt -quiet config" << "]" << endl;
	  }
        
	if (!m_simulation)
	  {
	    CmtSystem::execute ("cmt -quiet config");
	  }
      }
  }

  void checkout (const CmtSystem::cmt_string_vector& arguments)
      {
        if (arguments.size () < 1)
          {
            help ();
            return;
          }
        
        if (CmtSystem::getenv ("CVSROOT") == "")
          {
            cout << "# Please set CVSROOT first !" << endl;
            return;
          }
              
        if (CmtSystem::getenv ("CMTCVSTEST") != "")
          {
            m_cmtcvstest = true;
          }
        else
          {
            m_cmtcvstest = false;
          }

        m_home_dir = CmtSystem::pwd ();
        m_checkout_dir = "";
        m_offset = "";
        m_branch_suffix = "";

        cmt_string module;
              
        m_recursive = false;

        bool need_version_tag = false;
        cmt_string version_tag;

        bool need_checkout_dir = false;
        bool need_offset = false;
        bool need_requirements_file = false;

	m_simulation = false;
        //m_verbose = true;
        m_verbose = false;

        bool need_branch_suffix = false;

        m_head = true;

        m_offset = CmtSystem::getenv ("CMTCVSOFFSET");
        if (m_offset != "") 
          {
            m_offset += "/";
            m_offset.replace_all ("//", "/");
          }

        for (int arg = 0; arg < arguments.size (); arg++)
          {
            const cmt_string& option = arguments[arg];

            if (need_version_tag)
              {
                need_version_tag = false;

                if (option == "HEAD")
                  {
                    m_head = true;
                  }
                else
                  {
                    version_tag = option;
                  }
              }
            else if (need_checkout_dir)
              {
                need_checkout_dir = false;
                m_checkout_dir = option;
              }
            else if (need_offset)
              {
                need_offset = false;
                m_offset = option;
                m_offset += '/';
                m_offset.replace_all ("//", "/");
              }
            else if (need_branch_suffix)
              {
                need_branch_suffix = false;
                m_branch_suffix = "-";
                m_branch_suffix += option;
              }
            else if (need_requirements_file)
              {
                need_requirements_file = false;
                m_head = false;
		checkout_from_requirements (option);
              }
            else
              {
                if (option == "-R")
                  {
                    m_recursive = true;
                  }
                else if (option == "-l")
                  {
                    m_recursive = false;
                  }
                else if (option == "-r")
                  {
                    need_version_tag = true;
                    m_head = false;
                  }
                else if (option == "-d")
                  {
                    need_checkout_dir = true;
                  }
                else if (option == "-o")
                  {
                    need_offset = true;
                  }
                else if (option == "-n")
                  {
                    m_simulation = true;
                  }
                else if (option == "-v")
                  {
                    m_verbose = true;
                  }
                else if (option == "-branch")
                  {
                    need_branch_suffix = true;
                  }
                else if (option == "-requirements")
                  {
                    need_requirements_file = true;
                  }
                else if (option == "--help")
                  {
                    help ();
                    return;
                  }
                else if (option[0] == '-')
                  {
                    help ();
                    return;
                  }
                else
                  {
                    do_checkout (option, version_tag);
                  }
              }
          }

      }

private:

  bool m_recursive;
  bool m_head;
  bool m_cmtcvstest;
  bool m_verbose;
  bool m_simulation;

  cmt_string m_home_dir;
  cmt_string m_checkout_dir;
  cmt_string m_offset;
  cmt_string m_branch_suffix;

  cmt_string m_last_module;
  cmt_string m_last_cvs_infos;
  cmt_string error_info;
  cmt_string tags_top_info;
  cmt_string tags_info;
  cmt_string cvsversions_top_info;
  cmt_string cvsversions_info;
  cmt_string branches_info;
  cmt_string subpackages_info;
};

//--------------------------------------------------------------------

void Grep::begin ()
{
  m_result = "";
}

void Grep::filter (const cmt_string& line)
{
    //if (CmtSystem::testenv ("CMTTESTAWK")) cout << "Grep::filter" << endl;

  if (m_result != "") m_result += " ";
  m_result += line;
}

const cmt_string& Grep::result () const
{
  return (m_result);
}

//--------------------------------------------------------------------

Cut::Cut (int field)
{
  m_field = field;
}

void Cut::begin ()
{
    //if (CmtSystem::testenv ("CMTTESTAWK")) cout << "Cut::begin" << endl;
  m_result = "";
}

void Cut::filter (const cmt_string& line)
{
    //if (CmtSystem::testenv ("CMTTESTAWK")) cout << "Cut::filter" << endl;

  static CmtSystem::cmt_string_vector words;
  
  CmtSystem::split (line, " \t", words);
  
  if (words.size () <= m_field) return;
  
  if (m_result != "") m_result += " ";
  m_result += words[m_field];
}

const cmt_string& Cut::result () const
{
  return (m_result);
}

//--------------------------------------------------------------------
//--------------------------------------------------------------------

History& History::instance ()
{
  static History h;
  return (h);
}

void History::clear ()
{
  m_installed = "";
}

void History::install (const cmt_string& line)
{
  m_installed += "|";
  m_installed += line;
  m_installed += "|";
}

bool History::is_installed (const cmt_string& line)
{
  if (m_installed.find (line) != cmt_string::npos)
    {
      return (true);
    }
  
  return (false);
}

History::History ()
{
}


//--------------------------------------------------------------------

void RecursivePass1::begin ()
{
  m_first = true;
  m_result = "";
}

void RecursivePass1::filter (const cmt_string& line)
{
    //if (CmtSystem::testenv ("CMTTESTAWK")) cout << "RecursivePass1::filter> " 
    //                                          << "line=[" << line << "]" << endl;

  if (line.find ("use CMT") != cmt_string::npos) return;
  if (line.find ("use cmt") != cmt_string::npos) return;
  
  History& h = History::instance ();
  
  if (h.is_installed (line)) return;
  
  CmtSystem::cmt_string_vector words;
  
  CmtSystem::split (line, " \t", words);

  enum
  {
    need_package,
    need_version,
    need_path,
    no_need
  } state = need_package;

  cmt_string package;
  cmt_string version;
  cmt_string path;

  for (int i = 1; i < words.size (); i++)
    {
      const cmt_string& s = words[i];

      if (s[0] == '-') continue;

      switch (state)
        {
          case need_package:
            package = s;
            state = need_version;
            break;
          case need_version:
            version = s;
            state = need_path;
            break;
          case need_path:
            path = s;
            state = no_need;
            break;
        }
    }
  
  if (version.find ("*") != cmt_string::npos)
    {
        /*
      cout << "# ================= Package " << package 
           << " version " << version << " " << path 
           << " has wild cards and will not be considered." << endl;
        */
      return;
    }

  /**
   *  At the first pass, we simply accumulate the not-yet handled
   *  use statements.
   */
  
  m_result += line;
  m_result += "\n";
  
  if (m_first)
    {
      m_first = false;
      cout << "  # --> now propagate cmt checkout to :" << endl;
    }
  
  cout << "  #     " << package << " " << version << " " << path << endl;
}

const cmt_string& RecursivePass1::result () const
{
  return (m_result);
}

//--------------------------------------------------------------------

RecursivePass2::RecursivePass2 (CvsImplementation& cvs) : m_cvs (cvs)
{
}

void RecursivePass2::begin ()
{
}

void RecursivePass2::filter (const cmt_string& line)
{
    //if (CmtSystem::testenv ("CMTTESTAWK")) cout << "RecursivePass2::filter> " 
    //                                          << "line=[" << line << "]" << endl;

  /**
   *   At the second pass, the lines are really handled. Thus
   *   the lines are stored into m_installed so as to avoid
   *   later on re-installation.
   */
  
  History& h = History::instance ();
  
  if (h.is_installed (line)) return;
  
  h.install (line);
  
  CmtSystem::cmt_string_vector words;
  
  CmtSystem::split (line, " \t", words);

  enum
  {
    need_package,
    need_version,
    need_path,
    no_need
  } state = need_package;

  cmt_string package;
  cmt_string version;
  cmt_string path;

  for (int i = 1; i < words.size (); i++)
    {
      const cmt_string& s = words[i];

      if (s[0] == '-') continue;

      switch (state)
        {
          case need_package:
            package = s;
            state = need_version;
            break;
          case need_version:
            version = s;
            state = need_path;
            break;
          case need_path:
            path = s;
            state = no_need;
            break;
        }
    }

  if (version.find ("*") != cmt_string::npos)
    {
        /*
      cout << "# ================= Package " << package 
           << " version " << version << " " << path 
           << " has wild cards and will not be considered." << endl;
        */
    }
  else
    {
      m_cvs.checkout_package (path, package, version);
    }
}

//--------------------------------------------------------------------

void Cvs::tags (const CmtSystem::cmt_string_vector& arguments)
{
  CvsImplementation cvs;

  cvs.tags (arguments);
}

void Cvs::branches (const cmt_string& module)
{
  CvsImplementation cvs;

  cvs.branches (module);
}

void Cvs::subpackages (const cmt_string& module)
{
  CvsImplementation cvs;

  cvs.subpackages (module);
}

void Cvs::checkout (const CmtSystem::cmt_string_vector& arguments)
{
  CvsImplementation cvs;

  cvs.checkout (arguments);
}

