//
// Copyright (C) 2005 Claes Nasten <pekdon{@}pekdon{.}net>
//
// This program is licensed under the GNU GPL.
// See the LICENSE file for more information.
//

#include "CfgParser.hh"
#include "Util.hh"

#include <iostream>
#include <memory>
#include <cassert>

enum {
  PARSE_BUF_SIZE = 1024
};

using std::cerr;
using std::endl;
using std::list;
using std::map;
using std::set;
using std::string;
using std::auto_ptr;

const string CfgParser::m_o_root_source_name = string("");
const char *CP_PARSE_BLANKS = " \t\n";

//! @brief CfgParser::Entry constructor.
CfgParser::Entry::Entry(const std::string &or_source_name, int i_line,
                        const std::string &or_name, const std::string &or_value) :
  m_op_entry_next (NULL), m_op_section (NULL),
  m_o_name (or_name), m_o_value (or_value),
  m_i_line (i_line), m_or_source_name (or_source_name)
{
}

//! @brief CfgParser::Entry destructor.
CfgParser::Entry::~Entry(void)
{
}

//! @brief Adds Entry to the end of Entry list at current depth.
CfgParser::Entry*
CfgParser::Entry::add_entry (const std::string &or_source_name, int i_line,
                             const std::string &or_name, const std::string &or_value)
{
  if (m_op_entry_next) {
    return m_op_entry_next->add_entry (or_source_name, i_line, or_name, or_value);
  } else {
    m_op_entry_next = new Entry (or_source_name, i_line, or_name, or_value);
    return m_op_entry_next;
  }
}

//! @brief Gets next entry that has a sub section.
CfgParser::Entry*
CfgParser::Entry::get_section_next (void)
{
  Entry *op_entry;

  for (op_entry = m_op_entry_next; op_entry;
       op_entry = op_entry->m_op_entry_next) {
    if (op_entry->m_op_section)
      return op_entry;
  }

  return NULL;
}

//! @brief Gets next entry without subsection matching the name or_name.
//! @param or_name Name of Entry to look for.
CfgParser::Entry*
CfgParser::Entry::find_entry (const std::string &or_name)
{
  CfgParser::Entry *op_it;

  for (op_it = m_op_entry_next; op_it; op_it = op_it->m_op_entry_next) {
    if (!op_it->m_op_section && (*op_it == or_name.c_str ()))
      return op_it;
  }

  return NULL;
}

//! @brief Gets the next entry with subsection matchin the name or_name.
//! @param or_name Name of Entry to look for.
CfgParser::Entry*
CfgParser::Entry::find_section (const std::string &or_name)
{
  CfgParser::Entry *op_it;

  for (op_it = m_op_entry_next; op_it; op_it = op_it->m_op_entry_next) {
    if (op_it->m_op_section && (*op_it == or_name.c_str ()))
      return op_it;
  }

  return NULL;
}


//! @brief Sets and validates data specified by key list.
void
CfgParser::Entry::parse_key_values (std::list<CfgParserKey*>::iterator o_begin,
                                    std::list<CfgParserKey*>::iterator o_end)
{
  CfgParser::Entry *op_value;
  list<CfgParserKey*>::iterator o_it;

  for (o_it = o_begin; o_it != o_end; ++o_it) {
    op_value = find_entry ((*o_it)->get_name ());
    if (op_value) {
      try {
        (*o_it)->parse_value (op_value->get_value ());

      } catch (string &ex) {
        cerr << " *** WARNING " << ex << endl << "  " << *op_value << endl;
      }
    }
  }
}


//! @brief Prints Entry tree.
void
CfgParser::Entry::print_tree (int level)
{
  Entry *op_entry;

  cerr << level << ": " << m_or_source_name << "@" << m_i_line << " |"
       << m_o_name << "| = |" << m_o_value << "| SECTION" << endl;

  for (op_entry = m_op_entry_next; op_entry;
       op_entry = op_entry->m_op_entry_next) {
    if (op_entry->m_op_section) {
      op_entry->m_op_section->print_tree (level + 1);

    } else {
      cerr << level << ": " << op_entry->m_or_source_name << "@"
           << op_entry->m_i_line << "\t |"
           << op_entry->m_o_name << "| = |" << op_entry->m_o_value << "|" << endl;
    }
  }
}

//! @brief Frees Entry tree.
void
CfgParser::Entry::free_tree (void)
{
  Entry *op_entry, *op_entry_free;

  for (op_entry = m_op_entry_next; op_entry; ) {
    if (op_entry->m_op_section) {
      op_entry->m_op_section->free_tree ();
    }

    op_entry_free = op_entry;
    op_entry = op_entry->m_op_entry_next;
    delete op_entry_free;
  }
}

//! @brief Operator <<, return info on source, line, name and value.
std::ostream&
operator<< (std::ostream &or_stream, const CfgParser::Entry &or_entry)
{
  or_stream  << or_entry.get_source_name () << "@" << or_entry.get_line ()
             << " " << or_entry.get_name () << " = " << or_entry.get_value ();
  return or_stream;
}

//! @brief CfgParser constructor.
CfgParser::CfgParser (void) :
  m_op_source (NULL),
  m_o_root_entry (m_o_root_source_name, 0, "ROOT", ""),
  m_op_entry (&m_o_root_entry)
{
}

//! @brief CfgParser destructor.
CfgParser::~CfgParser (void)
{
  m_o_root_entry.free_tree ();
}

//! @brief Parses source and fills root section with data.
bool
CfgParser::parse (const std::string &or_src, CfgParserSource::Type i_type)
{
  // Init parse buffer and reserve memory.
  string o_buf, o_value;
  o_buf.reserve (PARSE_BUF_SIZE);

  // Open initial source.
  parse_source_new (or_src, i_type);
  if (m_o_source_list.size () == 0) {
    return false;
  }

  int i_c, i_next;
  while (m_o_source_list.size()) {
    m_op_source = m_o_source_list.back();

    while ((i_c = m_op_source->getc ()) != EOF) {
      switch (i_c) {
      case '\n':
        // To be able to handle entry ends AND { after \n a check
        // to see what comes after the newline is done. If { appears
        // we continue as nothing happened else we finish the entry.
        i_next = parse_skip_blank (m_op_source);
        if (i_next != '{')
          parse_entry_finish (o_buf, o_value);
        break;
      case ';':
        parse_entry_finish (o_buf, o_value);
        break;
      case '{':
        if (parse_name (o_buf))
          parse_section_finish (o_buf, o_value);
        else
          cerr << "EMPTY SECTION NAME!" << endl;
        o_buf.clear ();
        o_value.clear ();
        break;
      case '}':
        if (m_o_entry_list.size () > 0) {
          if (o_buf.size () && parse_name (o_buf)) {
            parse_entry_finish (o_buf, o_value);
            o_buf.clear ();
            o_value.clear ();
          }
          m_op_entry = m_o_entry_list.back ();
          m_o_entry_list.pop_back ();
        } else {
          cerr << "EXTRA } found!";
        }
        break;
      case '=':
        o_value.clear ();
        parse_value (m_op_source, o_value);
        break;
      case '#':
        parse_comment_line (m_op_source);
        break;
      case '/':
        i_next = m_op_source->getc ();
        if (i_next == '/') {
          parse_comment_line (m_op_source);
        } else if (i_next == '*') {
          parse_comment_c (m_op_source);
        } else {
          o_buf += i_c;
          m_op_source->ungetc (i_next);
        }
        break;
      default:
        o_buf += i_c;
        break;
      }
    }

    try {
      m_op_source->close ();

    } catch (string &ex) {
      cerr << ex << endl;
    }
    delete m_op_source;
    m_o_source_list.pop_back ();
    m_o_source_name_list.pop_back ();
  }

  if (o_buf.size()) {
    parse_entry_finish (o_buf, o_value);
  }

  return true;
}

//! @brief Creates and opens new CfgParserSource.
void
CfgParser::parse_source_new (const std::string &or_name,
                             CfgParserSource::Type i_type)
{
  int i_done = 0;
  string o_name(or_name);

  do {
    CfgParserSource *op_source = source_new (o_name, i_type);
    assert (op_source);

    // Open and set as active, delete if fails.
    try {
      op_source->open ();
      m_op_source = op_source;
      m_o_source_list.push_back (m_op_source);

    } catch (string &ex) {
      delete op_source;
      // Previously added in source_new
      m_o_source_name_list.pop_back ();


      // Display error message on second try
      if (i_done) {
        cerr << ex << endl;
      }

      // If the open fails and we are trying to open a file, try
      // to open the file from the current files directory.
      if (!i_done && (i_type == CfgParserSource::SOURCE_FILE)) {
        if (m_o_source_name_list.size () && (or_name[0] != '/')) {
          o_name = Util::getDir (m_o_source_name_list.back ());
          o_name += "/" + or_name;
        }
      }
    }
  } while (!i_done++ && (i_type == CfgParserSource::SOURCE_FILE));
}

//! @brief Parses from beginning to first blank.
bool
CfgParser::parse_name (std::string &or_buf)
{
  if (!or_buf.size ()) {
    cerr << "EMPTY NAME!" << endl;
    return false;
  }

  // Identify name.
  string::size_type o_begin, o_end;
  o_begin = or_buf.find_first_not_of (CP_PARSE_BLANKS);
  if (o_begin == string::npos) {
    // FIXME: cerr << "ONLY BLANKS IN NAME" << endl;
    return false;
  }
  o_end = or_buf.find_first_of (CP_PARSE_BLANKS, o_begin);

  // Check if there is any garbage after the value.
  if (o_end != string::npos) {
    if (or_buf.find_first_not_of (CP_PARSE_BLANKS, o_end) != string::npos) {
      //cerr << "GARBAGE AFTER NAME" << endl;
    }
  }

  // Set name.
  or_buf = or_buf.substr (o_begin, o_end - o_begin);

  return true;
}

//! @brief Parses from = to end of " pair.
bool
CfgParser::parse_value (CfgParserSource *op_source, std::string &or_value)
{
  // Init parse buffer and reserve memory.
  string o_buf;
  o_buf.reserve (PARSE_BUF_SIZE);

  // We expect to get a " after the =, however to do proper error reporting
  // we store the what we get between so we can show the output if it includes
  // anything else than spaces.
  int i_c, i_next;
  bool b_garbage = false;
  while ((i_c = op_source->getc ()) != EOF) {
    if (i_c == '"')
      break;

    o_buf += i_c;

    if (!isspace (i_c))
      b_garbage = true;
  }

  // Check if we got to a " or found EOF first.
  if (i_c == EOF) {
    cerr << "EOF before \"!" << endl;
    return false;
  }

  // Check if there was garbage between = and ".
  if (b_garbage) {
    //cerr << "GARBAGE between = and \", \"" << o_buf << "\"!" << endl;
  }

  // Parse until next ", and escape characters after \.
  o_buf.clear ();
  while ((i_c = op_source->getc ()) != EOF) {
    // Escape character after \, if newline drop it.
    if (i_c == '\\') {
      i_next = op_source->getc ();
      if (i_next != '\n')
        o_buf += i_next;
    } else if (i_c == '"') {
      break;
    } else {
      o_buf += i_c;
    }
  }

  if (i_c == EOF) {
    cerr << "EOF reached before closing \"" << endl;
  }

  or_value = o_buf;

  return false;
}

//! @brief Parses entry (name + value) and executes command accordingly.
void
CfgParser::parse_entry_finish (std::string &or_buf, std::string &or_value)
{
  if (!or_value.size ()) {
    or_buf.clear ();
    return;
  }

  if (parse_name (or_buf)) {
    if (or_buf[0] == '$') {
      variable_define (or_buf, or_value);

    } else  {
      variable_expand (or_value);

      if (or_buf == "INCLUDE")  {
        parse_source_new (or_value, CfgParserSource::SOURCE_FILE);
      } else if (or_buf == "COMMAND") {
        parse_source_new (or_value, CfgParserSource::SOURCE_COMMAND);
      } else {
        m_op_entry = m_op_entry->add_entry (m_op_source->get_name (),
                                            m_op_source->get_line (),
                                            or_buf, or_value);
      }
    }
  } else {
    cerr << "DROPPING " << or_value << " AS IT HAS NO NAME!" << endl;
  }

  or_value.clear ();
  or_buf.clear ();
}

//! @brief Creates new Section on {.
void
CfgParser::parse_section_finish (std::string &or_buf, std::string &or_value)
{
  m_op_entry = m_op_entry->add_entry (m_op_source->get_name (),
                                      m_op_source->get_line (),
                                      or_buf, or_value);
  m_o_entry_list.push_back (m_op_entry);

  // Create Entry representing Section and point to it.
  Entry *op_section = new Entry (m_op_source->get_name (),
                                 m_op_source->get_line (),
                                 or_buf, or_value);
  m_op_entry->set_section (op_section);

  // Set current Entry to newly created Section.
  m_op_entry = op_section;
}

//! @brief Parses Source until end of line discarding input.
void
CfgParser::parse_comment_line (CfgParserSource *op_source)
{
  int i_c;
  while (((i_c = op_source->getc ()) != EOF) && (i_c != '\n'))
    ;

  // Give back the newline, needed for flushing value before comment
  if (i_c == '\n') {
    op_source->ungetc (i_c);
  }
}

//! @brief Parses Source until */ is found.
void
CfgParser::parse_comment_c (CfgParserSource *op_source)
{
  int i_c;
  while ((i_c = op_source->getc ()) != EOF) {
    if ((i_c == '*') && (op_source->getc () == '/')) {
      break;
    }
  }

  if (i_c == EOF)  {
    cerr << "EOF reached before closing */" << endl;
  }
}

//! @brief Parses Source until next non whitespace char is found.
char
CfgParser::parse_skip_blank (CfgParserSource *op_source)
{
  int i_c;
  while (((i_c = op_source->getc ()) != EOF) && isspace (i_c))
    ;
  if (i_c != EOF) {
      op_source->ungetc (i_c);
  }
  return i_c;
}

//! @brief Creates a CfgParserSource of type i_type.
CfgParserSource*
CfgParser::source_new (const std::string &or_name,
                       CfgParserSource::Type i_type)
{
  CfgParserSource *op_source = NULL;

  // Create CfgParserSource.
  m_o_source_name_list.push_back (or_name);
  m_o_source_name_set.insert (or_name);
  switch (i_type) {
  case CfgParserSource::SOURCE_FILE:
    op_source = new CfgParserSourceFile (*m_o_source_name_set.find (or_name));
    break;
  case CfgParserSource::SOURCE_COMMAND:
    op_source = new CfgParserSourceCommand (*m_o_source_name_set.find (or_name));
    break;
  default:
    break;
  }

  return op_source;
}

//! @brief Defines a variable in the m_o_var_map/setenv.
void
CfgParser::variable_define (const std::string &or_name,
                            const std::string &or_value)
{
  m_o_var_map[or_name] = or_value;

  // If the variable begins with $_ it should update the environment aswell.
  if ((or_name.size() > 2) && (or_name[1] == '_')) {
    setenv (or_name.c_str () + 2, or_value.c_str (), 1);
  }
}

//! @brief Expands all $ variables in a string.
void
CfgParser::variable_expand (std::string &or_string)
{
  string::size_type o_begin = 0, o_end = 0;

  while ((o_begin = or_string.find_first_of ('$', o_end)) != string::npos) {
    o_end = o_begin + 1;

    // Skip escaped \$
    if ((o_begin > 0) && (or_string[o_begin - 1] == '\\')) {
      continue;
    }

    for (; o_end != or_string.size(); ++o_end) {
      if ((isalnum(or_string[o_end]) == 0) && (or_string[o_end] != '_'))  {
        break;
      }
    }

    string o_var_name (or_string.substr (o_begin, o_end - o_begin));

    map<string, string>::iterator o_it (m_o_var_map.find (o_var_name));
    if (o_it != m_o_var_map.end ()) {
      or_string.replace (o_begin, o_end - o_begin, o_it->second);
      o_end = o_begin + o_it->second.size ();

    } else  {
      cerr << "TRYING TO USE UNDEFINED VARIABLE: " << o_var_name << endl;
    }
  }
}


syntax highlighted by Code2HTML, v. 0.9.1