/*
 *	PROGRAM:		JRD Message Facility
 *	MODULE:			build_file.cpp
 *	DESCRIPTION:	Build message file
 *
 * The contents of this file are subject to the Interbase Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy
 * of the License at http://www.Inprise.com/IPL.html
 *
 * Software distributed under the License is distributed on an
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code was created by Inprise Corporation
 * and its predecessors. Portions created by Inprise Corporation are
 * Copyright (C) Inprise Corporation.
 *
 * All Rights Reserved.
 * Contributor(s): ______________________________________.
 *
 * 2002.02.15 Sean Leyne - Code Cleanup, removed obsolete "XENIX" port
 * 2002.02.15 Sean Leyne - Code Cleanup, removed obsolete "DELTA" port
 *
 * 2002.10.30 Sean Leyne - Removed support for obsolete "PC_PLATFORM" define
 *
 */

#include "firebird.h"
#include <string>
#include <vector>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ibase.h"
#include "../yvalve/msg.h"

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

const int max_levels = 4;
typedef msgnod* msgnod_ptr_array[max_levels];

static char* copy_terminate(char* dest, const char* src, size_t bufsize);
static void do_codes(const TEXT* gen_c_filename, const TEXT* gen_pas_filename);
static USHORT do_msgs(const TEXT*);
static void propagate(msgnod**, msgnod**, ULONG, ULONG);
static SLONG write_bucket(const msgnod*, USHORT);

static SLONG global_file_position;
static int global_file;

#ifdef WIN_NT
#include <io.h> // open, lseek, write, close
#endif

#include <sys/types.h>
#ifndef WIN_NT
#include <sys/file.h>
#endif

#ifndef O_RDWR
#include <fcntl.h>
#endif

#ifndef O_BINARY
#define O_BINARY	0
#endif


struct Message
{
	unsigned facCode;
	unsigned number;
	const char* symbol;
	const char* text;
	bool complete;
};

static std::vector<Message> messages;


static const std::vector<const char*> LICENSE_LINES = {
	"The contents of this file are subject to the Interbase Public",
	"License Version 1.0 (the \"License\"); you may not use this file",
	"except in compliance with the License. You may obtain a copy",
	"of the License at http://www.Inprise.com/IPL.html",
	"",
	"Software distributed under the License is distributed on an",
	"\"AS IS\" basis, WITHOUT WARRANTY OF ANY KIND, either express",
	"or implied. See the License for the specific language governing",
	"rights and limitations under the License.",
	"",
	"*** WARNING *** - This file is automatically generated"
};


int CLIB_ROUTINE main(int argc, char** argv)
{
/**************************************
 *
 *	m a i n
 *
 **************************************
 *
 * Functional description
 *	Top level routine.
 *
 **************************************/
	TEXT filename[MAXPATHLEN] = "";
	TEXT gen_c_filename[MAXPATHLEN] = "";
	TEXT gen_pas_filename[MAXPATHLEN] = "";

	const TEXT* const* const end_args = argv + argc;

	for (++argv; argv < end_args;)
	{
		const TEXT* p = *argv++;
		bool sw_bad = false;

		if (*p != '-')
			sw_bad = true;
		else
		{
			switch (UPPER(p[1]))
			{
				case 'F':
					if (argv >= end_args)
						sw_bad = true;
					else
						copy_terminate(filename, *argv++, MAXPATHLEN);
					break;

				case 'C':
					if (argv >= end_args)
						sw_bad = true;
					else
						copy_terminate(gen_c_filename, *argv++, MAXPATHLEN);
					break;

				case 'P':
					if (argv >= end_args)
						sw_bad = true;
					else
						copy_terminate(gen_pas_filename, *argv++, MAXPATHLEN);
					break;

				default:
					sw_bad = true;
			}
		}

		if (sw_bad)
		{
			printf("Invalid option \"%s\".  Valid options are:\n", p);
			printf("\t-F\tMessage file name\n");
			printf("\t-C\tC header file name\n");
			printf("\t-P\tPascal file name\n");
			exit(FINI_ERROR);
		}
	}

	#define FB_IMPL_MSG_NO_SYMBOL(facility, number, text) \
		Message{ FB_IMPL_MSG_FACILITY_##facility, number, "", text, false },

	#define FB_IMPL_MSG_SYMBOL(facility, number, symbol, text) \
		Message{ FB_IMPL_MSG_FACILITY_##facility, number, #symbol, text, false },

	#define FB_IMPL_MSG(facility, number, symbol, sqlCode, sqlClass, sqlSubClass, text) \
		Message{ FB_IMPL_MSG_FACILITY_##facility, number, #symbol, text, true },

	messages.insert(messages.end(), {
		#include "firebird/impl/msg/all.h"
	});

	#undef FB_IMPL_MSG_NO_SYMBOL
	#undef FB_IMPL_MSG_SYMBOL
	#undef FB_IMPL_MSG

	if (*filename)
		do_msgs(filename);

	if (*gen_c_filename || *gen_pas_filename)
		do_codes(gen_c_filename, gen_pas_filename);

	return FINI_OK;
}


static char* copy_terminate(char* dest, const char* src, size_t bufsize)
{
/**************************************
 *
 * c o p y _ t e r m i n a t e
 *
 **************************************
 *
 * Functional description
 *	Do the same as strncpy but ensure the null terminator is written.
 *	To avoid putting here #include "../common/utils_proto.h"
 *
 **************************************/
	if (!bufsize) // Was it a joke?
		return dest;

	--bufsize;
	strncpy(dest, src, bufsize);
	dest[bufsize] = 0;
	return dest;
}


static void do_codes(const TEXT* gen_c_filename, const TEXT* gen_pas_filename)
{
	unsigned symbol_count = 0;
	FILE* gen_c_file = nullptr;
	FILE* gen_pas_file = nullptr;

	if (*gen_c_filename)
	{
		if (!(gen_c_file = fopen(gen_c_filename, "w")))
		{
			perror(gen_c_filename);
			exit(FINI_ERROR);
		}

		fprintf(gen_c_file, "/*\n");

		for (const auto line : LICENSE_LINES)
			fprintf(gen_c_file, " * %s\n", line);

		fprintf(gen_c_file, " */\n\n");
	}

	if (*gen_pas_filename)
	{
		if (!(gen_pas_file = fopen(gen_pas_filename, "w")))
		{
			perror(gen_pas_filename);
			exit(FINI_ERROR);
		}
	}

	for (const auto& message : messages)
	{
		if (message.complete)
		{
			++symbol_count;

			if (*gen_c_filename)
			{
				fprintf(gen_c_file, "#define isc_%s %uL\n",
					message.symbol,
					(unsigned) FB_IMPL_MSG_ENCODE(message.number, message.facCode));
			}

			if (*gen_pas_filename)
			{
				fprintf(gen_pas_file, "\t isc_%s = %u;\n",
					message.symbol,
					(unsigned) FB_IMPL_MSG_ENCODE(message.number, message.facCode));

				fprintf(gen_pas_file, "\t gds_%s = %u;\n",
					message.symbol,
					(unsigned) FB_IMPL_MSG_ENCODE(message.number, message.facCode));
			}
		}
	}

	if (*gen_c_filename)
	{
		fprintf(gen_c_file, "#define isc_err_max %u\n", symbol_count);
		fclose(gen_c_file);
	}

	if (*gen_pas_filename)
		fclose(gen_pas_file);
}


static USHORT do_msgs(const TEXT* filename)
{
/**************************************
 *
 *	d o _ m s g s
 *
 **************************************
 *
 * Functional description
 *	Build the message file
 *
 **************************************/

	// Divy up memory among various buffers
	// Memory leaking until the program finishes?
	msgrec* leaf_node = (msgrec*) malloc((SLONG) MSG_BUCKET);
	msgrec* const leaf = leaf_node;
	const TEXT* const end_leaf = (TEXT*) leaf + MSG_BUCKET;

	// Open output file

	global_file = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666);
	if (global_file == -1)
	{
		printf("Can't open %s\n", filename);
		return FINI_ERROR;
	}
	global_file_position = 0;

	// Format and write header
	isc_msghdr header;
	header.msghdr_major_version = MSG_MAJOR_VERSION;
	header.msghdr_minor_version = MSG_MINOR_VERSION;
	header.msghdr_bucket_size = MSG_BUCKET;
	// CVC: Since this is an unused field that holds garbage in the *.msg file,
	// we'll initialize it to the date the FB project was registered with SF.
	header.msghdr_origin = 20000730; // 2000-07-30
	write_bucket((msgnod*) &header, sizeof(header));

	// Write out messages building B-tree

	USHORT n = 0;
	ULONG position = 0, prior_code = 0;
	msgnod_ptr_array buckets, nodes;
	nodes[0] = NULL;
	size_t len = 0;

	for (const auto& message : messages)
	{
		auto textLen = strlen(message.text);

		if (leaf_node->msgrec_text + textLen >= end_leaf)
		{
			position = write_bucket((msgnod*) leaf, n);
			propagate(buckets, nodes, prior_code, position);
			leaf_node = leaf;
		}

		leaf_node->msgrec_code = MSG_NUMBER(message.facCode, message.number);

		if (leaf_node->msgrec_code <= prior_code && prior_code != 0)
		{
			fprintf(stderr, "Out of order messages (src/include/firebird/impl/msg/*.h): %d x %d\n",
				leaf_node->msgrec_code, prior_code);
			exit(FINI_ERROR);
		}

		prior_code = leaf_node->msgrec_code;

		leaf_node->msgrec_length = textLen;
		// Let's not store trash in flags.
		leaf_node->msgrec_flags = 0;
		//n = offsetof(msgrec, msgrec_text) + textLen; // useless? See assignment below.
		TEXT* p = leaf_node->msgrec_text;
		memcpy(p, message.text, textLen);
		n = p + textLen - (SCHAR*) leaf; // For the next iteration.
		leaf_node = leaf_node->next();
	}

	// Write a high water mark on leaf node

	if (leaf_node->msgrec_text + len >= end_leaf)
	{
		n = (SCHAR *) leaf_node - (SCHAR *) leaf;
		position = write_bucket((msgnod*) leaf, n);
		propagate(buckets, nodes, prior_code, position);
		leaf_node = leaf;
	}

	leaf_node->msgrec_code = ~0;
	leaf_node->msgrec_length = 0;
	leaf_node->msgrec_flags = 0;
	n = (SCHAR *) leaf_node - (SCHAR *) leaf;
	position = write_bucket((msgnod*) leaf, n);

	// Finish off upper levels of tree

	header.msghdr_levels = 1;

	for (msgnod** ptr = nodes, **ptr2 = buckets; *ptr; ptr++, ptr2++)
	{
		msgnod* node = *ptr;
		node->msgnod_code = ~0;
		node->msgnod_seek = position;
		n = (SCHAR *) (node + 1) - (SCHAR *) * ptr2;
		position = write_bucket(*ptr2, n);
		++header.msghdr_levels;
	}

	header.msghdr_top_tree = position;

	// Re-write header record and finish

	lseek(global_file, LSEEK_OFFSET_CAST 0, 0);
	FB_UNUSED(write(global_file, &header, sizeof(header)));
	close(global_file);
	global_file = -1;

	return FINI_OK;
}


static void propagate(msgnod** buckets, msgnod** nodes, ULONG prior_code, ULONG position)
{
/**************************************
 *
 *	p r o p a g a t e
 *
 **************************************
 *
 * Functional description
 *	Propagate a full bucket upward.
 *
 **************************************/

	// Make sure current level has been allocated

	if (!*nodes)
	{
		*nodes = *buckets = (msgnod*) malloc((SLONG) MSG_BUCKET);
		nodes[1] = NULL;
	}

	// Insert into current bucket

	msgnod* node = (*nodes)++;
	node->msgnod_code = prior_code;
	node->msgnod_seek = position;

	// Check for full bucket.  If not, we're done

	const msgnod* const end = (msgnod*) ((SCHAR*) *buckets + MSG_BUCKET);

	if (*nodes < end)
		return;

	// Bucket is full -- write it out, propagate the split, and re-initialize

	position = write_bucket(*buckets, MSG_BUCKET);
	propagate(buckets + 1, nodes + 1, prior_code, position);
	*nodes = *buckets;
}


static SLONG write_bucket(const msgnod* bucket, USHORT length)
{
/**************************************
 *
 *	w r i t e _ b u c k e t
 *
 **************************************
 *
 * Functional description
 *	Write stuff, return position of stuff written.
 *
 **************************************/
	const USHORT padded_length = ROUNDUP(length, sizeof(SLONG));
	const SLONG position = global_file_position;
	int n = write(global_file, bucket, length);
	if (n == -1)
	{
		fprintf(stderr, "IO error on write()\n");
		exit(FINI_ERROR);
	}

	const SLONG zero_bytes = 0;
	n = write(global_file, &zero_bytes, padded_length - length);
	if (n == -1)
	{
		fprintf(stderr, "IO error on write()\n");
		exit(FINI_ERROR);
	}
	global_file_position += padded_length;

	return position;
}
