import copy
import urllib3

from atf.common.webservice.Response import Response
from atf.framework.FrameworkBase import *


class Request(FrameworkBase):
    r"""
    Webservices request object.
    It is a container for all webservice request attributes
    """

    (REQUEST_TIMEOUT) = 120
    """Default request timeout"""

    (ERR_WS_BASE) = FrameworkError.ERR_MESA + 0xff
    """Web service control error area"""
    (ERR_INCORRECT_TYPE) = ERR_WS_BASE + 1
    """ Raise the error when variable type is incorrect """
    (ERR_WEBSERVICE_EXCEPTION) = ERR_WS_BASE + 2
    """ Raise the error when exception is generated by URLlib or HTTPlib """

    conPool = urllib3.PoolManager(retries=False)
    """http connection pool manager"""

    def __init__(self,
                 method,
                 url,
                 headers=None,
                 auth=None,
                 data=None,
                 params=None,
                 timeout=None,
                 retries=False,
                 diagLevel=logging.WARNING):
        r"""
        Request object constructor
        @param method:
            Request method [get|post|put|delete|options|patch|head|trace|connect]
        @param url:
            Request url
        @param headers:
            Request headers
        @param auth:
            Authorization mechanism
        @param data:
            Request data
        @param params:
            Request parameters to add to URL
        @param retries:
            Configure the number of retries to allow before raising a :class:`~urllib3.exceptions.MaxRetryError` exception.
            Pass None to retry until you receive a response. 
            Pass a L{urllib3.util.retry.Retry} object for fine-grained control over different types of retries.
            Pass an integer number to retry connection errors that many times, but no other types of errors. 
            Pass zero to never retry.
            Pass `False`, then retries are disabled and any exception is raised immediately. Also, instead of raising a MaxRetryError on redirects,
            the redirect response will be returned.
        @param timeout:
            Request timeout for blocking operation
        @param diagLevel:
            Logger lever
        """
        FrameworkBase.__init__(self, diagLevel=diagLevel)

        self.__method   = method
        self.__url      = url
        self.__headers  = headers or {}
        self.__auth     = auth
        self.__data     = data or {}
        self.__params   = params or {}
        self.__retries  = retries
        self.__timeout  = timeout or Request.REQUEST_TIMEOUT

    def __repr__(self):
        r"""
        Prints out the L{Request} object
        """
        return "%s(%r)" % (self.__class__, self.__dict__)

    def send(self):
        r"""
        Construct and send request
        @return:
            L{Response} object
        """
        
        # Authorize request
        if self.__auth:
            headers = copy.deepcopy(self.__headers)
            self.__auth(headers)
        else:
            headers = self.__headers

        log = "\n===="
        log += "\nREQUEST"
        log += '\n[METHOD]:'.ljust(15) + str(self.__method)
        log += '\n[URL]:'.ljust(15) + str(self.__url)
        log += '\n[HEADERS]:'.ljust(15) + str(self.__headers)
        if self.__data:
            log += '\n[DATA]:'.ljust(15) + str(self.__data)
        log += '\n====\n'

        self.logger.debug(log)

        # Construction empty response object
        response = Response()

        #
        # Note that when we used urllib2 we caught some exceptions and
        # populated the Response object with their values.
        #
        # For urllib3 we just let the exceptions be handled by the 
        # original caller
        #

        # Create and send request
        url_response = Request.conPool.urlopen(
            method=self.__method,
            url=self.__url + self.__constructParams(self.__params),
            headers=headers,
            body=self.__data,
            redirect=False,
            timeout=self.__timeout,
            retries=self.__retries)

        rstatus     = url_response.status
        rurl        = url_response.get_redirect_location()
        rheaders    = url_response.getheaders()
        rdata       = url_response.data
        rmsg        = None
        rrequest    = self

        # Populate response object fields
        response.status     = rstatus
        response.url        = rurl
        response.headers    = rheaders
        response.data       = rdata
        response.msg        = rmsg
        response.request    = rrequest

        log = "\n===="
        log += "\nRESPONSE"
        log += '\n[STATUS]:'.ljust(15) + str(response.status)
        log += '\n[URL]:'.ljust(15) + str(response.url)
        log += '\n[HEADERS]:'.ljust(15) + str(response.headers)

        if response.msg:
            log += '\n[MESSAGE]'.ljust(15) + str(response.msg)
        log += '\n[DATA]:'.ljust(15) + str(response.data)
        log += '\n====\n'
        self.logger.debug(log)

        return response

    def __constructParams(self, params):
        r"""
        Construct a parameter string
        @param params:
            A dictionaty of parameters to apply
        @return:
            A string representation of params dictionary that can be applied to webservice URL
        """

        # if no parameters specified return empty string
        if not params:
            return ''

        paramString = '?'

        if isinstance(params, dict) == False:
            raise FrameworkError(Request.ERR_INCORRECT_TYPE,
                                 'expected param type is <dict>, got %s instead'
                                 % type(params))

        for k, v in params.iteritems():
            paramString += '%s=%s&' %(k,v)

        # strip last character, either & or ?
        return paramString[:-1]
