# This file contains some Python code which automates the generation of certain very repetitive C/C++ code

# Global constants
dtypes = ["uint8_t", "uint16_t", "uint32_t", "uint64_t", "int8_t", "int16_t", "int32_t", "int64_t", "float", "double"]
dtype_names = {dtype: ("float32" if dtype == "float" else ("float64" if dtype == "double" else dtype[:-2])) for dtype in dtypes}
explicit_instantiations_path = "../src/explicit_instantiations/"
disclaimer = """ * Disclaimer: This file was automatically generated using tools/code_generator.py.
 * If you want to change this file as a part of a library modification, the aforementioned script should be changed and re-run instead.
"""

# Generates all valid explicit instantiations of the ATC C++ interface template functions
def generate_atc_explicit_instantiations():
	
	with open("../include/c++/atc.hpp", "r") as fin:
		with open(explicit_instantiations_path + "atc.txt", "w") as fout:
			
			# Write header
			fout.write("""/*
 * This file declares all valid instantiations of the ATC C++ interface functions declared in atc.hpp and defined in atc.cpp.
 * It is separated from atc.cpp simply to keep that file more clean.
 *
""" + disclaimer + """ */
""")
			
			# Write explicit instantiations
			encountered_template = False
			should_print_newline = False
			template_typed_results = False
			for line in fin.readlines():
				if "template" in line:
					encountered_template = True
					template_typed_results = ("typed_results" in line)
				elif encountered_template:
					# This line follows a template declaration line, so it is a templated function declaration
					# Generate all matching explicit instantiations
					if should_print_newline:
						should_print_newline = False
						fout.write("\n")
					encountered_template = False
					for dtype in dtypes:
						parts = line.split("\t")[1].replace("dtype", dtype).replace("typed_results", "TypedResults").replace(" = Options()", "").split("(")
						fout.write("template " + parts[0] + "<" + dtype + (", TypedResults" if template_typed_results else "") + ">(" + parts[1])
				elif line.strip() == "":
					should_print_newline = True

# Generates the full files:
# - atc.h: The general ATC C interface.
# - c/atc.cpp: The implementation of the above file, although code duplication is minimized using the private header util-c.hpp.
# These files are very long and handle hundreds of pseudo-overloads, so automation is very useful here.
def generate_c_wrapper():
	
	# Pre-ambles for header and source
	
	header = """/*
 * This file provides the general C interface to ATC.
 * This is the main header that should be included by when using the C interface,
 * the other public C headers are automatically included as well.
 * 
""" + disclaimer + """ */

#pragma once

#include <stdbool.h>

#include "atc/data_type.h"
#include "atc/options.h"
#include "atc/results.h"
#include "atc/version.h"

#ifdef __cplusplus
extern "C" {
#endif
"""
	
	source = """/*
 * This file provides the implementation of atc.h.
 * 
""" + disclaimer + """ */

#include "atc.h"
#include "atc.hpp"
#include "util-c.hpp"
"""
	
	# Special strings for documentation purposes
	group_start = """\t/** \\addtogroup @GROUP_NAME@
	 *  @{
	 */
"""
	
	group_end = """\t/** @} */
"""
	
	group_names = {
		"compress": "c_compress",
		"decompress": "c_decompress",
		"compress_decompress": "c_compdecomp",
		"read_data_type": "c_rdtype",
		"read_decompressed_size": "c_rdsize",
		"calculate_decompressed_size": "c_cdsize"
	}
	
	# Generate declarations and definitions as we pass through the C++ interface
	with open("../include/c++/atc.hpp", "r") as f:
		in_namespace = False
		templated = False # Tracks if the current declaration is templated using dtype
		last_cpp_name = None
		for original_line in f.readlines():
			line = original_line
			line = line.strip()
			if in_namespace:
				if line == "}":
					in_namespace = False
				elif line == "":
					if last_cpp_name is not None:
						header += group_end
					header += "\t\n"
					source += "\n"
				elif line.startswith("//") or line.startswith("/**") or line.startswith("* ") or line.startswith("*/"):
					# Documentation, skip
					pass
				else:
					if "template" in line:
						templated = True
					else:
						
						# Function itself (excluding template) is declared on this line
						# Determine C declaration and definition part by part
						
						# Decompose the C++ declaration into the most important components
						cpp_return_type, line = line.split(" ", 1)
						cpp_name, line = line.split("(", 1)
						if line[-2:] != ");":
							raise Exception("Line with function declaration did not end with \");\":\n\"" + original_line + "\"")
						cpp_args = list(map(lambda arg: arg.split("=")[0].strip().rsplit(" ", 1), line[:-2].split(", "))) # List of (type, arg) tuples
						cpp_arg_types = {arg_name: arg_type for arg_type, arg_name in cpp_args}
						cpp_arg_names = cpp_arg_types.keys()
						
						# If the last cpp_name was different from the current one, start a new group for this name
						if cpp_name != last_cpp_name and set([cpp_name, last_cpp_name]) != set(["compress_decompress", "compress_decompress_to_memory"]):
							header += group_start.replace("@GROUP_NAME@", group_names[cpp_name])
						last_cpp_name = cpp_name
						
						# If return type is TypedResults<dtype> (or typed_results<dtype>) or an std::vector is used to pass original/compressed data,
						# this function is not replaced by a C version, since there are other more compatible C alternatives
						if not (cpp_return_type in ("TypedResults<dtype>", "typed_results<dtype>") or ("original_data" in cpp_arg_names and "std::vector" in cpp_arg_types["original_data"]) or ("compressed_data" in cpp_arg_names and "std::vector" in cpp_arg_types["compressed_data"])):
							
							# Function name uses the format atc_<name>_(f|m)(f|m)?(f|m)?(_results)?(_options)?(_(uint8|uint16|...|float64))?
							
							# Determine base C name
							base_c_name = "atc_" + cpp_name
							# Handle file/memory targets
							targets_string = ""
							compressed_data_in_memory = False
							for data_kind in ("original", "compressed", "decompressed"):
								if data_kind + "_file" in cpp_arg_names:
									targets_string += "f"
								elif data_kind + "_data" in cpp_arg_names or (data_kind == "compressed" and cpp_name in ("compress", "compress_decompress")):
									targets_string += "m"
									if data_kind == "compressed":
										compressed_data_in_memory = True
							if targets_string != "":
								base_c_name += "_" + targets_string
							
							# Determine C arguments and their types
							# Also determine argument values that should be passed to the corresponding C++ function
							# We don't include options yet, this is handled later
							c_args = [] # List of (type, arg) tuples
							cpp_arg_values = [] # List of strings
							for cpp_arg_type, cpp_arg_name in cpp_args:
								if cpp_arg_type == "const std::string&":
									c_args.append(("const char*", cpp_arg_name))
									cpp_arg_values.append("convert_string(%s)"%cpp_arg_name)
								elif cpp_arg_type == "DataType":
									c_args.append(("atc_data_type", cpp_arg_name))
									cpp_arg_values.append("convert_data_type(%s)"%cpp_arg_name)
								elif cpp_arg_type == "const std::vector<size_t>&":
									c_args.extend([("uint8_t", "modes"), ("const size_t*", cpp_arg_name)])
									cpp_arg_values.append("convert_sizes(modes, %s)"%cpp_arg_name)
								elif cpp_arg_name != "options":
									c_args.append((cpp_arg_type, cpp_arg_name))
									cpp_arg_values.append(cpp_arg_name)
							
							# Generate all functions for returning results/bool, taking options or not and all legal template instantiations
							# If compressed data is one of the outputs (so the operation is compress/compress_decompress) and is being stored in memory, only generate instantiations which return the results
							options_argument_present = "options" in cpp_arg_names
							for return_results in ([False, True] if (cpp_return_type == "Results" and not (cpp_name in ("compress", "compress_decompress") and compressed_data_in_memory)) else [True,]):
								c_return_type = ({"Results": "atc_results", "DataType": "atc_data_type", "size_t": "size_t"}[cpp_return_type] if return_results else "bool")
								extended_c_name1 = base_c_name + ("_results" if c_return_type == "atc_results" else "")
								for c_options_argument in ([False, True] if options_argument_present else [False,]):
									extended_c_name2 = extended_c_name1
									extended_c_args = c_args
									extended_cpp_arg_values = cpp_arg_values
									if c_options_argument:
										extended_c_name2 += "_options"
										extended_c_args = extended_c_args + [("const atc_options*", "options"),]
										extended_cpp_arg_values = extended_cpp_arg_values + ["convert_options(options)",]
									for template_type in (dtypes if templated else ["",]):
										extended_c_name3 = extended_c_name2
										if templated:
											extended_c_name3 += "_" + dtype_names[template_type]
										
										# Generate single C function
										
										# Declaration
										declaration = c_return_type + " " + extended_c_name3 + "("
										declaration += ", ".join([c_arg_type.replace("dtype", template_type) + " " + c_arg_name for c_arg_type, c_arg_name in extended_c_args])
										declaration += ")"
										header += "\t" + declaration + ";\n"
										
										# Definition
										definition = declaration + " {\n"
										definition += "\treturn wrap_evaluation([&] () { " + ("return " if return_results else "") + "atc::" + cpp_name + "(" + ", ".join(extended_cpp_arg_values) + "); });"
										definition += "\n}\n"
										source += definition
						
						# Reset template state
						templated = False
						
			elif line == "namespace atc {":
				in_namespace = True
	
	# Finish and write out header and source
	header += """#ifdef __cplusplus
}
#endif"""
	with open("../include/c/atc.h", "w") as f:
		f.write(header)
	with open("../src/c/atc.cpp", "w") as f:
		f.write(source)

generate_atc_explicit_instantiations()
generate_c_wrapper()
