#! /usr/bin/env python3

import argparse
import glob
import os
import shutil
import signal
import subprocess
import sys
import traceback


# Define the (baseline) version
baseVersion = "0.61"

# Define relevant base names
appBaseName = 'DistMaker'
libBaseName = 'distMaker'

# Define the libraries DistMaker depends on
libList = ['glum-2.0.0-RC3.jar', 'guava-18.0.jar']
#libList += ['commons-compress-1.15.jar']

# Define the paths to various executables
antPath = '/spare/apache/apache-ant-1.10.8'
antExe = os.path.join(antPath, 'bin/ant')
jdkPath = '/spare/jdk-11'
jarExe = os.path.join(jdkPath, 'bin/jar')
javaExe = os.path.join(jdkPath, 'bin/java')


def checkForInstalledApps():
	"""Checks for installed applications needed to build a release of the
	DistMaker library / package.
	"""
	# Ensure required applications are installed
	errList = []
	for aPath in [antExe, jarExe, javaExe]:
		if os.path.exists(aPath) == False:
			errList.append('System executable is missing: ' + aPath)
	if len(errList) > 0:
		print('There are configuration errors with the environment or system.')
#		print('System Path:' + str(sys.path))
		print('Please correct the following:')
		for aError in errList:
			print('\t' + aError)
		sys.exit(0)


def buildLibraryJar(aVersion):
	"""Method that builds the library jars. Upon sucessful execution
	jar files (binary + source) will be generated.
	"""
	# Note it is assumed that this is run from the path /proj/glum/

	# Compile the java files
	# Run the Ant script to build the class files
	os.environ["JAVA_HOME"] = jdkPath
	exeCmd = [antExe, '-f', 'tools/build.xml', 'compile']
	retCode = subprocess.call(exeCmd)
	if retCode != 0:
		print('Failed while trying to run ant script. Exiting...\n')
		sys.exit(0)
	print('Finished executing Ant script.\n')

	# Generate the manifest file
	manifestFN = 'release/Manifest.txt'
#	buildManifestFile(manifestFN)

	# Build the (bin) jar file
	jarBinFN = 'release/' + libBaseName + '-' + aVersion + '.jar'
#	exeCmd = [jarExe, 'cfm', jarBinFN, manifestFN, '-C', 'bin/classes', '.']
	exeCmd = [jarExe, 'cfM', jarBinFN, '-C', 'bin/classes', '.']
	retCode = subprocess.call(exeCmd)
	if retCode != 0:
		print('Failed to build jar file: ' + jarBinFN)
		exit(-1)

	# Build the (src) jar file
	jarSrcFN = 'release/' + libBaseName + '-' + aVersion + '-src.jar'
	exeCmd = ['zip', '-D9q', '../' + jarSrcFN, '-r', '.']
	retCode = subprocess.call(exeCmd, cwd='./src')
	if retCode != 0:
		print('Failed to build jar file: ' + jarSrcFN)
		exit(-1)

	# Remove the manifest file
#	os.remove(manifestFN)

def buildRelease(aExtraTag, aDoNotClean=False):
	"""Method that builds a release of DistMaker. Upon sucessful execution
	the following will be created:
	- distMaker-<version>.jar
	- distMaker-<version>-src.jar
	- DistMaker-<version>.tar.gz
	"""
	# Define the version to build
	version = baseVersion
	if aExtraTag != None:
		if aExtraTag.startswith('.') or aExtraTag.startswith('-'):
			version = baseVersion + aExtraTag
		else:
			version = baseVersion + '-' +  aExtraTag

	# Retrieve the install path
	installPath = getInstallRoot()
	installPath = os.path.dirname(installPath)

	# Define the paths of interest
	jarBinPathFN = os.path.join(installPath, 'release', libBaseName + '-' + version + '.jar')
	jarSrcPathFN = os.path.join(installPath, 'release', libBaseName + '-' + version + '-src.jar')

	workPath = os.path.join(installPath, 'release', appBaseName + '-' + version)
	destFileGZ = os.path.join(installPath, 'release', appBaseName + '-' + version + '.tar.gz')

	# Let the user know of the version we are building
	print('Building ' + appBaseName + ' release ' + version + '...\n')

	# Bail if there is a prior build
	failMsgL = []
	if os.path.exists(jarBinPathFN) == True:
		failMsgL += ['Library binary file already exists: ' + jarBinPathFN]
	if os.path.exists(jarSrcPathFN) == True:
		failMsgL += ['Library source file already exists: ' + jarSrcPathFN]
	if os.path.exists(workPath) == True:
		failMsgL += ['Release folder already exists: ' + workPath]
	if os.path.exists(destFileGZ) == True:
		failMsgL += ['Release package already exists: ' + destFileGZ]

	if len(failMsgL) > 0:
		errPrintln('Aborting ' + appBaseName + ' release build. Reasons:')
		for aFailMsg in failMsgL:
			errPrintln('   - ' + aFailMsg)
		errPrintln('')
		exit(-1)

	# Build the library jars
	buildLibraryJar(version)

	# Confirm that we have built the class files properly by retrieving the
	# version from the built application jar and comparing it to our version.
	tmpVersion = getLibraryBinVersion(jarBinPathFN)
	if tmpVersion != version:
		print('Failure: Embedded release: {}   Expected: {}\n\tAborting...'.format(tmpVersion, version))
		exit(-1)

	# Laydown the structure, and let the user know of the version we are building
	os.mkdir(workPath)

	# Copy the regular documents
	dstPath = os.path.join(workPath, 'doc')
	os.mkdir(dstPath)
	for aFile in ['QuickStartGuide.pdf', 'ReadMe.txt']:
		srcPath = os.path.join(installPath, 'doc', aFile)
		shutil.copy2(srcPath, dstPath)

	# Copy the legal documents
	dstPath = os.path.join(workPath, 'doc/legal')
	os.mkdir(dstPath)
	for aFile in glob.glob('doc/legal/*txt'):
		srcPath = os.path.join(installPath, aFile)
		shutil.copy2(srcPath, dstPath)

	# Copy the libraries
	dstPath = os.path.join(workPath, 'lib')
	os.mkdir(dstPath)
	for aLib in libList:
		srcPath = os.path.join(installPath, 'lib', aLib)
		shutil.copy2(srcPath, dstPath)

	# Copy the (newly) built DistMaker (binary) library
	shutil.copy2(jarBinPathFN, dstPath)

	# Copy the scripts
	dstPath = os.path.join(workPath, 'script')
	os.mkdir(dstPath)
	for aScript in ['appleUtils.py', 'linuxUtils.py', 'windowsUtils.py', 'buildDist.py', 'deployAppDist.py', 'deployJreDist.py', 'jreUtils.py', 'logUtils.py', 'miscUtils.py']:
		srcPath = os.path.join(installPath, 'script', aScript)
		shutil.copy2(srcPath, dstPath)

	# Setup the template tree
	dstPath = os.path.join(workPath, 'template')
	os.makedirs(dstPath + '/apple')
	os.makedirs(dstPath + '/background')
	os.makedirs(dstPath + '/launch4j')
	for aFile in ['appLauncher.jar', 'JreCatalog.txt', 'apple/.DS_Store.template', 'apple/JavaAppLauncher', 'background/background.png', 'launch4j/launch4j-3.14-linux-x64.tgz', 'launch4j/launch4j-3.14-linux.tgz', 'launch4j/launch4j-3.14-macosx-x86.tgz']:
		srcPath = os.path.join(installPath, 'template', aFile)
		shutil.copy2(srcPath, dstPath + '/' + aFile)

	# Form the archive
	exeCmd = ['tar', '-czf', destFileGZ, '-C', os.path.dirname(workPath), os.path.basename(workPath)]
	retCode = subprocess.call(exeCmd)
	if retCode != 0:
		print('Failed to build tar.gz file: ' + destFileGZ)
		exit(-1)

	# Remove the workPath
	if aDoNotClean == False:
		shutil.rmtree(workPath)

	print(appBaseName + ' release ' + version + ' built. Assets:')
	for aFilePath in [jarBinPathFN, jarSrcPathFN, destFileGZ]:
		print('   - ' + aFilePath)
	print('')


def errPrintln(aMessage=''):
	"""Print the specified string with a trailing newline to stderr. Each tab
	character will be replaced with: 3 spaces"""
	aMessage = aMessage.replace('\t', '   ')
	sys.stderr.write(aMessage + '\n')


def getLibraryBinVersion(aJarBinPath):
	"""Method that will return the version of the specified binary jar file.
	The binary jar file will be queried (package distmaker.DistApp) and the
	output will be parsed. Any failures will result in the abrupt exit of this
	script."""
	try:
		cpStr = aJarBinPath
		cpStr += ':lib/' + ':lib/'.join(libList)
		exeCmd = [javaExe, '-cp', cpStr, 'distMaker.DistApp', '--version']
		output = subprocess.check_output(exeCmd).decode('utf-8')
		version = output.split()[1].strip()
		return version
	except Exception as aExp:
		traceback.print_exc()
		exit(-1)


def getInstallRoot():
	"""Returns the root path where the running script is installed."""
	argv = sys.argv;
	installRoot = os.path.dirname(argv[0])
# 	print('appInstallRoot: ' + appInstallRoot)
	return installRoot


def handleSignal(signal, frame):
	"""Signal handler, typically used to capture ctrl-c."""
	print('User aborted processing!')
	sys.exit(0)


if __name__ == "__main__":
	# Logic to capture Ctrl-C and bail
	signal.signal(signal.SIGINT, handleSignal)

	# Require python version 3.6 or later
	targVer = (3, 6)
	if sys.version_info < targVer:
		print('The installed version of python is too old. Please upgrade.')
		print('   Current version: ' + '.'.join(str(i) for i in sys.version_info))
		print('   Require version: ' + '.'.join(str(i) for i in targVer))
		sys.exit(-1)

	tmpDescr = 'Utility to build a ' + appBaseName + ' release\n'
	parser = argparse.ArgumentParser(prefix_chars='-', description=tmpDescr, add_help=False, fromfile_prefix_chars='@')
	parser.add_argument('--help', '-h', help='Show this help message and exit.', action='help')
	parser.add_argument('--doNotClean', default=False, action='store_true', help='Do NOT remove temporary work folder created while generating release.')
	parser.add_argument('--doFullBuild', default=False, action='store_true', help='Force a full build of the main jar file. (Unsupported action)')
	parser.add_argument('--extraTag', default=None, action='store', help='Specify an extra tag for to the version.')

	# Intercept any request for a  help message and bail
	argv = sys.argv;
	if '-h' in argv or '-help' in argv or '--help' in argv:
		parser.print_help()
		exit()

	# Parse the args
	parser.formatter_class.max_help_position = 50
	args = parser.parse_args()

	# TODO: Finish this functionality
	if args.doFullBuild == True:
		print("Unsupported action: [--doFullBuild]. Skipping...")

	# Ensure required applications are installed
	checkForInstalledApps()

	buildRelease(args.extraTag, args.doNotClean)
