/* 
 * © 2007-2008 MySQL AB, 2008-2009 Sun Microsystems, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
#include "stdafx.h"
#include "editor_table_inserts.h"

#include "editor_table.h"

using namespace grt;
using namespace bec;



TableInsertsGridBE::TableInsertsGridBE(TableEditorBE *table)
                   : _owner(table), _placeholder_row(false)
{
}



static const gchar *skip_whitespace(const gchar *ptr)
{
  while (isspace(*ptr)) ++ptr;
  return ptr;
}


static const gchar *skip_token(const gchar *ptr)
{
  if (*ptr == '\'')
  {
    ++ptr;
    while (*ptr && *ptr != '\'') 
    {
      if (*ptr == '\\' && *(ptr+1))
        ++ptr;
      if (*ptr == '\'' && *(ptr+1)=='\'')
        ++ptr;
      ++ptr;
    }
    if (*ptr == '\'') ++ptr;
  }
  else
    while (*ptr == '-' || isalnum(*ptr) || *ptr == '_' || *ptr == '.') ++ptr;

  return ptr;
}


db_SimpleDatatypeRef TableInsertsGridBE::get_column_simpletype(int column)
{
  db_ColumnRef col(_owner->get_table()->columns()[column]);

  if (col->simpleType().is_valid())
    return col->simpleType();
  else if (col->userType().is_valid())
    return col->userType()->actualType();

  return db_SimpleDatatypeRef();
}


bool TableInsertsGridBE::set_inserts_text(const std::string &text)
{
  gchar **lines= g_strsplit(text.c_str(), ";\n", -1);
  gchar **ptr= lines;

  rows.clear();

  // make sure that the columns list if up-to-date
  refresh();


  std::vector<std::string> row;

  int columns= get_column_count();

  while (*ptr)
  {
    gchar *str= g_strchomp(g_strchug(*ptr));
    if (g_str_has_prefix(str, "INSERT INTO"))
    {
      str= strstr(str, "VALUES (");
      if (str)
      {
        const gchar *start, *end;

        str= strchr(str, '(')+1;

        start= str;

        row= std::vector<std::string>(columns);

        for (int i= 0; i < columns; i++)
        {
          start= skip_whitespace(start);
          end= skip_token(start);

          row[i]= std::string(start, end);

          start= skip_whitespace(end);
          if (*start == ',')
            start++;
          else
            break;
        }
        rows.push_back(row);
      }
    }
    // next line
    ++ptr;
  }


  g_strfreev(lines);

  return true;
}


std::string TableInsertsGridBE::get_inserts_text()
{
  std::string text;
  std::string name= _owner->get_name();

  std::string prefix;

  prefix.append("INSERT INTO `");
  prefix.append(name);
  prefix.append("` (");
  for (size_t c= get_column_count(), i= 0; i < c; i++)
  {
    if (i > 0)
      prefix.append(", ");
    prefix.append("`").append(get_column_caption(i)).append("`");
  }
  prefix.append(") VALUES (");

  for (std::deque<std::vector<std::string> >::const_iterator row= rows.begin();
    row != rows.end(); ++row)
  {
    text.append(prefix);

    size_t i= 0;
    for (std::vector<std::string>::const_iterator field= row->begin();
      field != row->end(); ++field)
    {
      if (field != row->begin())
        text.append(", ");

      text.append(_owner->get_columns()->quote_value_if_needed(get_column(i++), *field));
    }

    text.append(");\n");
  }

  return text;
}



void TableInsertsGridBE::refresh()
{
  // gets the columns from the table and creates columns list for this model
  // also updates the list of inserts so it reflects the new columns

  ListRef<db_Column> columns(_owner->get_table()->columns());

  std::list<db_ColumnRef> new_list;
  std::list<int> column_reordering;

  bool changed= false;

  // look for new columns. we don't need to look for removed ones
  // because they will simply be ignored in update_data_rows()
  for (size_t c= columns.count(), i= 0; i < c; i++)
  {
    db_ColumnRef column(columns[i]);

    // see if the column is in the old list
    bool found= false;
    size_t oindex= 0;
    for (std::list<db_ColumnRef>::const_iterator iter= this->columns.begin();
      iter != this->columns.end(); ++iter, ++oindex)
    {
      if (*iter == column)
      {
        found= true;
        break;
      }
    }

    new_list.push_back(column);

    if (!found)
    {
      // not present in old list, add it
      column_reordering.push_back(-1);
      changed= true;
    }
    else
    {
      if (i != oindex)
        changed= true;
      column_reordering.push_back(oindex);
    }
  }

  if (changed || this->columns.size() != new_list.size())
    update_data_rows(column_reordering);

  this->columns= new_list;
}



void TableInsertsGridBE::update_data_rows(const std::list<int> &mapping)
{
  std::deque<std::vector<std::string> > new_rows;

  for (std::deque<std::vector<std::string> >::const_iterator row= rows.begin();
    row!= rows.end(); ++row)
  {
    std::vector<std::string> new_row;

    for (std::list<int>::const_iterator iter= mapping.begin();
      iter != mapping.end(); ++iter)
    {      
      if (*iter >= 0 && *iter < (int)row->size())
        new_row.push_back((*row)[*iter]);
      else
        new_row.push_back("NULL");
    }
    new_rows.push_back(new_row);
  }
  rows= new_rows;
}


int TableInsertsGridBE::get_column_count()
{
  return (int)_owner->get_table()->columns().count();
}


std::string TableInsertsGridBE::get_column_caption(int column)
{
  if (column >= get_column_count())
    return "";

  return _owner->get_table()->columns()[column]->name();
}

db_ColumnRef TableInsertsGridBE::get_column(int column)
{
  if (column >= get_column_count())
    return db_ColumnRef();

  return _owner->get_table()->columns()[column];
}

TableInsertsGridBE::ColumnType TableInsertsGridBE::get_column_type(int column)
{
  if (column >= (int)_owner->get_table()->columns().count())
    return StringType;

  db_SimpleDatatypeRef type(get_column_simpletype(column));

  if (type.is_valid())
  {
    std::string type_group= type->group()->name();

    if (type_group == "numeric")
      return NumericType;
    if (type_group == "string" || type_group == "text")
      return StringType;
    if (type_group == "datetime")
      return DatetimeType;
    if (type_group == "blob")
      return BlobType;
  }
  return UnknownType;
}


void TableInsertsGridBE::enable_placeholder_row(bool flag)
{
  _placeholder_row= flag;
}


int TableInsertsGridBE::count()
{
  return rows.size() + (_placeholder_row ? 1 : 0);
}


bool TableInsertsGridBE::get_field(const NodeId &node, int column, std::string &value)
{
  if (node[0] < (int)rows.size() && column >= 0 && column < (int)columns.size())
  {
    value= rows[node[0]][column];
    return true;
  }
  return false;
}


void TableInsertsGridBE::add_new_row(int column, const std::string &value)
{
  std::vector<std::string> defaults;

  for (int c= get_column_count(), i= 0; i < c; i++)
  {
    db_ColumnRef col(_owner->get_table()->columns()[i]);
    std::string def= col->defaultValue();

    if (column == i)
      defaults.push_back(_owner->get_columns()->quote_value_if_needed(col, value));
    else
    {
      if (!def.empty())
      {
        db_SimpleDatatypeRef type(get_column_simpletype(column));

        // hack to handle TIMESTAMP 0 ON UPDATE NOW()
        if (def[0] != '\'' && def.find(' ') != std::string::npos)
          def= def.substr(0, def.find(' '));

        defaults.push_back(def);
        continue;
      }

      switch (get_column_type(i))
      {
      case StringType: defaults.push_back("''"); break;
      case NumericType: defaults.push_back("0"); break;
      case DatetimeType: defaults.push_back("0"); break;
      default: defaults.push_back("NULL"); break;
      }
    }
  }
  rows.push_back(defaults);
}


bool TableInsertsGridBE::set_convert_field(const NodeId &node, int column, const std::string &value)
{
  if (node[0] <= (int)rows.size() && column >= 0 && column < (int)columns.size())
  {
    // init field and set other fields to defaults
    if ((int)rows.size() == node[0])
    {
      add_new_row(column, value);
      return true;
    }

    db_ColumnRef col(_owner->get_table()->columns()[column]);
    rows[node[0]][column]= _owner->get_columns()->quote_value_if_needed(col, value);

    return true;
  }
  return false;
}


bool TableInsertsGridBE::delete_node(const NodeId &node)
{
  if (node[0] < (int) rows.size())
  {
    rows.erase(rows.begin()+node[0]);
    return true;
  }
  return false;
}


bec::MenuItemList TableInsertsGridBE::get_popup_items_for_nodes(const std::vector<bec::NodeId> &nodes)
{
  MenuItemList items;

  MenuItem item;
  item.caption = _("Delete");
  item.name    = "delete";
  item.enabled = (nodes.size() >= 1);
  items.push_back(item);

  return items;
}


bool TableInsertsGridBE::activate_popup_item_for_nodes(const std::string &name, const std::vector<bec::NodeId> &orig_nodes)
{
  bool ret = false;
  
  std::vector<bec::NodeId> nodes(orig_nodes);
  std::sort(nodes.begin(), nodes.end());

  if (name == "delete")
  {
    if (nodes.size() > 0)
    {
      for (int i = nodes.size() - 1; i >= 0; --i)
        delete_node(nodes[i]);
    }
    ret = true;
  }
  
  return ret;
}
