#!/usr/bin/env python

"""spoke -- Git plugin for GitHub integration
Copyright (C) 2012  Alex Headley <aheadley@waysaboutstuff.com>

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 Street, Fifth Floor, Boston, MA  02110-1301, USA.
"""

import argparse
import os
from pprint import pprint
import functools
import inspect
import textwrap
import tempfile
import time
import subprocess

import git
import pygithub3

def guess_type(obj):
    ok_types = [int, str, bool]
    obj_type = type(obj)
    if obj_type in ok_types:
        return obj_type
    else:
        if obj_type == list or obj_type == tuple:
            if len(obj):
                obj_e_type = type(obj[0])
                if obj_e_type in ok_types and \
                        all(type(e) == obj_e_type for e in obj[1:]):
                    return obj_e_type
        return str

def guess_action(obj):
    return {
        bool: 'store_false' if obj else 'store_true',
    }.get(guess_type(obj), 'store')

def guess_nargs(obj):
    if guess_type(obj) == bool:
        return 0
    else:
        try:
            len(obj)
        except TypeError:
            return 1
        else:
            return '+'

def get_console_size():
    with os.popen('stty size', 'r') as p:
        return map(int, p.read().strip().split())

class ArgFunc(object):
    @staticmethod
    def define_args(**kwargs):
        def wrapper(func):
            for (arg, attrs) in kwargs.iteritems():
                if 'default' in attrs and 'name' not in attrs:
                    attrs['name'] = '--' + arg.replace('_', '-')
                if 'dest' not in attrs and 'name' in attrs:
                    attrs['dest'] = arg
            func._argfunc_attrs = kwargs
            return func
        return wrapper

    @staticmethod
    def auto_define_args(func):
        (args, pargs, kwargs, defaults) = inspect.getargspec(func)
        if args[0] == 'self' or args[0] == 'cls':
            args = args[1:]
        defaults = defaults if defaults is not None else []
        arg_no_defaults = args[:-len(defaults)]
        arg_defaults = zip(args[-len(defaults):], defaults)
        attrs = {}
        for arg in arg_no_defaults:
            arg_attrs = {
                'metavar': arg.upper(),
            }
            attrs[arg] = arg_attrs
        for (arg, default) in arg_defaults:
            arg_a
ttrs = {
                'name': '--' + arg.replace('_', '-'),
                'action': guess_action(default),
                'default': default,
                'dest': arg,
            }
            attrs[arg] = arg_attrs
        if pargs is not None:
            attrs[pargs] = {
                'name': pargs,
      
          'nargs': '*',
            }
        if kwargs is not None:
            pass
        func._argfunc_attrs = attrs
        return func


    def add_func(self, parser, func):
        if hasattr(func, '_argfunc_attrs'):
            for (arg, attrs) in func._argfunc_attrs.iteritems():
                fixed_attrs = attrs.copy()
                if 'name' in attrs:
                    command_name = fixed_attrs.pop('name')
                    fixed_attrs['dest'] = arg
                else:
                    command_name = arg
                parser.add_argument(command_name, **fixed_attrs)


    def add_obj(self, parser, obj):
        for func in (a for a in dir(obj) \
                if callable(obj, a) and hasattr(getattr(obj, a), '_argfunc_attrs')):
            self.add_func(parser, func)

class GithubActor(object):
    """
    """

    CONFIG_NS       = 'hub'
    GIT_REMOTE_NAME = 'github'
    FALLBACK_EDITOR = 'nano'

    _current_repo   = None
    _current_user   = None
    _github         = None

    def __init__(self, output=None):
        self._current_repo = self._init_repo()
        creds = self._get_github_credentials(self._current_repo)
        self._current_user = creds[0]
        self._github = self._init_github(creds[0], creds[1], self._current_repo)
        if output is not None:
            self._output = output

    def _output(self, obj, *pargs, **kwargs):
        if issubclass(obj.__class__, basestring):
            print unicode(obj).format(*pargs, **kwargs)
        else:
            try:
                pprint(obj, indent=2)
            except Exception:
                print repr(obj)

    def _init_repo(self):
        try:
            repo = git.Repo(os.getcwd())
        except git.exc.InvalidGitRepositoryError:
            repo = None
        return repo

    def _init_github(self, username, password, repo=None):
        repo_name = self._get_repo_name(repo)
        return pygithub3.Github(login=username, password=password,
            user=username, repo=repo_name)

    @property
    def _current_repo_name(self):
        return self._get_repo_name(self._current_repo)

    def _get_repo_name(self, repo):
        if repo is not None:
            return os.path.basename(repo.working_tree_dir)
        else:
            return None

    def _get_github_credentials(self, repo=None):
        if repo is None:
            user_cfg_file = os.path.expanduser('~/.gitconfig')
            if os.path.exists(user_cfg_file):
                cfg = git.config.GitConfigParser(user_cfg_file)
            else:
                raise ValueError("""Can\'t find a gitconfig file for github login info.
                                    Set the login info with:
                                        git config --global --add {0}.username <username>
                                        git config --global --add {0}.password <password>
                                 """.format(self.CONFIG_NS))
        else:
            cfg = repo.config_reader()

        return (cfg.get_value(self.CONFIG_NS, 'username'),
            cfg.get_value(self.CONFIG_NS, 'password'))

    def _get_padding(self, f, iterable):
        return max(len(f(i)) for i in iterable)

    def _require_in_repo(func):
        @functools.wraps(func)
        def wrapper(self, *pargs, **kwargs):
            if self._current_repo is None:
                self._output('You need to be in a repo for this command')
            else:
                return func(self, *pargs, **kwargs)
        try:
            wrapper._argfunc_attrs = func._argfunc_attrs
        except AttributeError:
            pass
        return wrapper

    @ArgFunc.auto_define_args
    def develop(self, org=None, **kwargs):
        """Clone a repo so you can start working on it, forking to your account
        if needed
        """
        target_user = kwargs.get('user', self._current_user)
        target_repo = kwargs.get('repo', self._current_repo_name)
        if os.path.exists(os.path.join(os.getcwd(), target_repo)):
            raise ValueError('Looks like the repo already exists at {0}'.format(
                os.path.join(os.getcwd(), target_repo)))
        if target_user != self._current_user:
            #need to fork first
            self._output('Looks like someone else\'s repo, forking...')
            try:
                fork = self._github.repos.forks.create(
                    user=target_user,
                    repo=target_repo,
                    org=org,
                )
            except AssertionError:
                pass
            self._output('Waiting for GitHub to stop forking around...')
            time.sleep(5)
        self._output('Getting repo info...')
        gh_repo = self._github.repos.get(
            user=self._current_user,
            repo=target_repo,
        )
        repo_path = os.path.join(os.getcwd(), gh_repo.name)
        self._output('Cloning re