/* 
 * Copyright (c) 2007, 2012, 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 "stdafx.h"

#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"

using namespace bec;

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

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

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

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

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

/**
 * More efficient statement splitter.
 */
int MysqlSqlFacadeImpl::splitSqlScript(const char *sql, size_t length, const std::string &initial_delimiter, 
                                       std::vector<std::pair<size_t, size_t> > &ranges)
{
  _stop = false;
  std::string delimiter = initial_delimiter.empty() ? ";" : initial_delimiter;
  const char *delimiter_head = delimiter.c_str();
  
  const char keyword[] = "delimiter";
  
  const char *head = sql;
  const char *tail = head;
  const char *end = head + length;
  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.
      {
        char end_char = *(tail + 2);
        if (*(tail + 1) == '-' && (end_char == ' ' || end_char == '\n' || end_char == '\r'))
        {
          // Skip everything until the end of the line.
          tail += 3;
          while (tail < end && *tail != '\n' && *tail != '\r')
            tail++;
          if (!have_content)
            head = tail;
        }
        else
          tail++;

        break;
      }
        
      case '#': // MySQL single line comment.
        while (tail < end && *tail != '\n' && *tail != '\r')
          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).
        char previous = tail > sql ? *(tail - 1) : 0;
        bool is_identifier_char = previous >= 0x80 
          || (previous >= '0' && previous <= '9')
          || ((previous | 0x20) >= 'a' && (previous | 0x20) <= 'z')
          || previous == '$'
          || previous == '_';
        if (tail == sql || !is_identifier_char)
        {
          const char *run = tail + 1;
          const 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 til the end of the line).
            tail = run++;
            while (run < end && *run != '\n' && *run != '\r')
              run++;
            delimiter = base::trim(std::string(tail, run - tail));
            delimiter_head = delimiter.c_str();

            // Skip over the delimiter statement and any following line breaks.
            while (*run == '\r' || *run == '\n')
              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 - sql, tail - head));
        head = ++tail;
        have_content = false;
      }
      else
      {
        const char *run = tail + 1;
        const 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 - 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 - 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::parseTriggers(db_TableRef table, const std::string sql)
{
  return Mysql_invalid_sql_parser::create(get_grt())->parse_triggers(db_mysql_TableRef::cast_from(table), 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()
{
  return Mysql_sql_statement_decomposer::create(get_grt());
}

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

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::IntegerRef());
    tuple.ginsert(grt::IntegerRef());
    tuple.ginsert(grt::IntegerRef());
  }

  
  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(get_grt());
  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, Column_name_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].repr() == "*")
                      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;
}

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

/**
 * 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;
}
