#!/usr/bin/env python

#
# NopSCADlib Copyright Chris Palmer 2021
# nop.head@gmail.com
# hydraraptor.blogspot.com
#
# This file is part of NopSCADlib.
#
# NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
# GNU General Public License as published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
#
# NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with NopSCADlib.
# If not, see <https://www.gnu.org/licenses/>.
#
#! Creates the changelog from the git log

from __future__ import print_function
import sys
import subprocess
import re
from tests import do_cmd

filename = 'CHANGELOG.md'

def tag_version(t):
    """ Format a version tag """
    return 'v%d.%d.%d' % t

def initials(name):
    """ Convert full name to initials with a tooltip """
    i = ''.join([n[0].upper() + '.' for n in name.split(' ')])
    return '[%s](# "%s")' % (i, name)

def get_remote_url():
    """ Get the git remote URL for the repository """
    url = subprocess.check_output(["git", "config", "--get", "remote.origin.url"]).decode("utf-8").strip("\n")
    if url.startswith("git@"):
        url = url.replace(":", "/", 1).replace("git@", "https://", 1)
    if url.endswith(".git"):
        url = url[:-4]
    return url

def iscode(word):
    """ try to guess if the word is code """
    endings = ['()', '*']
    starts = ['$', '--']
    anywhere = ['.', '_', '=', '[', '/']
    words = ['center', 'false', 'true', 'ngb']

    for w in words:
        if word == w:
            return True

    for end in endings:
        if word.endswith(end):
            return True

    for start in starts:
        if word.startswith(start):
            return True

    for any in anywhere:
        if word.find(any) >= 0:
            return True

    return False

def codify(word, url):
    """ if a word is deemed code enclose it backticks """
    if word:
        if re.match(r'#[0-9]+', word):
            return '[%s](%s "show issue")' % (word, url + '/issues/' + word[1:])
        if iscode(word):
            return  '`' + word + '`'
    return word


def fixup_comment(comment, url):
    comment = comment.replace('cnc_bit+_r', 'cnc_bit_r')
    """ markup code words and fix new paragraphs """
    result = ''
    word = ''
    code = False
    for i, c in enumerate(comment):
        if c == '`' or code:                # Already a code block
            result += c                     # Copy verbatim
            if c == '`': code = not code    # Keep track of state
        else:
            if c in ' \n' or (c == '.' and (i + 1 == len(comment) or comment[i + 1] in ' \n')): # if a word terminator
                result += codify(word, url) + c  # Add codified word before terminator
                word = ''
            else:
                word += c                   # Accumulate next word
    result += codify(word, url)             # In case comment ends without a terminator
    return result.replace('\n\n','\n\n * ') # Give new paragraphs a bullet point

class Commit(): # members dynamically added from commit_fields
    pass

blurb = """
# %s Changelog
This changelog is generated by `changelog.py` using manually added semantic version tags to classify commits as breaking changes, additions or fixes.

"""

if __name__ == '__main__':
    url = get_remote_url()

    commit_fields = {
        'hash':    "%H|",       # commit commit_hash
        'tag':     "%D|",       # tag
        'author': "%aN|",       # author name
        'date': "  %as|",       # author date short form
        'comment': "%B~"        # body
    }

    # Produce the git log
    format = ''.join([v for k, v in commit_fields.items()])
    text = subprocess.check_output(["git", "log", "--topo-order", "--format=" + format]).decode("utf-8")

    # Process the log into a list of Commit objects
    commits = []
    for line in text.split('~'):
        line = line.strip('\n')
        if line:
            fields = line.split('|')
            commit = Commit()
            for i, k in enumerate(commit_fields):
                exec('commit.%s = """%s"""' % (k, fields[i]), locals())
            # Convert version tag to tuple
            if commit.tag:
                match = re.match(r'.*tag: v([0-9]+)\.([0-9]+)\.([0-9]+).*', commit.tag)
                commit.tag = (int(match.group(1)), int(match.group(2)), int(match.group(3))) if match else ''
            commits.append(commit)

    # Format the results from the Commit objects
    with open(filename, "wt") as file:
        print(blurb % url.split('/')[-1], file = file)
        for i, c in enumerate(commits):
            if c.tag:
                ver = tag_version(c.tag)
                level, type = (3, 'Fixes') if c.tag[2] else (2, 'Additions') if c.tag[1] else (1, 'Breaking Changes') if c.tag[0] else (1, 'First publicised version')

                # Find the previous tagged commit
                j = i + 1
                diff = ''
                while j < len(commits):
                    if commits[j].tag:
                        last_ver = tag_version(commits[j].tag)
                        diff = '[...](%s "diff with %s")' % (url + '/compare/' + last_ver + '...' + ver, last_ver)
                        break
                    j += 1

                # Print version info
                print('%s [%s](%s "show release") %s %s' % ('#' * (level + 1), ver, url + '/releases/tag/' + ver, type, diff), file = file)

            # Print commits excluding merges

            if not c.comment.startswith('Merge branch') and not c.comment.startswith('Merge pull') and not re.match(r'U..ated ch.*log.*', c.comment):
                print('* %s [`%s`](%s "show commit") %s %s\n' % (c.date, c.hash[:7], url + '/commit/' + c.hash, initials(c.author), fixup_comment(c.comment, url)), file = file)
    do_cmd(('codespell -w -L od ' + filename).split())
