import threading
from PyAnalysisTools.base.YAMLHandle import YAMLLoader
from PyAnalysisTools.base import _logger, InvalidInputError


class DataSetStore(object):
    """
    Thread-safe singleton implementation to store dataset information and avoid re-reading db from yml file
    """
    _instance = None
    _lock = threading.Lock()

    def __new__(cls, *args):
        if DataSetStore._instance is None:
            with DataSetStore._lock:
                if DataSetStore._instance is None:
                    DataSetStore._instance = super(DataSetStore, cls).__new__(cls, *args)
        return DataSetStore._instance

    def __init__(self, dataset_info):
        if not hasattr(self, 'dataset_info'):
            self.dataset_info = YAMLLoader.read_yaml(dataset_info)


class Dataset(object):
    def __init__(self, **kwargs):
        kwargs.setdefault("is_data", False)
        kwargs.setdefault("is_mc", False)
        for k, v in kwargs.iteritems():
            setattr(self, k.lower(), v)
        if "cross_section" in kwargs and "*" in str(kwargs["cross_section"]):
            self.cross_section = eval(kwargs["cross_section"])


class XSInfo(object):
    def __init__(self, dataset):
        if dataset.is_data:
            return
        for attr, val in dataset.__dict__.iteritems():
            setattr(self, attr, val)
        self.xsec = self.cross_section
        if not hasattr(dataset, 'kfactor'):
            self.kfactor = 1.
        if not hasattr(dataset, 'filtereff'):
            self.filtereff = 1.


class XSHandle(object):
    def __init__(self, cross_section_file, read_dsid=False):
        if cross_section_file is None:
            self.invalid = True
            return
        self.invalid = False
        _logger.debug("XSHandle read cross section file {:s}".format(cross_section_file))
        cross_sections = YAMLLoader.read_yaml(cross_section_file)
        if not read_dsid:
            self.cross_sections = {value.process_name: XSInfo(value)
                                   for value in cross_sections.values() if value.is_mc}
        else:
            self.cross_sections = {key: XSInfo(val)
                                   for key, val in cross_sections.iteritems() if val.is_mc}

    def get_xs_scale_factor(self, process):
        """
        Retrieve cross-section scale factor for a single process calculated based on PMG (or custom) cross section,
        filter-efficiency and k-factor (last two are optional)
        :param process: process name
        :type process: str
        :return: cross section scale factor
        :rtype: float
        """
        if self.invalid:
            return 1.
        if process not in self.cross_sections:
            _logger.error("Could not found process %s in cross section configuration" % process)
            return 1.
        xs_info = self.cross_sections[process]
        xs_scale_factor = float(xs_info.xsec)
        if hasattr(xs_info, "filtereff"):
            xs_scale_factor *= xs_info.filtereff
        #TODO: temporary fix
        # if hasattr(xs_info, "kfactor"):
        #     xs_scale_factor *= xs_info.kfactor
        return xs_scale_factor

    def retrieve_xs_info(self, process):
        if self.invalid:
            raise InvalidInputError("Invalid config of XSHandle")
        xsec, filter_eff, kfactor = None, 1., 1.
        if process not in self.cross_sections:
            _logger.error("Could not found process %s in cross section configuration" % process)
            return xsec, filter_eff, kfactor
        xs_info = self.cross_sections[process]
        xsec = float(xs_info.xsec)
        if hasattr(xs_info, "filtereff"):
            filter_eff = xs_info.filtereff
        if hasattr(xs_info, "kfactor"):
            kfactor = xs_info.kfactor
        return xsec, filter_eff, kfactor

    def get_lumi_scale_factor(self, process, lumi, mc_events, fixed_xsec=None):
        """
        Retrieve scale factor to scale process to specific luminosity
        :param process: process name
        :type process: str
        :param lumi: luminosity
        :type lumi: float
        :param mc_events: number of initially produced MC events (actually sum of weights)
        :type mc_events: float
        :param fixed_xsec: use fixed cross section instead of stored for process (optional) - useful for limit setting
        :type fixed_xsec: float
        :return: luminosity scale factor
        :rtype: float
        """
        if fixed_xsec:
            xsec = fixed_xsec
        else:
            xsec = self.get_xs_scale_factor(process)
        return xsec * lumi * 1000. * 1000. / mc_events

    def get_ds_info(self, process, element):
        """
        Retrieve a configurable attribute from a given process
        :param process: process name
        :type process: str
        :param element: attribute name (e.g. label, name)
        :type element: str
        :return: attribute
        :rtype: type(attribute)
        """
        try:
            return getattr(self.cross_sections[process], element)
        except KeyError:
            _logger.error('Cannot find process {:s} in cross section table'.format(process))
        except AttributeError:
            _logger.error('Cannot find element {:s} for process {:s}'.format(element, process))
        return None

@@ -787,32 +787,100 @@
 ataset(object):%0A
+    %22%22%22%0A    Representation of a single dataset (data or MC)%0A    %22%22%22%0A
     def __init__
@@ -893,24 +893,24 @@
  **kwargs):%0A
-
         kwar
@@ -1220,32 +1220,148 @@
 XSInfo(object):%0A
+    %22%22%22%0A    Cross-section summary object. Contains data/MC info, cross-section, k-factor, filter-efficiency%0A    %22%22%22%0A
     def __init__
@@ -1709,16 +1709,16 @@
  = 1.%0A%0A%0A
-
 class XS
@@ -1729,24 +1729,80 @@
 le(object):%0A
+    %22%22%22%0A    Interface for cross-section scaling%0A    %22%22%22%0A
     def __in
@@ -3426,32 +3426,304 @@
 self, process):%0A
+        %22%22%22%0A        Get cross-section information (xsec, k-factor, filter-efficiency) for a single process%0A        :param process: process name%0A        :type process: str%0A        :return: cross-section, filter-efficiency, k-factor%0A        :rtype: tuple(float)%0A        %22%22%22%0A
         if self.

add docstrings