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

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

class Grep : public Awk
{
public:

  void begin ()
      {
        m_result = "";
      }

  void filter (const cmt_string& line)
      {
        if (m_result != "") m_result += " ";
        m_result += line;
      }

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

private:
  cmt_string m_result;
};

class Cut : public Awk
{
public:
  Cut (int field)
      {
        m_field = field;
      }

  void begin ()
      {
        m_result = "";
      }

  void filter (const cmt_string& line)
      {
        static CmtSystem::cmt_string_vector words;

        CmtSystem::split (line, " ", words);

        if (words.size () <= m_field) return;

        if (m_result != "") m_result += " ";
        m_result += words[m_field];
      }

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

private:
  cmt_string m_result;
  int m_field;
};

class CvsImplementation : public Awk
{
public:

    //
    // 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 3 types of
    // information :
    //
    //   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& info_type, const cmt_string& module, cmt_string& out)
      {
        out = "";
        
        if ((info_type != "tags_top") &&
            (info_type != "tags") &&
            (info_type != "branches") &&
            (info_type != "subpackages"))
          {
            cout << "# cmt cvstags <module>" << endl;
            cout << "# cmt cvsbranches <module>" << endl;
            cout << "# cmt cvssubpackages <module>" << endl;
            return;
          }
        
        if (module == "")
          {
            cout << "# cmt cvs" << info_type << " <module>" << 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}/mgr/requirements file
            #  2) build an output of the form :
            #      <infos>=info1 info2 info3 ...
            #
            # Currently this script can be found in
            #
            # ${CMTROOT}/mgr/buildcvsinfos.sh
            #
          */

        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 .cmtcvsinfos/";
        command += module;
        command += " CMT v1";
        
        CmtSystem::execute (command, out);

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

        Grep grep;

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

        if (grep.result () != "")
          {
            out = grep.result ();
            out.replace ("error=", "");
          }
        else
          {
            cmt_string prefix = info_type;
            prefix += "=";

            grep.run (out, prefix);

            out = grep.result ();

            if (out != "")
              {
                out.replace (prefix.c_str (), "");
              }
            
            if (out == "")
              {
                  //cout << "# Module " << module << " has no " << info_type << endl;
              }
          }

        CmtSystem::cd (home_dir);
        CmtSystem::remove_directory (tmp_dir);
      }

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

  bool match_version_request (const cmt_string& text, 
                              const cmt_regexp& version_exp,
                              cmt_string& version)
      {
        CmtSystem::cmt_string_vector vs;
      
        CmtSystem::split (text, " ", 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;
    
        at_head = false;
            
        module = "";

        if (prefix != "")
          {
            module = prefix;
            module += "/"; // This is for CVS only
          }

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

        show_cvs_infos ("tags_top", module, versions);

        cmt_string v = version_request;

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

        cmt_regexp version_exp (v);
    
        if (!match_version_request (versions, version_exp, version))
          {
              // We try on non-top versions
            show_cvs_infos ("tags", module, versions);
            if (!match_version_request (versions, version_exp, version))
              {
                  // We try without the prefix
                show_cvs_infos ("tags_top", package, versions);
                if (!match_version_request (versions, version_exp, version))
                  {
                      // We try on non-top versions
                    show_cvs_infos ("tags", package, versions);
                    if (!match_version_request (versions, version_exp, version))
                      {
                        cout << "# Package " << package << " not found in ${CVSROOT}" << endl;
                        return (false);
                      }
                  }
                else
                  {
                    at_head = true;
                  }
        
                module = package;
              }
          }
        else
          {
            at_head = true;
          }
    
          /**
           *   Here we have at least one version matching the requested expression.
           */
    
        return (true);
      }
  
  void checkout_package (const cmt_string& prefix,
                         const cmt_string& package,
                         const cmt_string& specified_version)
      {
        cmt_string version = specified_version;

        cmt_string echo_ppath;
        
        if (prefix != "")
          {
            echo_ppath = " path ";
            echo_ppath += prefix;
          }
        
        if (version == "")
          {
            cout << "# ================= No version specified for package " << package << endl;
            return;
          }
        
        cmt_string dir;
        
        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 out;

        if (CmtSystem::test_directory (dir))
          {
            cout << "# ================= Package " << package 
                 << " version " << version << echo_ppath 
                 << " already installed." << endl;
          }
        else
          {
            bool at_head = false;
            cmt_string module;

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

            if (m_head)
              {
                m_head = false;

                at_head = true;
              }
            else
              {
                at_head = false;
              }
            
            cout << "# ================= working on package " << package 
                 << " version " << version;
            if (at_head)
              {
                cout << "(At head) ";
              }
            cout << echo_ppath << endl;
            
            CmtSystem::cd (m_home_dir);
            
            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;

            CmtSystem::mkdir (dir);
            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 += module;

            CmtSystem::execute (command, out);


            cmt_string file_name;
            cmt_string text;
            
            cmt_string branches = CmtSystem::getenv ("CMTCVSBRANCHES");
            
            if (branches == "")
              {
                show_cvs_infos ("branches", module, branches);
              }

            CmtSystem::cmt_string_vector branch_vector;

            CmtSystem::split (branches, " ", branch_vector);

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

                cout << "  # do branch " << branch << endl;
            
                cmt_string command = "cvs -Q co -P ";
                if (!at_head)
                  {
                    command += "-r ";
                    command += version;
                  }
                command += " -d ";
                command += branch;
                command += " ";
                command += module;
                command += "/"; // This is for CVS only
                command += branch;

                if (CmtSystem::execute (command, out) == 0)
                  {
                    file_name = "CVS";
                    file_name += CmtSystem::file_separator ();
                    file_name += "Entries";
                    text.read (file_name);
                    text += "D/";
                    text += branch;
                    text += "////\n";
                    text.write (file_name);
                  }
              }
          }

        CmtSystem::cd (dir);

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

        if (m_recursive)
          {
            cmt_string uses;

            cmt_string text;

            CmtSystem::execute ("cmt show uses", text);

            m_prefix = prefix;

            m_show = true;
            run (text, "# use");
            m_show = false;
            run (text, "# use");
          }
      }

  void begin ()
      {
        m_first_show = true;
      }

  bool is_installed (const cmt_string& line)
      {
        for (int i = 0; i < m_installed.size (); i++)
          {
            cmt_string& installed = m_installed[i];

            if (installed == line) return (true);
          }

        return (false);
      }

  void filter (const cmt_string& line)
      {
        if (line.find ("#use CMT") != cmt_string::npos) return;
  
        CmtSystem::cmt_string_vector words;

        if (!m_show)
          {
            if (is_installed (line)) return;

            cmt_string& to_install = m_installed.add ();
            to_install = line;
          }

        CmtSystem::split (line, " ", words);
  
        cmt_string& package = words[2];
        cmt_string& version = words[3];
        cmt_string& path    = words[4];

        if (m_show)
          {
            if (m_first_show)
              {
                m_first_show = false;
                cout << "  # --> now propagate cmt checkout to :" << endl;
              }

            cout << "  #     " << package << " " << version << " " << path << endl;
          }
        else
          {
            checkout_package (path, package, version);
          }
      }

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

        show_cvs_infos ("tags_top", module, out);
        if (out != "") cout << out << " ";
        show_cvs_infos ("tags", module, out);
        if (out != "") cout << out << " ";
        cout << endl;
      }

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

        show_cvs_infos ("branches", module, out);

        cout << out << endl;
      }

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

        show_cvs_infos ("subpackages", module, out);

        cout << out << endl;
      }

  void checkout (const CmtSystem::cmt_string_vector& arguments)
      {
        if (arguments.size () < 1)
          {
            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 << "   -n      simulation mode on" << endl;
            cout << "   -v      verbose mode on" << endl;
            cout << "" << endl;
            return;
          }
        
        if (CmtSystem::getenv ("CVSROOT") == "")
          {
            cout << "# Please set CVSROOT first !" << endl;
            return;
          }
              
        m_home_dir = CmtSystem::pwd ();
        m_checkout_dir = "";
        m_prefix = "";

        cmt_string module;
              
        m_recursive = false;

        bool need_version_tag = false;
        cmt_string version_tag;

        bool need_checkout_dir = false;

        bool simulation = false;
        bool verbose = false;

        m_head = true;

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

            if (need_version_tag)
              {
                need_version_tag = false;
                version_tag = option;
              }
            else if (need_checkout_dir)
              {
                need_checkout_dir = false;
                m_checkout_dir = 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 == "-n")
                  {
                    simulation = true;
                    verbose = true;
                  }
                else if (option == "-v")
                  {
                    verbose = true;
                  }
                else
                  {
                    module = option;
                  }
              }
          }

          //CMTPATH=${CMTPATH}:${m_home_dir}; export CMTPATH
        m_installed.clear ();

        if (module == "") return;

        cmt_string prefix;
        cmt_string package;
        cmt_string version;
        
        if (version_tag == "")
          {
            Cut cut (0);
            
            show_cvs_infos ("tags_top", module, version);
            if (version == "") show_cvs_infos ("tags", module, version);

            cut.run (version);
            
            version = cut.result ();
          }
        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 += package;
        top_dir += CmtSystem::file_separator ();
        top_dir += version;
        
        checkout_package (prefix, package, version);
        
        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;
              }
          }

        if (m_recursive)
          {
            CmtSystem::execute ("cmt broadcast cmt config");
          }
        else
          {
            CmtSystem::execute ("cmt config");
          }
      }

private:

  bool m_recursive;
  bool m_show;
  bool m_first_show;
  bool m_head;

  cmt_string m_home_dir;
  cmt_string m_prefix;
  cmt_string m_checkout_dir;
  CmtSystem::cmt_string_vector m_installed;
};

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

  cvs.tags (module);
}

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

