/* 
 * © 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 "wb_config.h"

#include "db_conn_be.h"
#include "grt/grt_manager.h"
#include "grtdb/charset_utils.h"
#include "grtpp_util.h"
#include <boost/shared_ptr.hpp>
#include <algorithm>
#include <cctype>
#include <algorithm>
#include <memory>

#undef max


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


static const std::string control_name_prefix= "ctrl__";


grt::StringRef DbDriverParam::get_control_name() const
{
  return grt::StringRef(control_name_prefix + (*_inner->name())); 
}


DbDriverParam::ParamType DbDriverParam::decode_param_type(std::string type_name)
{
  ParamType result= ptUnknown;
  
  std::transform(type_name.begin(), type_name.end(), type_name.begin(), g_unichar_tolower);

  if (0 == type_name.compare("string"))
    result= ptString;
  else if (0 == type_name.compare("int"))
    result= ptInt;
  else if (0 == type_name.compare("boolean"))
    result= ptBoolean;
  else if (0 == type_name.compare("tristate"))
    result= ptTristate;
  else if (0 == type_name.compare("dir"))
    result= ptDir;
  else if (0 == type_name.compare("password"))
    result= ptPassword;

  return result;
}

DbDriverParam::DbDriverParam(const db_mgmt_DriverParameterRef &driver_param, const db_mgmt_ConnectionRef &stored_conn)
:
_inner(driver_param),
_type(ptUnknown)
{
  _type= decode_param_type(_inner->paramType());

  if (stored_conn.is_valid())
    set_value(stored_conn->parameterValues().get(driver_param->name(), driver_param->defaultValue()));
  else
    set_value(driver_param->defaultValue());
}


ControlType DbDriverParam::get_control_type() const
{
  switch (get_type())
  {
  case DbDriverParam::ptBoolean:
  case DbDriverParam::ptTristate:
    return ctCheckBox;
  case DbDriverParam::ptInt:
  case DbDriverParam::ptString:
  case DbDriverParam::ptPassword:
  case DbDriverParam::ptDir:
  default:
    return ctTextBox;
  }
}


void DbDriverParam::set_value(const grt::ValueRef &value)
{
  switch (_type)
  {
  case ptString:
  case ptPassword:
  case ptDir:
    {
      _value= grt::StringRef::cast_from(value);
      break;
    }

  case ptInt:
  case ptBoolean:
  case ptTristate:
    {
      if (value.type() == grt::IntegerType)
        _value= value;
      else
      {
        grt::StringRef s= grt::StringRef::cast_from(value);
        if (s.is_valid() && !(*s).empty())
        {
          int n= atol((*s).c_str());
          _value= grt::IntegerRef(n);
        }
        else
          _value= grt::IntegerRef();
      }

      break;
    }

  case ptUnknown:
  default:
    {
      break;
    }
  }
}


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


struct LayoutControl
{
  LayoutControl(int offset) : param_handle(NULL), type(ctUnknown) { pos.x= offset; }
  DbDriverParam *param_handle;
  ControlType type;
  Position pos;
  Size size;
  std::string caption;
};


class LayoutRow
{
public:
  typedef std::list<LayoutControl> LayoutControls;
  LayoutRow(int seq_no, int hmargin) : _seq_no(seq_no), _hmargin(hmargin), _offset(hmargin), _max_height(0) {}

private:
  LayoutControls _controls;
  int _seq_no;
  int _hmargin;
  int _offset;
  int _max_height;
  std::string _row_desc;

public:
  int seq_no() const { return _seq_no; }
  int offset() const { return _offset; }
  int max_height() const { return _max_height; }
  bool empty() const { return _controls.empty(); }
  void insert(LayoutControl &control)
  {
    _controls.push_back(control);
    _offset+= control.size.width + _hmargin;
    _max_height= std::max(_max_height, control.size.height);
  }
  void add_desc(const std::string &desc)
  {
    if (!desc.empty())
    {
      if (!_row_desc.empty())
        _row_desc.append(" - ");
      _row_desc.append(desc);
    }
  }
  LayoutControls *controls() { return &_controls; }
  LayoutControl * control(int index)
  {
    LayoutControls::iterator i= _controls.begin();
    if (!_controls.empty())
      while (index--)
        if (++i == _controls.end())
          break;
    return (i == _controls.end() ? NULL : &(*i));
  }
  LayoutControl desc_control() const
  {
    LayoutControl ctrl(_offset);
    ctrl.param_handle= _controls.begin()->param_handle;
    ctrl.type= ctDescriptionLabel;
    ctrl.caption= _row_desc;
    return ctrl;
  }
};


typedef std::list<LayoutRow> LayoutRows;


void DbDriverParams::init(
  const db_mgmt_DriverRef &driver,
  const db_mgmt_ConnectionRef &stored_conn,
  const sigc::slot<void> &clear_param_controls,
  const sigc::slot<void, DbDriverParam*, ControlType, const Position &, const Size &, const std::string &> &create_control,
  int first_row_label_width,
  int hmargin,
  int vmargin)
{
  clear_param_controls();
  free_dyn_mem();

  _driver= driver;
  grt::ListRef<db_mgmt_DriverParameter> params= driver->parameters();
  size_t param_count= params.count();

  _collection.resize(param_count);
  _control_name_index.clear();

  DbDriverParam *param_handle;

  // create param handles
  for (size_t n= 0; n < param_count; ++n)
  {
    db_mgmt_DriverParameterRef param= params.get(n);
    param_handle= new DbDriverParam(param, stored_conn);
    _collection[n]= param_handle;
    _control_name_index[control_name_prefix + *param->name()]= param_handle;
  }

  for (int layout_type= 0; layout_type < 2; ++layout_type) // separate cycle for controls tagged as advanced layout
  {
    LayoutRows rows;
    LayoutRow row(0, hmargin);
    int y_offset= vmargin;

    for (Collection::iterator i= _collection.begin(); i != _collection.end(); ++i)
    {
      param_handle= *i;
      db_mgmt_DriverParameterRef param= param_handle->object();

      // process change of layout row
      if (row.seq_no() != param->layoutRow() || layout_type)
      {
        if (!row.empty())
        {
          rows.push_back(row);
          y_offset+= row.max_height() + vmargin;
        }
        row = LayoutRow(param->layoutRow(), hmargin);
      }

      if (layout_type == param->layoutAdvanced())
      {
        if (-1 == row.seq_no() && !layout_type)
          continue;

        // create related label ctrl in UI
        if (param_handle->get_control_type() != ctCheckBox)
        {
          LayoutControl ctrl(row.offset());
          ctrl.param_handle= param_handle;
          ctrl.type= ctLabel;
          ctrl.caption= param->caption();
          if (row.empty())
            ctrl.size.width= first_row_label_width;
          ctrl.pos.y= y_offset;
          //create_control(ctrl.param_handle, ctrl.type, ctrl.pos, ctrl.size, ctrl.caption);
          row.insert(ctrl);
        }

        // create ctrl for param ed
        {
          LayoutControl ctrl(row.offset());
          ctrl.param_handle= param_handle;
          ctrl.type= param_handle->get_control_type();
          ctrl.size.width= param->layoutWidth();
          ctrl.pos.y= y_offset;
          if (param_handle->get_control_type() == ctCheckBox)
            ctrl.caption= param->caption();
          //create_control(ctrl.param_handle, ctrl.type, ctrl.pos, ctrl.size, ctrl.caption);
          row.insert(ctrl);
        }

        // add param description to the row desc
        row.add_desc(param->description());
      }      
    }
    if (!row.empty())
    {
      rows.push_back(row);
      y_offset+= row.max_height() + vmargin;
    }
  
    // visualize controls
    int row_index= 0;
    for (LayoutRows::iterator r= rows.begin(); r != rows.end(); ++r)
    {
      LayoutRow::LayoutControls *controls= r->controls();
      for (LayoutRow::LayoutControls::iterator c= controls->begin(); c != controls->end(); ++c)
      {
        c->pos.y= row_index;
        create_control(c->param_handle, c->type, c->pos, c->size, c->caption);
      }

      // row description control
      {
        LayoutControl ctrl= r->desc_control();
        ctrl.pos.y= row_index;

        create_control(ctrl.param_handle, ctrl.type, ctrl.pos, ctrl.size, ctrl.caption);
      }
      row_index++;
    }
  }
}


void DbDriverParams::free_dyn_mem()
{
  for (Collection::const_iterator i= _collection.begin(); i != _collection.end(); ++i)
    delete *i;
}


grt::DictRef DbDriverParams::get_params() const
{
  grt::DictRef params(_driver.get_grt());
  for (Collection::const_iterator i= _collection.begin(); i != _collection.end(); ++i)
  {
    DbDriverParam *param_handle= *i;
    if (param_handle->get_value().is_valid())
      params.set(param_handle->object()->name(), param_handle->get_value());
  }
  return params;
}


DbDriverParam * DbDriverParams::get(std::string control_name)
{
  String_index::const_iterator i= _control_name_index.find(control_name);
  if (_control_name_index.end() != i)
    return i->second;
  return NULL;
}


std::string DbDriverParams::validate() const
{
  std::string err_msg("");
  for (Collection::const_iterator i= _collection.begin(); i != _collection.end(); ++i)
  {
    DbDriverParam *param_handle= *i;
    const grt::StringRef &value= param_handle->get_value_repr();
    if ((!value.is_valid() || !(*value).length())
      && param_handle->object()->required())
    {
      std::string text;
      text.append("Required parameter '")
        .append(param_handle->object()->name())
        .append("' is not set. Please set it to continue.");
      err_msg= text;
    }
  }
  return err_msg;
}


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


static const char *DEFAULT_RDBMS_ID= "com.mysql.rdbms.mysql";


void DbConnection::init(const db_mgmt_ManagementRef &mgmt)
{
  _mgmt= mgmt;
}


void DbConnection::set_control_callbacks(
  const sigc::slot<void> &clear_param_controls,
  const sigc::slot<void, DbDriverParam*, ControlType, const Position &, const Size &, const std::string &> &create_control)
{
  _clear_param_controls= clear_param_controls;
  _create_control= create_control;
}


db_mgmt_ConnectionRef DbConnection::get_connection()
{
  return _connection;
}


void DbConnection::save_changes()
{
  _connection->driver(get_active_driver());
  grt::replace_contents(_connection->parameterValues(), _db_driver_param_handles.get_params());
}


void DbConnection::set_connection(const db_mgmt_ConnectionRef &connection)
{
  if (_connection != connection)
  {
    _connection= connection;
    
    _db_driver_param_handles.init(connection->driver().is_valid() ? connection->driver() : get_active_driver(),
                                  connection,
                                  _clear_param_controls,
                                  _create_control);
  }
}


void DbConnection::set_connection_keeping_parameters(const db_mgmt_ConnectionRef &connection)
{
  if (_connection != connection)
  {
    _connection= connection;
    _connection->driver(get_active_driver());
    grt::replace_contents(_connection->parameterValues(), _db_driver_param_handles.get_params());
  }
}


void DbConnection::set_active_driver(int driver_index)
{
  //! if (driver_index != _active_db_driver_index)
  {    
    _active_db_driver_index= driver_index;
    
    if (_connection.is_valid())
      _connection->driver(get_active_driver());

    _db_driver_param_handles.init(get_active_driver(),
      _connection,
      _clear_param_controls,
      _create_control);
    
    if (_connection.is_valid())
      save_changes();
  }
}


bool DbConnection::test_connection()
{
  std::auto_ptr<sql::Connection> dbc_conn= get_dbc_connection();
  return (dbc_conn.get() != NULL);
}


std::auto_ptr<sql::Connection> DbConnection::get_dbc_connection()
{
  std::auto_ptr<sql::Connection> dbc_conn= sql::DriverManager::getDriverManager()->getConnection(get_connection());

  if (dbc_conn.get() != NULL)
  {
    if (_rdbms.is_valid() && (_rdbms.id() == DEFAULT_RDBMS_ID))
    {
      // set SQL_MODE variable to be consistent with SQL stored in the model
      bec::GRTManager *grtm= bec::GRTManager::get_instance_for(_rdbms->get_grt());
      grt::ValueRef sql_mode_value= grtm->get_app_option("SqlMode");
      if (sql_mode_value.is_valid() && grt::StringRef::can_wrap(sql_mode_value))
      {
        std::string sql_mode_string= toupper(grt::StringRef::cast_from(sql_mode_value));
        boost::shared_ptr<sql::Statement> stmt(dbc_conn->createStatement());
        stmt->execute(strfmt("SET @DEFAULT_SQL_MODE=@@SQL_MODE, SQL_MODE='%s'", sql_mode_string.c_str()));
      }
    }
  }

  return dbc_conn;
}


std::string DbConnection::validate_driver_params() const
{
  return _db_driver_param_handles.validate();
}


void DbConnection::set_active_rdbms(int index)
{
  _rdbms= _mgmt->rdbms().get(index);
}


grt::ListRef<db_mgmt_Driver> DbConnection::get_driver_list()
{
  return _rdbms->drivers();
}


int DbConnection::get_default_rdbms_index() const
{
  size_t index= find_object_index_in_list(_mgmt->rdbms(), DEFAULT_RDBMS_ID);
  return (int)index;
}


int DbConnection::get_rdbms_default_driver_index() const
{
  size_t result= -1;
  if (_rdbms.is_valid())
  {
    db_mgmt_DriverRef defaultDriver= _rdbms->defaultDriver();
    result= find_object_index_in_list(_rdbms->drivers(), defaultDriver.id());
  }
  return (int)result;
}


grt::ListRef<db_mgmt_Rdbms> DbConnection::get_rdbms_list()
{
  return _mgmt->rdbms();
}
