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

#include <errno.h>
#include <stdio.h>

#ifndef WIN32
#include <unistd.h>
#endif

#include "cmt_generator.h"
#include "cmt_use.h"
#include "cmt_symbol.h"

#include "cmt_generators.h"

//------------------------------------------------------------------------
void SourceFile::set (const cmt_string name, Language& language, const cmt_string output)
{
  m_name = name;
  m_language = &language;
  m_output = output;
  
  CmtSystem::reduce_file_separators (m_name);
}

cmt_string SourceFile::name () const
{
  return (m_name);
}

Language& SourceFile::language () const
{
  return (*m_language);
}

cmt_string SourceFile::output () const
{
  return (m_output);
}
//------------------------------------------------------------------------

//--------------------------------------------------
CmtGenerator::CmtGenerator ()
{
  m_CONSTITUENT.set ("CONSTITUENT");
  m_LINKMACRO.set ("LINKMACRO");
  m_DOCPATH.set ("DOCPATH");
  m_PACKAGEPATH.set ("PACKAGEPATH");
  m_PACKAGEPREFIX.set ("PACKAGEPREFIX");
  m_PACKAGE.set ("PACKAGE");
  m_VERSION.set ("VERSION");
  m_MGRSTYLE.set ("MGRSTYLE");
  m_TITLE.set ("TITLE");
  m_GROUP.set ("GROUP");
  m_CONSTITUENT.set ("CONSTITUENT");
  m_CONSTITUENTSUFFIX.set ("CONSTITUENTSUFFIX");
  m_LIBRARYSUFFIX.set ("LIBRARYSUFFIX");
  m_USER.set ("USER");
  m_DATE.set ("DATE");
  m_PROTOTARGET.set ("PROTOTARGET");
  m_OBJS.set ("OBJS");
  m_CLASSES.set ("CLASSES");
  m_PROTOSTAMPS.set ("PROTOSTAMPS");
  m_NAME.set ("NAME");
  m_FILEPATH.set ("FILEPATH");
  m_FILESUFFIX.set ("FILESUFFIX");
  m_SUFFIX.set ("SUFFIX");
  m_FILENAME.set ("FILENAME");
  m_LINKMACRO.set ("LINKMACRO");
  m_LINE.set ("LINE");
  m_ADDINCLUDE.set ("ADDINCLUDE");
  m_FULLNAME.set ("FULLNAME");
  m_DIRNAME.set ("DIRNAME");
  m_OUTPUTNAME.set ("OUTPUTNAME");
  m_ALLOS9SOURCES.set ("ALLOS9SOURCES");
  m_NODEBUGUSELINKOPTS.set ("NODEBUGUSELINKOPTS");
  m_DEBUGUSELINKOPTS.set ("DEBUGUSELINKOPTS");
  m_USEINCLUDES.set ("USEINCLUDES");
  m_HASTARGETTAG.set ("HASTARGETTAG");
}

//--------------------------------------------------
void CmtGenerator::reset ()
{
  m_CONSTITUENT = "";
  m_LINKMACRO = "";
  m_DOCPATH = "";
  m_PACKAGEPATH = "";
  m_PACKAGEPREFIX = "";
  m_PACKAGE = "";
  m_VERSION = "";
  m_MGRSTYLE = "";
  m_TITLE = "";
  m_GROUP = "";
  m_CONSTITUENTSUFFIX = "";
  m_LIBRARYSUFFIX = "";
  m_USER = "";
  m_DATE = "";
  m_PROTOTARGET = "";
  m_OBJS = "";
  m_CLASSES = "";
  m_PROTOSTAMPS = "";
  m_NAME = "";
  m_FILEPATH = "";
  m_FILESUFFIX = "";
  m_SUFFIX = "";
  m_FILENAME = "";
  m_LINE = "";
  m_ADDINCLUDE = "";
  m_FULLNAME = "";
  m_OUTPUTNAME = "";
  m_ALLOS9SOURCES = "";
  m_NODEBUGUSELINKOPTS = "";
  m_DEBUGUSELINKOPTS = "";
  m_USEINCLUDES = "";
  m_HASTARGETTAG = "";
  m_PACKINCLUDES = "";
  m_PACKOS9      = false;
  m_GENERATOR    = "";

  is_library     = false;
  is_application = false;
  srcdir       = "";
  docdir       = "";
  cmtdir       = "";
  incdir       = "";
  src          = "$(src)";
  doc          = "$(doc)";
  inc          = "$(inc)";
  mgr          = "$(mgr)";
  cmt          = "$(cmt)";
  protos       = "";
  protonames   = "";
  os9sources   = "";

  m_source_files.clear ();

  Language::setup_all_fragments ();

  CmtSystem::cd (Cmt::get_current_dir ());

  cmt_string branch = CmtSystem::current_branch ();

  if ((branch == "mgr") || (branch == "cmt"))
    {
      if (CmtSystem::test_directory ("../src"))
        {
          srcdir = "..";
	  srcdir += CmtSystem::file_separator ();
          srcdir += "src";
	  srcdir += CmtSystem::file_separator ();
        }
      else
        {
          srcdir = "";
        }

      if (CmtSystem::test_directory ("../doc"))
        {
          docdir = "..";
	  docdir += CmtSystem::file_separator ();
          docdir += "doc";
	  docdir += CmtSystem::file_separator ();
        }
      else
        {
          docdir = "";
        }

      if (CmtSystem::test_directory ("../cmt"))
        {
          cmtdir = "..";
	  cmtdir += CmtSystem::file_separator ();
          cmtdir += "cmt";
	  cmtdir += CmtSystem::file_separator ();
        }
      else if (CmtSystem::test_directory ("../mgr"))
        {
          cmtdir = "..";
	  cmtdir += CmtSystem::file_separator ();
          cmtdir += "mgr";
	  cmtdir += CmtSystem::file_separator ();
        }
      else
        {
          cmtdir = CmtSystem::pwd ();
          cmtdir += CmtSystem::file_separator ();
        }

      if (CmtSystem::test_directory ("../src"))
        {
          incdir = "..";
	  incdir += CmtSystem::file_separator ();
          incdir += "src";
	  incdir += CmtSystem::file_separator ();
        }
      else
        {
          incdir = "";
        }
    }
  else
    {
      srcdir = ".";
      srcdir += CmtSystem::file_separator ();
      docdir = ".";
      docdir += CmtSystem::file_separator ();
      cmtdir = CmtSystem::pwd ();
      cmtdir += CmtSystem::file_separator ();
      incdir = ".";
      incdir += CmtSystem::file_separator ();
    }
}

//--------------------------------------------------
bool CmtGenerator::prepare_output (const cmt_string& package,
                                   const Constituent& constituent)
{
  m_PACKAGE = package;
  m_CONSTITUENT = constituent.name;
  m_CONSTITUENTSUFFIX = constituent.suffix;

  m_PACKOS9 = constituent.need_OS9;

  m_output_file_name = cmtdir + m_CONSTITUENT + ".";

  if (Cmt::build_nmake ())
    {
      m_output_file_name += "nmake";
    }
  else
    {
      m_output_file_name += "make";
    }
  
  m_output_file_name += "new";


  m_output_file = fopen (m_output_file_name.c_str (), "wb");
  if (m_output_file != NULL)
    {
      return (true);
    }
  else
    {
      return (false);
    }
}

//--------------------------------------------------
void CmtGenerator::check (const cmt_string& name)
{
  static cmt_string old;
  static cmt_string backup;

  old = name;

  int pos = old.find_last_of ("new");
  old.erase (pos);

  if (!CmtSystem::compare_files (old, name))
    {
      backup = old;
      backup += "sav";

      unlink (backup.c_str ());
      rename (old.c_str (), backup.c_str ());
      rename (name.c_str (), old.c_str ());
    }
  else
    {
      unlink (name);
    }
}

//--------------------------------------------------
void CmtGenerator::commit (const cmt_string& name)
{
  static cmt_string old;
  static cmt_string backup;

  old = name;

  int pos = old.find_last_of ("new");
  old.erase (pos);

  if (CmtSystem::test_file (old))
    {
      backup = old;
      backup += "sav";

      unlink (backup.c_str ());
      rename (old.c_str (), backup.c_str ());
    }

  rename (name.c_str (), old.c_str ());
}

//--------------------------------------------------
void CmtGenerator::terminate ()
{
  fclose (m_output_file);

    //--- Complete the operation --------------

  commit (m_output_file_name);
}

//--------------------------------------------------
void CmtGenerator::fill_outputs ()
{
  bool first = true;

  m_OBJS = "";

  for (int i = 0; i < m_source_files.size (); i++)
    {
      const SourceFile& file = m_source_files[i];
      const cmt_string output = file.output ();

      if (output != "")
        {
          if (first)
            {
              first = false;
            }
          else
            {
              m_OBJS += " ";
            }

          m_OBJS += output;
        }

      if (Cmt::get_debug ())
        {
          cout << "CmtGenerator::fill_outputs> output=" << output << " OBJS=" << m_OBJS << endl;
        }

    }

  if (Cmt::get_debug ())
    {
      cout << "CmtGenerator::fill_outputs> OBJS=" << m_OBJS << endl;
    }

}

//--------------------------------------------------
void CmtGenerator::prepare_use_context ()
{
  cmt_string path;
  cmt_string substitution;

  Use* use = &Use::current ();

  m_deps_builder.clear ();

  if (use->include_path != "none")
    {
      if (use->include_path == "")
	{
	  m_deps_builder.add (incdir, "$(src)");
	}
      else
	{
	  substitution = use->include_path;
	  
	  path = substitution;
	  Symbol::expand (path);
	  
	  CmtSystem::reduce_file_separators (path);

          m_deps_builder.add (path, substitution);
	}
    }

  m_deps_builder.add_includes (*use);

  Use::UsePtrVector& uses = Use::get_ordered_uses ();

  if (uses.size () > 0)
    {
      int number;

      for (number = 0; number < uses.size (); number++)
        {
          use = uses[number];
          if (use->discarded) continue;

          if (use->real_path != "")
            {
	      if (use->include_path != "none")
		{
		  if (use->include_path == "")
		    {
		      use->get_full_path (path);
                      path += CmtSystem::file_separator ();
		      path += "src";

		      substitution = "$(";
		      substitution += use->prefix;
		      substitution += "ROOT)";
		      substitution += CmtSystem::file_separator ();
		      substitution += "src";
		      substitution += CmtSystem::file_separator ();
		    }
		  else
		    {
		      substitution = use->include_path;

		      path = substitution;
		      Symbol::expand (path);

                      CmtSystem::reduce_file_separators (path);
		    }

		  m_deps_builder.add (path, substitution);
		}

              m_deps_builder.add_includes (*use);
            }
        }
    }
}

//--------------------------------------------------
void CmtGenerator::filter_path (cmt_string& text)
{
  CmtSystem::compress_path (text);

  text.replace_all ("./../src/../", "../");
  text.replace_all ("./../src/", "$(src)");

  text.replace_all (".\\..\\src\\..\\", "..\\");
  text.replace_all (".\\..\\src\\", "$(src)");

  text.replace_all ("../src/../", "../");
  text.replace_all ("../src/", "$(src)");

  text.replace_all ("..\\src\\..\\", "..\\");
  text.replace_all ("..\\src\\", "$(src)");

  text.replace_all ("../doc/../", "../");
  text.replace_all ("../doc/", "$(doc)");

  text.replace_all ("..\\doc\\..\\", "..\\");
  text.replace_all ("..\\doc\\", "$(doc)");

  text.replace_all ("$(src)$(src)", "$(src)");
}

/**
   Scan a complete file spec (with possibly wild cards and directory)
   given in full_name ad fill in a vector of found file names.

   Result of the scan is filtered against matching suffixes

   Returns the count of non empty file names really found.

*/
int CmtGenerator::get_all_files (const cmt_string& full_name,
				 const cmt_vector<cmt_regexp>& exclude_exprs,
				 const cmt_vector<cmt_regexp>& select_exprs,
				 CmtSystem::cmt_string_vector& files)
{
  static cmt_string suffix;
  static cmt_string name;

  bool has_excludes = false;
  bool has_selects = false;

  suffix = "";
  name = "";

  files.clear ();

  has_excludes = (exclude_exprs.size () > 0);
  has_selects = (select_exprs.size () > 0);

  CmtSystem::get_dot_suffix (full_name, suffix);

  bool wilcarded_suffix = false;

  if (suffix == ".*") wilcarded_suffix = true;

  int count = 0;

  if (full_name.find ('*') != cmt_string::npos)
    {
      CmtSystem::scan_dir (full_name, files);

      if (Cmt::get_debug ())
        {
          cout << "CMT::get_all_files> full_name=" << full_name <<
	    " pwd=" << CmtSystem::pwd () << endl;
          cout << "CMT::get_all_files> files.size=" <<  files.size () << endl;
        }

        /**

           We have to treat patterns of the form *.xxx (ie with a
           suffix) thus we filter out everything that could have been
           collected with a different suffix because the
           CmtSystem::scan_dir function only handles patterns of the
           form xxx* (ie with trailing *)

           [If the original suffix was empty (ie files specified using
           xx*) this means getting files without any dot-suffix. This
           may be incorrect??]

        */

      for (int j = 0; j < files.size (); j++)
        {
          cmt_string& n = files[j];

	  bool rejected = false;

          if (n == "")
            {
	      rejected = true;
	    }

	  if (!rejected && has_selects)
	    {
	      rejected = true;

	      for (int k = 0; k < select_exprs.size (); k++)
		{
		  const cmt_regexp& exp = select_exprs[k];
		  if (exp.match (n))
		    {
		      rejected = false;
		      break;
		    }
		}
	    }

	  if (!rejected && has_excludes)
	    {
	      for (int k = 0; k < exclude_exprs.size (); k++)
		{
		  const cmt_regexp& exp = exclude_exprs[k];
		  if (exp.match (n))
		    {
		      rejected = true;
		      break;
		    }
		}
	    }

	  if (!rejected)
	    {
              static cmt_string s;

              CmtSystem::get_dot_suffix (n, s);
              if (!wilcarded_suffix && (s != suffix)) 
                {
		  rejected = true;
                }
              else
                {
                  count++;
                }
            }

	  if (Cmt::get_debug ())
	    {
	      if (rejected)
		{
		  cout << "CMT::get_all_files> reject " <<  n << endl;
		}
	      else
		{
		  cout << "CMT::get_all_files> keep " <<  n << endl;
		}
	    }

	  if (rejected)
	    {
	      n = "";
	    }
        }
    }
  else
    {
      if (full_name != "")
        {
	  bool rejected = false;

	  if (has_excludes)
	    {
	      for (int k = 0; k < exclude_exprs.size (); k++)
		{
		  const cmt_regexp& exp = exclude_exprs[k];
		  if (exp.match (full_name))
		    {
		      rejected = true;
		      break;
		    }
		}
	    }

	  if (!rejected)
	    {
	      cmt_string& n = files.add ();

	      n = full_name;

	      count++;
	    }
        }
    }

  return (count);
}

//--------------------------------------------------
void CmtGenerator::set_full_name (cmt_string& full_name, cmt_string& file)
{
  full_name = "";

  Symbol::expand (file);

  if (file == "") return;
  
  if (!CmtSystem::absolute_path (file))
    {
      full_name = srcdir;
      if (full_name != "") full_name += CmtSystem::file_separator ();
    }
  
  full_name += file;

  CmtSystem::reduce_file_separators (full_name);
}

//------------------------------------------------------------------------
static ApplicationGenerator ApplicationContext;
static LibraryGenerator LibraryContext;
static DocumentGenerator DocumentContext;
static ReadmeGenerator ReadmeContext;
static PrototypeGenerator PrototypeContext;
static DefaultMakefileGenerator DefaultMakefileContext;
static MSDEVGenerator MSDEVContext;
static VSNETGenerator VSNETContext;
static MakeSetupGenerator MakeSetupContext;
static ConstituentsMakefileGenerator ConstituentsMakefileContext;
static DependencyGenerator DependencyContext;

//--------------------------------------------------
int Generator::build_msdev_workspace (const Constituent::ConstituentVector& constituents)
{
  return (MSDEVContext.build_workspace (constituents));
}

//--------------------------------------------------
int Generator::build_msdev (const Constituent& constituent)
{
  return (MSDEVContext.build_project (constituent));
}

//--------------------------------------------------
int Generator::build_vsnet_workspace (const Constituent::ConstituentVector& constituents)
{
  return (VSNETContext.build_workspace (constituents));
}

//--------------------------------------------------   
int Generator::build_vsnet (const Constituent& constituent)
{
  return (VSNETContext.build_project (constituent));
}

//--------------------------------------------------
void Generator::build_make_setup (const cmt_string& package)
{
  MakeSetupContext.build (package);
}

//--------------------------------------------------
void Generator::build_constituents_makefile (const cmt_string& package,
					     const CmtSystem::cmt_string_vector& arguments)
{
  ConstituentsMakefileContext.build (package, arguments);
}

//--------------------------------------------------
int Generator::build_constituent_makefile (const Constituent& constituent)
{
  const cmt_string& package = Cmt::get_current_package ();

  switch (constituent.type)
    {
    case Application:
      ApplicationContext.build (package, constituent);
      break;
    case Library:
      LibraryContext.build (package, constituent);
      break;
    case Document:
      DocumentContext.build (package, constituent);
      break;
    }

  return (0);
}

//--------------------------------------------------
void Generator::build_constituent_makefile (const cmt_string& name)
{
  const Constituent* constituent = Constituent::find (name);
  if (constituent != 0) build_constituent_makefile (*constituent);
}

//--------------------------------------------------
void Generator::build_default_makefile ()
{
  DefaultMakefileContext.build ();
}

//--------------------------------------------------
void Generator::build_dependencies (const CmtSystem::cmt_string_vector& arguments)
{
  DependencyContext.build (arguments);
}

//--------------------------------------------------
void Generator::build_prototype (const cmt_string& file_name)
{
  PrototypeContext.build (file_name);
}

//--------------------------------------------------
void Generator::build_readme (const CmtSystem::cmt_string_vector& arguments)
{
  ReadmeContext.build (arguments);
}

class WinDefAwk : public PAwk
{
public :
   WinDefAwk (const cmt_string& library_name)
    {
      m_name = library_name;
    }

  void begin ()
    {
      cout << "LIBRARY " << m_name << endl;
      cout << "EXPORTS" << endl;
    }

  void filter (const cmt_string& line)
    {
      if (line.find ("External") == cmt_string::npos) return;
      if (line.find ("??_") != cmt_string::npos)
	{
	  if (line.find ("operator/=") == cmt_string::npos) return;
	  // Keep operator /= .
	}

      CmtSystem::cmt_string_vector words;
      CmtSystem::split (line, " \t", words);
      if (words.size () >= 7)
        {
          int pos = 7;

          cmt_string& fifth_word = words[4];
          if (fifth_word == "()") pos = 7;
          else if (fifth_word == "External") pos = 6;
          else return;

          cmt_string& symbol = words[pos];
          if (symbol[0] == '_') symbol.erase (0, 1);
          symbol.replace_all ("\r", "");
          symbol.replace_all ("\n", "");

          if ((pos == 6) && 
              ((line.find(": static") != cmt_string::npos) ||
               (line.find("(class") != cmt_string::npos)) )
	    {
	      // static data members are not DATA :
	      // extern objects are not DATA :
	      cout << " " << symbol << " " << endl;
	    } 
	  else if (pos == 6)
	    {
	      // DATA :
	      cout << " " << symbol << "\tDATA" << endl;
	    } 
	  else
	    {
	      // code :
	      cout << " " << symbol << " " << endl;
	    } 
        }
    }

  void end ()
    {
    }

private:
  cmt_string m_name;
};

//--------------------------------------------------
void Generator::build_windefs (const cmt_string& library_name)
{
  cmt_string bin;
  cmt_string name;
  cmt_string suffix;

  CmtSystem::dirname (library_name, bin);
  CmtSystem::get_dot_suffix (library_name, suffix);
  CmtSystem::basename (library_name, suffix, name);
  
  if (!CmtSystem::cd (bin)) return;
  
  cmt_string text;
  cmt_string command;
  
  command = "dumpbin /symbols ";
  command += library_name;
	
  WinDefAwk filter (name);
  
  filter.run (command, "SECT");
}

//--------------------------------------------------
void Packager::begin ()
{
  m_package_name = "";
}

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

  CmtSystem::split (line, " ", words);
  if (words.size () > 1)
    {
      cmt_string& w = words[0];

      if (w == "package")
        {
          m_package_name = words[1];

	  int pos = m_package_name.find (";");
          if (pos != cmt_string::npos) m_package_name.erase (pos);
          m_package_name.replace_all (".", CmtSystem::file_separator ());
        }
    }
}

cmt_string& Packager::package_name ()
{
  return (m_package_name);
}

