#include "../UberLame_src/NewFix.h"
#include "../UberLame_src/CallStack.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <vector>
#include <algorithm>
#include <string>
#include <list>
#include <map>
#include <set>
#include "../UberLame_src/StdIOUtils.h"
#include "../UberLame_src/StlUtils.h"
#include "../UberLame_src/Dir.h"
#include "../UberLame_src/Hash.h"

#define APP_VERSION "1.01"

// 2016-07-11 - added line fix after conditional include

void PrintHelp()
{
	fprintf(stderr, "ExpandCLIncludes -- concatenates text files and expands #include refrences\n"
		"use: ExpandCLIncludes -i <input0> [-i <input1> ... -i <inputN>] -o <output> [options]\n"
		"\nwhere the [options] are:\n"
		"--help|-h                               - displays this help\n"
		"--fixup-line-numbers|-fln               - adds #line directives to fix line numbers in\n"
		"                                          compiler error outputs (enabled by default)\n"
		"--no-fixup-line-numbers|-nfln           - disables the above\n"
		"--follow-line-directives|-fld           - parses #line directives already in the input\n"
		"                                          files (enabled by default)\n"
		"--no-follow-line-directives|-nfld       - disables the above\n"
		"--line-number-fixup-with-filename|-lnff - the #line directives will also contain files\n"
		"                                          names (which are displayed by nvcc; implies -fln;\n"
		"                                          disabled by default)\n"
		"--no-line-number-fixup-with-filename|-nlnff - disables the above\n"
		"--specify-input-filenames|-sif          - will add a #line 1 directive before the first line\n"
		"                                          of each input file, followed by its name (does not\n"
		"                                          have any effect to the #included files; disabled by\n"
		"                                          default)\n"
		"--no-specify-input-filenames|-nsif      - disables the above\n"
		"--include-once|-io                      - includes each file only once; as the additional\n"
		"                                          macros the file is going to be compiled with are\n"
		"                                          unknown, it is not possible to reliably determine\n"
		"                                          whether a particular #include line will take effect\n"
		"                                          or whether the included contents will always expand\n"
		"                                          to the same code. for that, inclusin tokens are\n"
		"                                          defined to be checked by the compiler so that errors\n"
		"                                          are surely generated if a file ends up not being\n"
		"                                          included where it should be. use with care.\n"
		"                                          (disabled by default)\n"
		"--no-include-once|-nio                  - disables the above\n"
		"--output|-o                             - specifies the output file name\n"
		"--input|-i                              - specifies the input file name (may occur multiple\n"
		"                                          times, the relative order of the files is preserved)\n"
		"--include-path|-ip|-I                   - adds a directory to the include path (may occur\n"
		"                                          multiple times; by default only the current\n"
		"                                          directory is searched)\n"
		"--max-include-recurse|-mir              - specifies maximum recursion amount; note that since\n"
		"                                          no preprocessing is done, this will fail on files\n"
		"                                          which form an #include loop even despite #pragma\n"
		"                                          once and such (default 300)\n"
		"--verbose|-v                            - enables verbose (disabled by default)\n");
}

struct TIncludeInfo {
	const char *p_s_infile; // points to p_arg_list, no memory management needed
	const char *p_s_infile_to_print; // points to p_arg_list, no memory management needed
	CFILE_PtrGuard p_file; // opened file
	size_t n_next_line; // for #line fixups
	bool b_in_multiline_comment;
	std::string s_after_recurse;
	size_t n_if_depth; // for #line fixups and conditional includes
	std::vector<size_t> includes_before_endif; // levels of #if for #line fixups and conditional includes

	TIncludeInfo(const char *_p_s_infile = 0, FILE *p_fr = 0, size_t _n_next_line = 1)
		:p_s_infile(_p_s_infile), p_s_infile_to_print(_p_s_infile), p_file(p_fr),
		n_next_line(_n_next_line), b_in_multiline_comment(false),
		n_if_depth(0)
	{}

	TIncludeInfo(const TIncludeInfo &r_other) // this has side effects - steals the FILE from the owner
		:p_s_infile(r_other.p_s_infile), p_s_infile_to_print(r_other.p_s_infile_to_print),
		p_file(const_cast<CFILE_PtrGuard&>(r_other.p_file).p_YieldOwnership()),
		n_next_line(r_other.n_next_line), b_in_multiline_comment(r_other.b_in_multiline_comment),
		s_after_recurse(r_other.s_after_recurse), n_if_depth(r_other.n_if_depth),
		includes_before_endif(r_other.includes_before_endif)
	{}

	TIncludeInfo &operator =(const TIncludeInfo &r_other) // this has side effects - steals the FILE from the owner
	{
		p_s_infile = r_other.p_s_infile;
		p_s_infile_to_print = r_other.p_s_infile_to_print;
		p_file = const_cast<CFILE_PtrGuard&>(r_other.p_file);
		n_next_line = r_other.n_next_line;
		b_in_multiline_comment = r_other.b_in_multiline_comment;
		s_after_recurse = r_other.s_after_recurse;
		n_if_depth = r_other.n_if_depth;
		includes_before_endif = r_other.includes_before_endif;
		return *this;
	}
};

const char *p_s_Sanitize(std::string &r_s_sanitized, const char *p_s_str) // throw(std::bad_alloc), not reentrant
{
	r_s_sanitized.clear(); // !!

	for(; *p_s_str; ++ p_s_str) {
		switch(*p_s_str) {
		case '\'':
		case '\"':
		case '\\':
			r_s_sanitized += '\\';
			r_s_sanitized += *p_s_str;
			break;
		case '\n':
			r_s_sanitized += "\\n";
			break;
		case '\r':
			r_s_sanitized += "\\r";
			break;
		case '\t':
			r_s_sanitized += "\\t";
			break;
		case '\b':
			r_s_sanitized += "\\b";
			break;
		case '\a':
			r_s_sanitized += "\\a";
			break;
		case '\f':
			r_s_sanitized += "\\f";
			break;
		case '\v':
			r_s_sanitized += "\\v";
			break;
		default:
			r_s_sanitized += *p_s_str;
			break;
		}
	}
	return r_s_sanitized.c_str();
}

bool b_Is_If_Line(const std::string &r_s_str)
{
	const char *b = r_s_str.c_str(), *e = b + r_s_str.length(); // use c_str() here to be able to use strstr() below

	while(b != e && isspace(uint8_t(*b)))
		++ b;
	// skip space

	if(b == e || *b != '#')
		return false;
	++ b; // skip '#'

	while(b != e && isspace(uint8_t(*b)))
		++ b;
	if(b == e)
		return false;
	// skip more space

	return (strstr(b, "if") == b && b + 2 < e && !isalnum(uint8_t(b[2]))) ||
		   (strstr(b, "ifdef") == b && b + 5 < e && !isalnum(uint8_t(b[5]))) ||
		   (strstr(b, "ifndef") == b && b + 6 < e && !isalnum(uint8_t(b[6])));
	// see if there is a token, followed by
}

bool b_Is_EndIf_Line(const std::string &r_s_str)
{
	const char *b = r_s_str.c_str(), *e = b + r_s_str.length(); // use c_str() here to be able to use strstr() below

	while(b != e && isspace(uint8_t(*b)))
		++ b;
	// skip space

	if(b == e || *b != '#')
		return false;
	++ b; // skip '#'

	while(b != e && isspace(uint8_t(*b)))
		++ b;
	if(b == e)
		return false;
	// skip more space

	return (strstr(b, "endif") == b && (b + 5 == e || (b + 5 < e && !isalnum(uint8_t(b[5])))));
	// see if there is a token, followed by
}

int main(int n_arg_num, const char **p_arg_list)
{
	bool b_verbose = false;
	size_t n_max_include_depth = 300;
	const char *p_s_outfile = 0;
	std::vector<const char*> infile_list, include_path_list;
	bool b_follow_line_directives = true;
	bool b_fixup_line_numbers = true;
	bool b_line_fixup_specify_filename = false;
	bool b_specify_input_filenames = false;
	bool b_include_once = false;

	for(int i = 1; i < n_arg_num; ++ i) {
		if(!strcmp(p_arg_list[i], "--help") || !strcmp(p_arg_list[i], "-h")) {
			PrintHelp();
			return 0;
		} else if(!strcmp(p_arg_list[i], "--fixup-line-numbers") || !strcmp(p_arg_list[i], "-fln"))
			b_fixup_line_numbers = true;
		else if(!strcmp(p_arg_list[i], "--verbose") || !strcmp(p_arg_list[i], "-v"))
			b_verbose = true;
		else if(!strcmp(p_arg_list[i], "--no-fixup-line-numbers") || !strcmp(p_arg_list[i], "-nfln"))
			b_fixup_line_numbers = false;
		else if(!strcmp(p_arg_list[i], "--follow-line-directives") || !strcmp(p_arg_list[i], "-fld"))
			b_follow_line_directives = true;
		else if(!strcmp(p_arg_list[i], "--no-follow-line-directives") || !strcmp(p_arg_list[i], "-nfld"))
			b_follow_line_directives = false;
		else if(!strcmp(p_arg_list[i], "--line-number-fixup-with-filename") || !strcmp(p_arg_list[i], "-lnff")) {
			b_fixup_line_numbers = true;
			b_line_fixup_specify_filename = true;
		} else if(!strcmp(p_arg_list[i], "--no-line-number-fixup-with-filename") || !strcmp(p_arg_list[i], "-nlnff"))
			b_line_fixup_specify_filename = false;
		else if(!strcmp(p_arg_list[i], "--specify-input-filenames") || !strcmp(p_arg_list[i], "-sif"))
			b_specify_input_filenames = true;
		else if(!strcmp(p_arg_list[i], "--no-specify-input-filenames") || !strcmp(p_arg_list[i], "-nsif"))
			b_specify_input_filenames = false;
		else if(!strcmp(p_arg_list[i], "--include-once") || !strcmp(p_arg_list[i], "-io"))
			b_include_once = true;
		else if(!strcmp(p_arg_list[i], "--no-include-once") || !strcmp(p_arg_list[i], "-nio"))
			b_include_once = false;
		else if(i + 1 == n_arg_num) {
			fprintf(stderr, "error: argument \'%s\': missing value or an unknown argument\n", p_arg_list[i]);
			return -1;
		} else if(!strcmp(p_arg_list[i], "--output") || !strcmp(p_arg_list[i], "-o"))
			p_s_outfile = p_arg_list[++ i];
		else if(!strcmp(p_arg_list[i], "--input") || !strcmp(p_arg_list[i], "-i"))
			infile_list.push_back(p_arg_list[++ i]); // throws
		else if(!strcmp(p_arg_list[i], "--include-path") || !strcmp(p_arg_list[i], "-ip") || !strcmp(p_arg_list[i], "-I"))
			include_path_list.push_back(p_arg_list[++ i]);
		else if(!strcmp(p_arg_list[i], "--max-include-recurse") || !strcmp(p_arg_list[i], "-mir"))
			n_max_include_depth = atol(p_arg_list[++ i]);
		else {
			fprintf(stderr, "error: argument \'%s\': an unknown argument\n", p_arg_list[i]);
			return -1;
		}
	}
	// "parse" cmdline

	if(!p_s_outfile || infile_list.empty() || n_max_include_depth < 0) {
		PrintHelp();
		return -1;
	}
	// check sanity

	if(b_verbose)
		printf("ExpandCLIncludes " APP_VERSION " (built " __DATE__ ")\n\n");
	// print version

	try {
		FILE *p_fw;
#if defined(_MSC_VER) && !defined(__MWERKS__) && _MSC_VER >= 1400
		if(fopen_s(&p_fw, p_s_outfile, "w")) {
#else //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
		if(!(p_fw = fopen(p_s_outfile, "w"))) {
#endif //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
			fprintf(stderr, "error: failed to open \'%s\' for writing\n", p_s_outfile);
			return -1;
		}
		CFILE_PtrGuard guard(p_fw);
		// open output file

		std::set<std::string> guid_set;
		std::map<std::string, const char*> file_include_guard_map; // an include guard for each file, points to guid_set

		std::list<std::string> string_table; // strings pointed to by include_stack (addresses of the contents not invalidated by adding more elements)
		std::vector<TIncludeInfo> include_stack;

		for(size_t i = 0, n = include_path_list.size(); i < n; ++ i) {
			if(!CPath::b_Is_Absolute(include_path_list[i])) {
				std::string &r_s_path = *string_table.insert(string_table.end(), std::string(include_path_list[i]));
				if(!CPath::To_Absolute(r_s_path)) {
					fprintf(stderr, "error: failed to make absolute path \'%s\'\n", include_path_list[i]);
					return -1;
				}
				include_path_list[i] = r_s_path.c_str();
			}
		}
		// make sure that the include paths are all absolute ones

		std::vector<size_t> line_comments_char_idx;
		std::string s_line, s_line_comments, s_temp, s_temp2;
		for(size_t i = 0, n = infile_list.size(); i < n; ++ i) {
			include_stack.push_back(TIncludeInfo(infile_list[i]));

			if(b_verbose)
				printf("loading \'%s\' ...\n", infile_list[i]);
			// verbose

			while(!include_stack.empty()) {
				TIncludeInfo &r_current = include_stack.back();
				bool &b_in_multiline_comment = r_current.b_in_multiline_comment;

				FILE *p_fr = r_current.p_file.p_Get(); // does not yield ownership
				if(!p_fr) {
#if defined(_MSC_VER) && !defined(__MWERKS__) && _MSC_VER >= 1400
					if(fopen_s(&p_fr, r_current.p_s_infile, "r")) {
#else //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
					if(!(p_fr = fopen(r_current.p_s_infile, "r"))) {
#endif //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
						fprintf(stderr, "error: failed to open \'%s\'\n", r_current.p_s_infile);
						return -1;
					}
					r_current.p_file = CFILE_PtrGuard(p_fr);
					r_current.n_next_line = 1;
					// open input file

					if((i || include_stack.size() > 1) && b_include_once) { // don't check the first "root" file
						s_temp = r_current.p_s_infile;
						if(!CPath::b_Is_Absolute(s_temp) && !CPath::To_Absolute(s_temp)) {
							fprintf(stderr, "error: CPath::To_Absolute() failed\n");
							return -1;
						}
#if defined(_WIN32) || defined(_WIN64)
						std::transform(s_temp.begin(), s_temp.end(), s_temp.begin(), tolower);
						// windows is case independent
#endif // _WIN32 || _WIN64
						if(file_include_guard_map.count(s_temp)) {
							if(b_verbose && include_stack.size() > 1) {
								printf("%*sskipping \'%s\' (resolved as \'%s\')\n", (include_stack.size() - 1) * 4, "",
									r_current.p_s_infile_to_print, r_current.p_s_infile);
							}
							// verbose

							const char *p_s_include_guard = file_include_guard_map[s_temp];
							fprintf(p_fw, "#ifndef %s\n", p_s_include_guard);
							fprintf(p_fw, "#error \"include once error: file \'%s\' is not included\"\n",
								p_s_Sanitize(s_temp2, r_current.p_s_infile_to_print));
							fprintf(p_fw, "#endif // !%s\n", p_s_include_guard);
							// add a small piece of code which makes sure that the file was indeed included by now

							/*if(fseek(p_fr, 0, SEEK_END)) {
								fprintf(stderr, "error: seek error in \'%s\'\n", r_current.p_s_infile);
								return -1;
							}
							// skip the rest of the file*/ // no need
							_ASSERTE(r_current.s_after_recurse.empty());
							include_stack.erase(include_stack.end() - 1); // just do it this way
							continue;
							// pop the file from the stack and continue
						} else {
							if(b_verbose && include_stack.size() > 1) {
								printf("%*srecursing to \'%s\' (resolved as \'%s\')\n", (include_stack.size() - 1) * 4, "",
									r_current.p_s_infile_to_print, r_current.p_s_infile);
							}
							// verbose

							CPath::Get_Filename(s_temp2, s_temp);
							for(size_t i = 0, n = s_temp2.length(); i < n; ++ i) {
								if(isalpha(uint8_t(s_temp2[i])))
									s_temp2[i] = toupper(uint8_t(s_temp2[i]));
								else if(!isdigit(uint8_t(s_temp2[i])))
									s_temp2[i] = '_';
							}
							s_temp2.insert(0, "__"); // prepend by double underscore
							CStreamHash<TSHA1> hash;
							hash.Process_Data(s_temp.data(), s_temp.length() * sizeof(char));
							char p_s_buffer[42] = "_";
							hash.t_Result().p_s_ToString(p_s_buffer + 1,
								sizeof(p_s_buffer) - sizeof(p_s_buffer[0]));
							for(size_t i = 1, n = sizeof(p_s_buffer) / sizeof(p_s_buffer[0]); i < n; ++ i)
								p_s_buffer[i] = toupper(uint8_t(p_s_buffer[i]));
							s_temp2 += p_s_buffer;
							// create a semi-unique file ID

							if(guid_set.count(s_temp2))
								fprintf(stderr, "warning: file guard collision: %s\n", s_temp2.c_str());
							while(guid_set.count(s_temp2))
								s_temp2 += "_";
							// make sure it really is unique

							file_include_guard_map[s_temp] = (*guid_set.insert(s_temp2).first).c_str();
							// put it in the map

							const char *p_s_include_guard = file_include_guard_map[s_temp];
							fprintf(p_fw, "#ifndef %s\n", p_s_include_guard);
							fprintf(p_fw, "#define %s\n", p_s_include_guard);
							fprintf(p_fw, "#else // !%s\n", p_s_include_guard);
							fprintf(p_fw, "#error \"include once error: file \'%s\' is already included\"\n",
								p_s_Sanitize(s_temp2, r_current.p_s_infile_to_print));
							fprintf(p_fw, "#endif // !%s\n", p_s_include_guard);
							// add a small piece of code which inserts the token and makes sure this is the first time
						}
					} else if(include_stack.size() > 1) {
						if(b_verbose) {
							printf("%*srecursing to \'%s\' (resolved as \'%s\')\n", (include_stack.size() - 1) * 4, "",
								r_current.p_s_infile_to_print, r_current.p_s_infile);
						}
						// verbose
					}
					// handle include-once where the first occurence of each file is expanded and adds a token
					// and each next just checks for the presence of the token, so in the worst case this just
					// leads to compile errors later on (then just call ExpandCLIncludes again - but this time
					// without --include-once). this should ideally avoid bizzare behavior caused by a missing
					// header which does not produce compile errors. this still cannot be used for things like
					// templates by repeated include each time with different macros defined (but those should
					// generally guarantee build errors on their own so we're still on the safe side).

					s_temp = r_current.p_s_infile_to_print; // put the relative address here, so as to not expose the user's directory structure in the distributed code (provided the comments are stripped)
					std::replace(s_temp.begin(), s_temp.end(), '\\', '/');
					if((include_stack.size() == 1 && b_specify_input_filenames) ||
					   (include_stack.size() != 1 && b_fixup_line_numbers && b_line_fixup_specify_filename))
						fprintf(p_fw, "#line 1 \"%s\"\n", p_s_Sanitize(s_temp2, s_temp.c_str()));
					else if(include_stack.size() != 1 && b_fixup_line_numbers)
						fprintf(p_fw, "#line 1 // \"%s\"\n", p_s_Sanitize(s_temp2, s_temp.c_str()));
					// start the file
				} else /*if(r_current.n_next_line > 1)*/ {
					s_temp = r_current.p_s_infile_to_print; // put the relative address here, so as to not expose the user's directory structure in the distributed code (provided the comments are stripped)
					std::replace(s_temp.begin(), s_temp.end(), '\\', '/');
					if((include_stack.size() == 1 && b_specify_input_filenames) ||
					   (include_stack.size() != 1 && b_fixup_line_numbers && b_line_fixup_specify_filename)) {
						fprintf(p_fw, "#line " PRIsize " \"%s\"\n",
							r_current.n_next_line, p_s_Sanitize(s_temp2, s_temp.c_str()));
					} else if(b_fixup_line_numbers) {
						fprintf(p_fw, "#line " PRIsize " // \"%s\"\n",
							r_current.n_next_line, p_s_Sanitize(s_temp2, s_temp.c_str()));
					}
					// returning to a previously opened file

					if(b_in_multiline_comment && r_current.s_after_recurse.find("/*") != 0)
						fprintf(p_fw, "/*");
					// resume a multiline comment which started at the end of the last #include from which we just came back
				}
				// open a new file or resume with the parent file

				/*if(!r_current.s_after_recurse.empty())
					++ r_current.n_next_line;
				// we decremented the counter for the section above, now fix it*/ // nope

				bool b_file_recurse = false;
				// reading the file either ended with parsing include and we should recurse
				// or it simply ended and we should close it and remove it from the stack

				while(!feof(p_fr) || !r_current.s_after_recurse.empty()) {
					if(r_current.s_after_recurse.empty()) {
						if(!stl_ut::ReadLine(s_line, p_fr)) {
							fprintf(stderr, "error: failed to read \'%s\'\n", r_current.p_s_infile);
							return -1;
						}
						if(s_line.empty() && feof(p_fr))
							break; // don't add an extra empty line at the end
						// read line
					} else {
						s_line = r_current.s_after_recurse;
						r_current.s_after_recurse.clear();
						// resume with tokens on the right side of the #include
					}

					++ r_current.n_next_line;
					// increase the counter

					_ASSERTE(r_current.s_after_recurse.empty());
					// make sure that this is always empty before entering into the parser below

					s_line_comments.clear();
					line_comments_char_idx.clear();
					{
						const char *b0 = s_line.c_str(), *b = b0, *e = b + s_line.length();
						if(b_in_multiline_comment && b + 1 < e) { // if in a comment and if "*/" fits on that line
							while(b + 1 != e) {
								while(b + 1 != e && (*b != '*' || b[1] != '/'))
									++ b;
								_ASSERTE(b + 1 <= e);
								if(b + 1 == e || *b != '*' || b[1] != '/')
									break; // no terminator found
								b += 2; // skip */
								b_in_multiline_comment = false; // the comment ended
								break; // the rest will be handled using the "normal" code
							}
							if(b_in_multiline_comment)
								b = e; // to skip the below loop
						}
						while(b < e) {
							for(; b != e && *b != '/' && *b != '\"' && *b != '\''; ++ b) {
								s_line_comments += *b;
								line_comments_char_idx.push_back(b - b0);
							}
							// all characters that are not a start of the string or a start of a comment

							if(b == e)
								break;

							if(*b == '\"' || *b == '\'') {
								const char n_end_quote = *b;
								const char *p_s_begin = ++ b;
								while(b != e && *b != n_end_quote) {
									if(*b == '\\' && b + 1 != e)
										b += 2; // skip escape sequence
									else
										++ b; // skip a character
								}
								if(b == e || *b != n_end_quote) {
									fprintf(stderr, "error: \'%s\' line " PRIsize ": failed to strip comments\n",
										r_current.p_s_infile, r_current.n_next_line - 1);
									return -1;
								}
								++ b; // skip end quote
								s_line_comments.insert(s_line_comments.end(), p_s_begin - 1, b);
								for(size_t n_idx = (p_s_begin - 1) - b0, n_last_idx = b - b0; n_idx < n_last_idx; ++ n_idx)
									line_comments_char_idx.push_back(n_idx);
								// parse strings
							} else /*if(*b == '/')*/ {
								_ASSERTE(*b == '/');
								if(b + 1 == e) {
									s_line_comments += *b;
									line_comments_char_idx.push_back(b - b0);
									++ b;
									// just a simple forward slash
								} else if(b[1] == '/') // a comment till the end of the line
									break;
								else if(b[1] == '*') { // a multi-line comment
									b += 2; // skip /*
									if(b + 1 >= e) {
										b_in_multiline_comment = true;
										break; // no place to end the comment, it is till the end of the line
									}
									while(b + 1 != e && (*b != '*' || b[1] != '/'))
										++ b;
									_ASSERTE(b + 1 <= e);
									if(b + 1 == e || *b != '*' || b[1] != '/') {
										b_in_multiline_comment = true;
										break; // no terminator found
									}
									b += 2; // skip */
									s_line_comments += ' '; // make sure that comments cannot be used as token joiners
									line_comments_char_idx.push_back(size_t(-1));
								} else {
									s_line_comments += *b;
									line_comments_char_idx.push_back(b - b0);
									++ b;
									// just a simple forward slash
								}
								// parse comments
							}
						}
					}
#ifdef _DEBUG
					_ASSERTE(s_line_comments.length() == line_comments_char_idx.size());
					for(size_t j = 0, m = line_comments_char_idx.size(); j < m; ++ j) {
						_ASSERTE(line_comments_char_idx[j] == size_t(-1) ||
							s_line[line_comments_char_idx[j]] == s_line_comments[j]);
					}
					// verify the character indices
#endif // _DEBUG
					// handle comments

					if(s_line_comments.find('#') != std::string::npos) {
						//size_t n_hashtag_idx = 0, n_end_pos = s_line_comments.size(); // no need, the line will go to the output file as is
						size_t n_next_line_pos;
						if(b_Is_If_Line(s_line_comments)) {
							++ r_current.n_if_depth;
							// just that
						} else if(b_Is_EndIf_Line(s_line_comments)) {
							if(!r_current.n_if_depth) {
								fprintf(stderr, "warning: mismatched preprocessor conditionals in \'%s\':"
									" there might have been parse errors\n", r_current.p_s_infile);
							} else
								-- r_current.n_if_depth; // now it ended
							std::pair<bool, std::vector<size_t>::iterator> t_include =
								stl_ut::t_Sorted_Find(r_current.includes_before_endif.begin(),
								r_current.includes_before_endif.end(), r_current.n_if_depth + 1);
							if(t_include.first) {
								if(s_line/*s_line_comments*/.find('\\') == std::string::npos) {
									r_current.includes_before_endif.erase(t_include.second);

									fprintf(p_fw, "%s // about to fix after-include-line\n", s_line.c_str());
									// print the line

									b_file_recurse = true;
									break;
									// break and then continue again, will print the #line marker
								} else {
									fprintf(stderr, "warning: \'%s\' line " PRIsize ": multiline endif while line number fix pending\n",
										r_current.p_s_infile, r_current.n_next_line - 1);
								}
							}
						} else if(sscanf(s_line_comments.c_str(), " # line " PRIsize, &n_next_line_pos) == 1) {
							bool b_parsed_line = false;
							do {
								s_temp.clear(); // !! will be used to store the file name
								const char *b0 = s_line_comments.c_str(), *b = b0, *e = b + s_line_comments.length();
								while(b != e && isspace(uint8_t(*b)))
									++ b;
								// skip space

								/*n_hashtag_idx = b - b0;*/ // no need, the line will go to the output file as is
								if(b != e && *b == '#')
									++ b;
								else
									break;
								// skip '#'

								while(b != e && isspace(uint8_t(*b)))
									++ b;
								// skip space

								if(b != e && b + 4/*strlen("line")*/ < e && !memcmp(b, "line", 4/*strlen("line")*/))
									b += 4;
								else
									break;
								// skip "line"

								if(b == e || !isspace(uint8_t(*b)))
									break;
								while(b != e && isspace(uint8_t(*b)))
									++ b;
								// skip space

								for(n_next_line_pos = 0; b != e && isdigit(uint8_t(*b)); ++ b) {
									if(n_next_line_pos > SIZE_MAX / 10 || n_next_line_pos * 10 > SIZE_MAX - (*b - '0')) {
										fprintf(stderr, "warning: \'%s\' line " PRIsize ": integral constant overflow: \'%s\'\n",
											r_current.p_s_infile, r_current.n_next_line - 1, s_line.c_str());
									}
									n_next_line_pos = 10 * n_next_line_pos + *b - '0';
								}
								// parse the line number

								if(b != e && isspace(uint8_t(*b))) {
									while(b != e && isspace(uint8_t(*b)))
										++ b;
									// skip space

									const char *p_s_filename_begin = 0;
									if(b != e && *b == '\"')
										p_s_filename_begin = ++ b;
									while(b != e && *b != '\"') {
										if(*b == '\\' && b + 1 != e)
											b += 2; // skip escape sequence
										else
											++ b; // skip a character
									}
									if(b == e || *b != '\"')
										break;
									const char *p_s_filename_end = (p_s_filename_begin)? b : 0;
									++ b;
									// read file name

									_ASSERTE(s_temp.empty()); // did that above
									s_temp.insert(s_temp.begin(), p_s_filename_begin, p_s_filename_end);
									// put the name to path
								}

								while(b != e && isspace(uint8_t(*b)))
									++ b;
								// skip space

								/*n_end_pos = b - b0;
								// remember where it ended*/ // no need, the line will go to the output file as is

								b_parsed_line = true;
							} while(0);

							if(b_parsed_line && b_follow_line_directives) {
								r_current.n_next_line = n_next_line_pos;
								if(!s_temp.empty())
									r_current.p_s_infile_to_print = (*string_table.insert(string_table.end(), s_temp)).c_str();
							}
						} else {
							size_t n_hashtag_idx = 0, n_end_pos = s_line_comments.size();
							char n_close_brace;
							do {
								const char *b0 = s_line_comments.c_str(), *b = b0, *e = b + s_line_comments.length();
								while(b != e && isspace(uint8_t(*b)))
									++ b;
								// skip space

								n_hashtag_idx = b - b0;
								if(b != e && *b == '#')
									++ b;
								else
									break;
								// skip '#'

								while(b != e && isspace(uint8_t(*b)))
									++ b;
								// skip space

								if(b != e && b + 7/*strlen("include")*/ < e && !memcmp(b, "include", 7/*strlen("include")*/))
									b += 7;
								else
									break;
								// skip "include"

								while(b != e && isspace(uint8_t(*b)))
									++ b;
								// skip space

								if(b != e && (*b == '<' || *b == '\"')) {
									n_close_brace = (*b == '<')? '>' : '\"';
									++ b;
								} else {
									fprintf(stderr, "warning: \'%s\' line " PRIsize ": did not understand #include directive: \'%s\'\n",
										r_current.p_s_infile, r_current.n_next_line - 1, s_line.c_str());
									break;
								}
								// skip <

								const char *p_s_filename = b;
								while(b != e && *b != n_close_brace) {
									if(*b == '\\' && b + 1 != e)
										b += 2; // skip escape sequence
									else
										++ b; // skip a character
								}
								const char *p_s_filename_end = b;
								// read the filename

								if(b == e || *b != n_close_brace) {
									fprintf(stderr, "warning: \'%s\' line " PRIsize ": did not understand #include directive: \'%s\'\n",
										r_current.p_s_infile, r_current.n_next_line - 1, s_line.c_str());
									break;
								}
								++ b;
								// make sure the close brace is there
								
								/*while(b != e && isspace(uint8_t(*b)))
									++ b;
								// skip space*/ // don't, will skip over comments that were edited out of s_line_comments

								n_end_pos = b - b0;
								// see where we stopped parsing

								std::string &r_s_include_file = *string_table.insert(string_table.end(),
									std::string(p_s_filename, p_s_filename_end));
								// get an include file

								if(include_stack.size() == n_max_include_depth) {
									fprintf(stderr, "error: \'%s\' line " PRIsize ": maximum include recursion exceeded\n",
										r_current.p_s_infile, r_current.n_next_line - 1, s_line.c_str());
									return -1;
								}

								if(CPath::b_Is_Absolute(r_s_include_file))
									include_stack.push_back(TIncludeInfo(r_s_include_file.c_str()));
								else {
									bool b_have_file = false;
									if(n_close_brace == '\"') {
										if(!CPath::b_Is_Absolute(r_s_include_file)) {
											const TIncludeInfo &r_parent = include_stack.back();
											if(!CPath::Get_Path(s_temp2, /*((include_stack.size() == 1)? // in case we are including from a top-level file, use what is in infile_list[i] directly, so that #line does not change its address like it would (does not help with the included files though, may want to fix that) // fixed
											   infile_list[i] :*/ r_parent.p_s_infile/*)*/) ||
											   !CPath::To_Absolute(s_temp2) ||
											   !CPath::Join(s_temp, s_temp2, r_s_include_file) ||
											   !CPath::WeakNormalize(s_temp)) {
												fprintf(stderr, "error: failed to make absolute path \'%s\'\n", r_s_include_file.c_str());
												return -1;
											}
											b_have_file = CPath::b_Exists(s_temp);
										}
										// look in the current path first

										if(!b_have_file) {
											if(!CPath::To_Absolute(s_temp, r_s_include_file) || !CPath::WeakNormalize(s_temp)) {
												fprintf(stderr, "error: failed to make absolute path \'%s\'\n", r_s_include_file.c_str());
												return -1;
											}
											b_have_file = CPath::b_Exists(s_temp);
										}
										// try using the path on its own from the current working directory
									}
									// in case it is #include "something" rather than #include <something>

									if(!b_have_file) {
										for(size_t j = 0, m = include_path_list.size(); j < m; ++ j) {
											if(!CPath::Join(s_temp, include_path_list[j], r_s_include_file.c_str()) ||
											   !CPath::WeakNormalize(s_temp)) {
												fprintf(stderr, "error: failed to join paths \'%s\' + \'%s\'\n",
													include_path_list[j], r_s_include_file.c_str());
												return -1;
											}
											if((b_have_file = CPath::b_Exists(s_temp)))
												break;
										}
									}
									// look in the places specified by the include dirs

									if(b_have_file) {
										std::string &r_s_abs_include_file =
											*string_table.insert(string_table.end(), s_temp); // put this under a new record
										include_stack.push_back(TIncludeInfo(r_s_abs_include_file.c_str()));
										include_stack.back().p_s_infile_to_print = r_s_include_file.c_str();
									} else {
										fprintf(stderr, "error: \'%s\' line " PRIsize
											": failed to resolve include of \'%s\': file not found\n",
											r_current.p_s_infile, r_current.n_next_line - 1,
											r_s_include_file.c_str());
										return -1;
									}
								}
								// resolve the include path

								b_file_recurse = true;
							} while(0);

							if(b_file_recurse) {
								_ASSERTE(include_stack.size() >= 2);
								TIncludeInfo &r_current = *(include_stack.end() - 2);
								// might have reallocated upon inserting the recurse, need to refresh the variable

								if(r_current.n_if_depth > 0)
									stl_ut::t_Unique_Insert(r_current.includes_before_endif, r_current.n_if_depth);
								// for fixing line numbers after conditional includes

								if(n_hashtag_idx > 0 || line_comments_char_idx[n_hashtag_idx] > 0) {
									s_temp.clear();
									s_temp.insert(s_temp.end(), s_line.begin(),
										s_line.begin() + line_comments_char_idx[n_hashtag_idx]);
									s_temp2 = s_temp;
									stl_ut::TrimSpace(s_temp);
									if(!s_temp.empty())
										fprintf(p_fw, "%s ", s_temp2.c_str()); // write it, might be important
								}
								// there is something on the line prior to the include

								// t_odo - handle also the post-line // in the branch below

								_ASSERTE(s_line[line_comments_char_idx[n_hashtag_idx]] == '#');
								size_t n_tail_erase = (n_end_pos != line_comments_char_idx.size())?
									line_comments_char_idx[n_end_pos] : ((n_end_pos == line_comments_char_idx.size())?
									line_comments_char_idx.back() + 1 : s_line.length());
								if(n_tail_erase < s_line.length()) {
									_ASSERTE(r_current.s_after_recurse.empty());
									r_current.s_after_recurse.insert(r_current.s_after_recurse.end(),
										s_line.begin() + n_tail_erase, s_line.end());
									s_line.erase(n_tail_erase);
									if(!r_current.s_after_recurse.empty())
										-- r_current.n_next_line; // will continue with the *same* line as before recursing into the include
								}
								//s_line.erase(line_comments_char_idx[n_hashtag_idx], 1); // don't erase the hashtag
								s_line.erase(0, line_comments_char_idx[0]); // erase any preceding comment
								stl_ut::TrimSpace(s_line);
								_ASSERTE(!s_line.empty() && s_line[0] == '#');
								while(s_line.length() > 1 && isspace(uint8_t(s_line[1])))
									s_line.erase(1, 1); // inefficiently erase the spaces between the '#' and 'i'
								_ASSERTE(!s_line.empty() && s_line[0] == '#' && s_line[1] == 'i');
								// remove the preceding and following code from around the include statement

								_ASSERTE((n_close_brace == '\"' && s_line == (std::string("#include \"") +
									include_stack.back().p_s_infile_to_print + "\"")) ||
									(n_close_brace == '>' && s_line == (std::string("#include <") +
									include_stack.back().p_s_infile_to_print + ">")));
								// there should be nothing else at this point

								//fprintf(p_fw, "// %s\n", s_line.c_str()); // keep the original include as well
								fprintf(p_fw, "// #include %s\n", include_stack.back().p_s_infile); // put the absolute *in the comment*; the #line will only contain relative address to not expose the user's directory structure in the distributed code (provided the comments are stripped)
								break; // recurse to a different file
							}
						}
						// parse includes
					}
					// process include directives

					fprintf(p_fw, "%s\n", s_line.c_str());
					// an ordinary line, just copy it to the output
				}

				if(!b_file_recurse) {
					if(include_stack.back().n_if_depth > 0) {
						fprintf(stderr, "warning: mismatched preprocessor conditionals in \'%s\':"
							" there might have been parse errors\n", r_current.p_s_infile);
					}
					if(!include_stack.back().includes_before_endif.empty()) {
						fprintf(stderr, "warning: unresolved line number fixes after conditional"
							" includes in \'%s\': line numbers may not match source\n", r_current.p_s_infile);
					}
					_ASSERTE(feof(p_fr));
					if(ferror(p_fr)) {
						fprintf(stderr, "error: I/O error while reading \'%s\'\n", r_current.p_s_infile);
						return -1;
					}
					include_stack.erase(include_stack.end() - 1);
				}
				// in case we did not recurse then remove this from the stack
			}
		}
		// go through all the files and concatenate them / recurse to includes

		if(ferror(p_fw)) {
			fprintf(stderr, "error: I/O error while writing \'%s\'\n", p_s_outfile);
			return -1;
		}
		// make sure to look for I/O errors
	} catch(std::bad_alloc&) {
		fprintf(stderr, "error: not enough memory\n");
		return -1;
	}

	return 0;
}
