#include <stdlib.h>
#include <stdio.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:

  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)
      {
        cmt_string out;

        if (module == "")
          {
            cout << "# cmt cvs needs a module name" << endl;
            return;
          }
        
        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 << "# Cannot create the temporary directory [" << tmp_dir << "]" << endl;
                return;
              }
          }
        
          //trap "rm -rf ${tmp_dir}" 0 1 2 15
        
        if (!CmtSystem::cd (tmp_dir))
          {
            cout << "# Cannot move to the temporary directory " << tmp_dir << endl;
            return;
          }
        
          /*
            #
            # 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";
        
        CmtSystem::execute (command, out);

          //cout << "# after cvsinfos out=[" << out << "]" << endl;

        /**
           Now retrieve all info fields :

             error=
             tags_top=
             tags=
             branches=
             subpackages=

         */

        Grep grep;

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

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

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

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

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

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

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

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

        grep.run (out, "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 (out, "branches=");

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

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

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

        CmtSystem::cd (home_dir);
          //cout << "# (removing tmp_dir= " << tmp_dir << " home=" << home_dir<< ")" << endl;
        CmtSystem::remove_directory (tmp_dir);
      }

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

    if (offset != "") 
      {
	full_name = offset;
	full_name += "/";
	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
            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.
           */

          //cout << " in get version : module=[" << module << "]" << endl;

        show_cvs_infos (module);

        if (error_info != "")
          {
            versions = "";
            cout << "# 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;
        dir += CmtSystem::file_separator ();
        dir += version;

        {
          cmt_string fs = CmtSystem::file_separator ();
          cmt_string dfs = fs;
          dfs += fs;

          dir.replace_all (dfs, fs);
        }

        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 (full_prefix != "")
            {
              echo_ppath = " path ";
              echo_ppath += prefix;
            }

          cout << echo_ppath << endl;
        }

        CmtSystem::dirname (dir, dir);

        if (!CmtSystem::mkdir (dir))
          {
            return (false);
          }

        CmtSystem::cd (dir);

        cout << "  # get top files " << endl;
            
        cmt_string command = "cvs -Q co -P -l ";
        if (!at_head)
          {
            command += "-r ";
            command += version;
          }
        command += " -d ";
        command += version;
        command += " ";
        command += module;
                
        if (m_cmtcvstest)
          {
            cmt_string cvsroot;
            
            CmtSystem::get_cvsroot (cvsroot);
            
            cout << "## cvsroot=" << cvsroot << " command[" << command << "]" << endl;
          }
        
        int status = CmtSystem::execute (command, out);
        
          //cout << "-> status = " << status << " out=[" << out << "]" << endl;
        
        if (!CmtSystem::cd (version))
          {
            CmtSystem::mkdir (version);
            if (!CmtSystem::cd (version))
              {
                cout << "# Error creating the version directory :" << version << endl;
                cout << "#---------------------------------------------------------" << endl;
                return (false);
              }
          }
                
        dir += CmtSystem::file_separator ();
        dir += version;
        
        cmt_string 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 = "";
        
        file_name = "CVS";
        file_name += CmtSystem::file_separator ();
        file_name += "Entries";
        
        if (!text.read (file_name))
          {
              // This happens when there were no top files
          }
        
        for (i = 0; i < branch_vector.size (); i++)
          {
            cmt_string& branch = branch_vector[i];
            
              //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";
          }
        
        if (m_cmtcvstest)
          {
            cmt_string cvsroot;
            
            CmtSystem::get_cvsroot (cvsroot);
            
            cout << " cvsroot=" << cvsroot << " command[" << command << "]" << endl;
          }
        
        if (CmtSystem::execute (command, out) == 0)
          {
            cout << "# Error getting package contents:" << endl;
            cout << out << endl;
            cout << "#---------------------------------------------------------" << endl;
          }
        else
          {
            if (!CmtSystem::test_directory ("CVS"))
              {
                  /**
                   * The CVS repository had not been created (this is generally
                   * due to the lack of top files)
                   */
                
                  //cout << "file_name=" << file_name << 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";
                
                  //cout << "f=" << f << " s=[" << 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";
                
                  //cout << "f=" << f << " s=[" << s << "]" << endl;
                s.write (f);
              }
            
              // Now the CVS/Entries is ready to be created.
            text.write (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)
      {
        cmt_string version = specified_version;
	cmt_string empty;
	cmt_string full_prefix;

	full_prefix = m_offset;
	full_prefix += prefix;

        cmt_string echo_ppath;
        
        if (full_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;
          }
          */

          //if (CmtSystem::test_directory (dir))

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

        CmtSystem::cd (dir);

          // 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");
          }
        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");
              }
            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 == "") 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;
    
    checkout_package (prefix, package, version);
    
    //cout << "after checkout_package pwd=[" << CmtSystem::pwd () << "] top_dir=[" << top_dir << "]" << endl;
    
    if (!CmtSystem::cd (top_dir)) return;
    
    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");
      }
    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");
          }
        else
          {
            cout << "# " << package << "not a CMT package" << endl;
            return;
          }
      }
    
    //cout << "end of checkout pwd=[" << CmtSystem::pwd () << "]" << endl;
    
    if (m_recursive)
      {
        CmtSystem::execute ("cmt broadcast cmt config");
      }
    else
      {
        CmtSystem::execute ("cmt 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;

        bool simulation = false;
        bool 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")
                  {
                    simulation = true;
                    verbose = true;
                  }
                else if (option == "-v")
                  {
                    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;

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

  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" << 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;
  
  /**
   *  At the first pass, we simply accumulate the not-yet handled
   *  use statements.
   */
  
  m_result += line;
  m_result += "\n";
  
  CmtSystem::cmt_string_vector words;
  
  CmtSystem::split (line, " \t", words);
  
  cmt_string& package = words[1];
  cmt_string& version = words[2];
  cmt_string& path    = words[3];
  
  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" << 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);
  
  cmt_string& package = words[1];
  cmt_string& version = words[2];
  cmt_string& path    = words[3];
  
  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);
}

