#include "stdafx.h"

#ifndef _WIN32
#include <list>
#include <string>
#endif

#include <boost/foreach.hpp>
#include "driver_manager.h"
#include <gmodule.h>
#include <cppconn/connection.h>
#include <cppconn/statement.h>
#include <cppconn/exception.h>

#include "boost/foreach.hpp"

namespace sql {


typedef std::map<std::string, std::string> Param_types;


static bool conv_to_dbc_value(const std::string &key, const grt::ValueRef value, ConnectOptionsMap &properties, Param_types &param_types)
{
  ConnectPropertyVal tmp;

  switch (value.type())
  {
  case grt::IntegerType:
    {
    grt::IntegerRef val= grt::IntegerRef::cast_from(value);
    std::string param_type;
    if (param_types.find(key) != param_types.end())
      param_type= param_types[key];
    if ((param_type == "tristate") || (param_type == "boolean"))
      tmp = (long long)(*val != 0);
    else
      tmp = (long long)(*val);
    properties[key] = tmp;
    }
    break;

  case grt::DoubleType:
    {
    grt::DoubleRef val= grt::DoubleRef::cast_from(value);
    tmp = *val;
    properties[key] = tmp;
    }
    break;

  case grt::StringType:
    {
    grt::StringRef val= grt::StringRef::cast_from(value);
    tmp = SQLString(val.c_str(), (*val).length());
    properties[key] = tmp;
    }
    break;

  case grt::UnknownType:
  //case grt::AnyType: // equal to grt::UnknownType
  case grt::ListType:
  case grt::DictType:
  case grt::ObjectType:
    break;
  }

  return true;
}


DriverManager *DriverManager::getDriverManager()
{
  static DriverManager *dm= new DriverManager;
  return dm;
}


DriverManager::DriverManager()
:
_driver_path(".")
{
}


void DriverManager::set_driver_dir(const std::string &path)
{
  _driver_path= path;
}


std::auto_ptr<Connection> DriverManager::getConnection(const db_mgmt_ConnectionRef &connectionProperties)
{
  // 0. determine correct driver filename

  std::string library= connectionProperties->driver()->driverLibraryName();

#ifdef _WIN32
  library.append(".dll");
#elif defined(__APPLE__)
  library.append(".dylib");
#else
  library.append(".so");
#endif

  // 1. find driver

  GModule *gmodule= g_module_open((_driver_path + "/" + library).c_str(), G_MODULE_BIND_LOCAL);
  if (NULL == gmodule)
  {
    g_message("Error: %s", g_module_error());
    throw SQLException(std::string("Database driver: Failed to open library '").append(_driver_path + "/" + library).append("'. Check settings.").c_str());
  }
  
  Driver *(* get_driver_instance)()= NULL;
  g_module_symbol(gmodule, "sql_mysql_get_driver_instance", (gpointer*)&get_driver_instance);
  if (NULL == get_driver_instance)
    throw SQLException("Database driver: Failed to get library instance. Check settings.");

  // 2. call driver->connect()
  Param_types param_types;
  {
    grt::ListRef<db_mgmt_DriverParameter> params= connectionProperties->driver()->parameters();
    for (size_t n= 0, count= params.count(); n < count; ++n)
    {
      db_mgmt_DriverParameterRef param= params.get(n);
      param_types[param->name()]= param->paramType();
    }
  }
  //std::map<std::string, ConnectPropertyVal> properties;
  ConnectOptionsMap properties;
  grt::DictRef parameter_values= connectionProperties->parameterValues();
  parameter_values.foreach(sigc::bind(&conv_to_dbc_value, sigc::ref(properties), sigc::ref(param_types)));

  {
    ConnectPropertyVal tmp;
    tmp = (long long)5;
    properties["OPT_CONNECT_TIMEOUT"]= tmp;
  }

  // Connector/C++ has different key names for some connection properties
  ConnectOptionsMap::iterator prop_iter;
  if ((prop_iter= properties.find("host")) != properties.end())
    properties["hostName"]= prop_iter->second;
  if ((prop_iter= properties.find("username")) != properties.end())
    properties["userName"]= prop_iter->second;
  
  // passing some empty values confuse the connector
  {
    std::list<std::string> prop_names;
    prop_names.push_back("socket");
    prop_names.push_back("schema");
    BOOST_FOREACH (const std::string &prop_name, prop_names)
    {
      if ((prop_iter= properties.find(prop_name)) != properties.end())
      {
        sql::SQLString &val= boost::get<sql::SQLString>(prop_iter->second);
        if (val->empty())
          properties.erase(prop_iter);
      }
    }
  }
  
  Driver *driver= get_driver_instance();
  if (NULL == driver)
    throw SQLException("Database driver: Failed to get driver instance. Check  settings.");

  std::auto_ptr<Connection> conn(driver->connect(properties));
  
  // 3. run startup script
  {
    std::auto_ptr<Statement> stmt(conn->createStatement());
    stmt->execute("SET CHARACTER SET utf8");
    stmt->execute("SET NAMES utf8");
  }

  std::string def_schema= parameter_values.get_string("schema", "");
  if (!def_schema.empty())
    conn->setSchema(def_schema);

  return conn;
}


} // namespace sql
