<?php
class Creole
{
    /**
     * Constant that indicates a connection object should be used.
     */
    const PERSISTENT = 1;

    /**
     * Flag to pass to the connection to indicate that no case conversions
     * should be performed by ResultSet on keys of fetched rows.
     * @deprecated use COMPAT_ASSOC_LOWER
     */
    const NO_ASSOC_LOWER = 16;

    /**
     * Flag to pass to the connection to indicate that a to-lower case conversion
     * should be performed by ResultSet on keys of fetched rows.
     */
    const COMPAT_ASSOC_LOWER = 32;

    /**
     * Flag to pass to the connection to indicate that an rtrim() should be performed
     * on strings (using ResultSet->getString(), etc.).
     */
    const COMPAT_RTRIM_STRING = 64;

    /**
     * Flag to indicate that all compatibility flags should be set.
     */
    const COMPAT_ALL = 96;

    /**
     * Map of built-in drivers.
     * Change or add your own using registerDriver()
     * @see registerDriver()
     * @var array Hash mapping phptype => driver class (in dot-path notation, e.g. 'mysql' => 'creole.drivers.mysql.MySQLConnection').
     */
    private static $driverMap = array
    (
        'mysql'  => 'drivers.mysql.MySQLConnection',
        'mysqli' => 'drivers.mysqli.MySQLiConnection',
        'pgsql'  => 'drivers.pgsql.PgSQLConnection',
        'sqlite' => 'drivers.sqlite.SQLiteConnection',
        'oracle' => 'drivers.oracle.OCI8Connection',
        'mssql'  => 'drivers.mssql.MSSQLConnection',
        'odbc'   => 'drivers.odbc.ODBCConnection'
    );

    /**
     * Map of already established connections
     * @see getConnection()
     * @var array Hash mapping connection DSN => Connection instance
     */
    private static $connectionMap = array();

    public static function registerDriver( $phptype, $dotpath )
    {
        self::$driverMap[$phptype] = $dotpath;
    }

    /**
     * Removes the driver for a PHP type.  Note that this will remove user-registered
     * drivers _and_ the default drivers.
     * @param string $phptype The PHP type for driver to de-register.
     * @see registerDriver()
     */
    public static function deregisterDriver( $phptype )
    {
        unset( self::$driverMap[$phptype] );
    }

    /**
     * Returns the class path to the driver registered for specified type.
     * @param string $phptype The phptype handled by driver (e.g. 'mysql', 'mssql', '*').
     * @return string The driver class in dot-path notation (e.g. creole.drivers.mssql.MSSQLConnection)
     *                  or NULL if no registered driver found.
     */
    public static function getDriver( $phptype )
    {
        if ( isset( self::$driverMap[$phptype] ) ) 
        {
            return self::$driverMap[$phptype];
        }
        else
        {
            return null;
        }
    }

    public static function getConnection( $dsn, $flags = 0 )
    {
        $dsninfo = $dsn;

        ksort( $dsninfo );
        $connectionMapKey = crc32( serialize( $dsninfo + array( 'compat_flags' => ( $flags & Creole::COMPAT_ALL ) ) ) );

        if( isset( self::$connectionMap[$connectionMapKey] ) )
        {
            if( isset( self::$connectionMap[$connectionMapKey][1] ) )
            {
                // is persistent
                // a persistent connection with these parameters is already there,
                // so we return it, no matter what was specified as persistent flag
                $con = self::$connectionMap[$connectionMapKey][1];
            } 
            else
            {
                // we don't have a persistent connection, and since the persistent
                // flag wasn't set either, we just return the non-persistent connection
                $con = self::$connectionMap[$connectionMapKey][0];
            }

            if ( $con->isConnected() )
                return $con;
        }

        $type = $dsninfo['phptype'];

        if ( !isset( self::$driverMap[$type] ) ) 
        {
            throw new SQLException( "No driver has been registered to handle connection type: $type" );
        }

        $clazz = self::import( self::$driverMap[$type] );
        $obj = new $clazz();

        if ( !( $obj instanceof Connection ) ) 
        {
            throw new SQLException("Class does not implement creole.Connection interface: $clazz");
        }

        try 
        {
            $obj->connect( $dsninfo, $flags );
        } 
        catch( SQLException $sqle )
        {
            $sqle->setUserInfo( $dsninfo );
            throw $sqle;
        }
        $persistent = ( $flags & Creole::PERSISTENT ) === Creole::PERSISTENT;
        return self::$connectionMap[$connectionMapKey][( int ) $persistent] = $obj;
    }

    public static function import( $class ) 
    {
        $pos = strrpos( $class, '.' );
        // get just classname ('path.to.ClassName' -> 'ClassName')
        if ( $pos !== false ) 
        {
            $classname = substr( $class, $pos + 1 );
        }
        else
        {
          $classname = $class;
        }
        if ( !class_exists( $classname, false ) )
        {
            $path = strtr( $class, '.', DIRECTORY_SEPARATOR ) . '.php';
            
            $ret = include_once( $path );
            if ( $ret === false )
            {
                throw new SQLException( "Unable to load driver class: " . $class );
            }
            if ( !class_exists( $classname ) ) 
            {
                throw new SQLException( "Unable to find loaded class: $classname (Hint: make sure classname matches filename)" );
            }
            
        }
        return $classname;
    }
}