import sys
import errno
import socket

from threading import Thread
from nose.plugins import Plugin
from webob.dec import wsgify

from NaayaTestCase import NaayaTestCase

HTTP_PORT = 15380
SELENIUM_GRID_PORT = 5555
BROWSERS = ['*firefox',
    '*firefoxproxy',
    '*pifirefox',
    '*firefox3',
    '*firefox2',
    '*firefoxchrome',
    '*chrome',
    '*googlechrome',
    '*konqueror',
    '*safari',
    '*safariproxy',
    '*iexplore',
    '*iexploreproxy',
    '*piiexplore',
    '*opera']

@wsgify.middleware
def no_hop_by_hop(req, app):
    response = req.get_response(app)
    del response.headers['Connection']
    return response

def create_user(db, user_id, password, roles):
    import transaction
    app = db.open().root()['Application']
    app.acl_users._doAddUser(user_id, password, roles, [])
    transaction.commit()

def wsgiref_http_server(addr, port, app):
    from wsgiref.simple_server import (make_server, WSGIRequestHandler,
                                       WSGIServer, ServerHandler)

    IGNORED_ERRNO = (errno.EPIPE, errno.ECONNRESET, errno.EAGAIN)
    def should_ignore(exc_info):
        exc_type, exc_value = exc_info[:2]
        if exc_type is socket.error and exc_value.args[0] in IGNORED_ERRNO:
            return True
        else:
            return False

    class QuietServer(WSGIServer):
        def handle_error(self, request, client_address):
            if not should_ignore(sys.exc_info()):
                WSGIServer.handle_error(self, request, client_address)

    class QuietServerHandler(ServerHandler):
        """
        Used to suppress tracebacks of broken pipes.
        """
        def log_exception(self, exc_info):
            if not should_ignore(exc_info):
                ServerHandler.log_exception(self, exc_info)

    class QuietHandler(WSGIRequestHandler):
        """
        Used to suppress WSGI logging interface and the tracebacks generated by
        broken pipes when browsers are closing the connection.
        """
        def log_message(self, format, *args):
            """
            Do nothing. This will ensure that all accesses will not be logged,
            cluttering the output.
            """
            pass

        def handle(self):
            """
            Handle a single HTTP request.

            Code is taken verbatim from the parent class, changing only the
            handler variable to our own handler
            """
            self.raw_requestline = self.rfile.readline()
            if not self.parse_request(): # An error has been sent, just exit
                return

            # this is what we changed
            handler = QuietServerHandler(self.rfile, self.wfile,
                                         self.get_stderr(), self.get_environ())
            # end of changes
            handler.request_handler = self # backpointer for logging
            handler.run(self.server.get_app())

    class WsgirefServer(object):
        def __init__(self, httpd):
            self.httpd = httpd
            self._running = True

        def start(self):
            while self._running:
                self.httpd.handle_request()

        def stop(self):
            self._running = False

    httpd = make_server('127.0.0.1', HTTP_PORT, no_hop_by_hop(app),
                        server_class=QuietServer, handler_class=QuietHandler)
    httpd.socket.settimeout(1)

    return WsgirefServer(httpd)

def cherrypy_http_server(host, port, app):
    from cherrypy.wsgiserver import CherryPyWSGIServer
    return CherryPyWSGIServer((host, port), app)

class NaayaHttpThread(Thread):
    def __init__(self, tzope, httpd_name):
        super(NaayaHttpThread, self).__init__()
        self.tzope = tzope
        self.httpd_name = httpd_name

    def run(self):
        app = self.tzope.wsgi_app
        if self.httpd_name == 'wsgiref':
            self._server = wsgiref_http_server('0.0.0.0', HTTP_PORT, app)
        elif self.httpd_name == 'cherrypy':
            self._server = cherrypy_http_server('0.0.0.0', HTTP_PORT, app)
        else:
            raise ValueError("Unknown http server %r" % self.httpd_name)
        self._server.start()

    def stop(self):
        self._server.stop()

class SeleniumTestCase(NaayaTestCase):
    _selenium_page_timeout = "5000"

    def run(self, result=None):
        if not hasattr(self, 'selenium'):
            from nose import SkipTest
            raise SkipTest("%s needs selenium" % type(self))
        super(SeleniumTestCase, self).run(result)

    def login_user(self, user, password):
        self.selenium.open("/portal/login_html", True)
        self.selenium.wait_for_page_to_load(self._selenium_page_timeout)
        self.selenium.type("__ac_name", user)
        self.selenium.type("__ac_password", password)
        self.selenium.click("submit")
        self.selenium.wait_for_page_to_load(self._selenium_page_timeout)

    def logout_user(self):
        self.selenium.open("/portal/login_html", True)
        self.selenium.wait_for_page_to_load(self._selenium_page_timeout)
        self.selenium.click("link=Logout")
        self.selenium.wait_for_page_to_load(self._selenium_page_timeout)

class NaayaSeleniumTestPlugin(Plugin):
    """
    Nose plugin that prepares the environment for a SeleniumTestCase to run
    """

    name = 'naaya-selenium'

    def __init__(self, tzope):
        super(NaayaSeleniumTestPlugin, self).__init__()
        self.tzope = tzope
        self.http_thread = None
        self.selenium = None
        self.app = None

    def options(self, parser, env):
        Plugin.options(self, parser, env)
        parser.add_option("--ny-instance-port", dest="HTTP_PORT",
                help="Change the port where the browser will look for a "
                        "Naaya instance. Default %s" % HTTP_PORT, type=int,
                        default=HTTP_PORT)
        parser.add_option("--selenium-grid-port", dest="SELENIUM_GRID_PORT",
                help="Change the selenium grid port. MUST be the same used by "
                        "selenium grid (default %s)" % SELENIUM_GRID_PORT,
                        type=int, default=SELENIUM_GRID_PORT)
        parser.add_option("--ny-selenium-browsers", dest="browsers",
                help="Select the browsers used in testing. Use one value from "
                "%s" % BROWSERS, default="*firefox")
        parser.add_option("--ny-cherrypy", dest='httpd_name',
                action='store_const', const='cherrypy', default='wsgiref',
                help="Use CherryPy when starting a test http server")

    def configure(self, options, config):
        global HTTP_PORT, SELENIUM_GRID_PORT
        super(NaayaSeleniumTestPlugin, self).configure(options, config)

        HTTP_PORT = options.HTTP_PORT
        SELENIUM_GRID_PORT = options.SELENIUM_GRID_PORT
        self.browsers = options.browsers
        self.httpd_name = options.httpd_name

    def skip_test_callable(self, test):
        """This replaces the run() method in TestCase `test`"""
        return

    def prepareTestCase(self, testCase):

        if self.http_thread is None:
            th = NaayaHttpThread(self.tzope, self.httpd_name)
            th.start()
            self.http_thread = th

        if self.selenium is None:
            from selenium import selenium
            my_selenium = selenium("localhost", SELENIUM_GRID_PORT,
                    self.browsers, "http://localhost:%s/" % HTTP_PORT)

            try:
                my_selenium.start()
            except socket.error, e:
                assert False, "Could no connect to selenium: %s" % e
            # only set self.selenium if no exception was thrown so far
            self.selenium = my_selenium

        if self.app is None:
            #Set reference to app as well
            cleanup, portal_db_layer = self.tzope.db_layer()
            self.app = portal_db_layer.open().root()['Application']

        testCase.test.selenium = self.selenium
        testCase.test.app = self.app

    def afterTest(self, test):
        if self.selenium is not None:
            self.selenium.delete_all_visible_cookies()

    def finalize(self, result):
        if self.http_thread is not None:
            self.http_thread.stop()
            self.http_thread.join()
            self.http_thread = None

        if self.selenium is not None:
            self.selenium.stop()
            self.selenium = None
