# Copyright 2015 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>.

"""Handle deb-version and related substitutions."""

import re
import subprocess

from debian.changelog import Changelog

from gitbuildrecipe.recipe import (
    BranchSubstitutionVariable,
    DateVariable,
    FormattedError,
    GitCommitVariable,
    LatestTagVariable,
    RevdateVariable,
    RevnoVariable,
    RevtimeVariable,
    SubstitutionUnavailable,
    TimeVariable,
    branch_vars,
    simple_vars,
    )


class DebUpstreamVariable(BranchSubstitutionVariable):

    basename = "debupstream"

    minimum_format = 0.1

    def __init__(self, branch_name, version):
        super(DebUpstreamVariable, self).__init__(branch_name)
        self._version = version

    @classmethod
    def from_changelog(cls, branch_name, changelog):
        if len(changelog._blocks) > 0:
            return cls(branch_name, changelog._blocks[0].version)
        else:
            return cls(branch_name, None)

    def get(self):
        if self._version is None:
            raise SubstitutionUnavailable(
                self.name,
                "No previous changelog to take the upstream version from")
        # Should we include the epoch?
        return self._version.upstream_version


class DebVersionVariable(BranchSubstitutionVariable):

    basename = "debversion"

    minimum_format = 0.3

    def __init__(self, branch_name, version):
        super(DebVersionVariable, self).__init__(branch_name)
        self._version = version

    @classmethod
    def from_changelog(cls, branch_name, changelog):
        if len(changelog._blocks) > 0:
            return cls(branch_name, changelog._blocks[0].version)
        else:
            return cls(branch_name, None)

    def get(self):
        if self._version is None:
            raise SubstitutionUnavailable(self.name,
                "No previous changelog to take the version from")
        return str(self._version)


dfsg_regex = re.compile(r"[+.]*dfsg[.]*[0-9]+")

version_regex = re.compile(r"([~+])(svn[0-9]+|bzr[0-9]+|git[0-9a-f]+)")

def version_extract_base(version):
    version = dfsg_regex.sub("", version)
    return version_regex.sub("\\1", version)


class DebUpstreamBaseVariable(DebUpstreamVariable):

    basename = "debupstream-base"
    minimum_format = 0.3

    def get(self):
        version = super(DebUpstreamBaseVariable, self).get()
        version = version_extract_base(version)
        if version[-1] not in ("~", "+"):
            version += "+"
        return version


ok_to_preserve = [
    DebUpstreamVariable,
    DebUpstreamBaseVariable,
    DebVersionVariable,
    ]
deb_branch_vars = [
    DebVersionVariable,
    DebUpstreamBaseVariable,
    DebUpstreamVariable,
    ]


class NotFullyExpanded(FormattedError):

    _fmt = (
        "deb-version not fully expanded: %(deb_version)s.  Valid "
        "substitutions in recipe format %(format_version)s are: "
        "%(available_tokens)s")

    def __init__(self, deb_version, format_version, available_tokens):
        super().__init__(
            deb_version=deb_version, format_version=format_version,
            available_tokens=available_tokens)


def check_expanded_deb_version(base_branch):
    checked_version = base_branch.deb_version
    if checked_version is None:
        return
    for token in ok_to_preserve:
        if issubclass(token, BranchSubstitutionVariable):
            for name in base_branch.list_branch_names():
                checked_version = checked_version.replace(
                    token.determine_name(name), "")
            checked_version = checked_version.replace(
                    token.determine_name(None), "")
        else:
            checked_version = checked_version.replace(
                token.name, "")
    if "{" in checked_version:
        available_tokens = [var.name for var in simple_vars if
                            var.available_in(base_branch.format_version)]
        for var_kls in branch_vars + deb_branch_vars:
            if not var_kls.available_in(base_branch.format_version):
                continue
            for name in base_branch.list_branch_names():
                available_tokens.append(var_kls.determine_name(name))
            available_tokens.append(var_kls.determine_name(None))
        raise NotFullyExpanded(
            base_branch.deb_version, base_branch.format_version,
            available_tokens)


def substitute_changelog_vars(base_branch, branch_name, cl):
    """Substitute variables related to a changelog.

    :param base_branch: The base `RecipeBranch` to operate on.
    :param branch_name: The name of the `RecipeBranch` to substitute (None
        for the root branch).
    :param cl: The `Changelog` object to use.
    """
    debupstream_var = DebUpstreamVariable.from_changelog(branch_name, cl)
    base_branch.deb_version = debupstream_var.replace(base_branch.deb_version)
    if base_branch.format_version < 0.3:
        # The other variables were introduced in recipe format 0.3
        return
    debupstreambase_var = DebUpstreamBaseVariable.from_changelog(
        branch_name, cl)
    base_branch.deb_version = debupstreambase_var.replace(
        base_branch.deb_version)
    pkgversion_var = DebVersionVariable.from_changelog(branch_name, cl)
    base_branch.deb_version = pkgversion_var.replace(base_branch.deb_version)


def substitute_branch_vars(base_branch, child_branch):
    """Substitute the branch variables for the given branch name in deb_version.

    :param base_branch: The base `RecipeBranch` to operate on.
    :param child_branch: The child `RecipeBranch` to operate on.
    """
    if base_branch.deb_version is None:
        return
    revno_var = RevnoVariable(child_branch)
    base_branch.deb_version = revno_var.replace(base_branch.deb_version)
    # XXX assert absence of {svn-revno} for now
    if base_branch.format_version < 0.4:
        # The other variables were introduced in recipe format 0.4
        return
    git_commit_var = GitCommitVariable(child_branch)
    base_branch.deb_version = git_commit_var.replace(base_branch.deb_version)
    latest_tag_var = LatestTagVariable(child_branch)
    base_branch.deb_version = latest_tag_var.replace(base_branch.deb_version)
    revdate_var = RevdateVariable(child_branch)
    base_branch.deb_version = revdate_var.replace(base_branch.deb_version)
    revtime_var = RevtimeVariable(child_branch)
    base_branch.deb_version = revtime_var.replace(base_branch.deb_version)
    changelog_exists = False
    show_process = subprocess.Popen(
        ["git", "-C", child_branch._get_git_path(),
         "show", "%s:debian/changelog" % child_branch.get_revspec()],
        stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
    try:
        cl = Changelog(file=show_process.stdout)
    finally:
        show_process.stdout.close()
        changelog_exists = (show_process.wait() == 0)
    if changelog_exists:
        substitute_changelog_vars(base_branch, child_branch.name, cl)


def substitute_time(base_branch, time):
    """Substitute the time into deb_version if needed.

    :param time: A `datetime.datetime` with the desired time.
    """
    if base_branch.deb_version is None:
        return
    base_branch.deb_version = TimeVariable(time).replace(
        base_branch.deb_version)
    base_branch.deb_version = DateVariable(time).replace(
        base_branch.deb_version)
