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

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

#include "cmt_project.h"
#include "cmt_database.h"
#include "cmt_system.h"
#include "cmt_awk.h"
#include "cmt_syntax.h"
#include "cmt_tag.h"
#include "cmt_error.h"

class ProjectReader : public Awk
{
public:

  ProjectReader ()
  {
  }
  
  const cmt_string& get_project_name () const
  {
    return (m_project);
  }
  
  void filter (const cmt_string& line)
  {
    CmtSystem::cmt_string_vector words;
    CmtSystem::split (line, " \t", words);
    if (words[0] == "project")
      {
	m_project = words[1];
      }
  }
  
private:
  cmt_string m_project;
};

class ProjectPatcher : public Awk
{
public:

  ProjectPatcher (const cmt_string& p) : m_project (p)
  {
  }

  void commit ()
  {
    m_output.write (Project::get_project_file_name ());
  }

  void filter (const cmt_string& line)
  {
    CmtSystem::cmt_string_vector words;
    CmtSystem::split (line, " \t", words);
    if (words[0] == "project")
      {
	m_output += "project ";
	m_output += m_project;
      }
    else
      {
	m_output += line;
      }

    m_output += "\n";
  }
  
private:
  cmt_string m_output;
  const cmt_string& m_project;
};

IProjectFactory& ProjectFactory::instance ()
{
  static ProjectFactory me;
  
  return (me);
}

void ProjectFactory::reset ()
{
  Project::clear_all ();
}

static bool get_release_from_path (const CmtSystem::cmt_string_vector& items, 
				   const cmt_string& path, 
				   const cmt_string& name, 
				   cmt_string& real_name, 
				   cmt_string& release)
{
  bool result = false;

  release = "";
  real_name = "";

  cmt_string p = path;

  if ((items.size () == 0) && (name == ""))
    {
      // There is no CMTPROJECTPATH and no expected project name.
      // We have no way to find a project structure
      // So we only expect a 2-level structure.

      CmtSystem::basename (p, release);
      CmtSystem::dirname (p, p);
      CmtSystem::basename (p, real_name);
      
      return (false);
    }

  for (;;)
    {
      if (p == "")
	{
	  // Protection: we found no matching project name
	  // and path was outside any CMTPROJECTPATH

	  p = path;

	  CmtSystem::basename (p, release);
	  CmtSystem::dirname (p, p);
	  CmtSystem::basename (p, real_name);

	  return (false);
	}

      cmt_string n;

      CmtSystem::basename (p, n);
      CmtSystem::dirname (p, p);

      if (n == name)
	{
	  real_name = name;
	  result = true;
	  break;
	}

      CmtSystem::basename (p, real_name);

      for (int i = 0; i < items.size (); i++)
	{
	  const cmt_string& item = items[i];
	  if (p == item)
	    {
	      // We have reached an item of CMTPROJECTPATH, no need to continue
	      return (false);
	    }
	}

      if (release == "")
	{
	  release = n;
	}
      else
	{
	  cmt_string r;

	  r = n;
	  r += CmtSystem::file_separator ();
	  r += release;
	  release = r;
	}
    }

  //cerr << "$CMT> GRFP> path=" << path << " name=" << name << " rel=" << release << endl;

  return (result);
}
 

/*
  Every new CMTPATH entry becomes associated with a dedicated PROJECT
  This function will understand this new entry to CMTPATH and understand it:
  - it can correspond to an existing project (ie already declared)
  - it's a new project
     - then it tries to read and parse its project file 
 */
Project* ProjectFactory::create_project (const cmt_string& specified_name,
					 const cmt_string& path,
					 const cmt_string& source,
					 Project* parent)
{
  cmt_string compressed_path = path;
  CmtSystem::compress_path (compressed_path);
  bool specify_name = (specified_name != "");

  if (Cmt::get_debug ())
    {
      cout << "Creating project " << path << " with parent " << ((parent==0)? "0" : parent->get_name ()) << endl;
      Project::show_all ();
    }

  cmt_string sep;
  sep = CmtSystem::path_separator ();

  cmt_string cmtprojectpath = Symbol::get_env_value ("CMTPROJECTPATH"); 
  CmtSystem::cmt_string_vector items;
  CmtSystem::split (cmtprojectpath, sep, items);

  cmt_string here = CmtSystem::pwd ();
  if (!CmtSystem::cd (compressed_path)) return (0);
  cmt_string pwd = CmtSystem::pwd ();

  static Project::ProjectVector& Projects = Project::projects ();

  int i;
  
  for (i = 0; i < Projects.size (); i++)
    {
      Project& p = Projects[i];
      
      if ((p.get_cmtpath () == compressed_path) ||
	  (p.get_cmtpath_pwd () == pwd) ||
	  (specify_name && (p.get_name () == specified_name)))
	{
	  cmt_string r;
	  cmt_string n;

	  get_release_from_path (items, compressed_path, p.get_name (), n, r);

	  if (r != p.get_release ())
	    {
	      cerr << "#CMT> Project " << p.get_name ()
		   << " requested with conflicting releases " << p.get_release () << " and " << r << endl;
	      CmtError::set (CmtError::project_release_conflict, p.get_name ());
	    }
  
	  if (parent != 0)
	    {
	      p.add_parent (parent);
	      parent->add_child (&p);

	      // Since p is a new parent, we should propagate the settings UP.

	      parent->update_strategies_from_children ();
	    }

	  CmtSystem::cd (here);
	  return (&p);
	}
    }


  Project* project = 0;
  Project* cmt = 0;
  
  bool is_current = false;
  
  cmt_string name = specified_name;
  cmt_string project_name;
  cmt_string release;

  //
  // Figure out if this is the current project
  //
  if (here.find (pwd) == 0) is_current = true;

  cmt_string text;

  /*
    Now Figure out the project name from the project file
       or does not specify the project name
   */
  bool has_project_file = false;

  if (CmtSystem::cd ("cmt") && CmtSystem::test_file (Project::get_project_file_name ()))
    {
      has_project_file = true;
      text.read (Project::get_project_file_name ());

      ProjectReader reader;

      reader.run (text);

      project_name = reader.get_project_name ();
    }

  enum
    {
      without_project_file   = 0x01,
      with_project_file      = 0x02,
      without_project_name   = 0x04,
      with_project_name      = 0x08,
      without_specified_name = 0x10,
      with_specified_name    = 0x20,
      names_mismatch         = 0x40,
      names_match            = 0x80
    };

  int status = ((has_project_file) ? with_project_file : without_project_file) |
    ((has_project_file && (project_name != "")) ? with_project_name : without_project_name) |
    ((specify_name) ? with_specified_name : without_specified_name) |
    ((specify_name && has_project_file && (project_name == specified_name)) ? names_match : names_mismatch);

  if (source == "default path")
    {
      name = "CMT";
    }
  else
    {
      cmt_string n;
	  
      switch (status)
	{
	case with_project_file | without_project_name | without_specified_name | names_mismatch:

	  // The project is neither specified from the caller nor from the project file

	  /*
	    if (!Cmt::get_quiet ())
	    {
	    cerr << "#CMT> Warning: project name unspecified in project file." << endl;
	    }
	  */
      
	  get_release_from_path (items, compressed_path, "", name, release);

	  break;
	  
	case with_project_file | without_project_name | with_specified_name | names_mismatch:
	  
	  // The name is only specified from the caller
	  // find this specified name in the path
	  
	  if (get_release_from_path (items, compressed_path, specified_name, name, release))
	    {
	      // The specified name has been found from the path.
	      // We believe in the release spec.
	    }
	  else
	    {
	      // The specified name is not in the path.
	      /*
	      if (!Cmt::get_quiet ())
		{
		  cerr << "#CMT> Warning: specified project name "
		       << specified_name
		       << " from the caller does not match path." << endl;
		}
	      */
	      name = specified_name;
	    }
	  
	  break;
	  
	case with_project_file | with_project_name | with_specified_name | names_match:
	  
	  // We have a double specification: from the caller and from the project file.
	  // And both specifications are consistent.  
	  
	  if (get_release_from_path (items, compressed_path, specified_name, name, release))
	    {
	      // The specified name has been found from the path.
	      // We believe in the release spec.
	    }
	  else
	    {
	      // The specified name is not in the path.
	      /*
	      if (!Cmt::get_quiet ())
		{
		  cerr << "#CMT> Warning: specified project name "
		       << specified_name
		       << " from project file and from caller does not match path." << endl;
		}
	      */
	      name = specified_name;
	    }
	  
	  break;
	  
	case with_project_file | with_project_name | with_specified_name | names_mismatch:
	  
	  // We have a double specification: from the caller and from the project file.
	  // Specifications are inconsistent!!
	  
          /*
	  if (!Cmt::get_quiet ())
	    {
	      cerr << "#CMT> Warning: specified project name "
		   << specified_name
		   << " inconsistent with name "
		   << project_name
		   << " from project file." << endl;
	    }
          */
	  
	  if (get_release_from_path (items, compressed_path, specified_name, n, release))
	    {
	      // name from caller wins.
	    }
	  else if (get_release_from_path (items, compressed_path, project_name, name, release))
	    {
	      // name from project file wins.
	    }
	  else
	    {
	      // The specified name is not in the path.
	      
	      if (!Cmt::get_quiet ())
		{
		  cerr << "#CMT> Warning: none of specified project names "
		       << specified_name
		       << " from graph and "
		       << project_name
		       << " from project file match path." << endl;
		}

	      name = specified_name;
	    }
	  
	  break;
	  
	case with_project_file | with_project_name | without_specified_name | names_mismatch:
	  
	  // Project name is specified in the project file but not from the caller.
	  
	  if (get_release_from_path (items, compressed_path, project_name, name, release))
	    {
	      // The specified name has been found from the path.
	      // We believe in the release spec.

	    }
	  else
	    {
	      // The specified name is not in the path.

	      /*
	      if (!Cmt::get_quiet ())
		{
		  cerr << "#CMT> Warning: specified project name "
		       << project_name
		       << " from project file does not match path." << endl;
		}
	      */

	      name = project_name;
	    }
	  
	  break;
	  
	case without_project_file | without_project_name | without_specified_name | names_mismatch:
	  
	  // The project is not specified from the caller and there is no project file
	  // This corresponds to the backward compatibility
	  // For the moment, assume /name/release/ structure where release is one level only
	  
	  /*
	    if (!Cmt::get_quiet ())
	    {
	    cerr << "#CMT> Warning: project name is not specified "
	    << " (no project file)." << endl;
	    }
	  */
	  
	  CmtSystem::basename (compressed_path, release);
	  CmtSystem::dirname (compressed_path, name);
	  CmtSystem::basename (name, name);

          if (name == "")
            {
              name = release;
              release = "";
            }
	  
	  break;
	  
	case without_project_file | without_project_name | with_specified_name | names_mismatch:
	  
	  // The name is only specified from the caller
	  // find this specified name in the path
	  
	  if (get_release_from_path (items, compressed_path, specified_name, name, release))
	    {
	      // The specified name has been found from the path.
	      // We believe in the release spec.
	    }
	  else
	    {
	      // The specified name is not in the path.
	      /*
	      if (!Cmt::get_quiet ())
		{
		  cerr << "#CMT> Warning: specified project name "
		       << specified_name
		       << " from project graph does not match path." << endl;
		}
	      */
	      name = specified_name;
	    }
	  
	  break;
	}
    }

  if (name == "")
    {
      name = "Project";
    }

  project = Project::add (name, release);
      
  if (parent != 0)
    {
      project->add_parent (parent);
      parent->add_child (project);

      // Since project is a new child, we should propagate the settings UP.

      parent->update_strategies_from_children ();
    }
  else
    {
      // this project has no parent thus it should become the top project.
      // Let's look for all projects without parent.
      // they will become children of this project.

      for (i = 0; i < Projects.size (); i++)
	{
	  Project* p = &(Projects[i]);
	  if (p->get_name () == name) continue;
	  if (!p->has_parents ())
	    {
	      project->add_child (p);
	      p->add_parent (project);
	    }
	}

      // Since project is a new parent, we should upgrade its settings

      project->update_strategies_from_children ();
    }

  if (source == "default path")
    {
      cmt = project;
      is_current = false;
    }

  project->set_cmtpath (compressed_path);
  project->set_cmtpath_pwd (pwd);
  project->set_cmtpath_source (source);

  if (is_current)
    {
      //
      // The current project defines a tag with its name
      //

      Tag* tag;
      
      tag = Tag::add (project->get_name (), PriorityConfig, "PROJECT", 0);
      tag->mark ();
    }

  if (text != "")
    {
      // Last step is to parse the project file

      if (Cmt::get_debug ())
	{
	  cout << "About to parse project file [" << text << "]" << endl;
	}

      SyntaxParser::parse_project_file_text (text, 
					     Project::get_project_file_name (),
					     project);
    }

  CmtSystem::cd (here);

  return (project);
}

/*----------------------------------------------------------*/
/*                                                          */
/*  Operations on Projects                                  */
/*                                                          */
/*----------------------------------------------------------*/

//----------------------------------------------------------
bool Project::create (const cmt_string& name, 
		      const cmt_string& release, 
		      const cmt_string& path)
{
  cout << "------------------------------------------" << endl;
  cout << "Configuring environment for project " << name << " " << release;

  if (path != "")
    {
      cout << " in " << path;
    }

  cout << endl;
  cout << "CMT version " << Cmt::get_cmt_version () << "." << endl;
  cout << "------------------------------------------" << endl;

  if (path != "")
    {
      if (!CmtSystem::mkdir (path))
	{
          cout << "Cannot create the " << path << " directory" << endl;
          return (false);
	}

      if (!CmtSystem::cd (path))
	{
          cout << "Cannot access the " << path << " directory" << endl;
          return (false);
	}
    }

  if (!CmtSystem::mkdir (name))
    {
      cout << "Cannot create the " << name << " directory" << endl;
      return (false);
    }

  if (!CmtSystem::cd (name))
    {
      cout << "Cannot access the " << name << " directory" << endl;
      return (false);
    }

  if (release != "")
    {
      if (!CmtSystem::mkdir (release))
	{
	  cout << "Cannot create the " << release << " directory" << endl;
	  return (false);
	}
      
      if (!CmtSystem::cd (release))
	{
	  cout << "Cannot access the " << release << " directory" << endl;
	  return (false);
	}
    }

  if (!CmtSystem::test_directory ("cmt"))
    {
      if (!CmtSystem::mkdir ("cmt"))
        {
          cout << "Cannot create the cmt directory" << endl;
          return (false);
        }
      else
        {
          cout << "Installing the cmt directory" << endl;
        }
    }

  CmtSystem::cd ("cmt");

  if (!CmtSystem::test_file (get_project_file_name ()))
    {
      cout << "Creating a new project file" << endl;

      ofstream f (get_project_file_name ());
      if (f)
        {
          f << "project " << name << endl;
          f << endl;
          f.close ();
        }
      else
	{
          cout << "Cannot create the project file" << endl;
          return (false);
	}
    }
  else
    {
      cmt_string text;
      text.read (get_project_file_name ());

      ProjectPatcher p (name);

      p.run (text);
      p.commit ();

      cout << "project file already there" << endl;
    }

  return (true);
}

//----------------------------------------------------------
Project* Project::find_by_name (const cmt_string& name)
{
  static ProjectVector& Projects = projects ();

  for (int i = 0; i < Projects.size (); i++)
    {
      Project& p = Projects[i];

      if (p.m_name == name) return (&p);
    }

  return (0);
}

//----------------------------------------------------------
Project* Project::find_by_cmtpath (const cmt_string& cmtpath)
{
  cmt_string compressed_path = cmtpath;
  CmtSystem::compress_path (compressed_path);

  static ProjectVector& Projects = projects ();

  for (int i = 0; i < Projects.size (); i++)
    {
      Project& p = Projects[i];

      if (p.m_cmtpath == compressed_path) return (&p);
      if (p.m_cmtpath_pwd == compressed_path) return (&p);
    }

  return (0);
}

//----------------------------------------------------------
Project* Project::get_current ()
{
  cmt_string here = CmtSystem::pwd ();

  static ProjectVector& Projects = projects ();

  Project* result = 0;

  for (int i = (Projects.size () - 1); i >= 0; i--)
    {
      Project& p = Projects[i];

      if (here.find (p.m_cmtpath_pwd) == 0) 
	{
	  result = &p;
	}

      if (here.find (p.m_cmtpath) == 0) 
	{
	  result = &p;
	}
    }

  return (result);
}

//----------------------------------------------------------
Project* Project::add (const cmt_string& name,
		       const cmt_string& release)
{
  static ProjectVector& Projects = projects ();

  //cout << "Project::add> name=" << name << endl;

  {
    Project* project;

    project = find_by_name (name);
    if (project != 0) 
      {
	if (!Cmt::get_quiet ())
	  {
	    if (release != project->get_release ())
	      {
		cerr << "#CMT> Project " << name << " requested with conflicting releases " << project->get_release () << " and " << release << endl;
		CmtError::set (CmtError::project_release_conflict, name);
	      }
	  }

	// Project objects are recreated here to follow the hierarchy
	// This duplication is needed for properly applying the strategies
	Project& p = Projects.add ();

	p.set_name (name);
	p.set_release (release);
	p.configure ();

	return (&p);

	//return (project);
      }
  }

  Project& project = Projects.add ();
  project.clear ();
  project.set_name (name);
  project.set_release (release);
  project.configure ();

  return (&project);
}

//----------------------------------------------------------
Project::ProjectVector& Project::projects ()
{
  static Database& db = Database::instance ();
  static ProjectVector& Projects = db.projects ();

  return (Projects);
}

/*----------------------------------------------------------*/
void Project::clear_all ()
{
  static ProjectVector& Projects = projects ();

  for (int i = 0; i < Projects.size (); i++)
    {
      Project& project = Projects[i];
      project.clear ();
    }

  Projects.clear ();
}

/*----------------------------------------------------------*/
void Project::show_all ()
{
  static Project::ProjectVector& Projects = Project::projects ();
  
  for (int i = 0; i < Projects.size (); i++)
    {
      Project& p = Projects[i];
      p.m_visited = false;
    }

  Project* p = get_current ();

  if (p == 0)
    {
      if (Projects.size () == 0) return;

      p = &(Projects[0]);
    }

  p->show ();
}

/*----------------------------------------------------------*/
void Project::show_specified_strategies_for_all ()
{
  static ProjectVector& Projects = projects ();

  for (int i = 0; i < Projects.size (); i++)
    {
      const Project& project = Projects[i];
      project.show_specified_strategies ();
    }
}

/*----------------------------------------------------------*/
class VisitorForShowPaths : public IProjectVisitor
{
public:
  VisitorForShowPaths ()
  {
  }

  void pre (Project* p)
  {
    const cmt_string& w = p->get_cmtpath_pwd ();
    const cmt_string& s = p->get_cmtpath_source ();

    if (s == "default path") return;

    if (CmtSystem::test_directory (w))
      {
	cout << "# Add path " << w << " from " << s << endl;
      }
  }

  void in (Project* p)
  {
    const cmt_string& w = p->get_cmtpath_pwd ();
    const cmt_string& s = p->get_cmtpath_source ();

    if (s == "default path") return;

    if (CmtSystem::test_directory (w))
      {
	cout << "# Add path " << w << " from " << s << endl;
      }
  }

  void post (Project* p)
  {
  }
};

/*----------------------------------------------------------*/
void Project::show_paths ()
{
  VisitorForShowPaths visitor;

  start_visit (visitor);
}

//----------------------------------------------------------
const cmt_string& Project::get_project_file_name ()
{
  static const cmt_string name = "project.cmt";

  return (name);
}

//----------------------------------------------------------
void Project::fill_selection (int depth, CmtSystem::cmt_string_vector& path_selections)
{
  static ProjectVector& Projects = projects ();

  for (int i = 0; i < Projects.size (); i++)
    {
      Project& project = Projects[i];

      const cmt_string& p = project.get_cmtpath ();
      const cmt_string& pwd = project.get_cmtpath_pwd ();
      const cmt_string& src = project.get_cmtpath_source ();

      if (src != "default path")
	{
	  if (depth > 0)
	    {
	      cmt_string& s1 = path_selections.add ();
	      s1 = p;
	      cmt_string& s2 = path_selections.add ();
	      s2 = pwd;
	      depth--;

	      if (depth == 0) break;
	    }
	}
    }
}

//----------------------------------------------------------
void Project::broadcast (IProjectAction& action)
{
  static ProjectVector& Projects = projects ();

  for (int i = 0; i < Projects.size (); i++)
    {
      const Project& project = Projects[i];

      if (!action.run (project)) break;
    }
}

//----------------------------------------------------------
void Project::reverse_broadcast (IProjectAction& action)
{
  static ProjectVector& Projects = projects ();

  for (int i = (Projects.size () - 1); i >= 0; i--)
    {
      const Project& project = Projects[i];

      if (!action.run (project)) break;
    }
}

//----------------------------------------------------------
void Project::scan_paths (PathScanner& scanner, PathScanner::actor& a)
{
  static ProjectVector& Projects = projects ();

  int i;

  for (i = 0; i < Projects.size (); i++)
    {
      Project& p  = Projects[i];
      p.m_visited = false;
    }

  for (i = 0; i < Projects.size (); i++)
    {
      const Project& project = Projects[i];
       
      const cmt_string& p = project.m_cmtpath;
      scanner.scan_path (p, a);
    }
}

//----------------------------------------------------------
void Project::scan_paths_for_package (PathScanner& scanner, const cmt_string& name)
{
  static ProjectVector& Projects = projects ();

  for (int i = 0; i < Projects.size (); i++)
    {
      const Project& project = Projects[i];

      const cmt_string& p = project.m_cmtpath;
      scanner.scan_package (p, name);
    }
}

//----------------------------------------------------------
cmt_string Project::find_in_cmt_paths (const cmt_string& path)
{
  const cmt_string pwd = CmtSystem::pwd ();

  static ProjectVector& Projects = projects ();

  for (int i = 0; i < Projects.size (); i++)
    {
      const Project& project = Projects[i];

      const cmt_string& p = project.m_cmtpath;
      const cmt_string& w = project.m_cmtpath_pwd;
      const cmt_string& s = project.m_cmtpath_source;

      if (s == "default path") continue;

      if (CmtSystem::test_directory (p))
	{
	  if (path.find (p) != cmt_string::npos)
	    {
	      return (p);
	    }

	  // To become the current area, a path must correspond to the current package
	  if (path.find (w) != cmt_string::npos)
	    {
	      return (p);
	    }
	}

      if (p == w) continue;

      if (CmtSystem::test_directory (w))
	{
	  if (path.find (w) != cmt_string::npos)
	    {
	      return (w);
	    }
	}
    }

  return ("");
}

//----------------------------------------------------------
void Project::visit (IProjectVisitor& visitor)
{
  if (m_visited) return;
  m_visited = true;

  int i;

  for (i = 0; i < get_children_size (); i++)
    {
      Project* child = get_child (i);

      if (child->visited ()) continue;

      visitor.in (child);
    }

  for (i = 0; i < m_children.size (); i++)
    {
      Project* child = m_children[i];
      child->visit (visitor);
    }
}

//----------------------------------------------------------
void Project::start_visit (IProjectVisitor& visitor)
{
  static Project::ProjectVector& Projects = Project::projects ();
  
  for (int i = 0; i < Projects.size (); i++)
    {
      Project& p = Projects[i];
      p.m_visited = false;
    }

  Project* p = get_current ();

  if (p == 0)
    {
      if (Projects.size () == 0) return;

      p = &(Projects[0]);
    }

  visitor.pre (p);
  p->visit (visitor);
  visitor.post (p);
}

//----------------------------------------------------------
class VisitorForFillCMTPATH : public IProjectVisitor
{
public:
  VisitorForFillCMTPATH (cmt_string& buffer) : m_buffer (buffer)
  {
    buffer = "path CMTPATH \"\" \n";
  }

  void pre (Project* p)
  {
    const cmt_string& w = p->get_cmtpath_pwd ();
    const cmt_string& s = p->get_cmtpath_source ();

    if (s == "default path") return;

    if (CmtSystem::test_directory (w))
      {
	m_buffer += "path_append CMTPATH \"";
	m_buffer += w;
	m_buffer += "\" \n";
      }
  }

  void in (Project* p)
  {
    const cmt_string& w = p->get_cmtpath_pwd ();
    const cmt_string& s = p->get_cmtpath_source ();

    if (s == "default path") return;

    if (CmtSystem::test_directory (w))
      {
	m_buffer += "path_append CMTPATH \"";
	m_buffer += w;
	m_buffer += "\" \n";
      }
  }

  void post (Project* p)
  {
    //cerr << "Buffer = " << m_buffer << endl;
  }

private:
  cmt_string& m_buffer;

};

//----------------------------------------------------------
void Project::fill_cmtpaths (cmt_string& buffer)
{
  /*
    Try to re-create all CMTPATH items from project definitions.
    The goal is to generate CMTPATH even if this EV was not pre-set
    which is the case when CMTPROJECTPATH is only used
   */

  VisitorForFillCMTPATH visitor (buffer);

  start_visit (visitor);
}

//----------------------------------------------------------
Project::Project () : m_name ("")
{
  clear ();
}

//----------------------------------------------------------
const cmt_string& Project::get_name () const
{
  return (m_name);
}

//----------------------------------------------------------
const cmt_string& Project::get_release () const
{
  return (m_release);
}

//----------------------------------------------------------
const cmt_string& Project::get_container () const
{
  return (m_container);
}

//----------------------------------------------------------
const cmt_string& Project::get_container_version () const
{
  return (m_container_version);
}

//----------------------------------------------------------
const cmt_string& Project::get_cmtpath () const
{
  return (m_cmtpath);
}

//----------------------------------------------------------
const cmt_string& Project::get_cmtpath_pwd () const
{
  return (m_cmtpath_pwd);
}

//----------------------------------------------------------
const cmt_string& Project::get_cmtpath_source () const
{
  return (m_cmtpath_source);
}

//----------------------------------------------------------
int Project::get_children_size () const
{
  return (m_children.size ());
}

//----------------------------------------------------------
Project* Project::get_child (int index) const
{
  if (index < 0) return (0);
  if (index >= m_children.size ()) return (0);
  return (m_children[index]);
}

//----------------------------------------------------------
bool Project::visited () const
{
  return (m_visited);
}

//----------------------------------------------------------
void Project::set_name (const cmt_string& name)
{
  m_name = name;
}

//----------------------------------------------------------
void Project::set_release (const cmt_string& release)
{
  m_release = release;
}

//----------------------------------------------------------
void Project::set_container (const cmt_string& container)
{
  m_container = container;
}

//----------------------------------------------------------
void Project::set_container_version (const cmt_string& container_version)
{
  m_container_version = container_version;
}

//----------------------------------------------------------
void Project::set_cmtpath (const cmt_string& path)
{
  m_cmtpath = path;
}

//----------------------------------------------------------
void Project::set_cmtpath_pwd (const cmt_string& path)
{
  m_cmtpath_pwd = path;
}

//----------------------------------------------------------
void Project::set_cmtpath_source (const cmt_string& source)
{
  m_cmtpath_source = source;
}

//----------------------------------------------------------
void Project::clear ()
{
  m_name = "";
  m_release = "";
  m_cmtpath = "";
  m_cmtpath_pwd = "";
  m_cmtpath_source = "";

  m_parents.clear ();
  m_children.clear ();

  m_configured = false;

  m_strategies.clear ();
}

//----------------------------------------------------------
bool Project::has_parents () const
{
  return ((m_parents.size () > 0));
}

//----------------------------------------------------------
bool Project::has_parent (Project* p) const
{
  if (p == 0) return (false);
  if (p == this) return (false);

  const cmt_string& name = p->get_name ();

  int i;

  for (i = 0; i < m_parents.size (); i++)
    {
      const Project* parent = m_parents[i];
      if (parent == 0) continue;

      if (parent->get_name () == name)
	{
	  // registered as a parent
	  return (true);
	}

      if (parent->has_parent (p))
	{
	  // recurse
	  return (true);
	}
    }

  return (false);
}

//----------------------------------------------------------
bool Project::has_child (Project* p) const
{
  if (p == 0) return (false);
  if (p == this) return (false);

  const cmt_string& name = p->get_name ();

  int i;

  for (i = 0; i < m_children.size (); i++)
    {
      const Project* child = m_children[i];
      if (child == 0) continue;

      if (child->get_name () == name)
	{
	  // registered as a child
	  return (true);
	}

      if (child->has_child (p))
	{
	  // recurse
	  return (true);
	}
    }

  return (false);
}

//----------------------------------------------------------
void Project::add_parent (Project* p)
{
  if (p == 0) return;
  if (p == this) return;

  //cerr << "Adding parent " << p->get_name () << " to " << m_name << endl;

  if (has_child (p)) return;
  if (has_parent (p)) return;

  m_parents.push_back (p);
}

//----------------------------------------------------------
void Project::add_child (Project* p)
{
  if (p == 0) return;
  if (p == this) return;

  if (has_child (p)) return;
  if (has_parent (p)) return;

  m_children.push_back (p);
}

//----------------------------------------------------------
void Project::configure ()
{
  if (m_configured) return;
  m_configured = true;

  set_default_strategy ("SetupConfig");
  set_default_strategy ("SetupRoot");
  set_default_strategy ("SetupCleanup");
  set_default_strategy ("BuildPrototypes");
  set_default_strategy ("InstallArea");
  set_default_strategy ("VersionDirectory");
}

/**---------------------------------------------------------
 A container statement is met in the project file
*/
void Project::container_action (const cmt_string& name, const cmt_string& version)
{
  //cerr << "Container action " << name << " " << version << endl;

  set_container (name);
  set_container_version (version);
}

/**---------------------------------------------------------
 A use statement is met in the project file
*/
void Project::use_action (const cmt_string& name, const cmt_string& release)
{
  if (Cmt::get_debug ())
    {
      cout << "Use action " << name << " " << release << endl;
    }

  // A project with its release is specified
  //
  // Is this project already visible?
  // If not: look for it
  //   + get CMTPROJECTPATH
  //   + search from all entries of CMTPROJECTPATH : p(i)/<name>/<release>
  //   + when found, this should become a new CMTPATH entry
  //   +             the new project is then parsed ... etc...

  cmt_string cmtprojectpath = Symbol::get_env_value ("CMTPROJECTPATH");

  cmt_string sep;
  sep = CmtSystem::path_separator ();

  //cerr << "cmtprojectpath = " << cmtprojectpath << endl;
  CmtSystem::cmt_string_vector items;
  CmtSystem::split (cmtprojectpath, sep, items);

  bool found = false;

  for (int i = 0; i < items.size (); i++)
    {
      const cmt_string& item = items[i];
      cmt_string p = item;
      p += CmtSystem::file_separator ();
      p += name;
      if (release != "")
	{
	  p += CmtSystem::file_separator ();
	  p += release;
	}

      if (CmtSystem::test_directory (p))
	{
	  //cerr << "Project directory " << p << " exists " << endl;

	  found = true;

	  IProjectFactory& factory = ProjectFactory::instance ();

	  factory.create_project (name, p, "ProjectPath", this);

	  break;
	}
    }

  if (!found)
    {
      Project* p = Project::find_by_name (name);

      if (p != 0)
	{
	  found = true;

	  p->add_parent (this);
	  add_child (p);

	  update_strategies_from_children ();
	}
    }

  if (!found && (cmtprojectpath != ""))
    {
      cerr << "#CMT> Project " << name << " " << release << " requested by " << m_name << " not found in CMTPROJECTPATH" << endl;
    }
}


//----------------------------------------------------------
Project& Project::operator = (const Project& other)
{
  m_name = other.m_name;
  m_cmtpath = other.m_cmtpath;
  m_cmtpath_pwd = other.m_cmtpath_pwd;
  m_cmtpath_source = other.m_cmtpath_source;

  return (*this);
}

//----------------------------------------------------------
bool Project::operator == (const cmt_string& name) const
{
  return ((m_name == name));
}

//----------------------------------------------------------
bool Project::operator != (const cmt_string& name) const
{
  return ((m_name != name));
}

//----------------------------------------------------------
void Project::show ()
{
  static int level = 0;

  bool is_current = false;

  cmt_string here = CmtSystem::pwd ();

  if (here.find (m_cmtpath) == 0) 
    {
      if (m_cmtpath_source != "default path")
	{
	  is_current = true;
	}
    }

  for (int tab = 0; tab < level; tab++) cout << "  ";
  cout << m_name << " " << m_release << " (in " << m_cmtpath << ")";

  if (is_current) cout << " (current)";

  int i;

  for (i = 0; i < m_parents.size (); i++)
    {
      Project* p = m_parents[i];
      if (p == 0) continue;
      cout << " P=" << p->get_name ();
    }

  for (i = 0; i < m_children.size (); i++)
    {
      Project* p = m_children[i];
      if (p == 0) continue;
      cout << " C=" << p->get_name ();
    }

  cout << endl;

  if (m_visited) return;

  m_visited = true;

  for (i = 0; i < m_children.size (); i++)
    {
      Project* p = m_children[i];
      if (p == 0) continue;
      level++;
      p->show ();
      level--;
    }
}


//----------------------------------------------------------
void Project::show_specified_strategies () const
{
  int i;

  for (i = 0; i < m_strategies.size (); i++)
    {
      const Strategy& s = m_strategies[i];
      if (s.m_specified)
	{
	  const StrategyDef* def = s.m_definition;
 
	  cout << "# Project " << m_name
	       << " sets " << def->m_keyword
	       << " strategy to " << ((s.m_value) ? def->m_on_value : def->m_off_value);

	  if (s.m_context != "")
	    {
	      cout << " (from package " << s.m_context << ")";
	    }

	  cout << endl;
	}
    }
}

//----------------------------------------------------------
bool Project::has_strategy (const StrategyDef* definition) const
{
  int i;

  for (i = 0; i < m_strategies.size (); i++)
    {
      const Strategy& s = m_strategies[i];
      if (s.m_definition == definition)
	{
	  return (true);
	}
    }

  return (false);
}

//----------------------------------------------------------
bool Project::get_strategy (const cmt_string& name) const
{
  static StrategyMgr& mgr = StrategyMgr::instance ();

  StrategyDef* def = mgr.find_strategy (name);
  if (def == 0)
    {
      cerr << "#CMT> strategy " << name << " undefined" << endl;
      return (false);
    }

  return (get_strategy (def));
}

//----------------------------------------------------------
bool Project::is_specified (const StrategyDef* definition) const
{
  int i;

  for (i = 0; i < m_strategies.size (); i++)
    {
      Strategy& s = m_strategies[i];
      if (s.m_definition == definition)
	{
	  // This strategy is applied in this project
	  return (s.m_specified);
	}
    }

  // This strategy is not applied in this project
  return (false);
}

//----------------------------------------------------------
bool Project::get_strategy (const StrategyDef* def) const
{
  int i;

  for (i = 0; i < m_strategies.size (); i++)
    {
      Strategy& s = m_strategies[i];
      if (s.m_definition == def)
	{
	  // This strategy is applied in this project
	  if (s.m_specified)
	    {
	      return (s.m_specified_value);
	    }
	  return (s.m_value);
	}
    }

  // This strategy is not applied in this project
  return (def->m_default_value);
}

//----------------------------------------------------------
void Project::set_default_strategy (const cmt_string& name)
{
  static StrategyMgr& mgr = StrategyMgr::instance ();

  StrategyDef* def = mgr.find_strategy (name);
  if (def == 0)
    {
      cerr << "#CMT> strategy " << name << " undefined" << endl;
      return;
    }

  update_strategy (def, def->m_default_value);
}


//----------------------------------------------------------
void Project::set_strategy (const cmt_string& name, const cmt_string& value, const cmt_string& context)
{
  static StrategyMgr& mgr = StrategyMgr::instance ();

  StrategyDef* def = mgr.find_strategy (name);
  if (def == 0)
    {
      cerr << "#CMT> strategy " << name << " undefined" << endl;
      return;
    }

  bool b_value = false;

  if (value == def->m_on_value)
    {
      b_value = true;
    }
  else if (value == def->m_off_value)
    {
      b_value = false;
    }
  else
    {
      cerr << "#CMT> requested strategy value " << value << " undefined in strategy " << name << endl;
      return;
    }

  set_strategy (def, b_value, context);
}

//----------------------------------------------------------
void Project::set_strategy (StrategyDef* definition, bool b_value, const cmt_string& context)
{
  bool need_strategy = true;

  int i;

  for (i = 0; i < m_strategies.size (); i++)
    {
      Strategy& s = m_strategies[i];
      if (s.m_definition == definition)
	{
	  // This strategy is already applied in this project. Let's change it's value
	  s.set (definition, b_value, get_name ());
	  if (context != "")
	    {
	      if (s.m_context != "") s.m_context += " ";
	      s.m_context += context;
	    }
	  need_strategy = false;
	  break;
	}
    }

  if (need_strategy)
    {
      // This strategy is not yet applied in this project.

      Strategy& s = m_strategies.add ();
      s.clear ();
      s.set (definition, b_value, get_name ());
      s.m_context = context;
    }
  
  for (i = 0; i < m_parents.size (); i++)
    {
      Project* project = m_parents[i];

      project->update_strategy (definition, b_value);
    }
}

/**----------------------------------------------------------
   The strategy value is changed because of indirect influences
   - default strategy at initialization time
   - change in the children
   - change in the children list

   (This is not a specification : see the set_strategy method)
 */
void Project::update_strategy (StrategyDef* definition, bool b_value)
{
  bool need_strategy = true;
  bool specified = false;

  int i;

  for (i = 0; i < m_strategies.size (); i++)
    {
      Strategy& s = m_strategies[i];
      if (s.m_definition == definition)
	{
	  need_strategy = false;

	  if (!s.m_specified)
	    {
	      // This strategy is already applied in this project. Let's change it's value
	      s.update (definition, b_value, get_name ());
	    }
	  else
	    {
	      specified = true;
	    }
	  break;
	}
    }

  if (need_strategy)
    {
      // This strategy is not yet applied in this project.

      Strategy& s = m_strategies.add ();
      s.clear ();
      s.update (definition, b_value, get_name ());
    }

  if (!specified)
    {
      for (i = 0; i < m_parents.size (); i++)
	{
	  Project* project = m_parents[i];

	  project->update_strategy (definition, b_value);
	}
    }
}

/**----------------------------------------------------------
   At least one of the children has changed this strategy
   Or the list of children has changed.
   We need to update the strategy value accordingly
   This will not change the specified value for this strategy
 */
void Project::update_strategy_from_children (StrategyDef* definition)
{
  // If this strategy is specified we don't care what happens from the children

  //cerr << "Updating strategy " << definition->m_name << " from children for project " << m_name << endl;

  int i;

  for (i = 0; i < m_strategies.size (); i++)
    {
      Strategy& s = m_strategies[i];
      if (s.m_definition == definition)
	{
	  // This strategy is applied in this project.

	  if (s.m_specified)
	    {
	      // There will be no impact since the strategy is specified

	      //cerr << "This strategy is specified in this project" << endl;
	      return;
	    }

	  break;
	}
    }

  // The strategy is not specified locally so we will now figure out
  // which strategy has to be considered from the mixture of specifications
  // from all children.

  // Algorithm:
  // - We consider children by pairs
  // - a child that specifies its strategy wins over a child that does not
  // - when the two children have the same level of priority we consider the priority value

  Project* selected = 0;
  bool selected_is_specified = false;
  bool selected_value = definition->m_default_value;

  for (i = 0; i < m_children.size (); i++)
    {
      Project* p = m_children[i];

      //cerr << "Checking strategy for child " << p->get_name () << endl;

      bool is_specified = p->is_specified (definition);
      bool value = p->get_strategy (definition);

      if (selected == 0)
	{
	  selected = p;
	  selected_is_specified = is_specified;
	  selected_value = value;
	  continue;
	}

      if (is_specified == selected_is_specified)
	{
	  if (selected_value != value)
	    {
	      // same level of priority but different values -> we must decide
	      bool priority_value = definition->m_priority_value;
	      if (value == priority_value)
		{
		  selected = p;
		  selected_is_specified = is_specified;
		  selected_value = value;
		}
	    }
	}
      else
	{
	  if (is_specified)
	    {
	      selected = p;
	      selected_is_specified = is_specified;
	      selected_value = value;
	    }
	}
    }

  update_strategy (definition, selected_value);  
}

/**----------------------------------------------------------
   At least one of the children has changed its strategies
   Or the list of children has changed.
   We need to update the strategy values accordingly
   This will not change the specified values
 */
void Project::update_strategies_from_children ()
{
  StrategyDef::StrategyDefs& defs = StrategyMgr::get_definitions ();

  //cerr << "Updating strategies from children for project " << m_name << endl;

  int i;

  for (i = 0; i < defs.size (); i++)
    {
      StrategyDef* def = defs[i];
      
      update_strategy_from_children (def);
    }

  for (i = 0; i < m_parents.size (); i++)
    {
      Project* p = m_parents[i];
      p->update_strategies_from_children ();
    }
}

/**----------------------------------------------------------
   The StrategyMgr singleton
 */
StrategyMgr& StrategyMgr::instance ()
{
  static StrategyMgr me;
  return (me);
}

/**----------------------------------------------------------
   The StrategyMgr constructor
   Here are primarily constructed all strategy definitions
 */
StrategyMgr::StrategyMgr ()
{
  m_defs.clear ();

  StrategyDef* s;

  s = new StrategyDef;
  s->m_keyword = "build";
  s->m_name = "BuildPrototypes";
  s->m_on_value = "prototypes";
  s->m_off_value = "no_prototypes";
  s->m_default_value = true;
  s->m_priority_value = false;

  m_defs.push_back (s);

  s = new StrategyDef;
  s->m_keyword = "build";
  s->m_name = "InstallArea";
  s->m_on_value = "with_installarea";
  s->m_off_value = "without_installarea";
  s->m_default_value = false;
  s->m_priority_value = true;

  m_defs.push_back (s);

  s = new StrategyDef;
  s->m_keyword = "setup";
  s->m_name = "SetupConfig";
  s->m_on_value = "config";
  s->m_off_value = "no_config";
  s->m_default_value = true;
  s->m_priority_value = false;

  m_defs.push_back (s);

  s = new StrategyDef;
  s->m_keyword = "setup";
  s->m_name = "SetupRoot";
  s->m_on_value = "root";
  s->m_off_value = "no_root";
  s->m_default_value = true;
  s->m_priority_value = false;

  m_defs.push_back (s);

  s = new StrategyDef;
  s->m_keyword = "setup";
  s->m_name = "SetupCleanup";
  s->m_on_value = "cleanup";
  s->m_off_value = "no_cleanup";
  s->m_default_value = true;
  s->m_priority_value = false;

  m_defs.push_back (s);

  s = new StrategyDef;
  s->m_keyword = "structure";
  s->m_name = "VersionDirectory";
  s->m_on_value = "with_version_directory";
  s->m_off_value = "without_version_directory";
  if (Cmt::get_current_structuring_style () != without_version_directory)
    {
      s->m_default_value = true;
      s->m_priority_value = false;
    }
  else
    {
      s->m_default_value = false;
      s->m_priority_value = true;
    }

  m_defs.push_back (s);
}

/**----------------------------------------------------------
   Find a strategy definition by its name
 */
StrategyDef* StrategyMgr::find_strategy (const cmt_string& name)
{
  static StrategyMgr& me = instance ();

  int i;

  for (i = 0; i < me.m_defs.size (); i++)
    {
      StrategyDef* def = me.m_defs[i];
      if (def->m_name == name)
	{
	  return (def);
	}
    }

  return (0);
}

/**----------------------------------------------------------
   Retreive the default value defined for a given strategy
 */
bool StrategyMgr::get_default_strategy (const cmt_string& name)
{
  StrategyDef* def = find_strategy (name);
  if (def == 0) return (false);
  return (def->m_default_value);
}

/**----------------------------------------------------------
   Retreive the priority value defined for a given strategy
   This value is used when two children of a project request two conflicting strategy values
 */
bool StrategyMgr::get_priority_strategy (const cmt_string& name)
{
  StrategyDef* def = find_strategy (name);
  if (def == 0) return (false);
  return (def->m_priority_value);
}

/**----------------------------------------------------------
   Return the vector of all existing strategy definitions
 */
StrategyDef::StrategyDefs& StrategyMgr::get_definitions ()
{
  static StrategyMgr& me = instance ();

  return (me.m_defs);
}

//-----------------------------------------------------------
Strategy::Strategy ()
{
  clear ();
}

//-----------------------------------------------------------
void Strategy::clear ()
{
  m_definition = 0;
  m_specified = false;
  m_specified_value = false;
  m_value = false;
  m_on_tag = 0;
  m_off_tag = 0;
}

/**----------------------------------------------------------
   Specify a new value for this strategy.
   This only happens when a strategy statement is met in a project file or in a requirements file.
 */
void Strategy::set (StrategyDef* definition, bool value, const cmt_string& project_name)
{
  //cerr << "Setting strategy " << definition->m_name << " for project " << project_name << " to " << value << endl;

  m_definition = definition;
  m_specified = true;
  m_specified_value = value;

  update (definition, value, project_name);
}

/**----------------------------------------------------------
   Change the effective value for this strategy.
   This has no impact on to the specified value.
   This will adapt the tag settings
 */
void Strategy::update (StrategyDef* definition, bool value, const cmt_string& project_name)
{
  //cerr << "Updating strategy " << definition->m_name << " for project " << project_name << " to " << value << endl;

  m_value = value;
  m_definition = definition;

  cmt_string to_tag_name = project_name;
  cmt_string to_untag_name = project_name;

  to_tag_name += "_";
  to_untag_name += "_";

  if (m_value)
    {
      to_tag_name += m_definition->m_on_value;
      to_untag_name += m_definition->m_off_value;
    }
  else
    {
      to_tag_name += m_definition->m_off_value;
      to_untag_name += m_definition->m_on_value;
    }

  m_on_tag = Tag::find (to_tag_name);
  m_off_tag = Tag::find (to_untag_name);

  if (m_on_tag == 0) 
    {
      m_on_tag = Tag::add (to_tag_name, PriorityConfig, "PROJECT", 0);
      m_off_tag = Tag::add (to_untag_name, PriorityConfig, "PROJECT", 0);

      m_on_tag->add_tag_exclude (m_off_tag);
      m_off_tag->add_tag_exclude (m_on_tag);
    }

  m_off_tag->unmark ();
  m_on_tag->mark ();
}

const cmt_string& StrategyDef::get_default_value () const
{
  if (m_default_value)
    {
      return (m_on_value);
    }
  else
    {
      return (m_off_value);
    }
}

