#!/usr/bin/env python

import urllib
import urllib2
import urlparse
import time
import getpass
import os
import stat
import ssl
import types

from dm.common.constants import dmServiceConstants
from dm.common.exceptions.configurationError import ConfigurationError
from dm.common.exceptions.authorizationError import AuthorizationError
from dm.common.exceptions.invalidSession import InvalidSession
from dm.common.exceptions.urlError import UrlError
from dm.common.utility.loggingManager import LoggingManager
from dm.common.utility.configurationManager import ConfigurationManager
from dm.common.utility.osUtility import OsUtility
from dm.common.client.dmExceptionMapper import DmExceptionMapper
from dm.common.client.dmHttpsHandler import DmHttpsHandler

class SessionManager:
    """ Class for session management. """

    def __init__(self):
        self.sessionCookie = None
        self.host = None
        self.logger = LoggingManager.getInstance().getLogger(self.__class__.__name__)
        self.username = ''
        self.password = ''
        self.urlOpener = None
        cm = ConfigurationManager.getInstance()
        self.sessionCacheFile = cm.getSessionCacheFile()
        self.requireSessionCredentials = cm.getRequireSessionCredentials()

    def setHost(self, host):
        self.host = host

    def hasSession(self):
        """ Return true if we have session established. """
        if self.sessionCookie is not None:
            return True
        return False

    def establishSession(self, url, username, password, selector='/dm/login'):
        self.host = url
        self.username = username
        self.password = password
        self.sessionCookie = self.loadSession()
        if self.sessionCookie is not None:
            return

        # Could not load session.
        try:
            # User will be asked for username/password if they are not
            # provided.
            data = { 'username' : self.getUsername(username), 'password' : self.getPassword(password) }
            self.logger.debug('Establishing session for user %s @ %s)' % (username, url))
            (response,responseData) = self.sendRequest(url='%s%s' % (url, selector), method='POST', contentType='application/x-www-form-urlencoded', data=urllib.urlencode(data))
        except urllib2.URLError, ex:
            self.logger.exception('%s' % ex)
            raise UrlError(exception=ex)

        #self.logger.debug('Got headers: %s' % response.headers)
        # This will save session cookie.
        self.sessionCookie = self.checkResponseHeadersForErrorsAndSaveSession(response.headers)
        self.logger.debug('User %s session cookie: %s' % (username, self.sessionCookie))

    def getUsername(self, username):
        if username is None and self.requireSessionCredentials:
            return self.askForUsername()
        return username

    def askForUsername(self):
        defaultUsername = getpass.getuser()
        username = raw_input('Username [%s]: ' % defaultUsername)
        username = username.strip()
        if not len(username):
            username = defaultUsername
        return username

    def getPassword(self, password):
        if password is None and self.requireSessionCredentials:
            return self.askForPassword()
        return password

    def askForPassword(self):
        password = getpass.getpass()
        password = password.strip()
        if not len(password):
            raise AuthorizationError('Empty password provided.')
        return password

    def clearSessionFile(self):
        if self.sessionCacheFile is None:
            return
        try:
            self.logger.debug('Clearing session cache: %s' % (self.sessionCacheFile))
            OsUtility.removeFile(self.sessionCacheFile)
        except Exception, ex:
            # ignore errors.
            self.logger.warn('Could not clear session cache: %s' % (ex))
            pass

    def saveSession(self, sessionCookie):
        if self.sessionCacheFile is None:
            return
        if sessionCookie is None:
            return
        try:
            f = open(self.sessionCacheFile, 'w')
            f.write('%s' % sessionCookie)
            f.close()
            os.chmod(self.sessionCacheFile, stat.S_IRUSR|stat.S_IWUSR|stat.S_IXUSR)
        except Exception, ex:
            self.logger.warn('Could not save session: %s' % (ex))

    def loadSession(self):
        if self.sessionCacheFile is None:
            return None
        try:
            f = open(self.sessionCacheFile, 'r')
            session = f.read()
            expires = session.split(';')[1].split('=')[-1]
            t = time.mktime(time.strptime(expires, '%a, %d %b %Y %H:%M:%S %Z'))
            now = time.time()
            if t < now:
                return None
            else:
                self.logger.debug('Loaded session from %s: %s' % (self.sessionCacheFile, session))
                return session
        except Exception, ex:
            self.logger.warn('Could not load session: %s' % (ex))
        return None

    def getUrlOpener(self, protocol):
        if not self.urlOpener:
            if protocol == dmServiceConstants.DM_SERVICE_PROTOCOL_HTTPS:
                # HTTPS, use custom https handler, which 
                # should work even if any of cert/key/cacert files is None
                cm = ConfigurationManager.getInstance()
                keyFile = cm.getSslKeyFile()
                certFile = cm.getSslCertFile()
                self.urlOpener = urllib2.build_opener(DmHttpsHandler)
                #self.logger.debug('Using Dm HTTPS Handler')
            else:
                # HTTP, use standard http handler
                self.urlOpener = urllib2.build_opener(urllib2.HTTPHandler)
        # Install opener before returning it.
        urllib2.install_opener(self.urlOpener)
        return self.urlOpener

    def sendRequest(self, url, method, contentType='html', data={}):
        """ Send http request without cookies. """
        if url.find('://') < 0:
            url = '%s%s' % (self.host, url)
        parsedUrl = urlparse.urlparse(url)
        protocol = parsedUrl[0]
        path = parsedUrl[2]
        #self.logger.debug('Sending request: %s' % url)
        encodedData = ''
        if data is not None:
            if type(data) == types.DictType and len(data):
                encodedData=urllib.urlencode(data)
                contentType='application/x-www-form-urlencoded'
            elif type(data) == types.StringType:
                encodedData = data
        request = urllib2.Request(url, data=encodedData)
        request.get_method = lambda: method
        request.add_header('Content-Type', contentType)
        request.add_header('Content-Length', str(len(data)))
        if self.sessionCookie != None:
            request.add_header('Cookie', self.sessionCookie)
        try:
            opener = self.getUrlOpener(protocol)
            response = opener.open(request)
        except urllib2.HTTPError, ex:
            # If we see dm headers, dm exception will be thrown,
            # otherwise we'll throw UrlError
            self.checkResponseHeadersForErrors(ex.hdrs)
            self.logger.exception('%s' % ex)
            raise UrlError(exception=ex)
        except urllib2.URLError, ex:
            self.logger.exception('%s' % ex)
            raise UrlError(exception=ex)

        # Check headers for errors and update session cookie
        sessionCookie = self.checkResponseHeadersForErrorsAndSaveSession(response.headers)
        if sessionCookie != None:
            self.sessionCookie = sessionCookie
        responseData = response.read()
        return (response, responseData)

    def sendSessionRequest(self, url, method, contentType='html', data={}):
        """ Send session request. """
        if self.sessionCookie is None:
            self.establishSession(self.host, self.username, self.password)
        try:
            return self.sendRequest(url, method, contentType, data)
        except InvalidSession, ex:
            self.clearSessionFile()
            self.establishSession(self.host, self.username, self.password)
            return self.sendRequest(url, method, contentType, data)

    def checkResponseHeadersForErrorsAndSaveSession(self, responseHeaders):
        try:
            DmExceptionMapper.checkStatus(responseHeaders)
            sessionCookie = responseHeaders.get('Set-Cookie')
            self.saveSession(sessionCookie)
            return sessionCookie
        except (AuthorizationError, InvalidSession), ex:
            self.clearSessionFile()
            raise

    def checkResponseHeadersForErrors(self, responseHeaders):
        try:
            DmExceptionMapper.checkStatus(responseHeaders)
        except AuthorizationError, ex:
            self.clearSessionFile()
            raise

#######################################################################
# Testing.
if __name__ == '__main__':
    sm = SessionManager.createSession()