#ifndef _DRIVER_MANAGER_H_
#define _DRIVER_MANAGER_H_


#include "cppdbc_public_interface.h"
#include "grts/structs.db.mgmt.h"
#include <cppconn/driver.h>
#include <boost/cstdint.hpp>
#include <boost/shared_ptr.hpp>
#include <memory>
#include <set>

#ifndef THROW
#ifdef _MSC_VER
#define THROW(...)
#else
#define THROW(...) throw (__VA_ARGS__)
#endif
#endif


namespace sql
{
typedef boost::shared_ptr<Connection> ConnectionPtr;

  
class TunnelConnection
{
public:
  virtual void connect(db_mgmt_ConnectionRef connectionProperties)= 0;
  virtual void disconnect()= 0;
  virtual int get_port()= 0;
};


class ConnectionWrapper
{
  ConnectionPtr _conn;
  boost::shared_ptr<TunnelConnection> _tunnel_conn;
public:
  ConnectionWrapper() {}
  ConnectionWrapper(std::auto_ptr<Connection> conn,TunnelConnection* tunn_conn):_conn(conn),_tunnel_conn(tunn_conn) {}
  void reset() {_conn.reset();}
  Connection* operator->() {return _conn.get();}
  Connection* get() {return _conn.get();}
  ConnectionPtr get_ptr() {return _conn;}
};

  
class CPPDBC_PUBLIC_FUNC Authentication
{
protected:
  db_mgmt_ConnectionRef _props;
  std::string _service;
  char *_password;
  
  Authentication();
  Authentication(const db_mgmt_ConnectionRef &props, const std::string &service = "");
public:
  typedef boost::shared_ptr<Authentication> Ref;
  
  static Ref create(const db_mgmt_ConnectionRef &props, const std::string &service = "");
  ~Authentication();
  
  db_mgmt_ConnectionRef connectionProperties() const { return _props; }
  std::string service() const { return _service; }
  
  void set_password(const char *password);
  const char* password() const { return _password; }
  void invalidate();
  bool is_valid() const { return _password != NULL; }
};

typedef std::set<Authentication::Ref> AuthenticationSet;

class CPPDBC_PUBLIC_FUNC AuthenticationError : public std::runtime_error
{
  Authentication::Ref _authobj;
public:
  AuthenticationError(const AuthenticationError &other) : std::runtime_error(other), _authobj(other._authobj) {}
  AuthenticationError(const std::string &what, Authentication::Ref auth)
  : std::runtime_error(what), _authobj(auth) {}
  virtual ~AuthenticationError() THROW () {}
  
  Authentication::Ref authentication() { return _authobj; }
};


class CPPDBC_PUBLIC_FUNC DriverManager
{
  std::string _driver_path;
  std::list<Driver *> _drivers;

public:
  // Returns the DriverManager singleton
  static DriverManager *getDriverManager();

  DriverManager();

  // Sets the path to the directory where drivers are located
  void set_driver_dir(const std::string &path);

  // Callback to initialize connection with DBMS specific startup script
  typedef boost::function<void (Connection*, const db_mgmt_ConnectionRef&)> ConnectionInitSlot;

  // Returns a Connection object for the give connection params
  ConnectionWrapper getConnection(const db_mgmt_ConnectionRef &connectionProperties, ConnectionInitSlot connection_init_slot= ConnectionInitSlot());

  ConnectionWrapper getConnection(const db_mgmt_ConnectionRef &connectionProperties, AuthenticationSet &passwords, ConnectionInitSlot connection_init_slot= ConnectionInitSlot());
  
  TunnelConnection *getTunnel(const db_mgmt_ConnectionRef &connectionProperties);
  
  // Returns the list of available drivers
  std::list<Driver *> getDrivers();
  
public:
  typedef boost::function<TunnelConnection* (const db_mgmt_ConnectionRef &)> TunnelFactoryFunction;
  typedef boost::function<bool (const db_mgmt_ConnectionRef &, std::string &)> PasswordFindFunction;
  typedef boost::function<std::string (const db_mgmt_ConnectionRef &, bool)> PasswordRequestFunction;

  void setTunnelFactoryFunction(TunnelFactoryFunction function);
  void setPasswordFindFunction(PasswordFindFunction function);
  void setPasswordRequestFunction(PasswordRequestFunction function);
  
  std::string requestPassword(const db_mgmt_ConnectionRef &conn, bool forceAsk) { return _requestPassword(conn, forceAsk); }
private:
  TunnelFactoryFunction _createTunnel;
  PasswordFindFunction _findPassword;
  PasswordRequestFunction _requestPassword;
  
  std::string _cachedPassword;
  std::string _cacheKey;
  time_t _cacheTime;
  
  ConnectionWrapper getConnection(const db_mgmt_ConnectionRef &connectionProperties, AuthenticationSet *passwords, ConnectionInitSlot connection_init_slot);
};


class Dbc_connection_handler
{
public:
  Dbc_connection_handler() : id(-1), autocommit_mode(true), is_stop_query_requested(false) {}
  typedef boost::shared_ptr<Dbc_connection_handler> Ref;
  typedef ConnectionWrapper ConnectionRef;
  ConnectionRef ref;
  std::string name;
  boost::int64_t id;
  std::string active_schema;
  bool autocommit_mode;
  bool is_stop_query_requested;
};


} // namespace sql


#endif // _DRIVER_MANAGER_H_
