/* 
 * Copyright (c) 2007, 2010, 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);
}


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);
}


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


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


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


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, 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;
}
