#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#include <algorithm>
#include "myx_sql_tree_item.h"
#include "myx_util_functions.h"
#include "myx_lex_helpers.h"
#include <functional>
#include <assert.h>

namespace mysql_parser
{

const char* find_str_in_array_ci(const char *arr[], size_t arr_size, const char* str)
{
  for (size_t n= 0; n < arr_size; ++n)
    if (are_strings_eq_ci(arr[n], str))
      return arr[n];
  return NULL;
}


bool are_strings_eq(const char *str1, const char *str2, bool case_sensitive)
{
  if (case_sensitive)
    return ((str1 == str2) ||
      (
        ((NULL != str1) && (NULL != str2)) &&
        (strlen(str1) == strlen(str2)) &&
        (0 == strcmp(str1, str2))
      ));
  else
    return are_strings_eq_ci(str1, str2);
}


bool are_strings_eq_ci(const char *str1, const char *str2)
{
  return ((str1 == str2) ||
    (
      ((NULL != str1) && (NULL != str2)) &&
      (toupper(str1[0]) == toupper(str2[0])) &&
      (strlen(str1) == strlen(str2)) &&
      (0 == strncasecmp(str1, str2, strlen(str1)))
    ));
}


MyxSQLTreeItem::SubItemList MyxSQLSimpleTreeItem::_empty_list;


MYX_PUBLIC_FUNC std::ostream& operator << (std::ostream& os, const MyxSQLTreeItem& item)
{
  if(item.value()[0] != '\0')
  {
    char *local_value= new char[item.value_length()+1];
    memcpy(local_value, item.value(), item.value_length());
    local_value[item.value_length()]= 0;
    sql::symbol item_name= item.name();
    os << "<elem name='" << (item_name ? sql::symbol_names[item_name] : "") << "' value='" << local_value << "'>";
    delete[] local_value;
  }
  else
    os << "<elem name='" << item.name() << "'>";

  {
    MyxSQLTreeItem::SubItemList *subitems= item.subitems();
    if (subitems)
      for (MyxSQLTreeItem::SubItemList::const_iterator i= subitems->begin(), i_end= subitems->end(); i != i_end; ++i)
        os << *i;
  }

  os << "</elem>";

  return os;
}


MyxSQLTreeItem::MyxSQLTreeItem(sql::symbol name, const char *value, int len, int stmt_lineno, int stmt_boffset, int stmt_eoffset, SubItemList *items)
  : _del_list(true), 
    _name(name),
    _value(static_cast<char *>(memcpy(new char[len+1], value, len))), 
    _valuelen(len),
    _subitems(items),
    _stmt_lineno(stmt_lineno),
    _stmt_boffset(stmt_boffset),
    _stmt_eoffset(stmt_eoffset)
{
  _value[len]= '\0';

  if (-1 != _stmt_eoffset && (_stmt_boffset + _valuelen) > _stmt_eoffset)
    _stmt_eoffset= _stmt_boffset + _valuelen;
}


template<class T>
class Delete_helper : public std::unary_function<T, void>
{
public:
  inline void operator()(T *ptr)
  {
    delete ptr;
  }
};


MyxSQLTreeItem::~MyxSQLTreeItem()
{
  delete[] _value;
  if (_del_list && _subitems)
  {
    std::for_each(_subitems->begin(), _subitems->end(), Delete_helper<MyxSQLTreeItem>());
    delete _subitems;
  }
}


const MyxSQLTreeItem * MyxSQLTreeItem::left_most_subitem() const
{
  return (_subitems) ? (*_subitems->begin())->left_most_subitem() : this;
}


const MyxSQLTreeItem * MyxSQLTreeItem::right_most_subitem() const
{
  return (_subitems) ? (*_subitems->rend())->right_most_subitem() : this;
}


int MyxSQLTreeItem::stmt_lineno() const
{
  if ((-1 == _stmt_lineno) && _subitems)
    return (*_subitems->begin())->stmt_lineno();
  return _stmt_lineno;
}


int MyxSQLTreeItem::stmt_boffset() const
{
  if ((-1 == _stmt_boffset) && _subitems)
    return (*_subitems->begin())->stmt_boffset();
  return _stmt_boffset;
}


int MyxSQLTreeItem::stmt_eoffset() const
{
  if ((-1 == _stmt_eoffset) && _subitems)
    return (*_subitems->rbegin())->stmt_eoffset();
  return _stmt_eoffset;
}


/*
Tries to find sequence of items begining from subitem (if specified, otherwise from 1st subitem).
Returns last item from found sequence.
*/
const MyxSQLTreeItem * MyxSQLTreeItem::subseq__(const MyxSQLTreeItem *subitem, sql::symbol name, va_list args) const
{
  MyxSQLTreeItem::SubItemList::iterator i= _subitems->begin();
  MyxSQLTreeItem::SubItemList::iterator i_end= _subitems->end();

  // skip some elements if start subitem is specified
  if (subitem)
    i= std::find(i, i_end, subitem);

  for (; i != i_end; ++i)
  {
    subitem= *i;
    if (!subitem->name_equals(name))
      return NULL;
#ifdef __GNUC__
    name= (sql::symbol)va_arg(args, int);
#else
    name= va_arg(args, sql::symbol);
#endif
    if (!name)
      return subitem; // return last item from found sequence
  }

  return NULL;
}


const MyxSQLTreeItem * MyxSQLTreeItem::subseq_(sql::symbol name, ...) const
{
  va_list args;
  va_start(args, name);
  const MyxSQLTreeItem * subitem= subseq__(NULL, name, args);
  va_end(args);
  return subitem;
}


const MyxSQLTreeItem * MyxSQLTreeItem::subseq_(const MyxSQLTreeItem *subitem, sql::symbol name, ...) const
{
  va_list args;
  va_start(args, name);
  subitem= subseq__(subitem, name, args);
  va_end(args);
  return subitem;
}


/*
Tries to find sequence within whole range of children items starting from 'subitem' (if specified, otherwise from 1st subitem).
Returns last item from found sequence.
*/
const MyxSQLTreeItem * MyxSQLTreeItem::find_subseq__(const MyxSQLTreeItem *subitem, sql::symbol name, va_list args) const
{
  MyxSQLTreeItem::SubItemList::iterator i= _subitems->begin();
  MyxSQLTreeItem::SubItemList::iterator i_end= _subitems->end();

  // skip some elements if start subitem is specified
  if (subitem)
    i= std::find(i, i_end, subitem);

  for (; i != i_end; ++i)
  {
    subitem= *i;
    if (subitem->name_equals(name) && (subitem= subseq__(subitem, name, args)))
      return subitem; // return last item from found sequence
  }

  return NULL;
}


const MyxSQLTreeItem * MyxSQLTreeItem::find_subseq_(sql::symbol name, ...) const
{
  va_list args;
  va_start(args, name);
  const MyxSQLTreeItem * subitem= find_subseq__(NULL, name, args);
  va_end(args);
  return subitem;
}


const MyxSQLTreeItem * MyxSQLTreeItem::find_subseq_(const MyxSQLTreeItem *subitem, sql::symbol name, ...) const
{
  va_list args;
  va_start(args, name);
  subitem= find_subseq__(subitem, name, args);
  va_end(args);
  return subitem;
}


/*
Tries to find sequence of items begining from subitem (if specified, otherwise from 1st subitem).
Returns last item from found sequence.
*/
const MyxSQLTreeItem * MyxSQLTreeItem::check_words(sql::symbol words[], size_t words_count, const MyxSQLTreeItem *start_item) const
{
  const MyxSQLTreeItem *result= NULL;

  if (NULL != _subitems)
  {
    MyxSQLTreeItem::SubItemList::iterator i= _subitems->begin();
    MyxSQLTreeItem::SubItemList::iterator i_end= _subitems->end();

    // skip some elements if needed
    if (NULL != start_item)
      for (; (*i != start_item) && (i != i_end); ++i);

    // now check given sequence
    size_t n= 0;
    for (; n != words_count && i != i_end; ++i, ++n)
    {
      result= *i;
      if (!result->name_equals(words[n]))
        return NULL;
    }
    if (n < words_count)
      return NULL;
  }

  return result;
}


/*
Tries to find sequence within whole range of children items starting from 'subitem' (if specified, otherwise from 1st subitem).
Returns last item from found sequence.
*/
const MyxSQLTreeItem * MyxSQLTreeItem::find_words(sql::symbol words[], size_t words_count, const MyxSQLTreeItem *start_item) const
{
  MyxSQLTreeItem::SubItemList::iterator i= _subitems->begin();
  MyxSQLTreeItem::SubItemList::iterator i_end= _subitems->end();

  // skip some elements if needed
  if (NULL != start_item)
    for (; (*i != start_item) && (i != i_end); ++i);

  // now try to find given sequence
  const MyxSQLTreeItem *item= NULL;
  size_t n= 0;
  for (; i != i_end; ++i)
  {
    item= *i;
    if (item->name_equals(words[n]))
    {
      if (words_count == ++n)
        break;
    }
    else
      n= 0;
  }

  return (words_count == n) ? item : NULL;
}


const MyxSQLTreeItem * MyxSQLTreeItem::search_by_names(sql::symbol names[], size_t path_count) const
{
  const MyxSQLTreeItem *result= NULL;
  for (size_t n= 0; n < path_count; ++n)
    if ((result= subitem_by_name(names[n])))
      break;
  return result;
}


const MyxSQLTreeItem * MyxSQLTreeItem::search_by_paths(sql::symbol * paths[], size_t path_count) const
{
  const MyxSQLTreeItem *result= NULL;
  for (size_t n= 0; n < path_count; ++n)
    if ((result= subitem_by_path(paths[n])))
      break;
  return result;
}


std::string MyxSQLTreeItem::restore_sql_text(const std::string &sql_statement, const MyxSQLTreeItem *first_subitem, const MyxSQLTreeItem *last_subitem) const
{
  int boffset= first_subitem ? first_subitem->_stmt_boffset : -1;
  int eoffset= last_subitem ? last_subitem->_stmt_eoffset : -1;

  restore_sql_text(boffset, eoffset, first_subitem, last_subitem);

  if (-1 != boffset && -1 != eoffset)
  {
    std::string sql_text;
    sql_text.reserve(eoffset - boffset);
    std::copy(sql_statement.begin()+boffset, sql_statement.begin()+eoffset, std::back_inserter(sql_text));
    return sql_text;
  }

  return std::string();
}


void MyxSQLTreeItem::restore_sql_text(int &boffset, int &eoffset, const MyxSQLTreeItem *first_subitem, const MyxSQLTreeItem *last_subitem) const
{
  if (-1 == boffset || (boffset > _stmt_boffset && -1 != _stmt_boffset))
    boffset= _stmt_boffset;
  if (-1 == eoffset || (eoffset < _stmt_eoffset && -1 != _stmt_eoffset))
    eoffset= _stmt_eoffset;
  if (NULL != _subitems)
  {
    MyxSQLTreeItem::SubItemList::const_iterator i = _subitems->begin();
    const MyxSQLTreeItem::SubItemList::const_iterator i_end= _subitems->end();
    if (first_subitem)
      for (; (i_end != i) && (*i != first_subitem); ++i);
    for (; (i != i_end) && (*i != last_subitem); ++i)
      (*i)->restore_sql_text(boffset, eoffset, NULL, NULL);
  }
}


// warning this sql has incorrect syntax
void MyxSQLTreeItem::build_sql(std::string &sql_text) const
{
  if ((NULL != _value) && (0 < strlen(_value)))
  {
    sql_text.append(_value);

    const char *nl_keywords[]= {"begin", "end", ";"};
    if (find_str_in_array_ci(nl_keywords, ARR_CAPACITY(nl_keywords), _value))
      sql_text.append("\n");
    else
      sql_text.append(" ");
  }

  if (NULL != _subitems)
    for (MyxSQLTreeItem::SubItemList::const_iterator i = _subitems->begin(), i_end= _subitems->end(); i != i_end; ++i)
      (*i)->build_sql(sql_text);
}


const MyxSQLTreeItem * MyxSQLTreeItem::subitem_(sql::symbol name, ...) const
{
  va_list args;
  va_start(args, name);
  const MyxSQLTreeItem *item= subitem__(name, args);
  va_end(args);
  return item;
}


const MyxSQLTreeItem *MyxSQLTreeItem::subitem__(sql::symbol name, va_list args) const
{
  const MyxSQLTreeItem *item= this;

  while (name && item)
  {
    item= item->subitem_by_name(name);
    assert(sizeof(int) == sizeof(sql::symbol));
#ifdef __GNUC__
    name= (sql::symbol)va_arg(args, int);
#else
    name= va_arg(args, sql::symbol);
#endif
  }

  return item;
}


const MyxSQLTreeItem * MyxSQLTreeItem::subitem_(int position, ...) const
{
  MyxSQLTreeItem::SubItemList::const_iterator i= _subitems->begin();
  if ((0 <= position) && (_subitems->size() > (size_t)position))
  {
    MyxSQLTreeItem::SubItemList::const_iterator i= _subitems->begin();
    for (; 0 < position; --position) ++i;
    return *i;
  }
  return NULL;
}


const MyxSQLTreeItem *MyxSQLTreeItem::subitem_by_name(sql::symbol name, const MyxSQLTreeItem *start_item) const
{
  if (!(_subitems))
    return NULL;

  MyxSQLTreeItem::SubItemList::const_iterator i= _subitems->begin();
  MyxSQLTreeItem::SubItemList::const_iterator i_end= _subitems->end();

  if (start_item)
    i= find(i, i_end, start_item);

  for (; i != i_end; ++i)
    if ((*i)->name_equals(name))
      return *i;

  return NULL;
}


const MyxSQLTreeItem * MyxSQLTreeItem::subitem_by_name(sql::symbol name, size_t position) const
{
  if (!(_subitems))
    return NULL;

  if (_subitems->size() > position)
  {
    MyxSQLTreeItem::SubItemList::const_iterator i= _subitems->begin();
    MyxSQLTreeItem::SubItemList::const_iterator i_end= _subitems->end();

    for (; 0 < position; --position)
      ++i;

    for (; i != i_end; ++i)
      if ((*i)->name_equals(name))
        return *i;
  }

  return NULL;
}


const MyxSQLTreeItem *MyxSQLTreeItem::rsubitem_by_name(sql::symbol name, size_t position) const
{
  if (_subitems->size() > position)
  {
    MyxSQLTreeItem::SubItemList::const_reverse_iterator i= _subitems->rbegin();
    MyxSQLTreeItem::SubItemList::const_reverse_iterator i_end= _subitems->rend();

    for (; 0 < position; --position)
      ++i;

    for (; i != i_end; ++i)
      if ((*i)->name_equals(name))
        return *i;
  }

  return NULL;
}


const MyxSQLTreeItem * MyxSQLTreeItem::subitem_by_path(sql::symbol path[]) const
{
  const MyxSQLTreeItem *item= this;
  sql::symbol *name= path;

  while ((item) && *name)
    item= item->subitem_by_name(*name++);
  return item;
}


char * MyxSQLTreeItem::subitems_as_string(const char *delim) const
{
  std::string to;

  if (_subitems)
  {
    const char *current_delim= "";
    for(MyxSQLTreeItem::SubItemList::const_iterator i = _subitems->begin(), i_end= _subitems->end(); i != i_end; ++i) 
    {
      MyxSQLTreeItem *subitem = *i;
      if(subitem->subitems()->size() > 0) 
      {
        char *s= subitem->subitems_as_string(delim);
        to += current_delim;
        current_delim= delim;
        to += s;
        delete[] s;
      }
      else
      {
        to += current_delim;
        current_delim= delim;
        to += subitem->value();
      }
    }
  }

  return strcpy(new char[to.length()+1], to.c_str());
}


std::set<MyxSQLTreeItem *> MyxSQLTreeItem::_ast_branches;
MyxSQLTreeItem * MyxSQLTreeItem::_tree= NULL;

void MyxSQLTreeItem::tree(MyxSQLTreeItem *tree)
{
  _tree= tree; mysql_parser::tree= _tree;
}

void MyxSQLTreeItem::add_ast_branch(void *ast_brunch)
{
  _ast_branches.insert(static_cast<MyxSQLTreeItem *>(ast_brunch));
}

void MyxSQLTreeItem::remove_ast_branch(void *ast_brunch)
{
  _ast_branches.erase(static_cast<MyxSQLTreeItem *>(ast_brunch));
}

void MyxSQLTreeItem::make_items_cleanup()
{
  if (_tree)
  {
    delete _tree;
    _tree= NULL;
  }
  else // absence of root item is a consequence of error during parsing. cleanup of generated ast branches is needed.
  {
    for (std::set<MyxSQLTreeItem*>::iterator i= _ast_branches.begin(), i_end= _ast_branches.end(); i != i_end; ++i)
      delete *i;
  }
  _ast_branches.clear();
}


//extern "C"
//{

  extern void *new_simple_tree_item(sql::symbol name, const char *value, int stmt_boffset, int stmt_eoffset)
  {
    MyxSQLSimpleTreeItem *item= new MyxSQLSimpleTreeItem(name, value, strlen(value), -1, stmt_boffset, stmt_eoffset);
    MyxSQLTreeItem::add_ast_branch(item);
    return item;
  }

  extern void *new_tree_item(sql::symbol name, const char *value, void *list)
  {
    MyxSQLTreeItem *item= new MyxSQLTreeItem(name, value, strlen(value), -1, -1, -1, static_cast<MyxSQLTreeItem::SubItemList *>(list));
    MyxSQLTreeItem::add_ast_branch(item);
    return item;
  }

  extern void *set_item_name(void *item, sql::symbol name)
  {
    if (NULL != item)
      static_cast<MyxSQLTreeItem *>(item)->set_name(name);
    return item;
  }

  extern void *new_tree_item_list()
  {
    return new MyxSQLTreeItem::SubItemList;
  }

  extern void delete_tree_item(void *tree_item)
  {
    if (!tree_item)
      return;

    MyxSQLTreeItem::remove_ast_branch(tree_item);
    MyxSQLTreeItem* item= static_cast<MyxSQLTreeItem *>(tree_item);
    delete item;
  }

  extern void tree_item_list_add(void *tree_item_list, void *tree_item)
  {
    if (!tree_item)
      return;

    MyxSQLTreeItem::SubItemList *list= static_cast<MyxSQLTreeItem::SubItemList *>(tree_item_list);
    MyxSQLTreeItem::remove_ast_branch(tree_item);
    MyxSQLTreeItem* item= static_cast<MyxSQLTreeItem *>(tree_item);
    list->push_back(item);
  }

  extern void *new_tree_item_list_reuse(void *tree_item_from)
  {
    if (NULL != tree_item_from)
    {
      MyxSQLTreeItem* item_from= static_cast<MyxSQLTreeItem *>(tree_item_from);
      item_from->del_list(false);
      return const_cast<MyxSQLTreeItem::SubItemList *>(item_from->subitems());
    }

    return new_tree_item_list();
  }


  extern void *tree_item_list_reuse(void *tree_item_from)
  {
    if (NULL != tree_item_from)
    {
      MyxSQLTreeItem* item_from= static_cast<MyxSQLTreeItem *>(tree_item_from);
      return item_from->subitems();
    }

    return new_tree_item_list();
  }


  extern void *tree_item_reuse(void *item, sql::symbol name, void *list)
  {
    return (NULL != item) ? set_item_name(item, name) : new_tree_item(name, "", list);
  }


  extern void tree_item_list_add_all(void *tree_item_list, void *tree_item)
  {
    if (!tree_item)
      return;

    MyxSQLTreeItem::SubItemList *list= static_cast<MyxSQLTreeItem::SubItemList *>(tree_item_list);
    MyxSQLTreeItem* item_from= static_cast<MyxSQLTreeItem *>(tree_item);
    item_from->del_list(false);
    list->insert(list->end(), item_from->subitems()->begin(), item_from->subitems()->end());
  }

  extern sql::symbol tree_item_name(const void *tree_item)
  {
    const MyxSQLTreeItem* item= static_cast<const MyxSQLTreeItem *>(tree_item);
    return item->name();
  }

  extern const char *tree_item_value(const void *tree_item)
  {
    const MyxSQLTreeItem* item= static_cast<const MyxSQLTreeItem *>(tree_item);
    return item->value();
  }

  extern void tree_item_dump_xml_to_file(const void *tree_item, const char *filename)
  {
    const MyxSQLTreeItem* item= static_cast<const MyxSQLTreeItem *>(tree_item);
    std::ofstream os(filename);
    os << *item;
  }

//} // extern "C"

} // namespace mysql_parser
