#!/usr/bin/env python3

from contextlib import contextmanager
from distutils.spawn import find_executable
import os
import os.path as path
import shutil
import subprocess
import sys
import tempfile


EXTENSIONS = [
    ".tar", ".tgz", ".tar.gz", ".tar.bz2", ".tbz", ".tbz2",
    ".tar.xz", ".tar.lzma", ".tlz", ".tar.Z",
]

EXPAND_TOOL = {
    '.gz': 'gunzip',
    '.bz2': 'bunzip2',
    '.xz': 'unxz',
    '.lama': 'unlzma',
    '.Z': 'uncompress',
}


@contextmanager
def changing_dir(d):
    curdir = os.getcwd()
    os.chdir(d)
    try:
        yield
    finally:
        os.chdir(curdir)


@contextmanager
def temp_directory():
    name = tempfile.mkdtemp()
    try:
        yield name
    finally:
        shutil.rmtree(name)


@contextmanager
def archive_mount(dira, tara):
    exit_code = subprocess.call(['archivemount', tara, dira])
    if exit_code != 0:
        sys.exit(exit_code)
    try:
        yield
    finally:
        subprocess.call(['fusermount', '-u', dira])


@contextmanager
def archive_expand(dira, tara):
    exit_code = subprocess.call(['tar', 'xf', tara, '-C', dira])
    if exit_code != 0:
        sys.exit(exit_code)
    yield


__doc__ = """{f}: Diff between two .tar files / two .gz files.
Recognized tar file extensions are: {extensions}

Usage:
  {f} -r <options>... <tar1> <tar2>
  {f} --git <options>... <tar1> <tar2>
  {f} <options>... <gzip1> <gzip2>

Options:
  --git     Use `git diff` instead of `diff`.
  -r        Recursive option, passed through to diff command.

All the other options will be passed through to `diff` command.
""".format(f=path.basename(__file__), extensions=', '.join(EXTENSIONS))


def main(argv):
    args = argv[1:]
    if not args:
        print(__doc__)
        return

    option_tar = False
    option_git = False
    options = []
    tar_files = []
    archive_file_and_info_pairs = []
    if not (find_executable('archivemount') and find_executable('fusermount')):
        option_tar = True
        print('> warning: `archivemount` not installed. fallback to use `tar`.', file=sys.stderr)
    for a in args:
        for e in EXTENSIONS:
            if a.endswith(e):
                tar_files.append(a)
                break  # for e
        else:
            for e, t in EXPAND_TOOL.items():
                if a.endswith(e):
                    archive_file_and_info_pairs.append((a, e, t))
                    break  # for e
            else:
                if a == '--git':
                    option_git = True
                    if not find_executable('git'):
                        sys.exit('error: `git` not installed')
                else:
                    options.append(a)

    mode = None
    if len(tar_files) == 2 and len(archive_file_and_info_pairs) == 0:
        mode = 'COMPARE_TAR'
    elif len(tar_files) == 0 and len(archive_file_and_info_pairs) == 2:
        mode = 'COMPARE_FILE'
    else:
        sys.exit("error: must specify two .tar files or two .gz files")
    for t in tar_files + [a for a, e, t in archive_file_and_info_pairs]:
        if not path.isfile(t):
            sys.exit("error: no such file: %s" % t)

    if mode == 'COMPARE_TAR':
        with temp_directory() as temp_dir:
            dira = path.join(temp_dir, 'a')
            os.mkdir(dira)
            dirb = path.join(temp_dir, 'b')
            os.mkdir(dirb)

            mount_func = archive_expand if option_tar else archive_mount
            with mount_func(dira, tar_files[0]):
                with mount_func(dirb, tar_files[1]):
                    with changing_dir(temp_dir):
                        diff = ['git', 'diff'] if option_git else ['diff']
                        exit_code = subprocess.call(diff + options + ['a', 'b'])
                    sys.exit(exit_code)
    elif mode == 'COMPARE_FILE':
        with temp_directory() as temp_dir:
            absas = [path.abspath(a) for a, e, t in archive_file_and_info_pairs]
            with changing_dir(temp_dir):
                for orig, (a, e, t) in zip(absas, archive_file_and_info_pairs):
                    if not find_executable(t):
                        sys.exit('error: `%s` not installed')
                    shutil.copyfile(orig, a)
                    subprocess.check_call([t, a])
                diff = ['git', 'diff'] if option_git else ['diff']
                noefiles = [a[:-len(e)] for a, e, t in archive_file_and_info_pairs]
                exit_code = subprocess.call(diff + options + noefiles)
            sys.exit(exit_code)
    else:
        assert False


if __name__ == '__main__':
    main(sys.argv)
