# -*- coding: utf8 -*-
'''
**********************************************
*      P R O T O C O L - C L A S S
*      for the biocase protocol
**********************************************
$RCSfile: biocase_response.py,v $
$Revision: 911 $
$Author: markus $
$Date: 2008-04-30 23:52:39 +0200 (Mi, 30. Apr 2008) $

Request and Response classes to support the biocase protocol.
'''

import types, sys, os
from datetime                             import datetime            
from xml.sax                            import parse, parseString
from cgi                                 import FieldStorage
from os.path                             import isfile

import xml.etree.ElementTree as ET
from biocase.tools.xmlutils              import element, subelement, addOptionalAttributes

import biocase.wrapper
from biocase.tools.various_functions        import isTrue
from biocase.wrapper.cmf_base       import CMFClass
from biocase.wrapper.operations     import recordStatus
from biocase.wrapper.errorclasses        import *
from biocase.wrapper.protocol.BioCASe.biocase_handler    import requestHandler
from biocase.wrapper.protocol.base_request   import RequestBaseClass
from biocase.wrapper.protocol.base_response  import ResponseBaseClass
from biocase.wrapper.sql.operators       import compressOperator

import biocase.configuration
cfg = biocase.configuration.Cfg()

import logging
log = logging.getLogger("pywrapper.protocol.biocase")


# ------------------------------------------------------------------------------------
class ResponseClass(ResponseBaseClass):
    '''Response class implementing teh biocase protocol for responses:
    http:www.biocase.org/protocol
    '''
    # protocol namespace and url:
    __protocol__    = u'BioCASE Unitlevel Access Protocol version 1.31'
    __protocolURL__ = u'http://www.bgbm.org/biodivinf/schema/protocol_1_31.xsd'
    __protocolNS__  = u'http://www.biocase.org/schemas/protocol/1.3'

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    def __init__(self, dsaObj, diagnosticsHandler):
        ResponseBaseClass.__init__(self, dsaObj, diagnosticsHandler)
        # status about paging, record count etc.
        self.recordStatus = recordStatus()

        # root element
        ns=self.__class__.__protocolNS__
        xsi = 'http://www.w3.org/2001/XMLSchema-instance'
        self.doc = element(ns, "response")
        addOptionalAttributes(self.doc, xsi, schemaLocation=ns+' '+self.__class__.__protocolURL__)
        # header information:
        self._header = subelement(self.doc, ns, "header")
        subelement(self._header, ns, "version", os.name, software='os')
        subelement(self._header, ns, "version", sys.version, software='python')
        subelement(self._header, ns, "version", biocase.__version__, software='pywrapper')
        # add sendTime header info (2001-09-11T09:30:47-05:00)
        subelement(self._header, ns, "sendTime", datetime.now().isoformat())
        try:
            source="%s@%s" %(dsaObj.name, cfg.getIP())
        except:
            source="???"
            log.warn('Source hostname could not be determined')
        subelement(self._header, ns, "source", source)
        self._reqtype = subelement(self._header, ns, "type")
        # create biocase protocol content element. Will carry the self.content payload later (ABCD root element for example)
        self._content = subelement(self.doc, ns, "content")
        # create diagnostics root element
        self._diagnostics = subelement(self.doc, ns, "diagnostics")

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    def postprocessMessage(self, requestObj, dbmodVersion):
        ns=self.__class__.__protocolNS__
        try:
            # add DESTINATION header
            destinations=[]
            if len(requestObj.sources) > 0:
                for dest in requestObj.sources:
                    destinations.append(element(ns,"destination", dest))
            else:
                destinations.append(element(ns,"destination",'unknown'))
                log.warn('Destination hostname could not be determined')
            self._header[5:5]=destinations
            # fill REQUEST TYPE header
            if requestObj.type is not None :
                self._reqtype.text = requestObj.type
            # fill DBMOD SOFTWARE VERSION header
            if dbmodVersion is not None:
                self._header[3:3] = [element(ns, "version", dbmodVersion, software='dbmod')]
        except:
            log.exception("Error when postprocessing message header")
        try:
            # add RECORD STATUS content attributes
            if self.recordStatus is not None:
                attrs={"totalSearchHits":self.recordStatus.total, "recordDropped":self.recordStatus.drop, "recordCount":self.recordStatus.count, "recordStart":self.recordStatus.start}
                addOptionalAttributes(self._content, None, **attrs) # no namespace as in schema
        except:
            log.exception("Error when postprocessing record status")
        # add error DIAGNOSTICS
        if len(self.diagnosticsHandler.diagnostics) > 0:
            for d in self.diagnosticsHandler.diagnostics:
                subelement(self._diagnostics, ns, "diagnostic", content=d.msg, severity=d.severity, code=d.code)
        else:
            subelement(self._diagnostics, ns, "diagnostic", content="No problems encountered", severity="info")

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    def setCapabilitiesContent(self):
        self.type = "capabilities"
        log.debug("DOING CAPABILITIES")
        # create capabilities ElementTree:
        ns=self.__class__.__protocolNS__
        capa_elem = element(ns,"capabilities") # the root element
        # add to main message self.doc
        self._content.append(capa_elem)
        for schema in self.psfObj.schemas.values():
            # add all mapped attributes of DB into Concepts elements.
            # Use the full CMF class to read CMFs:
            log.debug("Capabilities: Inspect schema %s"%schema.name)
            CMFobj = CMFClass()
            CMFobj.loadCMFdata(schema.conceptMappingFile, self.psfObj)
            try:
                CMFobj.loadCMFdata(schema.conceptMappingFile, self.psfObj)
            except CouldNotPickleError:
                log.warn("CouldNotPickleError")
            except Exception, e:
                import sys
                log.debug(sys.exc_info()[0])
                log.debug(sys.exc_info()[1])
                CMFError(cmf=schema.name)
                continue
            # create schema element
            schema_elem = subelement(capa_elem, ns, "SupportedSchemas", namespace=schema.name)
            if isTrue(schema.request):
                addOptionalAttributes(schema_elem, None, request="true")
            else:
                addOptionalAttributes(schema_elem, None, request="false")
            if isTrue(schema.response):
                addOptionalAttributes(schema_elem, None, response="true")
            else:
                addOptionalAttributes(schema_elem, None, response="false")
            # loop thru concepts and output each xpath plus datatype & searchable in diagnostics
            concepts = CMFobj.path2mappings.keys()
            concepts.sort()
            for path in concepts:
                CMFelem = CMFobj.path2ElemObj[path]
                # is it searchable ?
                log.debug(path)
                concept_elem = subelement(schema_elem, ns, "Concept", path, searchable=str(CMFelem.isSearchable()))
                # which w3c data type does it have?
                if CMFelem.Type is not None:
                    addOptionalAttributes(concept_elem, None, datatype=CMFelem.Type)

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    def setCountContent(self, x):
        self.type = "count"
        # add to main message self.doc
        self._content.append(element(self.__class__.__protocolNS__, "count", str(x)))
        
    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    def setScanContent(self, list):
        self.type = "scan"
        ns=self.__class__.__protocolNS__
        # create scan ElementTree:
        scan_elem = element(ns, "scan")
        # add to main message self.doc
        self._content.append(scan_elem)
        # set recordCount attribute value
        self.recordStatus.count = len(list)
        self.recordStatus.start = 0
        self.recordStatus.drop  = 0
        self.recordStatus.total = len(list)
        # create XML scan list
        list.sort()
        for val in list:
            if val != None and len(val) > 0:
                subelement(scan_elem, ns, "value", val)
        # set content property

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    def setSearchContent(self, etree, recStatus):
        self.type = "search"
        # add to main message self.doc
        self._content.append(etree)
        self.recordStatus = recStatus
