#!/usr/bin/env python
# -*- coding: utf-8 -*-

import argparse
import sys
import os
import hashlib
import zipfile
import progressbar as pb
# progressbar3000 for python3
import requests
try:
	import xmlrpclib
except ImportError:
	import xmlrpc.client as xmlrpclib
import textwrap
import string
import shutil
import re
import difflib
import subprocess


client = xmlrpclib.ServerProxy('http://pypi.python.org/pypi')


def list_all():
	""" Lists all packages available in pypi database """
	list_packages = client.list_packages()
	for package in list_packages:
		print(package)


class Package_Search:
	def __init__(self, name, summary, version):
		self.name = name
		self.version = version
		self.summary = ""
		for i in range(0, len(summary), 62):
			self.summary += summary[i:62+i] + '\n\t\t'

	def __str__(self):
		return "Name\t\t" + self.name + "\nVersion\t\t" + self.version + "\nSummary\t\t" + self.summary + "\n"
		

def search(pkg_name):
	""" Searches for a particular package by the name classifier """
	values = client.search({'name': pkg_name})
	for value in values:
		package = Package_Search(value['name'], value['summary'], value['version'])
		print(package)

class Package_release_data:
	def __init__(self, attributes):
		# self.name = name
		# self.version = version
		# self.home_page = attributes['home_page']
		# self.license = attributes['license']
		# self.summary = attributes['summary']
		# self.maintainer = attributes['maintainer']
		# self.docs_url = attributes['docs_url']
		# self.requires_python = attributes['requires_python']
		# self.maintainer_email = attributes['maintainer_email']
		# self.cheesecake_code_kwalitee_id = attributes['cheesecake_code_kwalitee_id']
		# self.keywords = attributes['keywords']
		# self.package_url = attributes['package_url']
		# self.author = attributes['author']
		# self.author_email = attributes['author_email']
		# self.download_url = attributes['download_url']
		# self.platform = attributes['platform']
		# self.cheesecake_documentation_id = attributes['cheesecake_documentation_id']
		# self._pypi_hidden = attributes['_pypi_hidden']
		# self.description = attributes['description']
		# self.release_url = attributes['release_url']
		# self.downloads = attributes['downloads']
		# self._pypi_ordering = attributes['_pypi_ordering']
		# self.classifiers = attributes['classifiers']
		# self.bugtrack_url = attributes['bugtrack_url']
		# self.stable_version = attributes['stable_version']
		# self.cheesecake_installability_id = attributes['cheesecake_installability_id']

		variables = attributes.keys()
		for v in variables:
			setattr(self, v, attributes[v])

	def __str__(self):
		output = "Name\t\t" + self.name + "\nVersion\t\t" + self.version
		if self.maintainer and self.maintainer != 'UNKNOWN':
			output += "\nMaintainter\t" + self.maintainer
		output += "\nHome_page\t" + self.home_page
		output += "\nPackage_url\t" + self.package_url
		if self.download_url and self.download_url != 'UNKNOWN':
			output += "\nDownload_url\t" + self.download_url
		output += "\nRelease_url\t" + self.release_url
		if self.docs_url and self.docs_url != 'UNKNOWN':
			output += "\nDocs_url\t" + self.docs_url
		output += "\nDescription\t" + self.description
		return output

def release_data(pkg_name, pkg_version):
	""" Fetches the release data for a paticular package based on
	the package_name and package_version """
	# if pkg_version:
	values = client.release_data(pkg_name, pkg_version)
	if values:
		package = Package_release_data(values)
		print(package)
	else:
		print("No such package found.")
		print("Please specify the exact package name.")
	return
	# return


def fetch(pkg_name, dict):
	""" Fetches the distfile for a particular package name and release_url """
	print("Fetching distfiles...")
	checksum_md5 = dict['md5_digest']
	parent_dir = './sources'
	home_dir = parent_dir + '/' + 'python'
	src_dir = home_dir + '/py-' + pkg_name
	if not os.path.exists(parent_dir):
		os.makedirs(parent_dir)
	if not os.path.exists(home_dir):
		os.makedirs(home_dir)
	if not os.path.exists(src_dir):
		os.makedirs(src_dir)

	url = dict['url']
	file_name = src_dir + '/' + dict['filename']

	r = requests.get(url)
	if r.status_code == 200	:
		with open(file_name, 'wb') as f:
			meta = r.headers['content-length']
			file_size = int(meta)

			widgets = ['Fetching: ', pb.Percentage(), ' ',
					   pb.Bar(marker=pb.RotatingMarker(), left='[', right=']'),
					   ' ', pb.ETA(), ' ', pb.FileTransferSpeed()]
			pbar = pb.ProgressBar(widgets=widgets, maxval=int(file_size))
			pbar.start()

			file_size_dl = 0
			block_sz = 1024

			for chunk in r.iter_content(block_sz):
				if file_size_dl+block_sz > file_size:
					file_size_dl = file_size
				else:
					file_size_dl += block_sz
				f.write(chunk)
				pbar.update(file_size_dl)

			pbar.finish()

	checksum_md5_calc = hashlib.md5(open(file_name).read()).hexdigest()
	if str(checksum_md5) == str(checksum_md5_calc):
		print('Successfully fetched')
		ext = file_name.split('.')[-1]
		if ext == 'egg':
			zip = zipfile.ZipFile(file_name)
			for name in zip.namelist():
				if name.split("/")[0] == "EGG-INFO":
					zip.extract(name, src_dir)
		return file_name
	else:
		print('Aborting due to inconsistency on checksums\n')
		try:
			os.remove(file_name)
		except OSError as e:
			print("Error: %s - %s." % (e.filename, e.strerror))
		return False


def fetch_url(pkg_name, pkg_version, checksum=False, deps=False):
	""" Checks for the checksums and dependecies for a particular python package
	on the basis of package_name and package_version """
	values = client.release_urls(pkg_name, pkg_version)
	if checksum:
		for value in values:
			if value['filename'].split('.')[-1] in ('gz', 'zip'):
				return fetch(pkg_name, value)
	else:
		for value in values:
			return fetch(pkg_name, value)


def dependencies(pkg_name, pkg_version, deps=False):
	""" Finds dependencies for a particular package on the basis of
	package_name and package_version """
	flag = False
	if not deps:
		return
	values = client.release_urls(pkg_name, pkg_version)
	for value in values:
		if value['filename'].split('.')[-1] in ('gz', 'zip'):
			fetch(pkg_name, value)
	try:
		with open('./sources/python/py-'
				  + pkg_name + '/EGG-INFO/requires.txt') as f:
			list = f.readlines()
			list = [x.strip('\n') for x in list]
		f.close()
		try:
			if flag:
				shutil.rmtree('./sources/python/py-' + pkg_name + '/EGG-INFO',
							  ignore_errors=True)
				items = os.listdir('./sources/python/py-' + pkg_name)
				for item in items[:]:
					if item.split('.')[-1] not in ('gz', 'zip'):
						os.remove('./sources/python/py-'
								  + pkg_name + '/' + item)
						items.remove(item)
				if not items:
					os.rmdir('./sources/python/py-' + pkg_name)
		except:
			pass
		return list
	except:
		try:
			if flag:
				shutil.rmtree('./sources/python/py-'+pkg_name+'/EGG-INFO',
							  ignore_errors=True)
				items = os.listdir('./sources/python/py-'+pkg_name)
				for item in items[:]:
					if item.split('.')[-1] not in ('gz', 'zip'):
						os.remove('./sources/python/py-'+pkg_name+'/'+item)
						items.remove(item)
				if not items:
					os.rmdir('./sources/python/py-'+pkg_name)
		except:
			pass
		return False


def create_diff(old_file, new_file, diff_file):
	""" Creates a diff file for an existent port """
	with open(old_file) as f:
		a = f.readlines()
#    a = open(old_file).readlines()
	with open(new_file) as f:
		b = f.readlines()
#    b = open(new_file).readlines()
	diff_string = difflib.unified_diff(a, b, "Portfile.orig", "Portfile")
	with open(diff_file, 'w') as d:
		d.writelines(diff_string)


def search_port(name):
	""" Searches for an existent port by its name """
	try:
		command = "port file name:^py-" + name + "$"
		command = command.split()
		existing_portfile = \
			subprocess.check_output(command, stderr=subprocess.STDOUT).strip()
		return existing_portfile
	except Exception:
		return False


def checksums(pkg_name, pkg_version):
	""" Generates checksums for a package on the basis of the distfile fetched by
	its package_name and package_version """
	flag = False
	print("Attempting to fetch distfiles...")
	file_name = fetch_url(pkg_name, pkg_version, True)
	if file_name:
		checksums = []
		try:
			print("Generating checksums...")
			command = "openssl rmd160 "+file_name
			command = command.split()
			rmd160 = subprocess.check_output(command, stderr=subprocess.STDOUT)
			rmd160 = rmd160.split('=')[1].strip()
			checksums.insert(0, rmd160)

			command = "openssl sha256 "+file_name
			command = command.split()
			sha256 = subprocess.check_output(command, stderr=subprocess.STDOUT)
			sha256 = sha256.split('=')[1].strip()
			checksums.insert(1, sha256)

			dir = '/'.join(file_name.split('/')[0:-1])
			if flag:
				os.remove(file_name)
			try:
				if flag:
					os.rmdir(dir)
			except OSError:
				pass
			return checksums
		except:
			print("Error\n")
			return


def search_distfile(name, version):
	""" Searches if the distfile listed is present or not """
	try:
		url = client.release_urls(name, version)[0]['url']
		r = requests.get(url, verify=False)
		if not r.status_code == 200:
			raise Exception('No distfile')
	except:
		print("No distfile found")
		print("Please set a DISTFILE env var before generating the portfile")
		sys.exit(0)


def search_license(license):
	""" Maps the license passed to the already present list of
	licences available in Macports """
	license = license.lower()
	patterns = ['.*mit.*', '.*apache.*2', '.*apache.*', '.*bsd.*', '.*agpl.*3',
				'.*agpl.*2', '.*agpl.*', '.*affero.*3', '.*affero.*2',
				'.*affero.*', '.*lgpl.*3', '.*lgpl.*2', '.*lgpl.*', '.*gpl.*3',
				'.*gpl.*2', '.*gpl.*', '.*general.*public.*license.*3',
				'.*general.*public.*license.*2',
				'.*general.*public.*license.*', '.*mpl.*3', '.*mpl.*2',
				'.*mpl.*', '.*python.*license.*', '^python$', '.*']
	licenses = ['MIT', 'Apache-2', 'Apache', 'BSD', 'AGPL-3', 'AGPL-2', 'AGPL',
				'AGPL-3', 'AGPL-2', 'AGPL', 'LGPL-3', 'LGPL-2', 'LGPL',
				'GPL-3', 'GPL-2', 'GPL', 'GPL-3', 'GPL-2', 'GPL', 'MPL-3',
				'MPL-2', 'MPL', 'Python', 'Python', 'NULL']
	for i in range(len(patterns)):
		match = re.search(patterns[i], license)
		if match:
			return licenses[i]


def port_testing(name, portv='27'):
	""" Port Testing function for various phase implementations """
	euid = os.geteuid()
	if euid:
		args = ['sudo', sys.executable] + sys.argv + [os.environ]
		os.execlpe('sudo', *args)

	for phase in [port_fetch, port_checksum, port_extract, port_configure,
				  port_build, port_destroot, port_clean]:
		print(phase.__name__)
		phase_output = phase(name, portv)
		if phase_output:
			print(phase.__name__ + " - SUCCESS")
		else:
			print(phase.__name__ + " FAILED")
			port_clean(name, portv)
			print("Exiting")
			sys.exit(1)

		euid = os.geteuid()
		if euid:
			args = ['sudo', sys.executable] + sys.argv + [os.environ]
			os.execlpe('sudo', *args)


def port_fetch(name, portv='27'):
	""" Fetch phase implementation """
	try:
		command = "sudo port -t fetch dports/python/py-" + \
				  name + " subport=py" + portv + "-" + name
		command = command.split()
		subprocess.check_call(command, stderr=subprocess.STDOUT)
		return True
	except:
		return False


def port_checksum(name, portv='27'):
	""" Checksum phase implementation """
	try:
		command = "sudo port -t checksum dports/python/py-" + \
				  name + " subport=py" + portv + "-" + name
		command = command.split()
		subprocess.check_call(command, stderr=subprocess.STDOUT)
		return True
	except:
		return False


def port_extract(name, portv='27'):
	""" Checksum phase implementation """
	try:
		command = "sudo port -t extract dports/python/py-" + \
				  name + " subport=py" + portv + "-" + name
		command = command.split()
		subprocess.check_call(command, stderr=subprocess.STDOUT)
		return True
	except:
		return False


def port_patch(name, portv='27'):
	""" Patch phase implementation """
	try:
		command = "sudo port -t patch dports/python/py-" + \
				  name + " subport=py" + portv + "-" + name
		command = command.split()
		subprocess.check_call(command, stderr=subprocess.STDOUT)
		return True
	except:
		return False


def port_configure(name, portv='27'):
	""" Configure phase implementation """
	try:
		command = "sudo port -t configure dports/python/py-" + \
				  name + " subport=py" + portv + "-" + name
		command = command.split()
		subprocess.check_call(command, stderr=subprocess.STDOUT)
		return True
	except:
		return False


def port_build(name, portv='27'):
	""" Build phase implementation """
	try:
		command = "sudo port -t build dports/python/py-" + \
				  name + " subport=py" + portv + "-" + name
		command = command.split()
		subprocess.check_call(command, stderr=subprocess.STDOUT)
		return True
	except:
		return False


def port_destroot(name, portv='27'):
	""" Destroot phase implementation """
	try:
		command = "sudo port -t destroot dports/python/py-" + \
				  name + " subport=py" + portv + "-" + name
		command = command.split()
		subprocess.check_call(command, stderr=subprocess.STDOUT)
		return True
	except:
		return False


def port_clean(name, portv='27'):
	""" Clean phase implementation """
	try:
		command = "sudo port -t clean dports/python/py-" + \
				  name + " subport=py" + portv + "-" + name
		command = command.split()
		subprocess.check_call(command, stderr=subprocess.STDOUT)
		return True
	except:
		return False

# class Package_portfile:
# 	def __init__(self, dict, filename, dict2):


def create_portfile(dict, file_name, dict2):
	""" Creates a portfile on the basis of the release_data and release_url fetched
	on the basis of package_name and package_version """
	search_distfile(dict['name'], dict['version'])
	print("Creating Portfile for pypi package " + dict['name'] + "...")
	with open(file_name, 'w') as file:
		file.write('# -*- coding: utf-8; mode: tcl; tab-width: 4; ')
		file.write('indent-tabs-mode: nil; c-basic-offset: 4 ')
		file.write('-*- vim:fenc=utf-8:ft=tcl:et:sw=4:ts=4:sts=4\n')
		file.write('# $Id$\n\n')
		file.write('PortSystem          1.0\n')
		file.write('PortGroup           python 1.0\n\n')

		file.write('name                py-{0}\n'.format(dict['name']))
		file.write('version             {0}\n'.format(dict['version']))

		file.write('platforms           darwin\n')
		license = dict['license']
		license = search_license(license)
		file.write('license             {0}\n'.format(license))

		if dict['maintainer']:
			maintainers = ' '.join(dict['maintainer'])
			if not maintainers == "UNKNOWN":
				file.write('maintainers         {0}\n\n'.format(maintainers))
			else:
				file.write('maintainers         {0}\n\n'.format(
						   os.getenv('maintainer', 'nomaintainer')))
		else:
			print("No maintainers found...")
			print("Looking for maintainers in environment variables...")
			file.write('maintainers         {0}\n\n'.format(
					   os.getenv('maintainer', 'nomaintainer')))

		summary = dict['summary']
		if summary:
			summary = re.sub(r'[\[\]\{\}\;\:\$\t\"\'\`\=(--)]+',
							 ' ', summary)
			summary = re.sub(r'\s(\s)+', ' ', summary)
			summary = summary.encode('utf-8')
			summary = filter(lambda x: x in string.printable, summary)
			sum_lines = textwrap.wrap(summary)
			file.write('description         ')
			for sum_line in sum_lines:
				if sum_line:
					if not sum_lines.index(sum_line) == 0:
						file.write('                    ')
					if sum_line == sum_lines[-1]:
						file.write("{0}\n".format(sum_line))
					else:
						file.write("{0} \\\n".format(sum_line))
		else:
			file.write('description         None\n\n')

		file.write('long_description    ${description}\n\n')
		home_page = dict['home_page']

		if home_page and not home_page == 'UNKNOWN':
			file.write('homepage            {0}\n'.format(home_page))
		else:
			print("No homepage found...")
			print("Looking for homepage in environment variables...")
			file.write('homepage            {0}\n'.format(
					   os.getenv('home_page', '')))

		try:
			for item in dict2:
				if item['python_version'] == 'source':
					master_var = item['url']
					break

			if master_var:
				master_site = '/'.join(master_var.split('/')[0:-1])
				ext = master_var.split('/')[-1].split('.')[-1]
				if ext == 'zip':
					zip_set = True
				else:
					zip_set = False
		except:
			if dict['release_url']:
				master_site = dict['release_url']
				zip_set = False
			else:
				print("No master site found...")
				print("Looking for master site in environment variables...")
				master_site = os.getenv('master_site', '')
				zip_set = False

		if master_site:
			file.write('master_sites        {0}\n'.format(master_site))
			master_site_exists = True
		else:
			master_site_exists = False

		if zip_set:
			file.write('use_zip             yes\n')
			file.write('extract.mkdir       yes\n')

		file.write('distname            {0}-{1}\n\n'.format(
				   dict['name'], dict['version']))

		print("Attempting to generate checksums for " + dict['name'] + "...")
		checksums_values = checksums(dict['name'], dict['version'])
		if checksums_values:
			file.write('checksums           rmd160  {0} \\\n'.format(
					   checksums_values[0]))
			file.write('                    sha256  {0}\n\n'.format(
					   checksums_values[1]))

		python_vers = dict['requires_python']
		if python_vers:
			file.write('python.versions     25 26 27 {0}\n\n'.format(
					   dict['requires_python']))
		else:
			file.write('python.versions     25 26 27 32 33 34\n\n')

		print("Finding dependencies...")
		file.write('if {${name} ne ${subport}} {\n')
		file.write('    depends_build-append \\\n')
		file.write('                        ' +
				   'port:py${python.version}-setuptools\n')
		deps = dependencies(dict['name'], dict['version'], True)
		if deps:
			for i, dep in enumerate(deps):
				dep = dep.split('>')[0].split('=')[0]
				dep = dep.replace('[', '').replace(']', '')
				deps[i] = dep
			for dep in deps:
				if dep in ['setuptools', '', '\n']:
					while deps.count(dep) > 0:
						deps.remove(dep)

			if len(deps) > 0:
				file.write('    depends_run-append \\\n')

				for dep in deps[:-1]:
					file.write('                        ' +
							   'port:py${python.version}-' +
							   dep + ' \\\n')
				else:
					file.write('                        ' +
							   'port:py${python.version}-' +
							   deps[-1] + '\n')
			else:
				file.write("\n")
		file.write('\n')
		file.write('    livecheck.type      none\n')
		if master_site_exists:
			file.write('} else {\n')
			file.write('    livecheck.type      regex\n')
			file.write('    livecheck.url       ${master_sites}\n')
			file.write('}\n')
		else:
			file.write('}\n')
	print("Searching for existent port...")
	port_exists = search_port(dict['name'])
	if port_exists:
		print("Creating diff...")
		old_file = port_exists
		new_file = './dports/python/py-'+dict['name']+'/Portfile'
		diff_file = './dports/python/py-'+dict['name']+'/patch.Portfile.diff'
		create_diff(old_file, new_file, diff_file)
		print(str(os.path.abspath(diff_file))+"\n")
		print("\nIf you want to open a new ticket. Please visit")
		print("https://trac.macports.org/auth/login/?next=/newticket")
		print("to open a new ticket after logging in with your credentials.")
	else:
		print("No port found.")


def print_portfile(pkg_name, pkg_version=None):
	""" Creates the directories and other commands necessary
	for a development environment """
	root_dir = os.path.abspath("./dports")
	port_dir = os.path.join(root_dir, 'python')
	home_dir = os.path.join(port_dir, 'py-'+pkg_name)
	if not os.path.exists(root_dir):
		os.makedirs(root_dir)
		try:
			command = 'portindex dports/'
			command = command.split()
			subprocess.call(command, stderr=subprocess.STDOUT)
		except:
			pass
	if not os.path.exists(port_dir):
		os.makedirs(port_dir)
	if not os.path.exists(home_dir):
		os.makedirs(home_dir)

	print("Attempting to fetch data from pypi...")

	dict = client.release_data(pkg_name, pkg_version)
	dict2 = client.release_urls(pkg_name, pkg_version)
	if dict and dict2:
		print("Data fetched successfully.")
	elif dict:
		print("Release Data fetched successfully.")
	elif dict2:
		print("Release url fetched successfully.")
	else:
		print("No data found.")

	file_name = os.path.join(home_dir, "Portfile")
	create_portfile(dict, file_name, dict2)
	print("SUCCESS\n")


def main(argv):
	""" Main function - Argument Parser """
	parser = argparse.ArgumentParser(description="Pypi2Port Tester")
# Calls list_all() which lists al available python packages
	parser.add_argument('-l', '--list', action='store_true', dest='list',
						default=False, required=False,
						help='List all packages')
# Calls search with the package_name
	parser.add_argument('-s', '--search', action='store', type=str,
						dest='packages_search', nargs='*', required=False,
						help='Search for a package')
# Calls release_data with package_name and package_version
	parser.add_argument('-d', '--data', action='store',
						dest='packages_data', nargs='*', type=str,
						help='Releases data for a package')
# Calls fetch_url with the various package_releases
	parser.add_argument('-f', '--fetch', action='store', type=str,
						dest='package_fetch', nargs='*', required=False,
						help='Fetches distfiles for a package')
# Calls print_portfile with the release data available
	parser.add_argument('-p', '--portfile', action='store', type=str,
						dest='package_portfile', nargs='*', required=False,
						help='Prints the portfile for a package')
# Calls port_testing
	parser.add_argument('-t', '--test', action='store', type=str,
						dest='package_test', nargs='*', required=False,
						help='Tests the portfile for various phase tests')
	options = parser.parse_args()

	if options.list:
		list_all()
		return

	if options.packages_search:
		for pkg_name in options.packages_search:
			search(pkg_name)
		return

	if options.packages_data:
		pkg_name = options.packages_data[0]
		if len(options.packages_data) > 1:
			pkg_version = options.packages_data[1]
			release_data(pkg_name, pkg_version)
		else:
			if client.package_releases(pkg_name):
				pkg_version = client.package_releases(pkg_name)[0]
				release_data(pkg_name, pkg_version)
			else:
				print("No release found\n")
		return

	if options.package_fetch:
		pkg_name = options.package_fetch[0]
		if len(options.package_fetch) > 1:
			pkg_version = options.package_fetch[1]
			fetch_url(pkg_name, pkg_version)
		else:
			releases = client.package_releases(pkg_name)
			if releases:
				pkg_version = releases[0]
				fetch_url(pkg_name, pkg_version)
			else:
				print("No release found\n")
		return

	if options.package_portfile:
		pkg_name = options.package_portfile[0]
		if len(options.package_portfile) > 1:
			pkg_version = options.package_portfile[1]
			print_portfile(pkg_name, pkg_version)
		else:
			vers = client.package_releases(pkg_name)
			if vers:
				pkg_version = vers[0]
				print_portfile(pkg_name, pkg_version)
			else:
				print("No release found\n")
		return

	if options.package_test:
		if len(options.package_test) > 0:
			pkg_name = options.package_test[0]
			port_testing(pkg_name)
		else:
			print("No package name specified\n")
		return

	parser.print_help()
	parser.error("No input specified")

if __name__ == "__main__":
	main(sys.argv[1:])