# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is Reportek version 1.0.
#
# The Initial Developer of the Original Code is European Environment
# Agency (EEA).  Portions created by Finsiel are
# Copyright (C) European Environment Agency.  All
# Rights Reserved.
#
# Contributor(s):
# Soren Roug, EEA
# Miruna Badescu, Finsiel Romania


"""EnvelopeRemoteServicesManager

This class which Envelope subclasses from handles the integration with remote systems (GDEM, UNS, etc.)

"""

# Zope imports
from Globals import InitializeClass
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from AccessControl import ClassSecurityInfo
from Products.Reportek.Document import Document


# Product specific imports
import RepUtils
from constants import QAREPOSITORY_ID
from Products.Reportek.exceptions import EnvelopeReleasedException

import re

class EnvelopeRemoteServicesManager:
    """ This class which Envelope subclasses from handles the integration
        with various remote systems (GDEM, UNS, etc.)
    """

    security = ClassSecurityInfo()
    webq_xml_pat = re.compile(r'(?P<base>.+__\d+)(?P<additional>\.\d+)?\.xml')

    security.declareProtected('View', 'hasSpecificFile')
    def hasSpecificFile(self, schema):
        """ Checks if an envelope has an XML file with certain schema """
        return len([x for x in self.objectValues('Report Document') if x.xml_schema_location == schema])

    # duplicate function - check where it is called and remove
    security.declarePublic('hasFilesForSchema')
    def hasFilesForSchema(self, p_schema_url):
        """ If a values was provided for the p_schema_url, determine whether there are 
            XML files in the envelope with a certain schema. Otherwise checks if there are any files at all
        """
        l_list = [x for x in self.objectValues('Report Document') if x.xml_schema_location == p_schema_url]
        return len(l_list)

    def getFilesForSchema(self, schema_uri):
        """Return a list of Documents names in this envelope that are bound to the schema_uri."""
        return [ doc.id for doc in self.objectValues(Document.meta_type) ]


    # FIXME condition racing - concurent threads on the same envelope will collide
    security.declarePublic('getNextDocId')
    def getNextDocId(self, schema_uri=None, baseName=None):
        """ Find an available name for a document inside this envelope.
        Could be a new document per schema or another document (multilang)
        for a schema that already has some documents.
        schema_uri - give a name in the familly of this schema
        baseName - use this as a base for naming the document, otherwise envelope id will be used"""
        if not baseName:
            baseName = self.id
        else:
            baseName = RepUtils.cleanup_id(baseName)
            if len(baseName) < 3:
                baseName = self.id

        docs = [ doc for doc in self.objectValues('Report Document') ]
        # first file
        if not docs:
            return "%s__1.xml" % baseName

        docNames = [ doc.id for doc in docs ]

        docNamesForSchema = None
        if schema_uri:
            docNamesForSchema = [ doc.id for doc in docs if doc.xml_schema_location == schema_uri ]
        # try to add a document in the name__nn.mm.xml familly
        if docNamesForSchema:
            base = None
            for doc in docNamesForSchema:
                m = self.webq_xml_pat.match(doc)
                if m:
                    base = m.group('base')
                    break
                elif doc.endswith('.xml'):
                    base = doc[:-4]
                    break
            # no naming we know, but there are files for this schema
            if not base:
                base = baseName
            for i in xrange(1, 1000):
                candidate_id = "%s.%d.xml" % (base, i)
                if candidate_id not in docNames:
                    return candidate_id
            raise IndexError("More than 1000 schemas in a mapping")

        else:
            # No other files for this schema, but there are files whatsoever
            for i in xrange(1, 1000):
                candidate_id = "%s__%d.xml" % (baseName, i)
                if candidate_id not in docNames:
                    return candidate_id
            raise IndexError("More than 1000 schemas in a mapping")

    ##################################################
    # QA service
    ##################################################

    security.declareProtected('View', 'canRunQAOnFiles')
    def canRunQAOnFiles(self):
        """ Returns a list of QA script labels
            which can be manually run against the contained XML files
        """
        return getattr(self, QAREPOSITORY_ID).canRunQAOnFiles(self.objectValues('Report Document'))

    security.declareProtected('View', 'note')
    note = PageTemplateFile('zpt/envelope/note', globals())

    security.declareProtected('View', 'runQAScript')
    def runQAScript(self, p_file_url, p_script_id, REQUEST, return_inline=0):
        """ Runs the QA script with the specified id against
            the source XML file
            This method can be only called from the browser and the result is 
            displayed in a temporary page or, in case 'return_inline' is not false,
            the result is a string
        """
        l_qa_app = getattr(self, QAREPOSITORY_ID).getQAApplication()
        if not l_qa_app:
            if return_inline: return 'System error.'
            REQUEST.SESSION.set('note_content_type', 'text/html')
            REQUEST.SESSION.set('note_title', 'Error')
            REQUEST.SESSION.set('note_text', 'The operation could not be completed.')
            REQUEST.RESPONSE.redirect('note')
        try:
            l_file_id, l_tmp = getattr(self, QAREPOSITORY_ID)._runQAScript(p_file_url, p_script_id)
            if return_inline: return l_tmp[1].data
            REQUEST.SESSION.set('note_content_type', l_tmp[0])
            if l_file_id != 'xml':
                REQUEST.SESSION.set('note_title', 'QA result for file %s' %l_file_id)
            else: 
                REQUEST.SESSION.set('note_title', 'QA result for envelope')
            REQUEST.SESSION.set('note_tip', 'This page is only temporary. The page URL address can not be used as a reference to the result. <br /><br />Please use the "<em>File >> Save As</em>" option within your browser to save the validation results.')
            REQUEST.SESSION.set('note_text', l_tmp[1].data)
            REQUEST.RESPONSE.redirect('note')
        except Exception, err:
            l_err = str(err).replace('<', '&lt;')
            if return_inline: return 'The operation could not be completed because of the following error: %s' % l_err
            REQUEST.SESSION.set('note_content_type', 'text/html')
            REQUEST.SESSION.set('note_title', 'Error')
            REQUEST.SESSION.set('note_text', 'The operation could not be completed because of the following error: %s' % l_err)
            REQUEST.RESPONSE.redirect('note')

    security.declareProtected('View', 'runQAScripts')
    def runQAScripts(self, REQUEST):
        """ Runs multiple QA scripts with the specified file ids
            This method can be only called from the browser and the result is 
            displayed in a temporary page

            Parameters in query string:
            |   file_url1=script_id11,script_id12&...
            This function assumes that all qa results have the same content type: 'text/html'
        """
        l_qa_app = getattr(self, QAREPOSITORY_ID).getQAApplication()

        # extract the file ids and scripts from the REQUEST
        l_files_dict = {}
        for l_file, l_scripts in REQUEST.form.items():
            l_files_dict[l_file] = l_scripts.split(',')
        if len(l_files_dict.keys()) == 0 or not l_qa_app:
            REQUEST.SESSION.set('note_content_type', 'text/html')
            REQUEST.SESSION.set('note_title', 'Error in QA service')
            REQUEST.SESSION.set('note_text', 'The quality assessment operation could not be completed.')
            REQUEST.RESPONSE.redirect('note')

        try:
            l_res = []
            for l_file, l_scripts in l_files_dict.items():
                l_file_id = l_file.split('/')[-1]
                l_res.append('<h2>QA results for file %s</h2><hr />' %l_file_id)
                for l_script in l_scripts:
                    l_f, l_d = getattr(self, QAREPOSITORY_ID)._runQAScript(l_file, l_script)
                    l_res.append(l_d[1].data)

            REQUEST.SESSION.set('note_content_type', 'text/html')
            REQUEST.SESSION.set('note_title', 'Quality assessment results')
            REQUEST.SESSION.set('note_tip', 'This page is only temporary. The page url address can not be used as a reference to the result. <br /><br />Please use the "<em>File >> Save As</em>" option within your browser to save the validation results.')
            REQUEST.SESSION.set('note_text', ' '.join(l_res))
            REQUEST.RESPONSE.redirect('note')

        except:
            REQUEST.SESSION.set('note_content_type', 'text/html')
            REQUEST.SESSION.set('note_title', 'Error in QA service')
            REQUEST.SESSION.set('note_text', 'The quality assessment operation could not be completed.')
            REQUEST.RESPONSE.redirect('note')

    ##################################################
    # Remote applications functions
    ##################################################

    security.declareProtected('View', 'getDocumentsForRemoteService')
    def getDocumentsForRemoteService(self):
        """ Finds all Report Documents of type XML that have to begin/complete a remote operation
            Returns the dictionary of {xml_schema_location:[URL_file]}
        """
        l_res = {}
        l_valid_schemas = self.getDataflowMappingsContainer().getSchemasForDataflows(self.dataflow_uris)
        for docu in self.objectValues('Report Document'):
            if docu.content_type == 'text/xml' and docu.xml_schema_location and (docu.xml_schema_location in l_valid_schemas or not l_valid_schemas):
                l_key = str(docu.xml_schema_location)
                if l_res.has_key(l_key):
                    l_res[l_key].append(docu.absolute_url())
                else:
                    l_res[l_key] = [docu.absolute_url()]
        # add the envelope 'xml' method for each obligation
        for l_dataflow in self.dataflow_uris:
            l_res[l_dataflow] = [self.absolute_url() + '/xml']
        return l_res

    security.declareProtected('Use OpenFlow', 'triggerApplication')
    def triggerApplication(self, p_workitem_id, REQUEST=None):
        """ Triggers remote applications """
        app_path = self.getApplicationUrl(p_workitem_id)
        app_ob = self.restrictedTraverse(app_path)
        l_res = app_ob.__of__(self).callApplication(p_workitem_id, REQUEST)

        # returns the result just to be able to see the result in a browser if something goes wrong
        return l_res

    ##################################################
    # WebQ integration functions
    ##################################################

    security.declareProtected('Change Envelopes', 'webqKeepAlive')
    def webqKeepAlive(self):
        """This is used by webq to keep user logged in.
        Webq has all the browser request context we have, because the webq link
        is part of the same domain as BDR. Apache forwards the call to webq domain, keeping
        all the context (cookies). When webq calls this api, with the BDR specific cookies attached
        a login check will be made by PAS, refreshing the ZCacheable TTL for this user
        if he is logged in. 302 will be returned otherwise (to the login page).
        """
        return ''

    # Protected by the same permission as for adding Report Documents
    security.declareProtected('Change Envelopes', 'saveXML')
    def saveXML(self, file_id, file, schema_url='', title='', applyRestriction='', restricted='', REQUEST=''):
        """ Saves the XML file modified by the WebQ as Report Document 
            Called by HTTP
            For now, the return values are file_id- success and 0 - something went wrong, but
            they could be detailed with error codes and values
        """
        if self.released:
            raise EnvelopeReleasedException, "Envelope is released. The document cannot be saved."
        try:
            if file_id in self.objectIds():
                # the file will be replaced
                l_file_id = file_id
                l_file = self._getOb(file_id)
                l_file.manage_file_upload(file=file, content_type='text/xml')
            else:
                # the file will be added
                if not file_id:
                    file_id = RepUtils.generate_id('doc')
                self.manage_addDocument(id=file_id, title=title, file=file, content_type='text/xml')
                l_file = self._getOb(file_id)
            # enforce the schema_url attribute - maybe the 
            # parser did not detect it at file upload 
            if schema_url and l_file.schema_url != schema_url:
                l_file.schema_url = schema_url
            if applyRestriction:
                if restricted == '1':
                    self.manage_restrict([file_id], None)
                elif restricted == '0':
                    self.manage_unrestrict([file_id], None)
            REQUEST.RESPONSE.setHeader('Content-Type', 'text/plain')
            REQUEST.RESPONSE.write('1' + file_id)
            return '1'
        except Exception, err:
            REQUEST.RESPONSE.setHeader('Content-Type', 'text/plain')
            REQUEST.RESPONSE.write('0' + str(err))
            return '0'

    security.declareProtected('Change Envelopes', 'fetchFile')
    def fetchFile(self, p_location='', p_file_name='', REQUEST=None):
        """ Grabbs a file from a certain location and uploads it as Report Document
        """
        l_file, l_content_type = RepUtils.utGrabFromUrl(p_location)
        if l_file is None:
            if REQUEST is not None:
                return self.messageDialog(
                                message='Error fetching the file at: ' + p_location + '!',
                                action=self.absolute_url())
            return 0
        if hasattr(self, p_file_name):
            self.manage_delObjects(p_file_name)
        self.manage_addDocument(id=p_file_name, file=l_file, content_type=l_content_type)
        if REQUEST is not None:
            return self.messageDialog(
                            message='File successfully fetched!',
                            action=self.absolute_url())

    security.declareProtected('View', 'getXMLFiles')
    def getXMLFiles(self):
        """ Returns a list of the XML files in this envelope. 
            The return value is a struct where the key is the schemaurl and the value is a 
            list of structs containing fileurls and file-titles
        """
        l_ret_dict = {}
        for l_doc in self.objectValues('Report Document'):
            if l_doc.content_type == 'text/xml' and l_doc.xml_schema_location:
                if l_ret_dict.has_key(l_doc.xml_schema_location):
                    l_ret_dict[str(l_doc.xml_schema_location)].append((l_doc.absolute_url(), l_doc.title_or_id()))
                else:
                    l_ret_dict[str(l_doc.xml_schema_location)] = [(l_doc.absolute_url(), l_doc.title_or_id())]
        return l_ret_dict

    def getXMLSchemas(self):
        """ Gets all the schemas from the contained XML files """
        l_dict = {}
        l_list = [x.xml_schema_location for x in self.objectValues('Report Document') if x.xml_schema_location != '']
        # remove duplicates
        for l_item in l_list:
            l_dict[l_item] = ''
        return l_dict.keys()

    security.declarePublic('getValidXMLSchemas')
    def getValidXMLSchemas(self):
        """ The purpose is to know if to put an edit button and a record in 'view as...' select
            next to XML files
        """
        return self.getDataflowMappingsContainer().getSchemasForDataflows(self.dataflow_uris)

    def getWebQ_BeforeEditForm_URL(self):
        """ Retrieves the URL to the edit for of the XML file - if any """
        return self.getEngine().webq_before_edit_page

    def getWebQ_MenuEnvelope_URL(self):
        """ Retrieves the URL to the edit for of the XML file - if any """
        return self.getEngine().webq_envelope_menu



# Initialize the class in order the security assertions be taken into account
InitializeClass(EnvelopeRemoteServicesManager)
