#############################################################
#                                                           #
#   Author: Herve Menager                                   #
#   Organization:'Biological Software and Databases' Group, #
#                Institut Pasteur, Paris.                   #
#   Distributed under GPLv2 Licence. Please refer to the    #
#   COPYING.LIB document.                                   #
#                                                           #
#############################################################
import sys,os
import logging
import cgi, Cookie
import StringIO
from simpletal import simpleTAL, simpleTALES
import urllib
from lxml import etree
import simplejson
import urllib2
import time

MOBYLEHOME = None
if os.environ.has_key('MOBYLEHOME'):
    MOBYLEHOME = os.environ['MOBYLEHOME']

if (os.path.join(MOBYLEHOME,'Src')) not in sys.path:
    sys.path.append(os.path.join(MOBYLEHOME,'Src'))


try:
    import Mobyle.ConfigManager 
except ImportError , err:
    print """Content-type: text/plain\n\n
    Mobyle Internal Error.
    
    Mobyle encountered an internal error:
    The configuration file %s/Local/Config/Config.py is unreachable: %s
    Copy the Example/Local/Config/Config.template.py file to Local/Config/Config.py 
    and edit it to suit your needs (see INSTALL instructions).
    """% (MOBYLEHOME, err)
    sys.exit(1)

import Mobyle.MobyleLogger
from Mobyle.BMPSWorkflow import BMPSWorkflow
Mobyle.MobyleLogger.MLogger()

c_log = logging.getLogger('Mobyle.portal')

# script exceptions are also sent 
def cgi_excepthook(type, value, tback):
    # log the exception here
    c_log.error("portal error")
    c_log.error("caller script: %s, IP: %s" % (os.environ.get("SCRIPT_NAME"), os.environ.get("REMOTE_ADDR")))
    import traceback #@UnresolvedImport
    for line in traceback.format_exception(type, value, tback):
        c_log.error(line.strip('\n'))
    # then call the default handler
    sys.__excepthook__(type, value, tback) 

sys.excepthook = cgi_excepthook

from Mobyle.MobyleError import MobyleError, SessionError
from Mobyle.SessionFactory import SessionFactory
import Mobyle.Net
from Mobyle.Registry import registry

_extra_epydoc_fields__ = [('call', 'Called by','Called by')]
_interval = 10

class CustomResolver(etree.Resolver):
    """
    CustomResolver is a Resolver for lxml that allows (among other things) to
    handle HTTPS protocol, which is not handled natively by lxml/libxml2
    """
    def resolve(self, url, id, context):
        return self.resolve_file(urllib.urlopen(url), context)

class MbCGI(object):
    """
    This class defines the basic structure of a Mobyle CGI, that should be reused by every CGI in the Portal
    """

    response = {}
    
    request = cgi.FieldStorage()

    mime_type="text/html"
  
    def __init__(self, processFunction=None, useSession=False, mime_type=None, wrapper=None):
        self.osDir = os.path.dirname(__file__)
        self.cfg = Mobyle.ConfigManager.Config()
        self.process = processFunction
        if mime_type:
            self.mime_type=mime_type
        if wrapper:
            self.wrapper = wrapper
        if useSession:
            self.load_session()
        self.process(self)
        if useSession:
            self.print_session()
        self.render()

    def process(self):
        raise NotImplementedError      

    def render(self):
        raise NotImplementedError
    
    def print_headers(self):
        xjson = {}
        if hasattr(self, 'title'):
            xjson['title']=self.title
        if hasattr(self, 'error'):
            xjson['error']=self.error
        if(len(xjson.keys())):
            print "X-JSON: " + simplejson.dumps(xjson, encoding='ascii')
        print "Content-type: "+self.mime_type+"\n"

    def load_session(self):
        # SESSION LOAD
        self.sessionFactory = SessionFactory(self.cfg)
        c = Cookie.SimpleCookie(os.environ.get("HTTP_COOKIE"))
        if c.get("sessionKey"):
            self.sessionKey = c.get("sessionKey").value
        if (hasattr(self,'sessionKey') and self.sessionKey):
            try:
                if(c.get("authenticated") and c.get("authenticated").value=="True"):
                    email = Mobyle.Net.EmailAddress( c.get("email").value )
                    if c.get("ticket_id") is None:
                        self.session = None
                        self.sessionKey = None
                    else:
                        self.session = self.sessionFactory.getAuthenticatedSession(email,ticket_id=c.get("ticket_id").value)
                else:
                    self.session = self.sessionFactory.getAnonymousSession(self.sessionKey)
            except SessionError, errMsg:
                # if the session no longer exists, set session to None (should trigger a reload on the client side when reading the cookie)
                self.session = None
                self.sessionKey = None
                c_log.error(errMsg)
        else:
            # if no session is defined, create one
            if (self.cfg.anonymousSession()!='no'):
                self.session = self.sessionFactory.getAnonymousSession()  
                self.sessionKey = self.session.getKey()
            else:
                self.session = None
                self.sessionKey = None
        try:
            self.read_session()
        except SessionError:
            self.session = None
            self.sessionKey = None
            self.read_session()
        if self.session:
            BMPSWorkflow.load_user_workflows(self.session)
                
    def print_session(self):
        self.response['email'] = self.email
        print self.__getSessionCookieSetString__()
    
    def read_session(self):
        if self.session:
            self.email, self.authenticated, self.activated = self.session.getBaseInfo()
        else:
            self.email, self.authenticated, self.activated = (None, False, False)      
        return

    def __getSessionCookieSetString__(self):
        """
        I{__getSessionCookieSetString__} returns the mobyle session cookie string
        """
        c = Cookie.SimpleCookie()
        #c["resetPortal"]=str(self.resetPortal)
        if self.session:
            self.email, self.authenticated, self.activated = self.session.getBaseInfo()
            if self.sessionKey:
                c["sessionKey"]=str(self.sessionKey)
            else:
                c["sessionKey"]=''                
            if self.activated:
                c["activated"]=str(self.activated)
            else:
                c["activated"]=''
            if self.email:
                c["email"]=str(self.email)
            else:
                c["email"]=''
            if self.authenticated:
                c["authenticated"]=str(self.authenticated)
            else:
                c["authenticated"]=''
            if(hasattr(self.session,'ticket_id')):
                c["ticket_id"]=self.session.ticket_id
        else:
            c["sessionKey"]=''
            c["activated"]=''
            c["email"]=''
            c["authenticated"]=''
            c["ticket_id"]=''
        for morsel in c.values():
            morsel['path']='/'
        return c.output()

class ProxyCGI(MbCGI):
    """
    ProxyCGI is used to retrieve a URL on another server and pass its contents as if it originated our server
    it is used mainly in file_load.py to retrieve data located on another server.
    """

    url = ""
    """url of the "retrieved" file."""
    
    def render(self):
        try:
            handle = urllib2.urlopen(self.url)
            self.mime_type = handle.info().getheader("Content-Type")
            self.text = unicode(''.join(handle.readlines()), errors='ignore')
        except (urllib2.URLError ,urllib2.HTTPError), e:
            c_log.error('error while loading data from %s' % self.url)
        self.print_headers()
        print self.text


class TALCGI(MbCGI):

    template_file = ""
    """name of the template file used for the cache"""
 
    def render(self):
        os.chdir(self.osDir)
        self.context = simpleTALES.Context(allowPythonPath=1)    
        templateFile = open(self.template_file)
        self.template = simpleTAL.compileHTMLTemplate (templateFile)
        templateFile.close()
        self.context.addGlobal ("sitemacros", self.template)
        self.context.addGlobal("options", self.response)
        self.context.addGlobal("self", self)
        html = StringIO.StringIO()
        self.template.expand (self.context, html)
        self.print_headers()
        print html.getvalue()

class XSLCGI(TALCGI):

    xslParams = None
    """parameters used when calling an XSL file for rendering"""
  
    def render(self):
        if hasattr(self,'error_msg'):
            self.error=True
            self.template_file="error.html"
            super(XSLCGI, self).render()
        else:
            parser = etree.XMLParser(no_network = False)
            parser.resolvers.add(CustomResolver())
            xml = etree.parse(self.xmlUrl,parser)
            for uri,params in self.xslPipe:
                xslt_doc = etree.parse(uri,parser)
                transform = etree.XSLT(xslt_doc)
                try:
                    parser = etree.XMLParser( no_network = False )
                    xml = transform(xml, **params)
                except Exception, e:
                    c_log.error("error while running %s XSL." % str(uri), exc_info=True)
                    raise e
            self.print_headers()
            print xml

class JSONCGI(MbCGI):
    
    jsonMap = {}
    
    encoder = None
    
    mime_type = "text/plain"
  
    def render(self):
        if hasattr(self,'error_msg'):
            self.error=True
            self.jsonMap['errormsg']=self.error_msg
        self.print_headers()
        try:
            data = simplejson.dumps(self.jsonMap, encoding='ascii', default=self.encoder)
        except UnicodeDecodeError:
            try:
                data = simplejson.dumps(self.jsonMap, encoding='utf-8', default=self.encoder)
            except UnicodeDecodeError:
                try:
                    data = simplejson.dumps(self.jsonMap, encoding='iso-8859-1', default=self.encoder)
                except UnicodeDecodeError:
                    c_log.error("error during json content encoding")
        if not(hasattr(self, 'wrapper')):
            print data
        else:
            print self.wrapper % data

class XMLCGI(MbCGI):
    
    mime_type = "text/xml"

    def render(self):
        print "Cache-Control: no-cache"
        self.print_headers()
        print self.xmltext

class PNGCGI(MbCGI):
    
    mime_type = "image/png"

    def render(self):
        print "Cache-Control: no-cache"
        self.print_headers()
        print self.pngImage
        
def get_formatted_job_entries(job):
    """
    get_formatted_job_entries formats a list of jobs so that it can be both
    serialized to json and later used by the portal.
    """
    if job.has_key('date') and job['date'] is not None:
        jobdatestring = time.strftime( "%x    %H:%M", job['date'])
        jobsortdate = time.strftime( "%Y%m%d%H%M%S", job['date'])
    else:
        jobdatestring = '?'
        jobsortdate = '?'
    job['status_message'] = ""
    if hasattr(job['status'], 'message'):
        job['status_message'] = job['status'].message
    job['status'] = str(job['status'])
    job['url'] = job['jobID']
    job['jobID'] = job['jobPID']
    l = job['jobID'].split('.')
    if len(l)==2:
        server_name='local'
    else:
        server_name= l[0]
    values = {'server':server_name,
                         'programName': job['programName'] ,
                         'jobDate': jobdatestring ,
                         'jobSortDate':jobsortdate,
                         'status':job['status'] ,
                         'status_message':job['status_message'],
                         'dataUsed': job.get('dataUsed'),
                         'url':job['url'],
                         'jobID': job['jobID'],
                         'userName': job['userName']}
    if job.has_key('owner'):
        values['owner'] = job['owner']
    list = [values]
    if job.has_key('subjobs') and job['subjobs'] is not None:
        for subjob in job['subjobs']:
            list += get_formatted_job_entries(subjob)    
    return list

class HTMLCGI(MbCGI):

    mime_type = "text/html"
    def render(self):
        print "Cache-Control: no-cache"
        self.print_headers()
        print self.message

class TextCGI(MbCGI):

    mime_type = "text/plain"
    def render(self):
        print "Cache-Control: no-cache"
        self.print_headers()
        print self.message
