#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
from __future__ import absolute_import
import os
import re
import atexit
from six.moves import cPickle
from subprocess import Popen, PIPE
from collections import defaultdict
from six.moves import map

COVERAGE_FILE = ".cmake_coverage"

# coverage reports
coverage = defaultdict(set)


def update_coverage():
    # print 'Updating CMake coverage reports'
    if os.path.exists(COVERAGE_FILE):
        data = cPickle.load(open(COVERAGE_FILE))
        if "lines" not in data:
            data["lines"] = {}
    else:
        data = {"lines": {}}
    lines = data["lines"]
    for filename, linenumbers in coverage.items():
        lines[filename] = sorted(linenumbers.union(lines.get(filename, [])))
    with open(COVERAGE_FILE, "w") as report:
        cPickle.dump(data, report)


atexit.register(update_coverage)


def cmake_script(name, cwd=None):
    proc = Popen(["cmake", "--trace", "-P", name], stdout=PIPE, stderr=PIPE, cwd=cwd)
    out, err = proc.communicate()
    out, err = out.decode("utf-8"), err.decode("utf-8")
    trace_line = re.compile(r"^(/.*)\(([0-9]+)\): ")
    new_err = []
    for line in err.splitlines():
        match = trace_line.match(line)
        if match:
            coverage[match.group(1)].add(int(match.group(2)))
        else:
            new_err.append(line)
    return out, "\n".join(new_err), proc.returncode
    # return out, err, proc.returncode


def get_ranges(numbers):
    """
    >>> list(get_ranges([1,2,3,4,10,11,12,15]))
    [(1, 4), (10, 12), (15, 15)]
    """
    from itertools import groupby
    from operator import itemgetter

    for _key, group in groupby(
        enumerate(numbers), lambda index, number: number - index
    ):
        group = list(map(itemgetter(1), group))
        yield group[0], group[-1]


def get_active_lines(filename):
    bracket_comment_open = re.compile(r"#\[(=*)\[")
    bracket_close = None
    for i, l in enumerate(open(filename)):
        l = l.strip()
        if bracket_close:
            if bracket_close in l:
                bracket_close = None  # last line of the bracket comment
            continue  # this line is part of a bracket comment
        b = bracket_comment_open.match(l)
        if b:
            bracket_close = "]{0}]".format(b.group(1))
            continue  # first line of a bracket comment
        if l and not l.startswith("#") and not l.startswith("end"):
            yield i + 1


if __name__ == "__main__":
    data = cPickle.load(open(COVERAGE_FILE))
    lines = data["lines"]
    for filename in sorted(lines):
        if not os.path.exists(filename):
            print("Unknown file", filename)
            continue
        print(filename)
        active_lines = set(get_active_lines(filename))
        touched_lines = set(lines[filename])
        missed_lines = active_lines.difference(touched_lines)
        ranges = [
            str(a) if a == b else "%d-%d" % (a, b)
            for a, b in get_ranges(sorted(missed_lines))
        ]
        touched_count = len(touched_lines)
        active_count = len(active_lines)
        if touched_count == active_count:
            print("   coverage 100%")
        else:
            print(
                "   coverage %3d%%, missed: %s"
                % (float(touched_count) / active_count * 100, ", ".join(ranges))
            )
