"""Coverage data for coverage.py"""

import os, marshal, socket, types

class CoverageData:
    """Manages collected coverage data."""
    # Name of the data file (unless environment variable is set).
    filename_default = ".coverage"

    # Environment variable naming the data file.
    filename_env = "COVERAGE_FILE"

    def __init__(self):
        self.filename = None
        self.use_file = True

        # A map from canonical Python source file name to a dictionary in
        # which there's an entry for each line number that has been
        # executed:
        #
        #   {
        #       'filename1.py': { 12: True, 47: True, ... },
        #       ...
        #       }
        #
        self.executed = {}
        
    def usefile(self, use_file=True, filename_default=None):
        self.use_file = use_file
        if filename_default and not self.filename:
            self.filename_default = filename_default

    def read(self, parallel=False):
        """Read coverage data from the coverage data file (if it exists)."""
        data = {}
        if self.use_file and not self.filename:
            self.filename = os.environ.get(
                                    self.filename_env, self.filename_default)
            if parallel:
                self.filename += "." + socket.gethostname()
                self.filename += "." + str(os.getpid())
            if os.path.exists(self.filename):
                data = self._read_file(self.filename)
        self.executed = data

    def write(self):
        """Write the collected coverage data to a file."""
        if self.use_file and self.filename:
            self.write_file(self.filename)
            
    def erase(self):
        if self.filename and os.path.exists(self.filename):
            os.remove(self.filename)

    def write_file(self, filename):
        """Write the coverage data to `filename`."""
        f = open(filename, 'wb')
        try:
            marshal.dump(self.executed, f)
        finally:
            f.close()

    def read_file(self, filename):
        self.executed = self._read_file(filename)
        
    def _read_file(self, filename):
        """ Return the stored coverage data from the given file.
        """
        try:
            fdata = open(filename, 'rb')
            executed = marshal.load(fdata)
            fdata.close()
            if isinstance(executed, types.DictType):
                return executed
            else:
                return {}
        except:
            return {}

    def combine_parallel_data(self):
        """ Treat self.filename as a file prefix, and combine the data from all
            of the files starting with that prefix.
        """
        data_dir, local = os.path.split(self.filename)
        for f in os.listdir(data_dir or '.'):
            if f.startswith(local):
                full_path = os.path.join(data_dir, f)
                file_data = self._read_file(full_path)
                self._combine_data(file_data)

    def _combine_data(self, new_data):
        """Combine the `new_data` into `executed`."""
        for filename, file_data in new_data.items():
            self.executed.setdefault(filename, {}).update(file_data)

    def add_raw_data(self, data_points):
        """Add raw data.
        
        `data_points` is (filename, lineno) pairs.
        
        """
        for filename, lineno in data_points:
            self.executed.setdefault(filename, {})[lineno] = True

    def executed_files(self):
        """A list of all files that had been measured as executed."""
        return self.executed.keys()

    def executed_lines(self, filename):
        """A map containing all the line numbers executed in `filename`."""
        return self.executed[filename]

    def summary(self):
        """Return a dict summarizing the coverage data.
        
        Keys are the basename of the filenames, and values are the number of
        executed lines.  This is useful in the unit tests.
        
        """
        summ = {}
        for filename, lines in self.executed.items():
            summ[os.path.basename(filename)] = len(lines)
        return summ
