#!/usr/bin/python
from __future__ import unicode_literals
from __future__ import with_statement

import sys
import datetime
import logging
import argparse
import re

# make this script work for python2 and python3
# the try will fail on python3
try:
    input = raw_input
    from xmlrpclib import ServerProxy
except NameError:
    from xmlrpc.client import ServerProxy

BLANK_PKGBUILD = """\
#Automatically generated by pip2arch on {date}
pkgname={pkg.outname}
pkgver={pkg.version}
pkgrel=1
pkgdesc="{pkg.description}"
url="{pkg.url}"
depends=('{pkg.pyversion}' {depends})
makedepends=('{pkg.distributepackage}' {makedepends})
license=('{pkg.license}')
arch=('any')
source=('{pkg.download_url}')
md5sums=('{pkg.md5}')
build() {{
    cd $srcdir/{pkg.name}-{pkg.version}
    {pkg.pyversion} setup.py build
}}
package() {{
    cd $srcdir/{pkg.name}-{pkg.version}
    {pkg.pyversion} setup.py install --root="$pkgdir" --optimize=1 {pkg.setup_args}
}}
"""

SOURCEFILE_TYPE_RE = re.compile(".*\.(tar|zip|gz|z|bz2?|xz)", re.IGNORECASE)

class pip2archException(Exception): pass
class VersionNotFound(pip2archException): pass
class LackOfInformation(pip2archException): pass

class Package(object):
    logging.info('Creating Server Proxy object')
    client = ServerProxy('https://pypi.python.org/pypi')
    depends = []
    makedepends = []
    data_received = False
    setup_args = ''

    def get_package(self, name, outname, pyversion ,version=None):
        if version is None:
            versions = self.client.package_releases(name)
            if len(versions) > 1:
                version = self.choose_version(versions)
            else:
                logging.info('Using version %s' % versions[0])
                version = versions[0]
        self.version = version
        self.pyversion = pyversion

        data = self.client.release_data(name, version)
        logging.info('Got release_data from PyPi')

        raw_urls = self.client.release_urls(name, version)
        logging.info('Got release_urls from PyPi')
        if not len(data):
            raise VersionNotFound('PyPi did not return any information for version {0}'.format(self.version))
        elif not len(raw_urls):
            if 'download_url' in data:
                download_url = data['download_url']
                if SOURCEFILE_TYPE_RE.match(data['download_url']) is None:
                    raise LackOfInformation("Couldn't find any suitable source")
                else:
                    urls = {'url': download_url}
                    logging.warning('Got download link but no md5, you may have to search it by youself or generate it')
            else:
                raise LackOfInformation('PyPi did not return the necessary information to create the PKGBUILD')
        else:
            urls = {}
            for url in raw_urls:
                if SOURCEFILE_TYPE_RE.match(url['filename']):
                    urls = url
            if not urls:
                raise pip2archException
                ('Selected package version had no suitable sources')
        logging.info('Parsed release_urls data')


        self.distributepackage = 'python2-distribute' if\
            self.pyversion != 'python' else 'python3'
        logging.info("Set distribute package as {0}".format(self.distributepackage))

        if outname is not None:
            self.outname = outname.lower()
        elif any(re.search(r'Librar(ies|y)', item) for item in data['classifiers']):
            #if this is a library
            self.outname = '{pyversion}-{pkgname}'.format(
                pyversion=self.pyversion, pkgname=name).lower()
            logging.info('Automaticly added {0} to the front of the package'.format(self.pyversion))
        else:
            self.outname = name.lower()

        #check for licenes texts
        if len(data.get('license', '')) > 10:
            self.license = 'CUSTOM'
        else:
            self.license = data.get('license', 'UNKNOWN')

        try:
            self.name = data['name']
            self.description = data['summary']
            self.download_url = urls.get('url', '')
            self.md5 = urls.get('md5_digest', '')
            self.url = data.get('home_page', '')
            self.depends = data.get('requires', [])
        except KeyError:
            raise pip2archException('PyPi did not return needed information')
        logging.info('Parsed other data')
        self.data_received = True

    def search(self, term, interactive=False):
        results = self.client.search({'description': term[1:],
                                      'name': term[1:]},
                                      'or')
        logging.info('Got search results for term {term} from PyPi server'.format(term=term))

        #If no results
        if not results:
            print ('No packages found')
            return
        for i, result in enumerate(results):
            i += 1
            print ('{index}. {name} - {summary}'.format(index=i, name=result['name'], summary=result['summary']))

        #If we don't want talking, exit here
        if not interactive:
            #self.data_received = False
            return

        selection = raw_input('Enter the number of the PyPi package you would like to process\n')

        try:
            selection = int(selection.strip())
            selection -= 1
            chosen = results[selection]
        except (TypeError, IndexError):
            print ('Not a valid selection. Must be integer in range 1 - {length}'.format(length=len(results)))
            retry = raw_input('Retry? [Y/n]\n')
            if retry.strip()[0].lower() != 'n':
                #offer recurse on failure, maybe user will be smarter this time -.-
                return self.search(term)
            else:
                return

        name = chosen['name']
        outname = chosen['name']

        return self.get_package(name, outname)

    def choose_version(self, versions):
        print ('Multiple versions found:')
        print (', '.join(versions))
        ver = raw_input('Which version would you like to use? ')
        if ver in versions:
            return ver
        else:
            print ('That was NOT one of the choices...')
            print ('Try again')
            return self.choose_version(versions)

    def add_depends(self, depends):
        self.depends += depends

    def add_makedepends(self, makedepends):
        self.makedepends += makedepends

    def render(self):
        depends = "'" + "' '".join(d for d in self.depends) + "'" if self.depends else ''
        makedepends = "'" + "' '".join(d for d in self.makedepends) + "'" if self.makedepends else ''
        return BLANK_PKGBUILD.format(pkg=self,
                                     date=datetime.date.today(),
                                     depends=depends,
                                     makedepends=makedepends)

def set_logging_level(level_str):
    level = getattr(logging, level_str.upper())
    logging.root.setLevel(level)

def main():
    parser = argparse.ArgumentParser(description='Convert a PyPi package into an Arch Linux PKGBUILD.')
    parser.add_argument('pkgname', metavar='PKGNAME', action='store',
                        help='Name of PyPi package for pip2arch to process')
    parser.add_argument('-v', '--version', dest='version', action='store',
                        help='The version of the speciied PyPi package to process')
    parser.add_argument('-p', '--python-version', dest='pyversion', action='store',
                        default='python', choices=('python','python2'),
                        help='The python version to build and install the package with')
    parser.add_argument('-o', '--output', dest='outfile', action='store',
                        default='PKGBUILD',
                        help='The file to output the generated PKGBUILD to')
    parser.add_argument('-s', '--search', dest='search', action='store_true',
                        help="Search for given package name, instead of building PKGBUILD")
    parser.add_argument('-i', '--interactive', dest='interactive', action='store_true',
                        help="Makes all commands interactive, prompting user for input.")
    parser.add_argument('-d', '--dependencies', dest='depends', action='append',
                        help="The name of a package that should be added to the depends array")
    parser.add_argument('-m', '--make-dependencies', dest='makedepends', action='append',
                        help="The name of a package that should be added to the makedepends array")
    parser.add_argument('-n', '--output-package-name', dest='outname', action='store', default=None,
                        help='The name of the package that pip2arch will generate')
    parser.add_argument('--logging-level', dest='logging_level', action='store', default='warning', choices=('warning', 'info', 'debug'),
                        help='The level of logging messages to show')
    parser.add_argument('-b', '--build-args', dest='build_args', action='store',
                        help='Custom arguments for the python install setup.py file')

    args = parser.parse_args()

    set_logging_level(args.logging_level)

    p = Package()

    if args.search:
        p.search(args.pkgname, interactive=args.interactive)
    else:
        p.get_package(name=args.pkgname, pyversion= args.pyversion,
                      version=args.version, outname=args.outname)

    if args.depends:
        p.add_depends(args.depends)
    if args.makedepends:
        p.add_makedepends(args.makedepends)
    if args.build_args:
        p.setup_args = args.build_args
    if p.data_received:
        print ('Got package information')
        with open(args.outfile, 'w') as f:
            f.write(p.render())
        print ('PKGBUILD written')

if __name__ == '__main__':
    try:
        main()
    except pip2archException as e:
        sys.exit('Pip2Arch error: {0}'.format(e))
    else:
        sys.exit(0)