#!/usr/bin/python3

import sys
sys.path.append(sys.path[0] + "/../lib/python")

import os, re, subprocess
import locale

from debian_linux.debian import Changelog, Version

from config import Config, pattern_to_re


# Convert Python glob pattern to Git patterns.  Notable difference is
# that in Python globbing '**/' matches a directory and its
# descendants, but in Git it only matches descendants.  Assume there
# is at most one '**' in a pattern.
def pattern_to_git_patterns(pattern):
    yield f':{pattern}'
    if '**/' in pattern:
        yield f':{ pattern.replace("**/", "") }'


# Convert Python glob pattern to Git reegexp.  This assumes
# pattern_to_re() produces compatible regular expressions except for
# the use of '(?:...)' which we fix up.
def pattern_to_git_re(pattern):
    return pattern_to_re(pattern).pattern.replace('(?:', '(')


def print_stable_log(log, cur_ver, new_ver, files_include):
    inc_git_patterns = []
    inc_git_res = []
    for pattern in files_include:
        inc_git_patterns.extend(pattern_to_git_patterns(pattern))
        inc_git_res.append(pattern_to_git_re(pattern))

    git_rev_range = f'{cur_ver}..{new_ver}'

    # List commits changing files that we include
    with subprocess.Popen(['git', 'log', '--no-merges', '--pretty=%s',
                           git_rev_range, '--'] + inc_git_patterns,
                          stdout=subprocess.PIPE, text=True) \
            as proc:
        lines = proc.stdout.readlines()

    # List commits changing links that we include
    with subprocess.Popen(['git', 'log', '--no-merges', '--pretty=%s',
                           '-G', f'^Link: *({ "|".join(inc_git_res) }) ->',
                           git_rev_range, '--', 'WHENCE'],
                          stdout=subprocess.PIPE, text=True) \
            as proc:
        lines.extend(proc.stdout.readlines())

    # Strip useless subject prefix
    strip_re = re.compile(r'^linux-firmware: *')
    lines = [strip_re.sub('', line) for line in lines]

    # Sort and de-dupe lines
    lines.sort(key=str.casefold)
    last_line = None
    for line in lines:
        if line != last_line:
            log.write(f'    - {line}')
            last_line = line


def main(repo, new_ver):
    locale.setlocale(locale.LC_CTYPE, "C.UTF-8")
    os.environ['GIT_DIR'] = repo + '/.git'

    changelog = Changelog(version=Version)
    cur_ver = changelog[0].version.upstream

    # Nothing to update
    if cur_ver == new_ver:
        sys.exit(0)

    # Get list of file patterns that we include.  Don't get exclusions
    # because in some cases files are excluded from one binary package
    # so they can be included in another, and I don't think we can
    # construct a single pattern list that exactly matches our include/
    # exclude behaviour.
    config = Config()
    files_include = sum((config['base', package]['files']
                         for package in config['base',]['packages']),
                        [])

    new_pkg_ver = new_ver + '-1'

    # Three possible cases:
    # 1. The current version has been released so we need to add a new
    #    version to the changelog.
    # 2. The current version has not been released so we're changing its
    #    version string.
    #    (a) There are no stable updates included in the current version,
    #        so we need to insert an introductory line, the URL(s) and
    #        git log(s) and a blank line at the top.
    #    (b) One or more stable updates are already included in the current
    #        version, so we need to insert the URL(s) and git log(s) after
    #        them.

    changelog_intro = 'New upstream version:'

    # Case 1
    if changelog[0].distribution != 'UNRELEASED':
        subprocess.check_call(['dch', '-v', new_pkg_ver, '-D', 'UNRELEASED',
                               changelog_intro])

    with open('debian/changelog', 'r') as old_log:
        with open('debian/changelog.new', 'w') as new_log:
            line_no = 0
            inserted = False
            intro_line = '  * {}\n'.format(changelog_intro)

            for line in old_log:
                line_no += 1

                # Case 2
                if changelog[0].distribution == 'UNRELEASED' and line_no == 1:
                    print('{} ({}) UNRELEASED; urgency={}'
                          .format(changelog[0].source, new_pkg_ver,
                                  changelog[0].urgency),
                          file=new_log)
                    continue

                if not inserted:
                    # Case 2(a)
                    if line_no == 3 and line != intro_line:
                        new_log.write(intro_line)
                        print_stable_log(new_log, cur_ver, new_ver,
                                         files_include)
                        new_log.write('\n')
                        inserted = True
                    # Case 1 or 2(b)
                    elif line_no > 3 and line == '\n':
                        print_stable_log(new_log, cur_ver, new_ver,
                                         files_include)
                        inserted = True

                # Check that we inserted before hitting the end of the
                # first version entry
                assert not (line.startswith(' -- ') and not inserted)

                new_log.write(line)

    os.rename('debian/changelog.new', 'debian/changelog')

if __name__ == '__main__':
    if len(sys.argv) != 3:
        print('''\
Usage: {} REPO VERSION
REPO is the git repository to generate a changelog from
VERSION is the upstream version'''.format(sys.argv[0]),
              file=sys.stderr)
        sys.exit(2)
    main(*sys.argv[1:])
