/*
 * $Id$
 *
    Copyright (c) 2006-2020 Chung, Hyung-Hwan. All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:
    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
    2. 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.

    THIS SOFTWARE IS PROVIDED BY THE AUTHOR "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 AUTHOR 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.
 */

#include "main.h"
#include <hawk.h>
#include <hawk-glob.h>
#include <hawk-xma.h>
#include <stdio.h>
#include <locale.h>
#include <string.h>
#include <stdlib.h>

#if defined(_WIN32)
#	include <windows.h>
#elif defined(__OS2__)
#	define INCL_ERRORS
#	include <os2.h>
#	include <signal.h>
#elif defined(__DOS__)
#	include <dos.h>
#	include <signal.h>
#else
#	include <unistd.h>
#	include <errno.h>
#	include <signal.h>
#endif
/* ---------------------------------------------------------------------- */

typedef struct sig_state_t sig_state_t;
struct sig_state_t
{
	hawk_uintptr_t handler;
	hawk_uintptr_t old_handler;
#if defined(HAVE_SIGACTION)
	sigset_t  old_sa_mask;
	int       old_sa_flags;
#endif
};

static sig_state_t g_sig_state[HAWK_NSIG];

static int is_signal_handler_set (int sig)
{
	return !!g_sig_state[sig].handler;
}

#if defined(HAVE_SIGACTION)
static void dispatch_siginfo (int sig, siginfo_t* si, void* ctx)
{
	if (g_sig_state[sig].handler != (hawk_uintptr_t)SIG_IGN &&
	    g_sig_state[sig].handler != (hawk_uintptr_t)SIG_DFL)
	{
		((hawk_main_sig_handler_t)g_sig_state[sig].handler) (sig);
	}

	if (g_sig_state[sig].old_handler &&
	    g_sig_state[sig].old_handler != (hawk_uintptr_t)SIG_IGN &&
	    g_sig_state[sig].old_handler != (hawk_uintptr_t)SIG_DFL)
	{
		((void(*)(int, siginfo_t*, void*))g_sig_state[sig].old_handler) (sig, si, ctx);
	}
}
#endif

static void dispatch_signal (int sig)
{
	if (g_sig_state[sig].handler != (hawk_uintptr_t)SIG_IGN &&
	    g_sig_state[sig].handler != (hawk_uintptr_t)SIG_DFL)
	{
		((hawk_main_sig_handler_t)g_sig_state[sig].handler) (sig);
	}

	if (g_sig_state[sig].old_handler &&
	    g_sig_state[sig].old_handler != (hawk_uintptr_t)SIG_IGN &&
	    g_sig_state[sig].old_handler != (hawk_uintptr_t)SIG_DFL)
	{
		((hawk_main_sig_handler_t)g_sig_state[sig].old_handler) (sig);
	}
}

int hawk_main_set_signal_handler (int sig, hawk_main_sig_handler_t handler, int extra_flags)
{
	if (g_sig_state[sig].handler)
	{
		/* already set - allow handler change. ignore extra_flags. */
		if (g_sig_state[sig].handler == (hawk_uintptr_t)handler) return -1;
		g_sig_state[sig].handler = (hawk_uintptr_t)handler;
	}
	else
	{
	#if defined(HAVE_SIGACTION)
		struct sigaction sa, oldsa;

		if (sigaction(sig, HAWK_NULL, &oldsa) == -1) return -1;

		memset(&sa, 0, HAWK_SIZEOF(sa));
		if (oldsa.sa_flags & SA_SIGINFO)
		{
			sa.sa_sigaction = dispatch_siginfo;
			sa.sa_flags = SA_SIGINFO;
		}
		else
		{
			sa.sa_handler = dispatch_signal;
			sa.sa_flags = 0;
		}
		sa.sa_flags |= extra_flags;
		/*sa.sa_flags |= SA_INTERUPT;
		sa.sa_flags |= SA_RESTART;*/
		sigfillset (&sa.sa_mask); /* block all signals while the handler is being executed */

		if (sigaction(sig, &sa, HAWK_NULL) == -1) return -1;

		g_sig_state[sig].handler = (hawk_uintptr_t)handler;
		if (oldsa.sa_flags & SA_SIGINFO)
			g_sig_state[sig].old_handler = (hawk_uintptr_t)oldsa.sa_sigaction;
		else
			g_sig_state[sig].old_handler = (hawk_uintptr_t)oldsa.sa_handler;

		g_sig_state[sig].old_sa_mask = oldsa.sa_mask;
		g_sig_state[sig].old_sa_flags = oldsa.sa_flags;
	#else
		g_sig_state[sig].old_handler = (hawk_uintptr_t)signal(sig, handler);
		g_sig_state[sig].handler = (hawk_uintptr_t)dispatch_signal;
	#endif
	}

	return 0;
}

int hawk_main_unset_signal_handler (int sig)
{
#if defined(HAVE_SIGACTION)
	struct sigaction sa;
#endif

	if (!g_sig_state[sig].handler) return -1; /* not set */

#if defined(HAVE_SIGACTION)
	memset(&sa, 0, HAWK_SIZEOF(sa));
	sa.sa_mask = g_sig_state[sig].old_sa_mask;
	sa.sa_flags = g_sig_state[sig].old_sa_flags;

	if (sa.sa_flags & SA_SIGINFO)
	{
		sa.sa_sigaction = (void(*)(int,siginfo_t*,void*))g_sig_state[sig].old_handler;
	}
	else
	{
		sa.sa_handler = (hawk_main_sig_handler_t)g_sig_state[sig].old_handler;
	}

	if (sigaction(sig, &sa, HAWK_NULL) == -1) return -1;
#else
	signal (sig, (hawk_main_sig_handler_t)g_sig_state[sig].old_handler);
#endif

	g_sig_state[sig].handler = 0;
	/* keep other fields untouched */

	return 0;
}

/* -------------------------------------------------------- */

void hawk_main_do_nothing_on_signal (int unused)
{
	/* do nothing */
}

/* -------------------------------------------------------- */

int hawk_main_collect_into_xarg (const hawk_bcs_t* path, hawk_main_xarg_t* xarg)
{
	if (xarg->size <= xarg->capa)
	{
		hawk_bch_t** tmp;

		tmp = (hawk_bch_t**)realloc(xarg->ptr, HAWK_SIZEOF(*tmp) * (xarg->capa + 128 + 1));
		if (tmp == HAWK_NULL) return -1;

		xarg->ptr = tmp;
		xarg->capa += 128;
	}

	xarg->ptr[xarg->size] = strdup(path->ptr);
	if (xarg->ptr[xarg->size] == HAWK_NULL) return -1;
	xarg->size++;

	return 0;
}

void hawk_main_purge_xarg (hawk_main_xarg_t* xarg)
{
	if (xarg->ptr)
	{
		hawk_oow_t i;

		for (i = 0; i < xarg->size; i++) free(xarg->ptr[i]);
		free(xarg->ptr);

		xarg->size = 0;
		xarg->capa = 0;
		xarg->ptr = HAWK_NULL;
	}
}

int hawk_main_expand_wildcard (int argc, hawk_bch_t* argv[], int do_glob, hawk_main_xarg_t* xarg)
{
	int i;
	hawk_bcs_t tmp;

	for (i = 0; i < argc; i++)
	{
		int x;

		if (do_glob)
		{
			int glob_flags;
			hawk_gem_t fake_gem; /* guly to use this fake gem here */

			glob_flags = HAWK_GLOB_TOLERANT | HAWK_GLOB_PERIOD;
		#if defined(_WIN32) || defined(__OS2__) || defined(__DOS__)
			glob_flags |= HAWK_GLOB_NOESCAPE | HAWK_GLOB_IGNORECASE;
		#endif

			fake_gem.mmgr = hawk_get_sys_mmgr();
			fake_gem.cmgr = hawk_get_cmgr_by_id(HAWK_CMGR_UTF8); /* TODO: system default? */
			x = hawk_gem_bglob(&fake_gem, argv[i], (hawk_gem_bglob_cb_t)hawk_main_collect_into_xarg, xarg, glob_flags);
			if (x <= -1) return -1;
		}
		else x = 0;

		if (x == 0)
		{
			/* not expanded. just use it as is */
			tmp.ptr = argv[i];
			tmp.len = hawk_count_bcstr(argv[i]);
			if (hawk_main_collect_into_xarg(&tmp, xarg) <= -1) return -1;
		}
	}

	xarg->ptr[xarg->size] = HAWK_NULL;
	return 0;
}

/* -------------------------------------------------------- */

void hawk_main_print_xma (void* ctx, const hawk_bch_t* fmt, ...)
{
	va_list ap;
	va_start (ap, fmt);
	vfprintf (stderr, fmt, ap);
	va_end (ap);
}
/* -------------------------------------------------------- */

void hawk_main_print_error (const hawk_bch_t* fmt, ...)
{
	va_list va;
	fprintf (stderr, "ERROR: ");
	va_start (va, fmt);
	vfprintf (stderr, fmt, va);
	va_end (va);
}

void hawk_main_print_warning (const hawk_bch_t* fmt, ...)
{
	va_list va;
	fprintf (stderr, "WARNING: ");
	va_start (va, fmt);
	vfprintf (stderr, fmt, va);
	va_end (va);
}

/* -------------------------------------------------------- */

static int main_version(int argc, hawk_bch_t* argv[], const hawk_bch_t* real_argv0)
{
	printf ("%s %s\n", hawk_get_base_name_bcstr(real_argv0), HAWK_PACKAGE_VERSION);
	printf ("Copyright 2006-2022 Chung, Hyung-Hwan\n");
	return 0;
}

static void print_usage(FILE* out, const hawk_bch_t* real_argv0)
{
	const hawk_bch_t* b1 = hawk_get_base_name_bcstr(real_argv0);

	fprintf (out, "USAGE: %s [options] [mode specific options and parameters]\n", b1);
	fprintf (out, "Options as follows:\n");
	fprintf (out, " --usage                          print this message\n");
	fprintf (out, " --version                        print version\n");
	fprintf (out, " --awk/--hawk                     switch to the awk mode(default)\n");
	fprintf (out, " --sed                            switch to the sed mode\n");
}

static int main_usage(int argc, hawk_bch_t* argv[], const hawk_bch_t* real_argv0)
{
	print_usage(stdout, real_argv0);
	return 0;
}

static struct {
	const hawk_bch_t* name;
	int (*main) (int argc, hawk_bch_t* argv[], const hawk_bch_t* real_argv0);
} entry_funcs[] = {
	{ "awk",     main_hawk },
	{ "cut",     main_cut },
	{ "hawk",    main_hawk },
	{ "sed",     main_sed },
	{ "usage",   main_usage },
	{ "version", main_version }
};

int main(int argc, hawk_bch_t* argv[])
{
	int ret;

#if defined(_WIN32)
	char locale[100];
	UINT codepage;
	WSADATA wsadata;
	int sock_inited = 0;
#elif defined(__DOS__)
	extern BOOL _watt_do_exit;
	int sock_inited = 0;
#else
	/* nothing special */
#endif

#if defined(_WIN32)
	codepage = GetConsoleOutputCP();
	if (codepage == CP_UTF8)
	{
		/*SetConsoleOUtputCP(CP_UTF8);*/
		/*hawk_setdflcmgrbyid(HAWK_CMGR_UTF8);*/
	}
	else
	{
		/* .codepage */
		hawk_fmt_uintmax_to_bcstr(locale, HAWK_COUNTOF(locale), codepage, 10, -1, '\0', ".");
		setlocale(LC_ALL, locale);
		/* hawk_setdflcmgrbyid(HAWK_CMGR_SLMB); */
	}

#else
	setlocale(LC_ALL, "");
	/* hawk_setdflcmgrbyid(HAWK_CMGR_SLMB); */
#endif

#if defined(_WIN32)
	if (WSAStartup(MAKEWORD(2,0), &wsadata) != 0)
		hawk_main_print_warning("Failed to start up winsock\n");
	else sock_inited = 1;
#elif defined(__DOS__)
	/* TODO: add an option to skip watt-32 */
	_watt_do_exit = 0; /* prevent sock_init from exiting upon failure */
	if (sock_init() != 0) hawk_main_print_warning("Failed to initialize watt-32\n");
	else sock_inited = 1;
#endif

	if (argc >= 2)
	{
		hawk_oow_t i;
		const hawk_bch_t* first_opt = argv[1];
		for (i = 0; i < HAWK_COUNTOF(entry_funcs); i++) {
			if (first_opt[0] == '-' && first_opt[1] == '-' && hawk_comp_bcstr(&first_opt[2], entry_funcs[i].name, 0) == 0) {
				/* [NOTE]
				 *  if hawk is invoked via 'hawk --awk' or 'hawk --hawk',
				 *  the value ARGV[0] inside a hawk script is "--awk" or "--hawk" */
				ret = entry_funcs[i].main(argc -1, &argv[1], argv[0]);
				goto done;
			}
		}
	}

	ret = main_hawk(argc, argv, HAWK_NULL);

done:
#if defined(_WIN32)
	if (sock_inited) WSACleanup();
#elif defined(__DOS__)
	if (sock_inited) sock_exit();
#endif

	return ret;
}

/* ---------------------------------------------------------------------- */

#if defined(FAKE_SOCKET)
socket () {}
listen () {}
accept () {}
recvfrom () {}
connect () {}
getsockopt () {}
recv      () {}
setsockopt () {}
send      () {}
bind     () {}
shutdown  () {}

void* memmove (void* x, void* y, size_t z) {}
#endif
