import os
import sys
import inspect
import logging
import traceback

from .coverage_file import CoverageFile
from lighthouse.util.python import iteritems
from lighthouse.exceptions import CoverageParsingError

logger = logging.getLogger("Lighthouse.Reader")

MODULES_DIRECTORY = os.path.join(os.path.dirname(os.path.realpath(__file__)), "parsers")

class CoverageReader(object):
    """
    Middleware to automatically parse and load different coverage file formats.

    This class will dynamically load and make use of coverage file parsers
    that subclass from the CoverageFile abstraction and live within the
    reader's 'parsers' folder.

    This should allow end-users to write parsers for custom coverage file
    format without having to modify any of Lighthouse's existing code (ideally)
    """

    def __init__(self):
        self._installed_parsers = {}
        self._import_parsers()

    def open(self, filepath):
        """
        Open and parse a coverage file from disk.

        Returns a CoverageFile on success, or raises CoverageParsingError on failure.
        """
        coverage_file = None
        parse_failures = {}

        # attempt to parse the given coverage file with each available parser
        for name, parser in iteritems(self._installed_parsers):
            logger.debug("Attempting parse with '%s'" % name)

            # attempt to open/parse the coverage file with the given parser
            try:
                coverage_file = parser(filepath)
                break

            # log the exceptions for each parse failure
            except Exception as e:
                parse_failures[name] = traceback.format_exc()
                logger.debug("| Parse FAILED - " + str(e))
                #logger.exception("| Parse FAILED")

        #
        # if *all* the coverage file parsers failed, raise an exception with
        # information for each failure (for debugging)
        #

        if not coverage_file:
            raise CoverageParsingError(filepath, parse_failures)

        # successful parse
        logger.debug("| Parse OKAY")
        return coverage_file

    def _import_parsers(self):
        """
        Scan and import coverage file parsers from the 'parsers' directory.
        """
        target_subclass = CoverageFile
        ignored_files = ["__init__.py"]

        # loop through all the files in the parsers folder
        for filename in os.listdir(MODULES_DIRECTORY):

            # ignore specified files, and anything not *.py
            if filename in ignored_files or filename.endswith(".py") == False:
                continue

            # attempt to load a CoverageFile format from the current *.py file
            logger.debug("| Searching file %s" % filename)
            parser_file = filename[:-3]
            parser_class = self._locate_subclass(parser_file, target_subclass)

            if not parser_class:
                logger.warning("| - No object subclassing from %s found in %s..." \
                             % (target_subclass.__name__, parser_file))
                continue

            # instantiate and add the parser to our dict of imported parsers
            logger.debug("| | Found %s" % parser_class.__name__)
            self._installed_parsers[parser_class.__name__] = parser_class
        logger.debug("+- Done dynamically importing parsers")

        # return the number of modules successfully imported
        return self._installed_parsers

    def _locate_subclass(self, module_file, target_subclass):
        """
        Return the first matching target_subclass in module_file.

        This function is used to scan a specific file (module_file) in the
        Lighthouse parsers directory for class definitions that subclass from
        target_subclass.

        We use this to dynmically import, locate, and return objects that are
        utilizing our CoverageFile abstraction.
        """
        module = None
        module_class = None

        # attempt to import the given filepath as a python module
        try:
            module = __import__("lighthouse.reader.parsers." + module_file, globals(), locals(), ['object'])
        except Exception as e:
            logger.exception("| - Parser import failed")
            return None

        #
        # inspect the module for any classes that subclass from target_subclass
        #   eg: target_subclass == CoverageFile
        #

        class_members = inspect.getmembers(module, inspect.isclass)
        for a_class in class_members:

            # does the current class definition we're inspecting subclass
            # from target_subclass? if so, it is a match
            try:
                if a_class[1].__bases__[0] == target_subclass:
                    module_class = a_class[1]
                    break

            # this class does not subclass / etc / not interesting / ignore it
            except IndexError as e:
                pass

        # return discovered parser or None
        return module_class
