# Copyright (C) 2009 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 St, Fifth Floor, Boston, MA 02110-1301  USA

"""Helper routines for collecting status data.

Note: These might migrate down into bzrlib after they mature.
"""

import codecs
from cStringIO import StringIO

from bzrlib import (
    branch as mod_branch,
    errors, 
    revisionspec, 
    status,
    )


def get_status_info(wt, specific_files=None, versioned=False):
    """Get the status information for a working tree.

    Pending merges are available separately. See get_pending_merges.

    :return: delta, conflicts, nonexistents
    :raises OutOfDateTree: bzr update needs to be run
    """
    # Note: This is status.show_tree_status() returning the data
    wt.lock_read()
    try:
        if wt.last_revision() != wt.branch.last_revision():
            raise errors.OutOfDateTree(wt)
        return changes_between_trees(wt, wt.basis_tree(), specific_files,
            versioned)
    finally:
        wt.unlock()


def changes_between_trees(new, old, specific_files=None, versioned=False):
    """Get the changes between two trees.

    :return: delta, conflicts, nonexistents
    """
    old.lock_read()
    new.lock_read()
    try:
        specific_files, nonexistents \
            = status._filter_nonexistent(specific_files, old, new)
        want_unversioned = not versioned
        delta = new.changes_from(old,
                              specific_files=specific_files,
                              want_unversioned=want_unversioned)

        # filter out unknown files. We may want a tree method for this
        delta.unversioned = [unversioned for unversioned in
            delta.unversioned if not new.is_ignored(unversioned[0])]

        # Get the new conflicts only for now. XXX: get them from the delta.
        conflicts = new.conflicts()
        if specific_files is not None:
            conflicts = conflicts.select_conflicts(new, specific_files,
                ignore_misses=True, recurse=True)[1]

        return delta, conflicts, nonexistents
    finally:
        old.unlock()
        new.unlock()


def get_pending_merges(wt, verbose=False):
    """Get the pending merges in a working tree.
    
    :param verbose: if False, only the tips are returned
    """
    # simple implementation for now
    sio = codecs.getwriter('utf-8')(StringIO())
    status.show_pending_merges(wt, to_file=sio, verbose=verbose)
    lines = sio.getvalue().decode('utf-8').splitlines()
    # take off the top intro line, if any
    if lines:
        return lines[1:]
    else:
        return None


def get_submission_info(branch):
    """Get the status information for a branch versus its submit branch.

    :return: delta
    """
    new_tree = branch.basis_tree()
    rev_spec = revisionspec.RevisionSpec.from_string('submit:')
    old_tree = rev_spec.as_tree(branch)
    return changes_between_trees(new_tree, old_tree)[0]

def get_parent_submission_info(branch):
    """Get the status information for a branch versus its parent branch.

    We get delta as equivalent of bzr command:
        bzr status -r ancestor::parent..-1

    We use common ancestor of both branches as base revision to make sure
    we only show changes made in this branch and exclude side-effects
    of changes made in parent branch (see bug #956268 for details).

    :return: delta
    """
    new_tree = branch.basis_tree()
    old_branch = mod_branch.Branch.open_containing(branch.get_parent())[0]
    branch.lock_read()
    old_branch.lock_read()
    try:
        # Thanks to Aaron for code snippet
        # Get a graph, so we can find the common ancestor (LCA)
        graph = old_branch.repository.get_graph(branch.repository)
        # Find the LCA of the revisions (equivalent of `-r ancestor::parent`)
        lca = graph.find_unique_lca(branch.last_revision(),
                                    old_branch.last_revision())
        old_tree = old_branch.repository.revision_tree(lca)
        return changes_between_trees(new_tree, old_tree)[0]
    finally:
        old_branch.unlock()
        branch.unlock()
