# 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/>.

import os.path
import subprocess

from fixtures import (
    EnvironmentVariable,
    TempDir,
    )
from testtools import TestCase


class GitRepository:

    def __init__(self, path, allow_create=True):
        self.path = path
        if not os.path.exists(path):
            os.makedirs(path)
        if allow_create and not os.path.exists(os.path.join(path, ".git")):
            self._git_call("init", "-q")

    def _git_call(self, *args, **kwargs):
        subprocess.check_call(["git", "-C", self.path] + list(args), **kwargs)

    def _git_output(self, *args, **kwargs):
        return subprocess.check_output(
            ["git", "-C", self.path] + list(args),
            universal_newlines=True, **kwargs)

    def rev_parse(self, rev):
        return self._git_output("rev-parse", rev).rstrip("\n")

    def last_revision(self):
        return self.rev_parse("HEAD")

    def status(self):
        return self._git_output("status", "--porcelain").splitlines()

    def add(self, files):
        self._git_call("add", *files)

    def commit(self, message, date=None):
        env = os.environ.copy()
        if date is not None:
            env["GIT_COMMITTER_DATE"] = date
        self._git_call("commit", "-q", "--allow-empty", "-m", message, env=env)
        return self.last_revision()

    def branch(self, branch_name, commit):
        self._git_call("branch", branch_name, commit)

    def tag(self, tag_name, commit, force=False):
        args = ["tag"]
        if force:
            args.append("--force")
        args.extend([tag_name, commit])
        self._git_call(*args, stdout=subprocess.DEVNULL)

    def set_head(self, ref_path):
        self._git_call("symbolic-ref", "HEAD", ref_path)

    def get_parents(self, commit):
        return self._git_output(
            "log", "-1", "--format=%P", commit).rstrip("\n").split()

    def clone_to(self, new_path):
        subprocess.check_call(["git", "clone", "-q", self.path, new_path])
        return GitRepository(new_path, allow_create=False)

    def build_tree(self, shape):
        """Build a test tree according to a pattern.

        shape is a sequence of file specifications.  If the final character
        is '/', a directory is created.

        This assumes that all the elements in the tree being built are new.

        This doesn't add anything to the index or make any commits.

        :type shape: list or tuple
        :return: None
        """
        if not isinstance(shape, (list, tuple)):
            raise AssertionError(
                "Parameter 'shape' should be a list or a tuple.  Got %r "
                "instead" % (shape,))
        for name in shape:
            assert isinstance(name, str)
            if name.endswith("/"):
                os.mkdir(os.path.join(self.path, name))
            else:
                dirname = os.path.dirname(os.path.join(self.path, name))
                if not os.path.isdir(dirname):
                    os.makedirs(dirname)
                with open(os.path.join(self.path, name), "w") as f:
                    f.write("contents of %s\n" % name)

    def build_tree_contents(self, template):
        """Reconstitute some files from a text description.

        Each element of template is a tuple.  The first element is a
        filename, with an optional ending character indicating the type.

        The template is built relative to the repository's current working
        directory.

        ('foo/',) will build a directory.
        ('foo', 'bar') will write 'bar' to 'foo'.
        ('foo@', 'linktarget') will raise an error.
        """
        for tt in template:
            name = tt[0]
            output_path = os.path.join(self.path, name)
            if name.endswith("/"):
                os.mkdir(output_path)
            elif name.endswith("@"):
                os.symlink(tt[1], output_path[:-1])
            else:
                with open(output_path, "w") as f:
                    f.write(tt[1])


class GitTestCase(TestCase):

    def setUp(self):
        super().setUp()
        # git complains if the user name and email aren't configured at all.
        home = self.make_temporary_directory()
        self.useFixture(EnvironmentVariable("HOME", home))
        with open(os.path.join(home, ".gitconfig"), "w") as gitconfig:
            print("[user]", file=gitconfig)
            print('\tname = "Committer"', file=gitconfig)
            print('\temail = "committer@example.org"', file=gitconfig)

    def make_temporary_directory(self):
        """Create a temporary directory, and return its path."""
        return self.useFixture(TempDir()).path

    def use_temp_dir(self):
        """Use a temporary directory for this test."""
        tempdir = self.make_temporary_directory()
        cwd = os.getcwd()
        os.chdir(tempdir)
        self.addCleanup(os.chdir, cwd)
        return tempdir
