
#ifdef WIN32
#include <direct.h>
#define popen _popen
#define pclose _pclose
#endif

#include "cmt_awk.h"
#include "cmt_system.h"

class Parser
{
public:
  Parser (Awk* awk, const cmt_string pattern, const cmt_regexp* expression) :
          m_pattern (pattern), m_expression (expression), m_awk(awk)
      {
      }

    /** 
     *  this first level parsing function extracts individual lines
     *  from the text, taking care of both Unix and Windows EOL styles.
     *
     *   Then the second level parsing function parse_line is called.
     */
  Awk::condition parse (const cmt_string& text)
      {
        Awk::condition result = Awk::ok;

        cmt_string line;
        int pos;
        int max_pos;

        pos = 0;
        max_pos = text.size ();

        m_accumulator.erase (0);

        for (pos = 0; pos < max_pos;)
          {
            int eol = text.find (pos, '\n');
            
            if (eol == cmt_string::npos)
              {
                  // Last line, since there is no eol at all
                text.substr (pos, line);
                pos = max_pos;
              }
            else
              {
                int length = 1;

                int cr = text.find (pos, "\r\n");

                if (cr == (eol-1))
                  {
                    eol = cr;
                    length = 2;
                  }

                if (eol == pos)
                  {
                      // this is an empty line
                    line = "";
                    pos += length;
                  }
                else
                  {
                      // The eol was found beyond the current position
                      // (ie. this is a non empty line)
                    text.substr (pos, eol - pos, line);
                    pos = eol + length;
                  }
              }

            if (m_awk != 0) m_awk->inc_line_number ();

              //cout << "parse> line=[" << line << "]" << endl;

            result = parse_line (line);
            if (result != Awk::ok) break;
          }

        return (result);
      }

    /**
     *   This second level parsing function accumulates individual lines
     *   with real trailing back slashes.
     *    Eventually the possible text pattern or regular expression is
     *   checked and the Awk::filter function is called in case of
     *   succesful match onto the accumulated line.
     */
  Awk::condition parse_line (const cmt_string& line)
      {
        Awk::condition result = Awk::ok;
        int length;
        cmt_string temp_line = line;

          //
          // We scan the line for handling backslashes.
          //
          // Really terminating backslashes (ie those only followed by spaces/tabs
          // mean continued line
          //
          //

        bool finished = true;

        length = temp_line.size ();

        if (length == 0)
          {
              // An empty line following a backslash terminates the continuation.
            finished = true;
          }
        else
          {
            int back_slash = temp_line.find_last_of ('\\');
        
        if (back_slash != cmt_string::npos)
          {
              //
              // This is the last backslash
              // check if there are only space chars after it
              //
            
            bool at_end = true;
            
                for (int i = (back_slash + 1); i < length; i++)
                  {
                    char c = temp_line[i];
                    if ((c != ' ') && (c != '\t'))
                      {
                        at_end = false;
                        break;
                      }
                  }
                
                if (at_end)
                  {
                    temp_line.erase (back_slash);
                    finished = false;
                  }
                else
                  {
                      // This was not a trailing backslash.
                    finished = true;
                  }
              }
        
            m_accumulator += temp_line;
          }

          //cout << "parse_line1> accumulator=[" << m_accumulator << "]" << endl;
          //cout << "parse_line1> finished=[" << finished << "]" << endl;

        if (!finished)
          {
              // We still need to accumulate forthcoming lines
              // before parsing the resulting text.
            return (Awk::ok);
          }

          // now filter the complete accumulated line (if non empty)

        if (m_accumulator != "")
          {
            bool ok = false;
            
            if (m_expression != 0)
              {
                if (m_expression->match (m_accumulator))
                  {
                    ok = true;
                  }
              }
            else
              {
                if ((m_pattern == "") ||
                    (m_accumulator.find (m_pattern) != cmt_string::npos))
                  {
                    ok = true;
                  }
              }
            
            if (ok && (m_awk != 0))
              {
                  //cout << "parse_line> accumulator=[" << m_accumulator << "]" << endl;

                m_awk->filter (m_accumulator);
                result = m_awk->get_last_condition ();
              }

            m_accumulator.erase (0);
          }
        
        return (result);
      }

private:

  cmt_string m_accumulator;
  cmt_string m_pattern;
  const cmt_regexp* m_expression;
  Awk* m_awk;
};

//------------------------------------------------
Awk::Awk ()
{
  m_condition = ok;
}

//------------------------------------------------
Awk::~Awk ()
{
}

//------------------------------------------------
Awk::condition Awk::run (const cmt_string& text,
                         const cmt_string& pattern)
{
  m_line_number = 0;
  m_condition = ok;

  begin ();
  if (m_condition != ok) return (m_condition);

  if (CmtSystem::testenv ("CMTTESTAWK"))
    {
      Parser p (this, pattern, 0);

      m_condition = p.parse (text);
      if (m_condition != ok) return (m_condition);
    }
  else
    {
      cmt_string line;
      int pos = 0;
      int max_pos;

      max_pos = text.size ();

      for (pos = 0; pos < max_pos;)
        {
          int cr = text.find (pos, "\r\n");
          int nl = text.find (pos, '\n');
          
            // Get the first end-of-line (either lf or cr-lf)

            //--------------------
            //
            //     cr    1    0
            //   nl
            //
            //    1      a    b
            //
            //    0      c    d
            //
            //--------------------
          
          int first = nl;
          
          if (cr != cmt_string::npos)
            {
                // cases a or c

              if (nl == cmt_string::npos)
                {
                    // case a
                  first = cr;
                }
              else
                {
                    // case c
                  first = (nl < cr) ? nl : cr;
                }
            }
          
          if (first == cmt_string::npos)
            {
                // This is likely the last line since there is no end-of-line
              text.substr (pos, line);
              pos = max_pos;
            }
          else if (first > pos)
            {
                // The eol was found beyond the current position
                // (ie. this is a non empty line)
              text.substr (pos, first - pos, line);
              pos = first + 1;
            }
          else
            {
                // an empty line
              line = "";
              pos++;
            }
          
          m_line_number++;
          
          if (line != "")
            {
              if ((pattern == "") ||
                  (line.find (pattern) != cmt_string::npos))
                {
                  filter (line);
                  if (m_condition != ok) return (m_condition);
                }
            }
        }
    }

  end ();

  return (m_condition);
}

//------------------------------------------------
Awk::condition Awk::run (const cmt_string& text,
                         const cmt_regexp& expression)
{
  m_line_number = 0;
  m_condition = ok;

  begin ();
  if (m_condition != ok) return (m_condition);

  Parser p (this, "", &expression);

  m_condition = p.parse (text);
  if (m_condition != ok) return (m_condition);

    /*
  if (CmtSystem::testenv ("CMTTESTAWK"))
    {
    }
  else
    {
      cmt_string line;
      int pos = 0;
      int max_pos;

      max_pos = text.size ();

      for (pos = 0; pos < max_pos;)
        {
          int cr = text.find (pos, "\r\n");
          int nl = text.find (pos, '\n');
          
            // Get the first end-of-line (either lf or cr-lf)
          
          int first = nl;
          
          if (cr != cmt_string::npos)
            {
              if (nl == cmt_string::npos)
                {
                  first = cr;
                }
              else
                {
                  first = (nl < cr) ? nl : cr;
                }
            }
          
          if (first == cmt_string::npos)
            {
                // This is likely the last line since there is no end-of-line
              text.substr (pos, line);
              pos = max_pos;
            }
          else if (first > pos)
            {
                // The eol was found beyond the current position
                // (ie. this is a non empty line)
              text.substr (pos, first - pos, line);
              pos = first + 1;
            }
          else
            {
                // an empty line
              line = "";
              pos++;
            }
          
          m_line_number++;
          
          if (line != "")
            {
              if (expression.match (line))
                {
                  filter (line);
                  if (m_condition != ok) return (m_condition);
                }
            }
        }
    }
    */

  end ();

  return (m_condition);
}

//------------------------------------------------
void Awk::stop ()
{
  m_condition = stopped;
}

//------------------------------------------------
void Awk::abort ()
{
  m_condition = failed;
}

//------------------------------------------------
void Awk::allow_continuation ()
{
  m_continuation_allowed = true;
}

//------------------------------------------------
Awk::condition Awk::get_last_condition () const
{
  return (m_condition);
}

//------------------------------------------------
void Awk::begin ()
{
}

//------------------------------------------------
void Awk::filter (const cmt_string& /*line*/)
{
    //cout << "awk> " << line << endl;
}

//------------------------------------------------
void Awk::end ()
{
}

//------------------------------------------------
void Awk::inc_line_number ()
{
  m_line_number++;
}

//------------------------------------------------
Awk::condition FAwk::run (const cmt_string& file_name,
                          const cmt_string& pattern)
{
  if (!CmtSystem::test_file (file_name)) return (failed);

  CmtSystem::basename (file_name, m_file_name);
  CmtSystem::dirname (file_name, m_dir_name);

  cmt_string text;

  text.read (file_name);

  return (Awk::run (text, pattern));
}

//------------------------------------------------
Awk::condition FAwk::run (const cmt_string& file_name,
                          const cmt_regexp& expression)
{
  if (!CmtSystem::test_file (file_name)) return (failed);

  CmtSystem::basename (file_name, m_file_name);
  CmtSystem::dirname (file_name, m_dir_name);

  cmt_string text;

  text.read (file_name);

  return (Awk::run (text, expression));
}

//------------------------------------------------
Awk::condition PAwk::run (const cmt_string& command, 
                          const cmt_string& pattern)
{
  cmt_string line;

  m_line_number = 0;
  m_condition = ok;

  begin ();
  if (m_condition != ok) return (m_condition);

  FILE* f = popen (command.c_str (), "r"); 
  
  if (f == 0) return (failed);

  char buffer[8192]; 
  char* ptr;

  while ((ptr = fgets (buffer, sizeof (buffer), f)) != NULL) 
    {
      line = ptr;

      if (line.find ("\n") == cmt_string::npos)
        {
          cerr << "#CMT> Warning : Line too long and truncated in PAwk::run for command " << command << endl;
        }

      line.replace ("\n", "");

      m_line_number++;

      if (line != "")
        {
          if ((pattern == "") ||
              (line.find (pattern) != cmt_string::npos))
            {
              filter (line);
              if (m_condition != ok) return (m_condition);
            }
        }
    }

  pclose (f);

  end ();

  return (m_condition);
}

//------------------------------------------------
Awk::condition PAwk::run (const cmt_string& command, 
                          const cmt_regexp& expression)
{
  cmt_string line;

  m_line_number = 0;
  m_condition = ok;

  begin ();
  if (m_condition != ok) return (m_condition);

  FILE* f = popen (command.c_str (), "r"); 
  
  if (f == 0) return (failed);

  char buffer[256]; 
  char* ptr;

  while ((ptr = fgets (buffer, sizeof (buffer), f)) != NULL) 
    {
      line = ptr;

      line.replace ("\n", "");

      m_line_number++;

      if (line != "")
        {
          if (expression.match (line))
            {
              filter (line);
              if (m_condition != ok) return (m_condition);
            }
        }
    }

  pclose (f);

  end ();

  return (m_condition);
}

