#!/usr/bin/env python3

import os
import re
import sys

from file_types import lang_type, find_files

mode_line = re.compile('(-\*- *mode:.* *-\*-)')
shell_comment = re.compile(r'^\s*#')
lisp_comment = re.compile(r';')
cpp_comment = re.compile(r'//')
c_comment_start = re.compile(r'/\*')
c_comment_end   = re.compile(r'\*/')
def find_copyright_block(lines, lang_type):
    start = None
    if lang_type in ('python', 'make', 'shell', 'perl', 'scons'):
        for i,line in enumerate(lines):
            if i == 0 and (line.startswith('#!') or mode_line.search(line)):
                continue

            if shell_comment.search(line):
                if start is None:
                    start = i
            elif start is None:
                if line.strip():
                    return
            else:
                yield start, i-1
                start = None

    elif lang_type in ('lisp', ):
        for i,line in enumerate(lines):
            if i == 0 and mode_line.search(line):
                continue

            if lisp_comment.search(line):
                if start is None:
                    start = i
            elif start is None:
                if line.strip():
                    return
            else:
                yield start, i-1
                start = None

    elif lang_type in ('C', 'C++', 'swig', 'isa', 'asm', 'slicc',
                       'lex', 'yacc'):
        mode = None
        for i,line in enumerate(lines):
            if i == 0 and mode_line.search(line):
                continue

            if mode == 'C':
                assert start is not None, 'on line %d' % (i + 1)
                match = c_comment_end.search(line)
                if match:
                    yield start, i
                    mode = None
                continue

            cpp_match = cpp_comment.search(line)
            c_match = c_comment_start.search(line)

            if cpp_match:
                assert not c_match, 'on line %d' % (i + 1)
                if line[:cpp_match.start()].strip():
                    return
                if mode is None:
                    mode = 'CPP'
                    start = i
                else:
                    text = line[cpp_match.end():].lstrip()
                    if text.startswith("Copyright") > 0:
                        yield start, i-1
                        start = i
                continue
            elif mode == 'CPP':
                assert start is not None, 'on line %d' % (i + 1)
                if not line.strip():
                    continue
                yield start, i-1
                mode = None
                if not c_match:
                    return

            if c_match:
                assert mode is None, 'on line %d' % (i + 1)
                mode = 'C'
                start = i

            if mode is None and line.strip():
                return

    else:
        raise AttributeError("Could not handle language %s" % lang_type)

date_range_re = re.compile(r'([0-9]{4})\s*-\s*([0-9]{4})')
def process_dates(dates):
    dates = [ d.strip() for d in dates.split(',') ]

    output = set()
    for date in dates:
        match = date_range_re.match(date)
        if match:
            f,l = [ int(d) for d in match.groups() ]
            for i in range(f, l+1):
                output.add(i)
        else:
            try:
                date = int(date)
                output.add(date)
            except ValueError:
                pass

    return output

copyright_re = \
    re.compile(r'Copyright (\([cC]\)) ([-, 0-9]+)[\s*#/]*([A-z-,. ]+)',
               re.DOTALL)

authors_re = re.compile(r'^[\s*#/]*Authors:\s*([A-z .]+)\s*$')
more_authors_re = re.compile(r'^[\s*#/]*([A-z .]+)\s*$')

all_owners = set()
def get_data(lang_type, lines):
    data = []
    last = None
    for start,end in find_copyright_block(lines, lang_type):
        joined = ''.join(lines[start:end+1])
        match = copyright_re.search(joined)
        if not match:
            continue

        c,dates,owner = match.groups()
        dates = dates.strip()
        owner = owner.strip()

        all_owners.add(owner)
        try:
            dates = process_dates(dates)
        except Exception:
            print(dates)
            print(owner)
            raise

        authors = []
        for i in range(start,end+1):
            line = lines[i]
            if not authors:
                match = authors_re.search(line)
                if match:
                    authors.append(match.group(1).strip())
            else:
                match = more_authors_re.search(line)
                if not match:
                    for j in range(i, end+1):
                        line = lines[j].strip()
                        if not line:
                            end = j
                            break
                        if line.startswith('//'):
                            line = line[2:].lstrip()
                            if line:
                                end = j - 1
                                break
                    break
                authors.append(match.group(1).strip())

        info = (owner, dates, authors, start, end)
        data.append(info)

    return data

def datestr(dates):
    dates = list(dates)
    dates.sort()

    output = []
    def add_output(first, second):
        if first == second:
            output.append('%d' % (first))
        else:
            output.append('%d-%d' % (first, second))

    first = dates.pop(0)
    second = first
    while dates:
        next = dates.pop(0)
        if next == second + 1:
            second = next
        else:
            add_output(first, second)
            first = next
            second = next

    add_output(first, second)

    return ','.join(output)

usage_str = """usage:
%s [-v] <directory>"""

def usage(exitcode):
    print(usage_str % sys.argv[0])
    if exitcode is not None:
        sys.exit(exitcode)

if __name__ == '__main__':
    import getopt

    show_counts = False
    ignore = set()
    verbose = False
    try:
        opts, args = getopt.getopt(sys.argv[1:], "ci:v")
    except getopt.GetoptError:
        usage(1)

    for o,a in opts:
        if o == '-c':
            show_counts = True
        if o == '-i':
            ignore.add(a)
        if o == '-v':
            verbose = True

    files = []

    for base in args:
        if os.path.isfile(base):
            files += [ (base, lang_type(base)) ]
        elif os.path.isdir(base):
            files += find_files(base)
        else:
            raise AttributeError("can't access '%s'" %  base)

    copyrights = {}
    counts = {}

    for filename, lang in files:
        f = file(filename, 'r')
        lines = f.readlines()
        if not lines:
            continue

        lines = [ line.rstrip('\r\n') for line in lines ]

        lt = lang_type(filename, lines[0])
        try:
            data = get_data(lt, lines)
        except Exception as e:
            if verbose:
                if len(e.args) == 1:
                    e.args = ('%s (%s))' % (e, filename), )
                print("could not parse %s: %s" % (filename, e))
            continue

        for owner, dates, authors, start, end in data:
            if owner not in copyrights:
                copyrights[owner] = set()
            if owner not in counts:
                counts[owner] = 0

            copyrights[owner] |= dates
            counts[owner] += 1

    info = [ (counts[o], d, o) for o,d in list(copyrights.items()) ]

    for count,dates,owner in sorted(info, reverse=True):
        if show_counts:
            owner = '%s (%s files)' % (owner, count)
        print('Copyright (c) %s %s' % (datestr(dates), owner))
