#!/usr/bin/env python
from __future__ import print_function

COPYRIGHT = """\
Copyright (C) 2011-2012 OpenStack LLC.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.

See the License for the specific language governing permissions and
limitations under the License."""

import argparse
import datetime
import os
import shlex
import subprocess
import sys

import pkg_resources

if sys.version < '3':
    import ConfigParser
    import urllib
    import urlparse
    urlencode = urllib.urlencode
    urljoin = urlparse.urljoin
    urlparse = urlparse.urlparse
    do_input = raw_input
else:
    import configparser as ConfigParser

    import urllib.parse
    import urllib.request
    urlencode = urllib.parse.urlencode
    urljoin = urllib.parse.urljoin
    urlparse = urllib.parse.urlparse
    do_input = input

VERBOSE = False
UPDATE = False
LOCAL_MODE = 'GITREVIEW_LOCAL_MODE' in os.environ
CONFIGDIR = os.path.expanduser("~/.config/git-review")
GLOBAL_CONFIG = "/etc/git-review/git-review.conf"
USER_CONFIG = os.path.join(CONFIGDIR, "git-review.conf")
DEFAULTS = dict(branch='master')


class GitRestackException(Exception):
    pass


class CommandFailed(GitRestackException):

    def __init__(self, *args):
        Exception.__init__(self, *args)
        (self.rc, self.output, self.argv, self.envp) = args
        self.quickmsg = dict([
            ("argv", " ".join(self.argv)),
            ("rc", self.rc),
            ("output", self.output)])

    def __str__(self):
        return self.__doc__ + """
The following command failed with exit code %(rc)d
    "%(argv)s"
-----------------------
%(output)s
-----------------------""" % self.quickmsg


class GitDirectoriesException(CommandFailed):
    "Cannot determine where .git directory is."
    EXIT_CODE = 70


class GitMergeBaseException(CommandFailed):
    "Cannot determine merge base."
    EXIT_CODE = 71


class GitConfigException(CommandFailed):
    """Git config value retrieval failed."""
    EXIT_CODE = 128


def run_command_foreground(*argv, **kwargs):
    if VERBOSE:
        print(datetime.datetime.now(), "Running:", " ".join(argv))
    if len(argv) == 1:
        # for python2 compatibility with shlex
        if sys.version_info < (3,) and isinstance(argv[0], unicode):
            argv = shlex.split(argv[0].encode('utf-8'))
        else:
            argv = shlex.split(str(argv[0]))
    subprocess.call(argv)


def run_command_status(*argv, **kwargs):
    if VERBOSE:
        print(datetime.datetime.now(), "Running:", " ".join(argv))
    if len(argv) == 1:
        # for python2 compatibility with shlex
        if sys.version_info < (3,) and isinstance(argv[0], unicode):
            argv = shlex.split(argv[0].encode('utf-8'))
        else:
            argv = shlex.split(str(argv[0]))
    stdin = kwargs.pop('stdin', None)
    newenv = os.environ.copy()
    newenv['LANG'] = 'C'
    newenv['LANGUAGE'] = 'C'
    newenv.update(kwargs)
    p = subprocess.Popen(argv,
                         stdin=subprocess.PIPE if stdin else None,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.STDOUT,
                         env=newenv)
    (out, nothing) = p.communicate(stdin)
    out = out.decode('utf-8', 'replace')
    return (p.returncode, out.strip())


def run_command(*argv, **kwargs):
    (rc, output) = run_command_status(*argv, **kwargs)
    return output


def run_command_exc(klazz, *argv, **env):
    """Run command *argv, on failure raise klazz

    klazz should be derived from CommandFailed
    """
    (rc, output) = run_command_status(*argv, **env)
    if rc != 0:
        raise klazz(rc, output, argv, env)
    return output


def get_version():
    requirement = pkg_resources.Requirement.parse('git-restack')
    provider = pkg_resources.get_provider(requirement)
    return provider.version


def git_directories():
    """Determine (absolute git work directory path, .git subdirectory path)."""
    cmd = ("git", "rev-parse", "--show-toplevel", "--git-dir")
    out = run_command_exc(GitDirectoriesException, *cmd)
    try:
        return out.splitlines()
    except ValueError:
        raise GitDirectoriesException(0, out, cmd, {})


def git_config_get_value(section, option, default=None, as_bool=False):
    """Get config value for section/option."""
    cmd = ["git", "config", "--get", "%s.%s" % (section, option)]
    if as_bool:
        cmd.insert(2, "--bool")
    if LOCAL_MODE:
        __, git_dir = git_directories()
        cmd[2:2] = ['-f', os.path.join(git_dir, 'config')]
    try:
        return run_command_exc(GitConfigException, *cmd).strip()
    except GitConfigException as exc:
        if exc.rc == 1:
            return default
        raise


class Config(object):
    """Expose as dictionary configuration options."""

    def __init__(self, config_file=None):
        self.config = DEFAULTS.copy()
        filenames = [] if LOCAL_MODE else [GLOBAL_CONFIG, USER_CONFIG]
        if config_file:
            filenames.append(config_file)
        for filename in filenames:
            if os.path.exists(filename):
                if filename != config_file:
                    msg = ("Using global/system git-review config files (%s) "
                           "is deprecated")
                    print(msg % filename)
                self.config.update(load_config_file(filename))

    def __getitem__(self, key):
        value = git_config_get_value('gitreview', key)
        if value is None:
            value = self.config[key]
        return value


def load_config_file(config_file):
    """Load configuration options from a file."""
    configParser = ConfigParser.ConfigParser()
    configParser.read(config_file)
    options = {
        'scheme': 'scheme',
        'hostname': 'host',
        'port': 'port',
        'project': 'project',
        'branch': 'defaultbranch',
        'remote': 'defaultremote',
        'rebase': 'defaultrebase',
        'track': 'track',
        'usepushurl': 'usepushurl',
    }
    config = {}
    for config_key, option_name in options.items():
        if configParser.has_option('gerrit', option_name):
            config[config_key] = configParser.get('gerrit', option_name)
    return config


def main():
    usage = "git restack [BRANCH]"

    parser = argparse.ArgumentParser(usage=usage, description=COPYRIGHT)

    parser.add_argument("-v", "--verbose", dest="verbose", action="store_true",
                        help="Output more information about what's going on")
    parser.add_argument("--license", dest="license", action="store_true",
                        help="Print the license and exit")
    parser.add_argument("--version", action="version",
                        version='%s version %s' %
                        (os.path.split(sys.argv[0])[-1], get_version()))
    parser.add_argument("branch", nargs="?")

    parser.set_defaults(verbose=False)

    try:
        (top_dir, git_dir) = git_directories()
    except GitDirectoriesException as no_git_dir:
        pass
    else:
        no_git_dir = False
        config = Config(os.path.join(top_dir, ".gitreview"))
    options = parser.parse_args()
    if no_git_dir:
        raise no_git_dir

    if options.license:
        print(COPYRIGHT)
        sys.exit(0)

    global VERBOSE
    VERBOSE = options.verbose

    if options.branch is None:
        branch = config['branch']
    else:
        branch = options.branch

    if branch is None:
        branch = 'master'

    status = 0

    cmd = "git merge-base HEAD origin/%s" % branch
    base = run_command_exc(GitMergeBaseException, cmd)

    run_command_foreground("git rebase -i %s" % base, stdin=sys.stdin)

    sys.exit(status)


if __name__ == "__main__":
    main()