#!/usr/bin/env python
from __future__ import absolute_import
import os
import argparse
from functools import partial
import logging
import subprocess
import urllib2
import json
from urllib2 import HTTPError


def parse_args():
    parser = argparse.ArgumentParser(
            description='Keeps your Python package dependencies pinned, but '
            'fresh.')
    parser.add_argument('--verbose', '-v', action='store_true', default=False,
            help='Show more output')
    parser.add_argument('--raw', '-r', action='store_true', default=False,
            help='Print raw lines (suitable for passing to pip install)')
    parser.add_argument('--interactive', '-i', action='store_true', default=False,
            help='Ask interactively to install updates')
    parser.add_argument('--auto', '-a', action='store_true', default=False,
            help='Automatically install every update found')
    return parser.parse_args()


def get_pkg_info(pkg_name):
    logging.debug('Checking for updates of %r' % (pkg_name,))
    req = urllib2.Request('http://pypi.python.org/pypi/%s/json' % (pkg_name,))
    handler = urllib2.urlopen(req)
    status = handler.getcode()
    if status == 200:
        content = handler.read()
        return json.loads(content)
    else:
        raise ValueError('Package %r not found on PyPI.' % (pkg_name,))


def latest_version(pkg_name, silent=False):
    try:
        info = get_pkg_info(pkg_name)
    except (ValueError, HTTPError):
        if silent:
            return None
        else:
            raise
    return info['info']['version']


def get_latest_versions(pkg_names):
    get_latest = partial(latest_version, silent=True)
    versions = map(get_latest, pkg_names)
    return zip(pkg_names, versions)


def get_installed_pkgs(editables=False):
    for line in subprocess.check_output(['pip', 'freeze']).split('\n'):
        if not line:
            continue

        if line.startswith('-e'):
            if editables:
                yield line.split('#egg=', 1)[1], None, True
        else:
            name, version = line.split('==')
            yield name, version, False


def setup_logging(verbose):
    if verbose:
        level = logging.DEBUG
    else:
        level = logging.INFO

    logging.basicConfig(level=level, format='%(message)s')



class InteractiveAsker(object):
    def __init__(self):
        self.cached_answer = None

    def ask(self, prompt):
        if self.cached_answer is not None:
            return self.cached_answer

        answer = ''
        while answer not in ['y', 'n', 'a', 'q']:
            answer = raw_input('{} [Y]es, [N]o, [A]ll, [Q]uit '.format(prompt))
            answer = answer.strip().lower()

        if answer in ['q', 'a']:
            self.cached_answer = answer

        return answer

ask_to_install = partial(InteractiveAsker().ask, prompt='Upgrade now?')


def update_pkg(pkg, version):
    os.system('pip install {}=={}'.format(pkg, version))


def main():
    args = parse_args()
    setup_logging(args.verbose)

    if args.raw and args.interactive:
        raise SystemExit('--raw and --interactive cannot be used together')

    installed = list(get_installed_pkgs(editables=False))
    non_editables = [name for name, _, editable in installed if not editable]
    latest_versions = dict(get_latest_versions(non_editables))

    all_ok = True
    for pkg, installed_version, editable in installed:
        if editable:
            logging.debug('Skipping -e %s' % (pkg,))
            all_ok = False
            continue

        latest_version = latest_versions[pkg]
        if latest_version is None:
            logging.warning('No update information found for %s' % (pkg,))
            all_ok = False
        elif latest_version != installed_version:
            if args.raw:
                logging.info('%s==%s' % (pkg, latest_version))
            else:
                if args.auto:
                    update_pkg(pkg, latest_version)
                else:
                    logging.info('%s==%s is available (you have %s)' % (pkg,
                        latest_version, installed_version))
                    if args.interactive:
                        answer = ask_to_install()
                        if answer in ['y', 'a']:
                            update_pkg(pkg, latest_version)
            all_ok = False
        elif not args.raw:
            logging.debug('%s==%s is up-to-date' % (pkg, installed_version))

    if all_ok and not args.raw:
        logging.info('Everything up-to-date')


if __name__ == '__main__':
    main()
