# -*- coding: utf-8 -*-

import contextlib
import Queue
import threading

from ext import MetaSingleton
from ext.logger import Logger
from mysql_connection import MySQLConnection

logger = Logger.getLogger(__name__)


class NoConnectionsAvailable(RuntimeError):
    pass


class MySQLPool(object):
    __metaclass__ = MetaSingleton

    def __init__(self, pool_size, **kwargs):
        self._host = kwargs.get('host', 'localhost')
        self._port = kwargs.get('port', 3306)
        self._user = kwargs.get('user')
        self._password = kwargs.get('password')
        self._db = kwargs.get('db')
        self._pool_size = pool_size
        self._pool = Queue.LifoQueue(self._pool_size)
        self._lock = threading.Lock()
        self._thread_connections = threading.local()

        for _ in xrange(self._pool_size):
            self._pool.put(self._create_connection())

        with self.connection():
            pass

    def _create_connection(self):
        return MySQLConnection(host=self._host,
                               port=self._port,
                               user=self._user,
                               password=self._password,
                               db=self._db)

    def _acquire_connection(self, timeout=None):
        """Acquire a connection from the pool."""
        try:
            return self._pool.get(True, timeout)
        except Queue.Empty:
            raise NoConnectionsAvailable(
                    "No connection available from pool within specified "
                    "timeout")

    def _return_connection(self, connection):
        """Return a connection to the pool."""
        self._pool.put(connection)

    @contextlib.contextmanager
    def connection(self, timeout=None):

        connection = getattr(self._thread_connections, 'current', None)

        return_after_use = False
        if connection is None:
            # http://emptysquare.net/blog/another-thing-about-pythons-
            return_after_use = True
            connection = self._acquire_connection(timeout)
            with self._lock:
                self._thread_connections.current = connection

        try:
            yield connection
        except BaseException as e:
            logger.exception(e)
            logger.info("Replacing tainted pool connection")
            connection.refresh()
            connection.open()
            raise
        finally:
            if return_after_use:
                del self._thread_connections.current
                self._return_connection(connection)
