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

#include "cmt_model.h"
#include "cmt_fragment.h"
#include "cmt_symbol.h"

/**
 *   Filters out all predefined XML constructs.
 */
void CmtModel::filter (cmt_string& text)
{
  text.replace_all ("<cmt:tab/>",  "\t");
  text.replace_all ("<cmt:cr/>",   "\r");
  text.replace_all ("<cmt:lf/>",   "\n");
}

void CmtModel::display (cmt_string& text)
{
  text.replace_all ("&lt;", "<");
  text.replace_all ("&gt;", ">");
  text.replace_all ("\\\"", "\"");
  text.replace_all ("\\\'", "\'");
  text.replace_all ("<cmt:null/>", "");
  Symbol::expand (text);
  cout << text;
}

void CmtModel::expand (const cmt_string& input_text)
{
  int openmarker;
  int closemarker;

  CmtSystem::cmt_string_vector subargs;

  cmt_string text = input_text;
  cmt_string remaining;

  filter (text);

  for (;;)
    {
      /**
       *   Look for the next < ... /> pattern
       *   If not found, then simply dump the text as it is.
       */
      
      openmarker = text.find ("<");
      if (openmarker == cmt_string::npos) break;
      
      closemarker = text.find (openmarker, "/>");
      if (closemarker == cmt_string::npos) break;

      if (CmtSystem::testenv ("CMTTESTMODEL"))
        {
          cerr << "text=[" << text << "]" << endl;
        }
          

      /**
       *  Extract the command from the pattern
       */
      cmt_string command;
      text.substr (openmarker + 1, closemarker - openmarker - 1, command);

      if (CmtSystem::testenv ("CMTTESTMODEL"))
        {
          cerr << "command=[" << command << "]" << endl;
        }
          
      /**
       *   Get what is left from the original text beyond the pattern 
       */
      text.substr (closemarker + 2, remaining);

      /**
       *   Cut the original text up to the pattern start
       */
      text.erase (openmarker);
      
      /**
       *   Now display it
       */
      display (text);
          
      /**
       * Convert the extracted command into words
       */
      CmtSystem::split (command, " ", subargs);
          
      /**
       *  Recursively expand it
       */
      expand (subargs);
      
      /**
       *  The next iteration will operate on the remaining text
       */
      text = remaining;
    }
  
  if (CmtSystem::testenv ("CMTTESTMODEL"))
    {
      cerr << "text=[" << text << "]" << endl;
    }
          
  /**
   *   Display what is left after extracting the last pattern
   */
  display (text);
  cout << endl;
}

void CmtModel::strict_expand (const cmt_string& input_text)
{
  CmtSystem::cmt_string_vector subargs;

  cmt_string text = input_text;
  cmt_string remaining;

  // The patterns for syntax parsing

  cmt_string open_element = "<cmts:";
  cmt_string open_elements = "<cmtv:";
  cmt_string close_element = "/>";

  cmt_string skip_spaces = "[ \t]+";
  cmt_string word = "[^ \t/>]*";

  cmt_string attribute = "[^=/>]+=((\"([^\"]|\\\\\")*\")|(\'([^\']|\\\\\')*\'))";

  //cout << "attribute=[" << attribute << "]" << endl;

  cmt_regexp exp_open_element (open_element);
  cmt_regexp exp_open_elements (open_elements);
  cmt_regexp exp_close_element (close_element);

  cmt_regexp exp_skip_spaces (skip_spaces);
  cmt_regexp exp_word (word);

  cmt_regexp exp_attribute (attribute);

  cmt_regexp::iterator it;
  cmt_regexp::iterator its;

  static int expand_level = 0;

  //cout << "Text=[" << text << "]" << endl;

  filter (text);

  //cout << "Text=[" << text << "]" << endl;

  int pos = 0;

  for (;;)
    {
      bool multiple = false;

      it = exp_open_element.begin (text, pos);
      its = exp_open_elements.begin (text, pos);

      if ((it == exp_open_element.end ()) && ((its == exp_open_elements.end ()))) 
	{
	  // no more opening element

	  // Display the remaining text. The parsing is now completed.

	  remaining = text.substr (pos);
	  display (remaining);

	  break;
	}

      if (it == exp_open_element.end ())
	{
	  // no single element -> we have a multiple one
	  it = its;
	  multiple = true;
	}
      else if (its == exp_open_elements.end ())
	{
	  // no multiple element -> we have a single one
	}
      else if (it < its)
	{
	  // the single is before the multiple
	}
      else
	{
	  // the multiple is before the single
	  it = its;
	  multiple = true;
	}

      remaining = text.substr (pos, it._pos - pos);
      display (remaining);

      //cout << "pos=" << pos << " open_element -> p=" << it._pos << " l=" << it._length << endl;
      pos = it._pos + it._length;

      it = exp_word.begin (text, pos);
      if (it._pos != pos)
	{
	  cerr << "Syntax error: no element name" << endl;
	  break;
	}

      //cout << "pos=" << pos << " word -> p=" << it._pos << " l=" << it._length << endl;
      pos += it._length;

      cmt_string element = it (text);
      //cout << "pos=" << pos << " element=" << element << endl;

      subargs.clear ();

      {
	cmt_string& s = subargs.add ();
	s = element;
      }

      for (;;)
	{
	  it = exp_skip_spaces.begin (text, pos);
	  if (it._pos == pos)
	    {
	      //cout << "pos=" << pos << " skip_spaces -> p=" << it._pos << " l=" << it._length << endl;
	      pos += it._length;
	    }

	  //cout << "pos=" << pos << endl;

	  it = exp_close_element.begin (text, pos);
	  if (it._pos == pos)
	    {
	      //cout << "Recursing to [" << element << "]" << endl;

	      int i;

	      Variable::VariableVector variables;

	      /**
	       *   We start by decoding all [variable=value] pairs from the arguments
	       *   A vector of Variables is filled from them. 
	       */

	      int multiple_count = 0;
	      CmtSystem::cmt_string_vector values;
	      cmt_string name;
	      cmt_string value;

	      for (i = 1; i < subargs.size (); i++)
		{
		  const cmt_string& arg = subargs[i];

		  int p = arg.find ("=");

		  arg.substr (0, p, name);
		  arg.substr (p + 1, value);
		  if (value[0] == '"') 
		    {
		      value.erase (0, 1);
		      if (value[value.size()-1] == '"') value.erase (value.size ()-1);
		    }
		  else if (value[0] == '\'') 
		    {
		      value.erase (0, 1);
		      if (value[value.size()-1] == '\'') value.erase (value.size ()-1);
		    }

		  //cout << "+ name = [" << value << "]" << endl;

		  if (multiple)
		    {
		      values.clear ();
		      CmtSystem::split (value, " \t", values);
		      int n = values.size ();
		      if (n > multiple_count) multiple_count = n;
		    }

		  Variable* v = Variable::find (variables, name);
		  if (v == 0)
		    {
		      v = &(variables.add ());
		      v->set (name);
		    }

		  (*v) = value;
		}

	      cmt_string sub_text;

	      FragmentHandle fragment (element);

	      if (multiple)
		{
		  //cout << "+ count = " << multiple_count << endl;
		  for (int n = 0; n < multiple_count; n++)
		    {
		      for (i = 1; i < subargs.size (); i++)
			{
			  const cmt_string& arg = subargs[i];

			  int p = arg.find ("=");

			  arg.substr (p + 1, value);
			  if (value[0] == '"') 
			    {
			      value.erase (0, 1);
			      if (value[value.size()-1] == '"') value.erase (value.size ()-1);
			    }
			  else if (value[0] == '\'') 
			    {
			      value.erase (0, 1);
			      if (value[value.size()-1] == '\'') value.erase (value.size ()-1);
			    }

			  //cout << "+ values = [" << value << "]" << endl;

			  values.clear ();
			  CmtSystem::split (value, " \t", values);
			  if (n < values.size ())
			    {
			      value = values[n];
			    }
			  else
			    {
			      value = "";
			    }

			  Variable& v = variables[i-1];

			  //cout << "+ value = [" << value << "]" << endl;

			  v = value;
			}

		      fragment.copy (sub_text, variables, 0);

		      expand_level++;
		      strict_expand (sub_text);
		      expand_level--;
		    }
		}
	      else
		{
		  fragment.copy (sub_text, variables, 0);

		  expand_level++;
		  strict_expand (sub_text);
		  expand_level--;
		}

	      pos += it._length;
	      break;
	    }

	  //cout << "Try attribute at pos=" << pos << " [" << text.substr (pos) << "]" << endl;

	  it = exp_attribute.begin (text, pos);
	  if (it._pos != pos)
	    {
	      cerr << "Syntax error : no attribute before closing the element [" << text.substr (pos) << "]" << endl;
	      break;
	    }

	  //cout << "pos=" << pos << " attribute -> p=" << it._pos << " l=" << it._length << endl;
	  //cout << "pos=" << pos << " attr=[" << it (text) << "]" << endl;

	  {
	    cmt_string& s = subargs.add ();
	    s = it (text);
	  }

	  pos += it._length;
	}
    }

  if (expand_level == 0) cout << endl;
}

void CmtModel::test_regexp (const cmt_string& pattern, const cmt_string& input_text)
{
  cout << "Testing pattern [" << pattern << "] against [" << input_text << "]" << endl;
 
  cmt_regexp exp (pattern);

  cmt_regexp::iterator it;

  for (int pos = 0; pos < 10; pos++)
    {
      it = exp.begin (input_text, pos);

      if (it == exp.end ())
	{
	  cout << "No match" << endl;
	}
      else
	{
	  cout << "p=" << it._pos << " l=" << it._length << " -> [" << it (input_text) << "]" << endl;
	}
    }
}


/**
 *   Expands a model file
 *   Arguments are :
 *     model-name   : name of a model file
 *     var=value    : variable value to be expanded 
 *                    (will take the form ${var} )
 */
void CmtModel::expand (const CmtSystem::cmt_string_vector& arguments)
{
  int i;

  Variable::VariableVector variables;

  /**
   *   We start by decoding all [variable=value] pairs from the arguments
   *   A vector of Variables is filled from them. 
   */
  for (i = 0; i < arguments.size (); i++)
    {
      const cmt_string& arg = arguments[i];

      if (arg.find ("=") != cmt_string::npos)
        {
          cmt_string name;
          cmt_string value;
          int pos = arg.find ("=");

          arg.substr (0, pos, name);
          arg.substr (pos + 1, value);

          Variable* v = Variable::find (variables, name);
          if (v == 0)
            {
              v = &(variables.add ());
              v->set (name);
            }

          (*v) = value;
        }
    }

  /**
   *   Then model names are extracted.
   *   Each model may contain a set of <...> patterns.
   *   The expected syntax for each of them is :
   *     <model-name variable-name=value ...>
   *
   *   Therefore the current expand function is recursively restarted.
   *
   *   Text around patterns is displayed after replacements of all
   *   variable values detected by ${variable-name} patterns
   */
  cmt_string text;

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

      if (arg.find ("=") == cmt_string::npos)
        {
          FragmentHandle fragment (arg);
          fragment.copy (text, variables, 0);

          filter (text);

          int openmarker;
          int closemarker;

          CmtSystem::cmt_string_vector subargs;

          cmt_string remaining;

          for (;;)
            {
              /**
               *   Look for the next <...> pattern
               *   If not found, then simply dump the text as it is.
               */

              openmarker = text.find ("<");
              if (openmarker == cmt_string::npos) break;

              closemarker = text.find (openmarker, "/>");
              if (closemarker == cmt_string::npos)
                {
                  /**
                   *  The opening < was in the text
                   */

                  /**
                   *   Get what is left from the original text beyond the pattern 
                   */
                  text.substr (openmarker + 1, remaining);
                  text.erase (openmarker + 1);
                  
                  /**
                   *   Now display it
                   */
                  display (text);
                }
              else
                {
                  /**
                   *  Extract the command from the pattern
                   */
                  cmt_string command;
                  text.substr (openmarker + 1, closemarker - openmarker - 1, command);

                  /**
                   *   Get what is left from the original text beyond the pattern 
                   */
                  text.substr (closemarker + 2, remaining);
                  text.erase (openmarker);
                  
                  /**
                   *   Now display it
                   */
                  display (text);

                  /**
                   * Convert the extracted command into words
                   */
                  CmtSystem::split (command, " ", subargs);
                  
                  /**
                   *  Recursively expand it
                   */
                  expand (subargs);
                }

              /**
               *  The next iteration will operate on the remaining text
               */
              text = remaining;
            }

          /**
           *   Display what is left after extracting the last pattern
           */
          display (text);
        }
    }
}

