from io import BytesIO

from breezy import diff, errors, urlutils

try:
    from breezy.transport import NoSuchFile
except ImportError:
    from breezy.errors import NoSuchFile

from breezy.tree import find_previous_path

from .. import util
from ..controllers import TemplatedBranchView


def _process_diff(difftext):
    chunks = []
    chunk = None

    def decode_line(line):
        return line.decode('utf-8', 'replace')
    for line in difftext.splitlines():
        if len(line) == 0:
            continue
        if line.startswith(b'+++ ') or line.startswith(b'--- '):
            continue
        if line.startswith(b'@@ '):
            # new chunk
            if chunk is not None:
                chunks.append(chunk)
            chunk = util.Container()
            chunk.diff = []
            split_lines = line.split(b' ')[1:3]
            lines = [int(x.split(b',')[0][1:]) for x in split_lines]
            old_lineno = lines[0]
            new_lineno = lines[1]
        elif line.startswith(b' '):
            chunk.diff.append(util.Container(old_lineno=old_lineno,
                                             new_lineno=new_lineno,
                                             type='context',
                                             line=decode_line(line[1:])))
            old_lineno += 1
            new_lineno += 1
        elif line.startswith(b'+'):
            chunk.diff.append(util.Container(
                old_lineno=None,
                new_lineno=new_lineno,
                type='insert', line=decode_line(line[1:])))
            new_lineno += 1
        elif line.startswith(b'-'):
            chunk.diff.append(util.Container(
                old_lineno=old_lineno,
                new_lineno=None,
                type='delete', line=decode_line(line[1:])))
            old_lineno += 1
        else:
            chunk.diff.append(util.Container(
                old_lineno=None,
                new_lineno=None,
                type='unknown',
                line=repr(line)))
    if chunk is not None:
        chunks.append(chunk)
    return chunks


def diff_chunks_for_file(repository, filename, compare_revid, revid,
                         context_lines=None):
    if context_lines is None:
        context_lines = 3
    lines = {}
    compare_tree = repository.revision_tree(compare_revid)
    tree = repository.revision_tree(revid)
    try:
        lines[revid] = tree.get_file_lines(filename)
    except NoSuchFile:
        lines[revid] = []
        lines[compare_revid] = compare_tree.get_file_lines(filename)
    else:
        compare_filename = find_previous_path(tree, compare_tree, filename)
        if compare_filename is not None:
            lines[compare_revid] = compare_tree.get_file_lines(compare_filename)
        else:
            lines[compare_revid] = []

    buffer = BytesIO()
    try:
        diff.internal_diff(
            '', lines[compare_revid], '', lines[revid], buffer,
            context_lines=context_lines)
    except errors.BinaryFile:
        difftext = b''
    else:
        difftext = buffer.getvalue()

    return _process_diff(difftext)


class FileDiffUI(TemplatedBranchView):

    template_name = 'filediff'
    supports_json = True

    def get_values(self, path, kwargs, headers):
        revid = urlutils.unquote_to_bytes(self.args[0])
        compare_revid = urlutils.unquote_to_bytes(self.args[1])
        filename = urlutils.unquote(self.args[2])

        try:
            context_lines = int(kwargs['context'])
        except (KeyError, ValueError):
            context_lines = None

        chunks = diff_chunks_for_file(
            self._history._branch.repository, filename, compare_revid, revid,
            context_lines=context_lines)

        return {
            'chunks': chunks,
        }
