#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2012-2021 SoftBank Robotics. All rights reserved.
# Use of this source code is governed by a BSD-style license (see the COPYING file).
""" A set of function to know the status of a git repository. """
from __future__ import absolute_import
from __future__ import unicode_literals
from __future__ import print_function

import qisrc.git
from qisys import ui


def stat_tracking_remote(git, branch, tracking):
    """ Check if branch is ahead and / or behind tracking. """
    if branch is None or tracking is None:
        return 0, 0
    _, local_ref = git.call("rev-parse", branch, raises=False)
    _, remote_ref = git.call("rev-parse", tracking, raises=False)
    return stat_ahead_behind(git, local_ref, remote_ref)


def stat_fixed_ref(git, remote_ref):
    """ Check is HEAD is and and / or behind given ref. """
    _, local_ref = git.call("rev-parse", "HEAD", raises=False)
    return stat_ahead_behind(git, local_ref, remote_ref)


def stat_ahead_behind(git, local_ref, remote_ref):
    """
    Returns a tuple (ahead, behind) describing how far
    from the remote ref the local ref is.
    """
    behind = 0
    ahead = 0
    (ret, out) = git.call("rev-list", "--left-right",
                          "%s..%s" % (remote_ref, local_ref), raises=False)
    if ret == 0:
        ahead = len(out.split())
    (ret, out) = git.call("rev-list", "--left-right",
                          "%s..%s" % (local_ref, remote_ref), raises=False)
    if ret == 0:
        behind = len(out.split())
    return ahead, behind


class ProjectState(object):
    """ A class which represent a project and is cleanlyness. """

    def __init__(self, project):
        """ ProjectState Init """
        self.project = project
        self.fixed_ref = None
        self.not_on_a_branch = False
        self.incorrect_proj = False
        self.clean = True
        self.ahead = 0
        self.behind = 0
        self.ahead_manifest = 0
        self.behind_manifest = 0
        self.current_branch = None
        self.tracking = None
        self.valid = True
        self.status = None
        self.manifest_branch = None

    @property
    def sync(self):
        """ Tell if project is synced. """
        return self.ahead == 0 and self.behind == 0

    @property
    def sync_and_clean(self):
        """ Tell if project is synced and if it's clean. """
        return self.clean and self.sync


def check_state(project, untracked):
    """ Check and register the state of a project. """
    state_project = ProjectState(project)
    git = qisrc.git.Git(project.path)
    if not git.is_valid():
        state_project.valid = False
        return state_project
    state_project.clean = git.is_clean(untracked=untracked)
    if project.fixed_ref:
        state_project.ahead, state_project.behind = stat_fixed_ref(git, project.fixed_ref)
        state_project.fixed_ref = project.fixed_ref
        _set_status(git, state_project, untracked=untracked)
        return state_project
    state_project.current_branch = git.get_current_branch()
    state_project.tracking = git.get_tracking_branch()
    if project.default_remote and project.default_branch:
        state_project.manifest_branch = "%s/%s" % (project.default_remote.name, project.default_branch.name)
    if state_project.current_branch is None:
        state_project.not_on_a_branch = True
        return state_project
    if project.default_branch:
        if state_project.current_branch != project.default_branch.name:
            state_project.incorrect_proj = True
    (state_project.ahead, state_project.behind) = stat_tracking_remote(
        git,
        state_project.current_branch,
        state_project.tracking)
    if state_project.incorrect_proj:
        (state_project.ahead_manifest, state_project.behind_manifest) = stat_tracking_remote(
            git, state_project.current_branch, "%s/%s" % (
                project.default_remote.name, project.default_branch.name))
    _set_status(git, state_project, untracked=untracked)
    return state_project


def _set_status(git, state_project, untracked=False):
    """
    When project is not clean, display git status.
    (untracked files and the like)
    """
    if not state_project.sync_and_clean:
        out = git.get_status(untracked)
        if out is not None:
            state_project.status = [x for x in out.splitlines() if x.strip()]


def _print_behind_ahead(behind, ahead):
    """ Print Behind Ahead """
    numcommits = ""
    if behind:
        numcommits += "-" + str(behind)
    if behind and ahead:
        numcommits += "/"
    if ahead:
        numcommits += "+" + str(ahead)
    return numcommits


def print_state(project, max_len):
    """ Print a state project. """
    if project.valid:
        if project.ahead or project.behind:
            numcommits = _print_behind_ahead(project.behind, project.ahead)
            if project.fixed_ref:
                ui.info(ui.green, "*", ui.reset,
                        ui.blue, project.project.src.ljust(max_len), ui.reset,
                        ui.green, "fixed ref", project.fixed_ref,
                        ui.reset, ui.bold, ui.red,
                        numcommits)
            else:
                ui.info(ui.green, "*", ui.reset,
                        ui.blue, project.project.src.ljust(max_len), ui.reset,
                        ui.green, ":", project.current_branch,
                        "tracking",
                        ui.reset, ui.bold, ui.red,
                        numcommits, ui.green, project.tracking)
        else:
            if project.fixed_ref:
                ui.info(ui.green, "*", ui.reset,
                        ui.blue, project.project.src.ljust(max_len), ui.reset,
                        ui.green, "fixed ref", project.fixed_ref)
            else:
                ui.info(ui.green, "*", ui.reset,
                        ui.blue, project.project.src.ljust(max_len), ui.reset,
                        ui.green, ":", project.current_branch,
                        "tracking", project.tracking)
        if project.ahead_manifest or project.behind_manifest:
            numcommits = _print_behind_ahead(project.behind_manifest, project.ahead_manifest)
            ui.info(ui.bold, "Your branch", ui.green, project.current_branch,
                    ui.reset, ui.bold, "is", numcommits, "commits from branch",
                    ui.blue, project.manifest_branch)
    if not project.sync_and_clean:
        if project.status is not None:
            nlines = [x[:3] + project.project.src + "/" + x[3:]
                      for x in project.status]
            if nlines:
                print("\n".join(nlines))


def print_incorrect_projs(projects, max_len):
    """ Print list of projets which are not on correct branch. """
    incorrect_projs = [x for x in projects if x.incorrect_proj]
    if incorrect_projs:
        ui.info()
        max_branch_len = max(len(x.current_branch) for x in incorrect_projs)
        max_branch_len = max(max_branch_len, len("Current"))
        ui.warning("Some projects are not on the expected branch")
        ui.info(ui.blue, " " * 2, "Project".ljust(max_len + 3), ui.reset,
                ui.green, "Current".ljust(max_branch_len + 3),
                "Manifest")
        for project in incorrect_projs:
            ui.info(ui.green, " *", ui.reset,
                    ui.blue, project.project.src.ljust(max_len + 3),
                    ui.green, project.current_branch.ljust(max_branch_len + 3),
                    ui.green, project.project.default_branch.name)


def print_not_on_a_branch(projects):
    """ Print list of projects not on a branch. """
    not_on_a_branchs = [x for x in projects if x.not_on_a_branch]
    if not_on_a_branchs:
        ui.info()
        ui.warning("Some projects are not on any branch")
        for project in not_on_a_branchs:
            ui.info(ui.green, " *", ui.reset, ui.blue, project.project.src)
