import os
import sys
import time

from xml.etree import ElementTree

import instrumental
from instrumental.constructs import BooleanDecision

class CoverageObject(object):
    
    @property
    def line_rate(self):
        statements = self.statements
        if statements:
            total_statements = len(statements)
            hit_statements = len([statement for statement in statements
                                  if statement])
            return hit_statements / total_statements
        else:
            return 1.0
    
    @property
    def branch_rate(self):
        decisions = self.decisions
        if decisions:
            total_conditions = 2 * len(decisions)
            total_conditions_hit = sum(construct.number_of_conditions_hit()
                                       for construct in decisions)
            return total_conditions_hit / total_conditions
        else:
            return 1.0
    
    def to_element(self):
        element = ElementTree.Element(self.tag)
        if hasattr(self, 'name'):
            element.attrib['name']  = self.name
        element.attrib['complexity'] = '0.0'
        element.attrib['line-rate'] = "%f" % self.line_rate
        element.attrib['branch-rate'] = "%f" % self.branch_rate
        return element

class Coverage(CoverageObject):
    tag = 'coverage'
    
    def __init__(self, report):
        self._report = report
    
    @property
    def statements(self):
        _statements = []
        for modulename in self._report.statements:
            _statements += self._report.statements[modulename].values()
        return _statements
    
    @property
    def decisions(self):
        return [construct for construct in self._report.constructs.values()
                if isinstance(construct, BooleanDecision)]
        
    def to_element(self):
        coverage_element = super(Coverage, self).to_element()
        del coverage_element.attrib['complexity']
        coverage_element.attrib['timestamp'] = str(int(time.time()))
        coverage_element.version = instrumental.__version__
        
        comment = 'Generated by instrumental: http://bitbucket.org/desmaj/instrumental'
        coverage_element.append(ElementTree.Comment(comment))
        
        here = self._report.working_directory
        packages = {}
        for modulename in self._report.statements.keys():
            module = sys.modules[modulename]
            modules = packages.setdefault(module.__package__, [])
            modules.append((modulename, os.path.relpath(module.__file__, here)))
        
        packages_element = ElementTree.Element('packages')
        for packagename, modulenames in packages.items():
            package_coverage = PackageCoverage(packagename,
                                               packages[packagename],
                                               self._report.statements,
                                               self._report.constructs)
            package_element = package_coverage.to_element()
            packages_element.append(package_element)
        
        coverage_element.append(packages_element)
        
        return coverage_element

class PackageCoverage(CoverageObject):
    tag = 'package'
    
    def __init__(self, name, modules, statements, constructs):
        self.name = name
        self._modules = modules
        self._statements = statements
        self._constructs = constructs
    
    @property
    def statements(self):
        _statements = []
        for modulename, modulefile in self._modules:
            _statements += self._statements[modulename].values()
        return _statements
    
    @property
    def decisions(self):
        _decisions = []
        for modulename, modulefile in self._modules:
            _decisions += [construct 
                           for construct in self._constructs.values()
                           if (isinstance(construct, BooleanDecision)
                               and construct.modulename==modulename)]
        return _decisions
    
    def to_element(self):
        element = super(PackageCoverage, self).to_element()
        
        modules = ElementTree.Element('classes')
        element.append(modules)
        
        for modulename, modulefile in self._modules:
            module_constructs = [construct for construct in self._constructs.values()
                                 if construct.modulename==modulename]
            module_coverage = ModuleCoverage(modulename,
                                             modulefile,
                                             self._statements[modulename],
                                             module_constructs)
            modules.append(module_coverage.to_element())
        
        return element
        
class ModuleCoverage(CoverageObject):
    tag = 'class'
    
    def __init__(self, name, filename, statements, constructs):
        self.name = name
        self.filename = filename
        self._statements = statements
        self._constructs = constructs
    
    @property
    def statements(self):
        return self._statements.values()
    
    @property
    def decisions(self):
        _decisions = []
        for construct in self._constructs:
            if isinstance(construct, BooleanDecision):
                _decisions.append(construct)
        return _decisions
    
    def to_element(self):
        element = super(ModuleCoverage, self).to_element()
        element.attrib['filename'] = self.filename
        element.append(ElementTree.Element('methods'))
        
        lines_element = ElementTree.Element('lines')
        for lineno, hit in sorted(self._statements.items()):
            line_element = ElementTree.Element('line')
            line_element.attrib['line'] = str(lineno)
            line_element.attrib['hits'] = str(int(hit))
            lines_element.append(line_element)
        element.append(lines_element)
        
        return element
    
class XMLCoverageReport(object):
    
    def __init__(self, report):
        self._report = report
    
    def write(self, filename):
        tree = str(self)
        document = '\n'.join([
                '<?xml version="1.0" ?>',
                '<!DOCTYPE coverage ',
                "  SYSTEM 'http://cobertura.sourceforge.net/xml/coverage-03.dtd'>",
                tree,
                ])
        file(filename, 'w').write(document)
    
    def __str__(self):
        return ElementTree.tostring(self.tree.getroot())
    
    @property
    def tree(self):
        coverage_element = Coverage(self._report)
        coverage = coverage_element.to_element()
        
        _tree = ElementTree.ElementTree(coverage)
        return _tree
        
