# $Header: https://oberon.oapd.inaf.it/shins/branches/gigan-virgin_system/templates/seq/templateLoader.py 2013 2019-07-18 14:14:16Z depa $
import sys
import xml.etree.ElementTree as ET
from lxml import etree

from util import exception
from util.shinsLogger import logger
from util import variables as uvars
from util import functions as ufun


class TemplateLoader:
    """Class for loading setup information for a template.

    Class is instantiated by passing a node in an XML tree
    which refers to a template element.

    After that, calling the load() method causes the loading of
    detector, instrument, telescope, and tiptilt setup keywords from
    the XML tree, as well as the loading of the default xml setup and the
    reference xml setup (which has a .ref extension).

    Note that:
    - The template setup is loaded *after* the default setup, so 
      it takes precedence. i.e. it overrides any keyord which is also 
      present in the default setup.
    - The reference setup is loaded *after* the template setup, so 
      it takes precedence.  i.e. it overrides any keyord which is also 
      present in the default setup.
    """
    
    def __init__(self, tpl_node):
        self.tpl_node = tpl_node
        self.TPLID = ''
        self.TPLName = ''
        
        self.info_sup = {}
        self.det_sup = {}
        self.ins_sup = {}
        self.tel_sup = {}
        self.rtc_sup = {}
        
        # OB is parsed at sequencer level
        # This get_ob call also updates the
        # self.TPLID class attribute
        self.tpl_node = self.get_ob(self.tpl_node)
        
        
    def get_node_val(self, xml, xml_node):
        """Check if a given <node> exists and it's alone, then get its inner text"""
        node = xml.findall("./"+xml_node)
        n_node = len(node)
        if n_node != 1:
            logger.warning('{} <{}> in setup'.format(n_node, xml_node))
            raise exception.TemplateException('{} <{}> in setup'.format(n_node, xml_node))
        else:
            return node[0].text


    def get_ob(self, xml):
        """Check and get the xml with default setup or OB setup"""
        logger.info("Checking and get the xml with default setup or OB setup")
        # Get the <TPLID> value
        self.TPLID = self.get_node_val(xml, "TPLID") 
        
        # Get template name, optional.
        tplName = self.tpl_node.findall("./TPLName")
        n_tplnames = len(tplName)
        if n_tplnames == 1:
            self.TPLName = tplName[0].text
        return xml

     
    def get_ref(self, xml):
        """Check and get the xml with reference setup"""
        logger.info("Checking and get the xml with reference setup")

        # Get the <Script> value
        self.Script = self.get_node_val(xml, "Script")

        try:
            self.dpr_type = self.get_node_val(xml, "DPR_TYPE")
        except exception.TemplateException as e:
            self.dpr_type = "NONE"
        
        return xml


    def load_setup(self, xml):
        """Load setup"""
        
        # Load detector, instrument and telescope setup from template
        info_sup = self.get_setup_by_type(xml, "InfoSetup")
        det_sup  = self.get_setup_by_type(xml, "DetectorSetup")
        ins_sup  = self.get_setup_by_type(xml, "InstrumentSetup")
        tel_sup  = self.get_setup_by_type(xml, "TelescopeSetup")
        rtc_sup  = self.get_setup_by_type(xml, "TipTiltCtrlSetup")
        
        # Load mask combination
        if 'MaskComb' in ins_sup:
            det_mask_comb_sup, ins_mask_comb_sup, tel_mask_comb_sup = self.get_mask_combination(ins_sup)
            
            det_sup.update(det_mask_comb_sup)
            ins_sup.update(ins_mask_comb_sup)
            tel_sup.update(tel_mask_comb_sup)
            
            det_mask_comb_sup.clear()
            ins_mask_comb_sup.clear()
            tel_mask_comb_sup.clear()

        # Load filter combination
        if 'FiltComb' in ins_sup:
            det_filt_comb_sup, ins_filt_comb_sup, tel_filt_comb_sup = self.get_filt_combination(ins_sup)

            det_sup.update(det_filt_comb_sup)
            ins_sup.update(ins_filt_comb_sup)
            tel_sup.update(tel_filt_comb_sup)
            
            det_filt_comb_sup.clear()
            ins_filt_comb_sup.clear()
            tel_filt_comb_sup.clear()

        return info_sup, det_sup, ins_sup, tel_sup, rtc_sup


    def load(self):
        """Load a template, check the corresponding default setup and
        reference setup, then merge dicts"""

        
        # ob/blabla.xml
        def_xml_file = "{}/{}.xml".format(uvars.defDir, self.TPLID)
        # ref/blabla.ref
        ref_xml_file = "{}/{}.ref".format(uvars.refDir, self.TPLID)

        logger.debug("Filenames of default and reference setup xml:")
        logger.debug(def_xml_file)
        logger.debug(ref_xml_file)

        logger.info("Checking the validity of default, ob and reference setup xml.")

        def_xml = ufun.parse(def_xml_file)
        def_xml = self.get_ob(def_xml[0])

        # ob_xml "parse" is done in the sequencer
        # ob_xml "get ob" is done in init method
        
        ref_xml = ufun.parse(ref_xml_file)        
        ref_xml = self.get_ref(ref_xml)

        logger.info("Loading setups from default, ob and reference setup xml.")
        def_info, def_det, def_ins, def_tel, def_rtc = self.load_setup(def_xml) # ob/bla.xml
        ob_info, ob_det, ob_ins, ob_tel, ob_rtc = self.load_setup(self.tpl_node) # ~shins/test_OB/testbla.xml
        ref_info, ref_det, ref_ins, ref_tel, ref_rtc = self.load_setup(ref_xml) # ref/bla.ref

        
        logger.info("Merge setups. Order or precedence : ref, ob, def")
        # Info setup dictionary merging
        self.info_sup = def_info       # <InfoSetup> from ob/bla.xml
        self.info_sup.update(ob_info)  # <InfoSetup> from ~shins/test_OB/testbla.xml
        self.info_sup.update(ref_info) # <InfoSetup> from ref/bla.ref
        
        # Detector setup dictionary merging
        self.det_sup = def_det       # <DetectorSetup> from ob/bla.xml
        self.det_sup.update(ob_det)  # <DetectorSetup> from ~shins/test_OB/testbla.xml
        self.det_sup.update(ref_det) # <DetectorSetup> from ref/bla.ref
        
        # Instrument setup dictionary merging        
        self.ins_sup = def_ins       
        self.ins_sup.update(ob_ins)
        self.ins_sup.update(ref_ins)
        
        # Telescope setup dictionary merging
        self.tel_sup = def_tel
        self.tel_sup.update(ob_tel)
        self.tel_sup.update(ref_tel)

        # Tiptilt setup dictionary merging
        self.rtc_sup = def_rtc
        self.rtc_sup.update(ob_rtc)
        self.rtc_sup.update(ref_rtc)
        print(self.info_sup)
        # Re-creating root xml Template with TPLID and TPLNAME
        tplid = {"TPLID": self.TPLID,"TPLName": self.TPLName}
        tmp_xml = ufun.dict_to_xml("Template", tplid)

        # Re-adding updated dicts
        info_xml = ufun.dict_to_xml("InfoSetup", self.info_sup)
        det_xml  = ufun.dict_to_xml("DetectorSetup", self.det_sup)
        ins_xml  = ufun.dict_to_xml("InstrumentSetup", self.ins_sup)
        tel_xml  = ufun.dict_to_xml("TelescopeSetup", self.tel_sup)
        rtc_xml  = ufun.dict_to_xml("TipTiltCtrlSetup", self.rtc_sup)
        
        # Appending on Template node
        tmp_xml.append(info_xml)
        tmp_xml.append(det_xml)
        tmp_xml.append(ins_xml)
        tmp_xml.append(tel_xml)
        tmp_xml.append(rtc_xml)
        
        #logger.debug(etree.tostring(tmp_xml, pretty_print=True))
        logger.info("Validating updated xml")
        try:
            self.validate(tmp_xml, "/ObservationBlock.xsd")
            logger.info("XML valid!")
        except etree.DocumentInvalid as e:
            logger.error("Document invalid!")
            logger.error(e)
        except etree.XMLSchemaParseError as e:#lxml.etree.XMLSchemaParseError as e:
            logger.error('Bad Schema , invalid xsd!')
            logger.error(e)
        return


    def validate(self, xml, xsd_file):
        """Validate the XML tree against its schema.
        It can be a ref setup, or an OB
        """
        
        path = uvars.schemaDir + xsd_file # "/ObservationBlock.xsd"
        parser = etree.XMLParser(remove_comments=True)

        xsd_doc = etree.parse(path, parser=parser)
        xsd = etree.XMLSchema(xsd_doc)
    
        # parse xml
        xsd.assertValid(xml)
        logger.info("Ok validating file")
    
        return xml

    
    def get_setup_by_type(self, setup_tree, setup_type):
        """Retrieves and returns the setup keywords of the
        specified type"""
        setup_dic = {}
        path = './'+setup_type
        setup_list = setup_tree.findall(path)
        if setup_list:
            if len(setup_list[0]) >= 1:
                for el in setup_list[0]:
                    setup_dic[el.tag] = el.text
        return setup_dic


    def get_mask_combination(self, ins_s=None):
        """Loading mask combination"""
        logger.info("Loading mask combination")
        
        if 'MaskComb' in ins_s:
            comb_sup_file = '{}/{}.comb'.format(uvars.combDir, ins_s['MaskComb'])
            cs_tree = ET.parse(comb_sup_file)
            cs_node = cs_tree.getroot()

            det_mask_comb_sup = self.get_setup_by_type(cs_node, "DetectorSetup")
            ins_mask_comb_sup = self.get_setup_by_type(cs_node, "InstrumentSetup")
            tel_mask_comb_sup = self.get_setup_by_type(cs_node, "TelescopeSetup")

            # checking if combination matched instrument mode
            # valid mainly for instrument setup from template XML
            # since refence setup may not have insturment mode
            if 'InstrumentMode' in ins_s:
                if not (ins_s['InstrumentMode'] in ins_s['MaskComb']):
                    det_mask_comb_sup.clear()
                    ins_mask_comb_sup.clear()
                    tel_mask_comb_sup.clear()
                    logger.error("Mask combination does not match instrument mode")
                    raise TemplateException("Mask combination does not match instrument mode")

            return det_mask_comb_sup, ins_mask_comb_sup, tel_mask_comb_sup

        
    def get_filt_combination(self, ins_s=None):
        """Loading filter combination"""
        logger.info("Loading filter combination")
        
        if 'FiltComb' in ins_s:
            comb_sup_file = '{}/{}.comb'.format(uvars.combDir, ins_s['FiltComb'])
            cs_tree = ET.parse(comb_sup_file)
            cs_node = cs_tree.getroot()

            det_filt_comb_sup = self.get_setup_by_type(cs_node, "DetectorSetup")
            ins_filt_comb_sup = self.get_setup_by_type(cs_node, "InstrumentSetup")
            tel_filt_comb_sup = self.get_setup_by_type(cs_node, "TelescopeSetup")

            return det_filt_comb_sup, ins_filt_comb_sup, tel_filt_comb_sup
