# Copyright (C) 2010 Canonical Ltd
#
# This program 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 2 of the License, or
# (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

"""Logic to create commit templates."""

import re

import patiencediff

from ... import bugtracker, osutils

_BUG_MATCH = re.compile(r"lp:(\d+)")


class CommitTemplate:
    def __init__(self, commit, message, filespec):
        """Create a commit template for commit with initial message message.

        :param commit: A Commit object for the in progress commit.
        :param message: The current message (which may be None).
        :param filespec: List of files to match
        """
        self.commit = commit
        self.message = message
        self.filespec = filespec

    def make(self):
        """Make the template.

        If NEWS is missing or not not modified, the original template is
        returned unaltered. Otherwise the changes from NEWS are concatenated
        with whatever message was provided to __init__.
        """
        delta = self.commit.builder.get_basis_delta()
        found_old_path = None
        found_entry = None
        for old_path, new_path, _fileid, entry in delta:
            if new_path in self.filespec:
                found_entry = entry
                found_old_path = old_path
                break
        if not found_entry:
            return self.message
        if found_old_path is None:
            # New file
            _, new_chunks = next(
                self.commit.builder.repository.iter_files_bytes(
                    [(found_entry.file_id, found_entry.revision, None)]
                )
            )
            content = b"".join(new_chunks).decode("utf-8")
            return self.merge_message(content)
        else:
            # Get a diff. XXX Is this hookable? I thought it was, can't find it
            # though.... add DiffTree.diff_factories. Sadly thats not at the
            # right level: we want to identify the changed lines, not have the
            # final diff: because we want to grab the sections for regions
            # changed in new version of the file. So for now a direct diff
            # using patiencediff is done.
            old_revision = self.commit.basis_tree.get_file_revision(old_path)
            needed = [
                (found_entry.file_id, found_entry.revision, "new"),
                (found_entry.file_id, old_revision, "old"),
            ]
            contents = self.commit.builder.repository.iter_files_bytes(needed)
            lines = {}
            for name, chunks in contents:
                lines[name] = osutils.chunks_to_lines(list(chunks))
            new = lines["new"]
            sequence_matcher = patiencediff.PatienceSequenceMatcher(
                None, lines["old"], new
            )
            new_lines = []
            for group in sequence_matcher.get_opcodes():
                tag, i1, i2, j1, j2 = group
                if tag == "equal":
                    continue
                if tag == "delete":
                    continue
                new_lines.extend([l.decode("utf-8") for l in new[j1:j2]])
            if not self.commit.revprops.get("bugs"):
                # TODO: Allow the user to configure the bug tracker to use
                # rather than hardcoding Launchpad.
                bt = bugtracker.tracker_registry.get("launchpad")
                bugids = []
                for line in new_lines:
                    bugids.extend(_BUG_MATCH.findall(line))
                self.commit.revprops["bugs"] = bugtracker.encode_fixes_bug_urls(
                    [(bt.get_bug_url(bugid), bugtracker.FIXED) for bugid in bugids]
                )
            return self.merge_message("".join(new_lines))

    def merge_message(self, new_message):
        """Merge new_message with self.message.

        :param new_message: A string message to merge with self.message.
        :return: A string with the merged messages.
        """
        if self.message is None:
            return new_message
        return self.message + new_message
