#! /usr/bin/env python
#
#  BLIS    
#  An object-based framework for developing high-performance BLAS-like
#  libraries.
#
#  Copyright (C) 2014, The University of Texas at Austin
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions are
#  met:
#   - Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#   - Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#   - Neither the name(s) of the copyright holder(s) nor the names of its
#     contributors may be used to endorse or promote products derived
#     from this software without specific prior written permission.
#
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
#  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
#  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
#  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
#  HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
#  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
#  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
#  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
#  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
#

# ------------------------------------------------------------------------------

# Import modules
import sys
import os
import os.path
import getopt
import re
import string

# Global variables for command line options, with default settings.
script_name  = ""
dry_run_flag = False
verbose_flag = False

# Global constants
config_dirname     = "config"
source_dirname     = "frame"
object_dirname     = "obj"
object_extension   = ".obj"
leaf_list_path     = "build/leaf_list"
revision_filename  = "revision"
rev_varname        = "REVISION"
pwd_varname        = "PWD"
arch_varname       = "ARCH_STR"
build_varname      = "BUILD_STR"
ccompiler_varname  = "CCOMPILER_STR"


# ------------------------------------------------------------------------------

def print_usage():
	
	# Print help information.
	print " "
	print " %s" % script_name
	print " "
	print " Field G. Van Zee"
	print " "
	print " Create a config.mk file that is to be included by the nmake Makefile."
	print " This config.mk file is based on a template, but also includes variable"
	print " definitions that are needed for the specific build were are performing."
	print " The variables which are currently appended to config.mk at runtime are:"
	print "   - the revision string"
	print "   - the path to the current working directory"
	print "   - the build string (e.g. debug, release)"
	print "   - the architecture string (e.g. x86, x64)"
	print "   - the C compiler to use (e.g. icl, cl)"
	print "   - a list of paths to the object files to be compiled"
	print " The config.mk file is placed within the config subdirectory." 
	print " "
	print " Usage:"
	print "   %s [options] flat_dir arch build ccompiler path\\to\\config.mk.in" % script_name
	print " "
	print " The following options are accepted:"
	print " "
	print "   -d          dry-run"
	print "                 Go through all the motions, but don't actually output"
	print "                 the nmake definition file."
	print "   -v          verbose"
	print "                 Be verbose about actions (one line of output her action)."
	print " "

	# Exit the script.
	sys.exit()

# ------------------------------------------------------------------------------

def main():

	# Extern our global veriables.	
	global script_name
	global dry_run_flag
	global verbose_flag

	# Get the script name so we can use it in our output.
	( script_dir, script_name ) = os.path.split( sys.argv[0] )
	
	try:
		
		# Get the command line options.
		options, args = getopt.getopt( sys.argv[1:], "dv")
	
	except getopt.GetoptError, err:
	
		# print help information and exit:
		print str( err ) # will print something like "option -a not recognized"
		print_usage()
	
	# Parse our expected command line options.
	for o, a in options:
		
		if o == "-d":
			dry_run_flag = True
		elif o == "-v":
			verbose_flag = True
		else:
			assert False, "unhandled option"
	
	# Check the number of arguments after command line option processing.
	n_args = len( args )
	if n_args != 5:
		print_usage() 

	# Acquire the non-optional arguments.
	flat_dir         = args[0]
	arch_string      = args[1]
	build_string     = args[2]
	ccompiler_string = args[3]
	input_filepath   = args[4]

	# Acquire the list of leaf-type directories we will descend into.
	leaf_list = read_leaf_list()

	# Read the contents of the template file.
	template_file_line_list = read_template_file( input_filepath )

	# Initialize a new list for the lines to be output
	output_file_line_list = template_file_line_list

	# Read the revision number from the revision file.
	rev_num_str = read_revision_file( revision_filename )

	# Add a variable for the revision number of the code we're working with.
	rev_var_value = rev_varname + " = " + rev_num_str + "\n"
	output_file_line_list.append( rev_var_value )
	
	# Add a variable for the path to the current working directory and append
	# it to our list.
	pwd_var_value = pwd_varname + " = " + os.getcwd() + "\n"
	output_file_line_list.append( pwd_var_value )
	
	# Add a variable for the architecture string and append it to our list.
	arch_var_value = arch_varname + " = " + arch_string + "\n"
	output_file_line_list.append( arch_var_value )
	
	# Add a variable for the build type string and append it to our list.
	build_var_value = build_varname + " = " + build_string + "\n"
	output_file_line_list.append( build_var_value )
	
	# Add a variable for the C compiler string and append it to our list.
	ccompiler_var_value = ccompiler_varname + " = " + ccompiler_string + "\n"
	output_file_line_list.append( ccompiler_var_value )
	
	# Walk the flat subdirectories for each of the leaves.
	for leaf_spec in leaf_list:
		
		# Unpack the leaf_spec tuple.
		src_exts, hdr_exts = leaf_spec

		# Create the paths to the source and object subdirectories.
		src_dirpath = os.path.join( flat_dir, source_dirname )
		obj_dirpath = os.path.join( flat_dir, object_dirname, arch_string, build_string )

		# Get a list of files from the leaf subdirectory.
		src_filenames = os.listdir( src_dirpath )
		
		# This will be the nmake variable name to which we will assign the list
		# of source files.
		nmake_varname = "BLIS_OBJS"
		
		# Generate the line to output.
		leaf_line = generate_object_list( nmake_varname, src_filenames, src_exts, obj_dirpath )

		# Accumulate the lines.
		output_file_line_list.append( leaf_line )
	
	# Get the filename part of the input filepath.
	input_filedir, input_filename = os.path.split( input_filepath )

	# Remove the .in extension in the output filename.
	output_filename = re.sub( '.mk.in', '.mk', input_filename )
	
	# Construct the filepath for the output file.
	output_filepath = os.path.join( flat_dir, config_dirname, output_filename )

	# Write the output lines.
	write_output_file( output_filepath, output_file_line_list )

# ------------------------------------------------------------------------------

def read_revision_file( filepath ):

	# Try to open the revision file.
	try:
		
		revision_file = open( filepath, 'r' )
	
	except IOError, err:
		
		print "%s: Couldn't open revision file %s" % ( script_name, filepath )
		sys.exit(1)

	# Read the first (and only) line.
	line = revision_file.readline()

	# Close the file.
	revision_file.close()

	# Grab the string and strip the it of whitespace (should just be a newline).
	rev_num_str = line.strip()

	# Return the revision number string.
	return rev_num_str

# ------------------------------------------------------------------------------

def generate_object_list( nmake_varname, src_filenames, src_exts, obj_dirpath ):

	# Initialize the string as an assignment operation.
	the_line = nmake_varname + " = "
	
	# Return early if there are no source extensions for this leaf spec.
	if src_exts == []:
		return ""

	# Construct a pattern to match any file ending with any of the source file
	# extensions given. This string is going to look something like ".[cf]".
	src_pattern = '\.['
	for src_ext in src_exts:
		src_pattern = src_pattern + src_ext
	src_pattern = src_pattern + ']'

	# Consider all source files.
	for src_filename in src_filenames:
		
		obj_filename = re.sub( src_pattern, '.obj', src_filename )
		
		# Create the full path to the file.
		obj_filepath = os.path.join( obj_dirpath, obj_filename )
		
		# Be verbose if verbosity was requested.
		if verbose_flag == True:
			print "%s: adding file %s" % ( script_name, obj_filepath )
				
		# And then add it to the list.
		the_line = the_line + obj_filepath + " "

	# Be verbose if verbosity was requested.
	if verbose_flag == True:
		print "%s: %s" % ( script_name, the_line )
	
	# Append a newline to the end of the line, for file.writelines().
	the_line = the_line + "\n"

	# Return the new line.
	return the_line

# ------------------------------------------------------------------------------

def read_template_file( template_filepath ):
	
	# Open the template file as read-only.
	template_file = open( template_filepath, 'r' )

	# Read all lines in the template file.
	template_file_lines = template_file.readlines()

	# Close the file.
	template_file.close()

	# Return the list of lines in the template file.
	return template_file_lines

# ------------------------------------------------------------------------------

def write_output_file( output_filepath, output_lines ):

	# Take action only if this is not a dry run.
	if dry_run_flag == False:

		# Open the template file as writable.
		output_file = open( output_filepath, 'w' )

		# Write the lines.
		output_file.writelines( output_lines )

		# Close the file.
		output_file.close()

# ------------------------------------------------------------------------------

def read_leaf_list():

	# Open the leaf list file.
	leaf_file = open( leaf_list_path, 'r' )

	# Read the lines in the file.
	line_list = leaf_file.readlines()

	# Start with a blank list.
	leaf_list = []

	# Iterate over the lines.
	for line in line_list:

		# Split the specification by colon to separate the fields.
		fields = string.split( string.strip( line ), ':' )

		# Get the individual fields of the specification.
		src_exts = string.split( fields[0], ',' )
		hdr_exts = string.split( fields[1], ',' )
		
		# If it's a singleton list of an empty string, make it an empty list.
		if len(src_exts) == 1:
			if src_exts[0] == '':
				src_exts = []
		
		# If it's a singleton list of an empty string, make it an empty list.
		if len(hdr_exts) == 1:
			if hdr_exts[0] == '':
				hdr_exts = []

		# Pack the fields into a tuple.
		leaf_spec = ( src_exts, hdr_exts )

		# Append the tuple to our list.
		leaf_list.append( leaf_spec )

	# Return the list.
	return leaf_list

# ------------------------------------------------------------------------------

# Begin by executing main().
main()
