/* 
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; version 2 of the
 * License.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#include <glib.h>
#include <boost/signals2.hpp>

#include "mysql_sql_facade.h"
#include "mysql_sql_parser.h"
#include "mysql_sql_specifics.h"
#include "mysql_sql_normalizer.h"
#include "mysql_sql_inserts_loader.h"
#include "mysql_sql_schema_rename.h"
#include "mysql_sql_syntax_check.h"
#include "mysql_sql_semantic_check.h"
#include "mysql_invalid_sql_parser.h"
#include "mysql_sql_script_splitter.h"
#include "mysql_sql_statement_decomposer.h"
#include "mysql_sql_schema_rename.h"
#include "base/string_utilities.h"

#include "myx_statement_parser.h"
#include "mysql_sql_parser_fe.h"
#include "myx_sql_tree_item.h"

#include <antlr3.h>
#include "mysql-parser.h"
#include "MySQLLexer.h"

using namespace bec;

//--------------------------------------------------------------------------------------------------

int MysqlSqlFacadeImpl::splitSqlScript(const std::string &sql, std::list<std::string> &statements)
{
  return Mysql_sql_script_splitter::create()->process(sql, statements);
}

//--------------------------------------------------------------------------------------------------

static const unsigned char* skip_leading_whitespace(const unsigned char *head, const unsigned char *tail)
{
  while (head < tail && *head <= ' ')
    head++;
  return head;
}

//--------------------------------------------------------------------------------------------------

bool is_line_break(const unsigned char *head, const unsigned char *line_break)
{
  if (*line_break == '\0')
    return false;

  while (*head != '\0' && *line_break != '\0' && *head == *line_break)
  {
    head++;
    line_break++;
  }
  return *line_break == '\0';
}

//--------------------------------------------------------------------------------------------------

/**
 * A statement splitter to take a list of sql statements and split them into individual statements,
 * return their position and length in the original string (instead the copied strings).
 */
int MysqlSqlFacadeImpl::splitSqlScript(const char *sql, size_t length,
  const std::string &initial_delimiter, std::vector<std::pair<size_t, size_t> > &ranges,
  const std::string &line_break)
{
  _stop = false;
  std::string delimiter = initial_delimiter.empty() ? ";" : initial_delimiter;
  const unsigned char *delimiter_head = (unsigned char*)delimiter.c_str();
  
  const unsigned char keyword[] = "delimiter";
  
  const unsigned char *head = (unsigned char *)sql;
  const unsigned char *tail = head;
  const unsigned char *end = head + length;
  const unsigned char *new_line = (unsigned char*)line_break.c_str(); 
  bool have_content = false; // Set when anything else but comments were found for the current statement.
  
  while (!_stop && tail < end)
  {
    switch (*tail)
    {
      case '/': // Possible multi line comment or hidden (conditional) command.
        if (*(tail + 1) == '*')
        {
          tail += 2;
          bool is_hidden_command = (*tail == '!');
          while (true)
          {
            while (tail < end && *tail != '*')
              tail++;
            if (tail == end) // Unfinished comment.
              break;
            else
            {
              if (*++tail == '/')
              {
                tail++; // Skip the slash too.
                break;
              }
            }
          }

          if (!is_hidden_command && !have_content)
            head = tail; // Skip over the comment.
        }
        else
          tail++;

        break;
        
      case '-': // Possible single line comment.
      {
        const unsigned char *end_char = tail + 2;
        if (*(tail + 1) == '-' && (*end_char == ' ' || *end_char == '\t' || is_line_break(end_char, new_line)))
        {
          // Skip everything until the end of the line.
          tail += 2;
          while (tail < end && !is_line_break(tail, new_line))
            tail++;
          if (!have_content)
            head = tail;
        }
        else
          tail++;

        break;
      }
        
      case '#': // MySQL single line comment.
        while (tail < end && !is_line_break(tail, new_line))
          tail++;
        if (!have_content)
          head = tail;
        break;
        
      case '"':
      case '\'':
      case '`': // Quoted string/id. Skip this in a local loop.
      {
        have_content = true;
        char quote = *tail++;
        while (tail < end && *tail != quote)
        {
          // Skip any escaped character too.
          if (*tail == '\\')
            tail++;
          tail++;
        }
        if (*tail == quote)
          tail++; // Skip trailing quote char to if one was there.
        
        break;
      }
        
      case 'd':
      case 'D':
      {
        have_content = true;

        // Possible start of the keyword DELIMITER. Must be at the start of the text or a character,
        // which is not part of a regular MySQL identifier (0-9, A-Z, a-z, _, $, \u0080-\uffff).
        unsigned char previous = tail > (unsigned char *)sql ? *(tail - 1) : 0;
        bool is_identifier_char = previous >= 0x80 
          || (previous >= '0' && previous <= '9')
          || ((previous | 0x20) >= 'a' && (previous | 0x20) <= 'z')
          || previous == '$'
          || previous == '_';
        if (tail == (unsigned char *)sql || !is_identifier_char)
        {
          const unsigned char *run = tail + 1;
          const unsigned char *kw = keyword + 1;
          int count = 9;
          while (count-- > 1 && (*run++ | 0x20) == *kw++)
            ;
          if (count == 0 && *run == ' ')
          {
            // Delimiter keyword found. Get the new delimiter (everything until the end of the line).
            tail = run++;
            while (run < end && !is_line_break(run, new_line))
              run++;
            delimiter = base::trim(std::string((char *)tail, run - tail));
            delimiter_head = (unsigned char*)delimiter.c_str();

            // Skip over the delimiter statement and any following line breaks.
            while (is_line_break(run, new_line))
              run++;
            tail = run;
            head = tail;
          }
          else
            tail++;
        }
        else
          tail++;

        break;
      }
        
      default:
        if (*tail > ' ')
          have_content = true;
        tail++;
        break;
    }
    
    if (*tail == *delimiter_head)
    {
      // Found possible start of the delimiter. Check if it really is.
      size_t count = delimiter.size();
      if (count == 1)
      {
        // Most common case. Trim the statement and check if it is not empty before adding the range.
        head = skip_leading_whitespace(head, tail);
        if (head < tail)
          ranges.push_back(std::make_pair<size_t, size_t>(head - (unsigned char *)sql, tail - head));
        head = ++tail;
        have_content = false;
      }
      else
      {
        const unsigned char *run = tail + 1;
        const unsigned char *del = delimiter_head + 1;
        while (count-- > 1 && (*run++ == *del++))
          ;
        
        if (count == 0)
        {
          // Multi char delimiter is complete. Tail still points to the start of the delimiter.
          // Run points to the first character after the delimiter.
          head = skip_leading_whitespace(head, tail);
          if (head < tail)
            ranges.push_back(std::make_pair<size_t, size_t>(head - (unsigned char *)sql, tail - head));
          tail = run;
          head = run;
          have_content = false;
        }
      }
    }
  }
  
  // Add remaining text to the range list.
  head = skip_leading_whitespace(head, tail);
  if (head < tail)
    ranges.push_back(std::make_pair<size_t, size_t>(head - (unsigned char *)sql, tail - head));
  
  return 0;
}

//--------------------------------------------------------------------------------------------------

// A splitter using the grt (probably for python).
grt::BaseListRef MysqlSqlFacadeImpl::getSqlStatementRanges(const std::string &sql)
{
  grt::BaseListRef list(get_grt());
  std::list<std::pair<size_t,size_t> > ranges;
  Mysql_sql_script_splitter::create()->process(sql.c_str(), ranges);
  
  for (std::list<std::pair<size_t,size_t> >::const_iterator i = ranges.begin(); i != ranges.end(); ++i)
  {
    grt::BaseListRef item(get_grt());
    item.ginsert(grt::IntegerRef((long)i->first));
    item.ginsert(grt::IntegerRef((long)i->second));
    list.ginsert(item);
  }
  return list;
}

//--------------------------------------------------------------------------------------------------

Sql_parser::Ref MysqlSqlFacadeImpl::sqlParser()
{
  return Mysql_sql_parser::create(get_grt());
}

//--------------------------------------------------------------------------------------------------

int MysqlSqlFacadeImpl::parseSqlScriptString(db_CatalogRef catalog, const std::string sql)
{
  return parseSqlScriptStringEx(catalog, sql, DictRef());
}

//--------------------------------------------------------------------------------------------------

int MysqlSqlFacadeImpl::parseSqlScriptStringEx(db_CatalogRef catalog, const std::string sql, const grt::DictRef options)
{
  return Mysql_sql_parser::create(get_grt())->parse_sql_script(
    db_mysql_CatalogRef::cast_from(catalog), sql, options);
}

//--------------------------------------------------------------------------------------------------

int MysqlSqlFacadeImpl::parseSqlScriptFile(db_CatalogRef catalog, const std::string filename)
{
  return parseSqlScriptFileEx(catalog, filename, DictRef());
}


int MysqlSqlFacadeImpl::parseSqlScriptFileEx(db_CatalogRef catalog, const std::string filename, const grt::DictRef options)
{
  return Mysql_sql_parser::create(get_grt())->parse_sql_script_file(
    db_mysql_CatalogRef::cast_from(catalog), filename, options);
}


Invalid_sql_parser::Ref MysqlSqlFacadeImpl::invalidSqlParser()
{
  return Mysql_invalid_sql_parser::create(get_grt());
}

//--------------------------------------------------------------------------------------------------

int MysqlSqlFacadeImpl::parseInserts(db_TableRef table, const std::string &sql)
{
  return Mysql_invalid_sql_parser::create(get_grt())->parse_inserts(db_mysql_TableRef::cast_from(table), sql);
}

//--------------------------------------------------------------------------------------------------

int MysqlSqlFacadeImpl::parseTrigger(db_TriggerRef trigger, const std::string &sql)
{
  return Mysql_invalid_sql_parser::create(get_grt())->parse_trigger(trigger, sql);
}

//--------------------------------------------------------------------------------------------------

int MysqlSqlFacadeImpl::parseRoutine(db_RoutineRef routine, const std::string &sql)
{
  return Mysql_invalid_sql_parser::create(get_grt())->parse_routine(db_mysql_RoutineRef::cast_from(routine), sql);
}

//--------------------------------------------------------------------------------------------------

int MysqlSqlFacadeImpl::parseRoutines(db_RoutineGroupRef routineGroup, const std::string &sql)
{
  return Mysql_invalid_sql_parser::create(get_grt())->parse_routines(db_mysql_RoutineGroupRef::cast_from(routineGroup), sql);
}

//--------------------------------------------------------------------------------------------------

int MysqlSqlFacadeImpl::parseView(db_ViewRef view, const std::string &sql)
{
  return Mysql_invalid_sql_parser::create(get_grt())->parse_view(db_mysql_ViewRef::cast_from(view), sql);
}

//--------------------------------------------------------------------------------------------------

Sql_syntax_check::Ref MysqlSqlFacadeImpl::sqlSyntaxCheck()
{
  return Mysql_sql_syntax_check::create(get_grt());
}

//--------------------------------------------------------------------------------------------------

int MysqlSqlFacadeImpl::checkSqlSyntax(const std::string &sql)
{
  return Mysql_sql_syntax_check::create(get_grt())->check_sql(sql.c_str());
}

//--------------------------------------------------------------------------------------------------

int MysqlSqlFacadeImpl::checkTriggerSyntax(const std::string &sql)
{
  return Mysql_sql_syntax_check::create(get_grt())->check_trigger(sql.c_str());
}


int MysqlSqlFacadeImpl::checkViewSyntax(const std::string &sql)
{
  return Mysql_sql_syntax_check::create(get_grt())->check_view(sql.c_str());
}

//--------------------------------------------------------------------------------------------------

int MysqlSqlFacadeImpl::checkRoutineSyntax(const std::string &sql)
{
  return Mysql_sql_syntax_check::create(get_grt())->check_routine(sql.c_str());
}

//--------------------------------------------------------------------------------------------------

Sql_semantic_check::Ref MysqlSqlFacadeImpl::sqlSemanticCheck()
{
  return Mysql_sql_semantic_check::create(get_grt());
}

//--------------------------------------------------------------------------------------------------

Sql_specifics::Ref MysqlSqlFacadeImpl::sqlSpecifics()
{
  return Mysql_sql_specifics::create(get_grt());
}

//--------------------------------------------------------------------------------------------------

Sql_normalizer::Ref MysqlSqlFacadeImpl::sqlNormalizer()
{
  return Mysql_sql_normalizer::create(get_grt());
}

//--------------------------------------------------------------------------------------------------

std::string MysqlSqlFacadeImpl::normalizeSqlStatement(const std::string sql, const std::string schema_name)
{
  return Mysql_sql_normalizer::create(get_grt())->normalize(sql, schema_name);
}

//--------------------------------------------------------------------------------------------------

std::string MysqlSqlFacadeImpl::removeInterTokenSpaces(const std::string sql)
{
  return Mysql_sql_normalizer::create(get_grt())->remove_inter_token_spaces(sql);
}

//--------------------------------------------------------------------------------------------------

Sql_inserts_loader::Ref MysqlSqlFacadeImpl::sqlInsertsLoader()
{
  return Mysql_sql_inserts_loader::create(get_grt());
}

//--------------------------------------------------------------------------------------------------

Sql_schema_rename::Ref MysqlSqlFacadeImpl::sqlSchemaRenamer()
{
  return Mysql_sql_schema_rename::create(get_grt());
}

//--------------------------------------------------------------------------------------------------

int MysqlSqlFacadeImpl::renameSchemaReferences(db_CatalogRef catalog, const std::string old_schema_name, const std::string new_schema_name)
{
  return Mysql_sql_schema_rename::create(get_grt())->rename_schema_references(
    catalog, old_schema_name, new_schema_name);
}

//--------------------------------------------------------------------------------------------------

Sql_statement_decomposer::Ref MysqlSqlFacadeImpl::sqlStatementDecomposer(grt::DictRef db_opts)
{
  return Mysql_sql_statement_decomposer::create(get_grt(), db_opts);
}

//--------------------------------------------------------------------------------------------------

grt::StringListRef MysqlSqlFacadeImpl::splitSqlStatements(const std::string &sql)
{
  grt::StringListRef list(get_grt());
  std::list<std::string> statements;

  splitSqlScript(sql, statements);
 
  for (std::list<std::string>::const_iterator i = statements.begin(); i != statements.end(); ++i)
    list.insert(*i);

  return list;
}

//--------------------------------------------------------------------------------------------------

static grt::BaseListRef process_ast_node(grt::GRT *grt, int base_offset, const SqlAstNode& item)
{
  grt::BaseListRef tuple(grt);

  sql::symbol item_name= item.name();
  tuple.ginsert(grt::StringRef(item_name ? sql::symbol_names[item_name] : ""));
  
  bool has_value = false;
  if (!item.value().empty())
  {
    tuple.ginsert(grt::StringRef(item.value()));
    has_value = true;
  }
  else
    tuple.ginsert(grt::StringRef());

  {
    SqlAstNode::SubItemList *subitems= item.subitems();
    grt::BaseListRef children(grt);
    if (subitems)
    {
      for (SqlAstNode::SubItemList::const_iterator i= subitems->begin(), i_end= subitems->end(); i != i_end; ++i)
        children.ginsert(process_ast_node(grt, base_offset, **i));
    }
    tuple.ginsert(children);
  }
  
  if (has_value)
  {
    tuple.ginsert(grt::IntegerRef(base_offset));
    tuple.ginsert(grt::IntegerRef(item.stmt_boffset()));
    tuple.ginsert(grt::IntegerRef(item.stmt_eoffset()));
  }
  else
  {
    tuple.ginsert(grt::ValueRef());
    tuple.ginsert(grt::ValueRef());
    tuple.ginsert(grt::ValueRef());
  }

  
  return tuple;
}

//--------------------------------------------------------------------------------------------------

static int parse_callback(void* user_data, const MyxStatementParser *splitter, const char *sql, const SqlAstNode *tree,
    int stmt_begin_lineno, int stmt_begin_line_pos, int stmt_end_lineno, int stmt_end_line_pos,
    int err_tok_lineno, int err_tok_line_pos, int err_tok_len, const std::string &err_msg)
{
  grt::BaseListRef result = *(grt::BaseListRef*)user_data;

  if (err_msg.empty())
    result.ginsert(process_ast_node(result.get_grt(), splitter->statement_boffset(), *tree));
  else
    result.ginsert(grt::StringRef(err_msg));

  return 0; 
}

//--------------------------------------------------------------------------------------------------

grt::BaseListRef MysqlSqlFacadeImpl::parseAstFromSqlScript(const std::string &sql)
{
  Mysql_sql_parser_fe parser(bec::GRTManager::get_instance_for(get_grt())->get_app_option_string("SqlMode"));
  grt::BaseListRef result(get_grt());

  parser.is_ast_generation_enabled = true;
  parser.ignore_dml = false;

  parser.parse_sql_script(sql.c_str(), parse_callback, (void*)&result);

  return result;
}

//--------------------------------------------------------------------------------------------------

grt::BaseListRef MysqlSqlFacadeImpl::getItemFromPath(const std::string& path, const grt::BaseListRef source)
{
  if(!source.is_valid())
    return grt::BaseListRef();
  bool valid = true;
  grt::BaseListRef current_item = source;
  grt::BaseListRef current_child;

  std::vector<std::string> path_array = base::split(path, ",");

  for(size_t index = 0; valid && index < path_array.size(); index++)
  {
    bool found = false;

    for(size_t item_index = 0; item_index < current_item->count() && !found; item_index++)
    {
      /* Gets the #th child from the list */
      current_child = grt::BaseListRef::cast_from(current_item->get(item_index));

      /* Validates the next item name */
      grt::StringRef item_name = grt::StringRef::cast_from(current_child->get(0));

      found = (item_name == path_array[index]);
    }

    if( !found )
      valid = false;
    else if (index < path.size() && current_child.count() > 2)
      current_item = grt::BaseListRef::cast_from(current_child->get(2));
  }

  return valid ? current_item : grt::BaseListRef();
}

//--------------------------------------------------------------------------------------------------

bool MysqlSqlFacadeImpl::parseSelectStatementForEdit(const std::string &sql, std::string &schema_name, std::string &table_name, String_tuple_list &column_names)
{
  bool ret_val = false;

  /* gets the AST tree for the given statement */
  grt::BaseListRef result = parseAstFromSqlScript(sql);

  if (result.is_valid())
  {
    /* Ensures only a single statement has been processed */
    /* Correct results are given inside a list, errors are reported as strings */
    if (result->count() == 1 && result[0].type() == ListType )
    {
      /* Retrieves the node containing for a SELECT statement */
      std::string select_path = "verb_clause,statement,select,select_init,select_init2,select_part2";
      grt::BaseListRef select_item = getItemFromPath(select_path, result);

      if (select_item.is_valid())
      {
        /* Retrieves the information about the queried tables */
        std::string tables_path = "select_into,select_from,join_table_list,derived_table_list";
        grt::BaseListRef table_list = getItemFromPath(tables_path, select_item);


        if( select_item.is_valid() && table_list.is_valid() )
        {
          /* Validates only one table has been queried */
          if (table_list->count() == 1)
          {
            /* Gets the table/schema information from the queried table */
            std::string table_path = "esc_table_ref,table_ref,table_factor,table_ident";

            grt::BaseListRef table = getItemFromPath(table_path, table_list);
            if (table.is_valid())
            {
              grt::BaseListRef table_name_node = grt::BaseListRef();
              grt::BaseListRef schema_name_node = grt::BaseListRef();
              
              switch(table->count())
              {
                case 1:
                  table_name_node = grt::BaseListRef::cast_from(table->get(0));
                  break;
                case 2:
                  table_name_node = grt::BaseListRef::cast_from(table->get(1));
                  break;
                case 3:
                  schema_name_node = grt::BaseListRef::cast_from(table->get(0));
                  table_name_node = grt::BaseListRef::cast_from(table->get(2));
                  break;
              }
              
              schema_name = schema_name_node.is_valid() && schema_name_node.count() > 1? grt::StringRef::extract_from(schema_name_node->get(1)) : "";
              table_name  = table_name_node.is_valid()  && table_name_node.count()  > 1? grt::StringRef::extract_from(table_name_node->get(1))  : "";
              
              // Retrieves the column information 
              grt::BaseListRef column_list = getItemFromPath("select_item_list", select_item);
              
              if(column_list.is_valid())
              {
                ret_val = true;
                
                // Process the queried columns 
                
                {
                  for(size_t column_index = 0; ret_val && column_index < column_list->count(); column_index++)
                  {
                    grt::BaseListRef next_column = grt::BaseListRef::cast_from(column_list->get(column_index++)); //Note column_index is being increased to skip the "," item
                    
                    // Validation occurs only on non * columns
                    if (next_column[1].toString() == "*")
                      column_names.push_back(std::make_pair("*", "*"));
                    else
                    {
                      grt::BaseListRef generic_column_node  = getItemFromPath("expr,bool_pri,predicate,bit_expr,simple_expr", grt::BaseListRef::cast_from(next_column->get(2)));
                      // Tries to get the column as a simple identifier node
                      grt::BaseListRef column_name_node = grt::BaseListRef();

                      if (generic_column_node.is_valid())
                      {
                        grt::BaseListRef column_node  = getItemFromPath("simple_ident", generic_column_node);

                        if (column_node.is_valid())
                        {
                          grt::BaseListRef q_column_node = getItemFromPath("simple_ident_q", column_node);
                          if (q_column_node.is_valid())
                            column_name_node = grt::BaseListRef::cast_from(q_column_node->get(q_column_node->count() - 1));
                          else
                            column_name_node = grt::BaseListRef::cast_from(column_node->get(0));
                        
                          std::string column_name = column_name_node.is_valid() && column_name_node.count() > 1? grt::StringRef::extract_from(column_name_node->get(1)) : "";
                          std::string alias_name = "";

                          if (next_column->count() > 2)
                          {
                          grt::BaseListRef alias_ident_node = getItemFromPath("select_alias", grt::BaseListRef::cast_from(next_column->get(2)));
                            grt::BaseListRef alias_name_node = alias_ident_node.is_valid() ? grt::BaseListRef::cast_from(alias_ident_node->get(alias_ident_node->count()-1)) : grt::BaseListRef();
                            alias_name = alias_name_node.is_valid() && alias_name_node.count() > 1? grt::StringRef::extract_from(alias_name_node->get(1)) : column_name;
                          }
                        
                          column_names.push_back(std::make_pair(column_name, alias_name));
                        }
                        else
                          ret_val = false;
                      }
                      else
                      {
                        ret_val = false;
                        if (next_column->count() > 2 )
                        {
                        grt::BaseListRef wildcard_column_node  = getItemFromPath("table_wild", grt::BaseListRef::cast_from(next_column->get(2)));
                        if (wildcard_column_node.is_valid())
                        {
                          column_name_node = grt::BaseListRef::cast_from(wildcard_column_node->get(wildcard_column_node->count() - 1));
                            std::string column_name = column_name_node.is_valid() && column_name_node.count() > 1? grt::StringRef::extract_from(column_name_node->get(1)) : "";
                          column_names.push_back(std::make_pair(column_name, column_name));
                            ret_val = true;
                          }
                        }
                      }
                    }
                    
                    // These lines were intended to support creating columns from literals, as they are not
                    // editable, whenever a non real column is found, the whole method should return false
                    // Code will be let in place in case editable columns are allowed in the future
                    /*else
                     {
                     column_node = getItemFromPath("literal", generic_column_node);
                     
                     // Ensures it is a single literal node
                     if (column_node.is_valid() && column_node->count() == 1)
                     {
                     // Gets the literal node, no matter which
                     column_name_node = grt::BaseListRef::cast_from(column_node->get(0));
                     
                     // gets the literal type as for text_literals need to go one level deeper
                     if (grt::StringRef::extract_from(column_name_node->get(0)) == "text_literal")
                     {
                     column_name_node = grt::BaseListRef::cast_from(column_name_node->get(2));
                     column_name_node = grt::BaseListRef::cast_from(column_name_node->get(0));
                     }
                     }
                     }*/
                  }
                }
              }
            }
          }
        }
      }
    }
  }

  // Resets the data to be returned in anything caused it to fail
  if (!ret_val)
  {
    schema_name = "";
    table_name = "";
    column_names.clear();
  }

  return ret_val;
}

std::string MysqlSqlFacadeImpl::getTypeDescription(grt::BaseListRef type_node, std::vector<std::string> *additional_type_data_paths)
{
  // Sets the data type name..
  grt::BaseListRef temp_node = grt::BaseListRef::cast_from(type_node->get(0));
  std::string description =  grt::StringRef::extract_from(temp_node->get(1));

  if (additional_type_data_paths)
  {
    grt::BaseListRef path_node;
    for(size_t path_index = 0; path_index < additional_type_data_paths->size(); path_index++)
    {
      path_node =  getItemFromPath(additional_type_data_paths->at(path_index), type_node);
      if (path_node.is_valid())
      {
        for(size_t index = 0; index < path_node->count(); index++)
        {
          temp_node = grt::BaseListRef::cast_from(path_node->get(index));
          description +=  grt::StringRef::extract_from(temp_node->get(1));
        }
      }
    }
  }

  return description;
}

bool MysqlSqlFacadeImpl::parseRoutineDetails(const std::string &sql, std::string &type, std::string &name, String_tuple_list &parameters, std::string& return_value, std::string& comments)
{
  bool ret_val = false;
  grt::BaseListRef temp_node;
  std::string param_name, param_type;

  // gets the AST tree for the given ddl (which is expected to be a 
  // Stored procedure or function ddl
  grt::BaseListRef result = parseAstFromSqlScript(sql);

  if (result.is_valid())
  {
    // Ensures only a single statement has been processed
    // Correct results are given inside a list, errors are reported as strings
    if (result->count() == 1 && result[0].type() == ListType )
    {
      // Retrieves the parent node common to both procedures and functions
      std::string select_path = "verb_clause,statement,create,view_or_trigger_or_sp_or_event,definer_tail";
      grt::BaseListRef select_item = getItemFromPath(select_path, result);

      // Attempts to retrieve a function node
      grt::BaseListRef routine_item = getItemFromPath("sf_tail", select_item);
      type = "FUNCTION";
      std::string param_list = "sp_fdparam_list,sp_fdparams";
      std::string param = "sp_fdparam";

      // If failed then attempts with a procedure node
      if (!routine_item.is_valid())
      {
        routine_item = getItemFromPath("sp_tail", select_item);
        type = "PROCEDURE";
        param_list = "sp_pdparam_list,sp_pdparams";
        param = "sp_pdparam";
      }

      // If we've got a valid routine node...
      if (routine_item.is_valid())
      {
        // Retrieves the name of the routine...
        temp_node = getItemFromPath("sp_name", routine_item);
        temp_node = grt::BaseListRef::cast_from(temp_node->get(0));
        name = grt::StringRef::extract_from(temp_node->get(1));


        // These paths inside each parameter compplete the type definitions
        // They contain information like precision (x,y), length (x) and others
        std::vector<std::string> additional_type_data_paths;
        additional_type_data_paths.push_back("field_length");
        additional_type_data_paths.push_back("opt_field_length,field_length");
        additional_type_data_paths.push_back("opt_precision,precision");
        additional_type_data_paths.push_back("type_datetime_precision");
        additional_type_data_paths.push_back("opt_bin_mod");
        additional_type_data_paths.push_back("float_options,precision");
        additional_type_data_paths.push_back("float_options,field_length");
        additional_type_data_paths.push_back("field_options,field_opt_list,field_option");


        // Gets the node containing all the parameter definition...
        grt::BaseListRef parameters_node = getItemFromPath(param_list, routine_item);
        if (parameters_node.is_valid())
        {
          for(size_t index = 0; index < parameters_node->count(); index++)
          {
            // Each item is either a parameter definition...or a comma
            grt::BaseListRef parameters_item = grt::BaseListRef::cast_from(parameters_node->get(index));
            std::string node_type = grt::StringRef::extract_from(parameters_item->get(0));

            // Ignores command and only processes parameter definition nodes
            if (node_type == param)
            {
              grt::BaseListRef parameter = grt::BaseListRef::cast_from(parameters_item->get(2));

              int name_node = 0;

              param_type = "";
              // In the case of the procedures retrieves the parameter type (IN, OUT, INOUT)
              if (type == "PROCEDURE")
              {
                temp_node = grt::BaseListRef::cast_from(parameter->get(0));
                param_type = "[" + grt::StringRef::extract_from(temp_node->get(1)) + "] ";
                name_node++;
              }

              // Gets the parameter name
              temp_node = grt::BaseListRef::cast_from(parameter->get(name_node));
              param_name = grt::StringRef::extract_from(temp_node->get(1));

              // Gets the type node
              grt::BaseListRef type_node =  getItemFromPath("type_with_opt_collate,type", parameter);

              // Retrieves the full type definition...
              param_type += getTypeDescription(type_node, &additional_type_data_paths);
              parameters.push_back(std::make_pair(param_name, param_type));
            }
          }
        }

        // In the case of functions retrieves the return type
        if (type == "FUNCTION")
        {
          temp_node = getItemFromPath("type_with_opt_collate,type", routine_item);
          return_value = getTypeDescription(temp_node, &additional_type_data_paths);
        }

        // Finally searches for comments defined on the routine
        comments = "";
        temp_node = getItemFromPath("sp_c_chistics", routine_item);
        if (temp_node.is_valid())
        {
          for(size_t index = 0; index < temp_node->count() && !comments.length(); index++)
          {
            grt::BaseListRef item_node = grt::BaseListRef::cast_from(temp_node->get(index));
            item_node = getItemFromPath("sp_c_chistic,sp_chistic", temp_node);

            if (item_node.is_valid())
            {
              grt::BaseListRef sub_item = grt::BaseListRef::cast_from(item_node->get(0));

              if (grt::StringRef::extract_from(sub_item->get(0)) == "COMMENT_SYM")
              {
                sub_item = grt::BaseListRef::cast_from(item_node->get(1));
                comments = grt::StringRef::extract_from(sub_item->get(1));
              }
            }
          }
        }

        ret_val = true;
      }
    }
  }

  return ret_val;
}

//--------------------------------------------------------------------------------------------------
static std::string symbol_from_node(grt::ValueRef node)
{
  return grt::StringRef::cast_from(grt::BaseListRef::cast_from(node)[0]);
}

static std::string value_from_node(grt::ValueRef node)
{
  return grt::StringRef::cast_from(grt::BaseListRef::cast_from(node)[1]);
}

static grt::BaseListRef children_from_node(grt::ValueRef node)
{
  return grt::BaseListRef::cast_from(grt::BaseListRef::cast_from(node)[2]);
}

static bool extract_schema_object_idents(grt::BaseListRef node, std::string &schema_name, std::string &object_name, int offset = 0)
{
  int c = (int)node.count();
  if (c > offset+0 && symbol_from_node(node[offset+0]) == "ident")
  {
    if (c > offset+2 && symbol_from_node(node[offset+1]) == "46" && symbol_from_node(node[offset+2]) == "ident")
    {
      schema_name = value_from_node(node[offset+0]);
      object_name = value_from_node(node[offset+2]);
    }
    else
    {
      object_name = value_from_node(node[offset+0]);
      return true;
    }
  }
  return false;
}


bool MysqlSqlFacadeImpl::parseDropStatement(const std::string &sql, std::string &object_type, std::vector<std::pair<std::string, std::string> > &object_names)
{
  bool ret_val = false;

  /* gets the AST tree for the given statement */
  grt::BaseListRef result = parseAstFromSqlScript(sql);

  if (result.is_valid())
  {
    /* Ensures only a single statement has been processed */
    /* Correct results are given inside a list, errors are reported as strings */
    if (result->count() == 1 && result[0].type() == ListType )
    {
      /* Retrieves the node containing for a DROP statement */
      std::string drop_path = "verb_clause,statement,drop";
      grt::BaseListRef drop_item = getItemFromPath(drop_path, result);
      if (drop_item.is_valid() && drop_item.count() > 0)
      {
        // item 0 = DROP
        std::string type = symbol_from_node(drop_item[1]);
        if (type == "DATABASE")
        {
          object_type = "db.Schema";
          for (size_t i = 2; i < drop_item.count(); i++)
          {
            if (symbol_from_node(drop_item[i]) == "ident")
            {
              object_names.push_back(std::make_pair(value_from_node(drop_item[i]), ""));
              ret_val = true;
              break;
            }
          }
        }
        else if (type == "table_or_tables")
        {
          grt::BaseListRef table_list = getItemFromPath("table_list", drop_item);
          object_type = "db.Table";
          for (size_t i = 0; i < table_list.count(); i++)
          {
            if (symbol_from_node(table_list[i]) == "table_name")
            {
              std::string schema_name, table_name;
              extract_schema_object_idents(getItemFromPath("table_ident", children_from_node(table_list[i])),
                                          schema_name, table_name);
              object_names.push_back(std::make_pair(schema_name, table_name));
              ret_val = true;
            }
          }
        }
        else if (type == "VIEW_SYM")
        {
          grt::BaseListRef table_list = getItemFromPath("table_list", drop_item);
          object_type = "db.View";
          for (size_t i = 0; i < table_list.count(); i++)
          {
            if (symbol_from_node(table_list[i]) == "table_name")
            {
              std::string schema_name, table_name;
              extract_schema_object_idents(getItemFromPath("table_ident", children_from_node(table_list[i])),
                                           schema_name, table_name);
              object_names.push_back(std::make_pair(schema_name, table_name));
              ret_val = true;
            }
          }
        }
        else if (type == "PROCEDURE_SYM")
        {
          grt::BaseListRef args = children_from_node(drop_item);
          object_type = "db.StoredProcedure";
          std::string schema_name, routine_name;
          extract_schema_object_idents(getItemFromPath("sp_name", drop_item), schema_name, routine_name);
          object_names.push_back(std::make_pair(schema_name, routine_name));
          ret_val = true;
        }
        else if (type == "FUNCTION_SYM")
        {
          object_type = "db.Function";
          std::string schema_name, routine_name;
          extract_schema_object_idents(drop_item, schema_name, routine_name, 2);
          object_names.push_back(std::make_pair(schema_name, routine_name));
          ret_val = true;
        }
        else if (type == "TRIGGER_SYM")
        {
          grt::BaseListRef args = children_from_node(drop_item);
          object_type = "db.Trigger";
          std::string schema_name, routine_name;
          extract_schema_object_idents(getItemFromPath("sp_name", drop_item), schema_name, routine_name);
          object_names.push_back(std::make_pair(schema_name, routine_name));
          ret_val = true;
        }
        else if (type == "INDEX_SYM")
        {
          object_type = "db.Index";

          if (drop_item.count() > 3)
          {
            std::string index_name = value_from_node(drop_item[2]);
            std::string schema_name, table_name;
            if (extract_schema_object_idents(getItemFromPath("table_ident", children_from_node(drop_item[4])),
                                         schema_name, table_name))
            {
              object_names.push_back(std::make_pair(schema_name, table_name+"."+index_name));

              ret_val = true;
            }
          }
        }
      }
    }
  }
  return ret_val;
}

/**
 * Signals any ongoing process to stop. This must be called from a different thread than from where
 * the processing was started to make it work.
 */
void MysqlSqlFacadeImpl::stop_processing()
{
  _stop = true;
}
