#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "win32.h"

#ifdef HAVE_MATH_H
#include <math.h>
#endif
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
#ifdef HAVE_INTTYPES_H
#include <inttypes.h>
#endif
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_LIBCONFIG
#include <libconfig.h>
#endif
#ifdef HAVE_SYSLOG_H
#include <syslog.h>
#endif
#ifdef HAVE_TIME_H
#include <time.h>
#else
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#endif
#ifndef HAVE_GETTIMEOFDAY
#include "gettimeofday.h"
#endif

#include "htp.h"

#define DAEMON_RET_SUCCESS 0
#define DAEMON_RET_FAILURE 1

#define LOOP_RUN 0
#define LOOP_STOP 1
#define LOOP_DONE 2

/*
 * This global variable is used to coordinate the shutdown of the main work
 * loop (htpd_loop()).
 */
static int WorkLoopStatus = LOOP_RUN;

/*
 * These variables are global because the configuration is done by a seperate
 * function.
 */
static struct timeserver_st timeservers[128];
static unsigned int totaltimeservers = 0;
static unsigned int timeind = 0;
static unsigned int http_proxy_port = 0;
static char *http_proxy = NULL;
static long maxsleeptime = 43200, minsleeptime = 4096;

#ifdef _USE_WIN32_
/*
 * Win32 service stuff.
 */
static SC_HANDLE manager = NULL;
static SC_HANDLE service = NULL;
static SERVICE_STATUS htpServiceStat;
static SERVICE_STATUS_HANDLE htpServiceStat_handle = (SERVICE_STATUS_HANDLE) NULL;
static char svcName[] = "HTP Time Daemon";
#endif

/*
 * SYNOPSIS:
 *   int htpd_loop(void)
 *
 * ARGUMENTS:
 *   (none)
 *
 * RETURN VALUE:
 *   This function will only return `EXIT_SUCCESS'
 *
 * NOTES:
 *   This function is the primary worker loop.  It asks calls the time-getting
 *   code and finds the correct time and then sets the system time to that
 *   time.  It then goes to sleep for a bit until doing it again.   It should
 *   terminate if the `WorkLoopStatus' variable is ever set to anything other
 *   than LOOP_RUN.  It will set the WorkLoopStatus variable to LOOP_DONE
 *   before returning.
 *
 */
int htpd_loop(void) {
	struct timeval *newtime;
	long sleeptime;
	double delta;
#ifdef _USE_WIN32_
	long i;
#endif

	sleeptime = minsleeptime;

	WorkLoopStatus = LOOP_RUN;

	while (1) {
		newtime = htp_calctime(timeservers, totaltimeservers, http_proxy, http_proxy_port);

		if (newtime >= 0) {
			delta = set_clock(newtime, 1);
		} else {
			delta = 0.0;
		}

		/*
		 *  Divide sleep time by 0.5 if change == 0  (check less frequently)
		 *  Divide sleep time by 2   if change == 10 (check more frequently)
		 *  Divide sleep time by 1   if change == 5  (check as frequently)
		 */
		delta = fabs(delta);
		sleeptime = ((double) sleeptime) / (((delta * delta) / 66.0) + 0.5);

		if (sleeptime < minsleeptime) {
			sleeptime = minsleeptime;
		}
		if (sleeptime > maxsleeptime) {
			sleeptime = maxsleeptime;
		}

#ifdef HAVE_SYSLOG
		syslog(LOG_INFO, "Sleeping for %li seconds.", sleeptime);
#endif
#ifdef _USE_WIN32_
		/* Under win32 we might be asked by ourselves (thread) to quit
		   so we should do this in a reasonable amount of time. */
		for (i = 0; i < sleeptime; i += 10) {
			if (WorkLoopStatus != LOOP_RUN) {
				WorkLoopStatus = LOOP_DONE;
				return(EXIT_SUCCESS);
			}
			sleep(10);
		}
#else
		sleep(sleeptime);
#endif
	}

	WorkLoopStatus = LOOP_DONE;

	return(EXIT_SUCCESS);
}

#ifdef _USE_WIN32_
/*
 * SYNOPSIS:
 *   VOID WINAPI htpServiceControl(
 *                                 DWORD request
 *                                );
 *
 * ARGUMENTS:
 *   DWORD request          Action that is requested, only SERVICE_CONTROL_STOP
 *                          and SERVICE_CONTROL_SHUTDOWN are acceptable.
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *   This function should be run in the same process as the primary worker
 *   loop (htpd_loop()).  It will set the Win32 service status appropriately
 *   when asked to STOP (or SHUTDOWN).  It asks the main worker loop to
 *   terminate, which can take up to 10s (10000ms).  When it is called and
 *   the worker thread has finished (i.e., set WorkLoopStatus to LOOP_DONE)
 *   it exits as well.
 *
 */
VOID WINAPI htpServiceControl(DWORD request) {
	/* We only handle the `STOP' and `SHUTDOWN' requests. */
	if (request != SERVICE_CONTROL_STOP && request != SERVICE_CONTROL_SHUTDOWN) {
		return;
	}

	/* Figure out what we're doing. */
	switch (WorkLoopStatus) {
		case LOOP_RUN:
		case LOOP_STOP:
			/* If the loop is running, ask it to stop. */
			WorkLoopStatus = LOOP_STOP;

			/* Tell WIn32 that we're going peacfully. */
			htpServiceStat.dwCurrentState = SERVICE_STOP_PENDING;
			htpServiceStat.dwWaitHint = 10000;
			SetServiceStatus(htpServiceStat_handle, &htpServiceStat);
			break;
		case LOOP_DONE:
			/* The loop has finished and we can go away now. */
			htpServiceStat.dwCurrentState = SERVICE_STOPPED;
			htpServiceStat.dwWaitHint = 0;
			SetServiceStatus(htpServiceStat_handle, &htpServiceStat);

			exit(EXIT_SUCCESS);

			break;
	}

	return;
}

/*
 * SYNOPSIS:
 *   void WINAPI htpServiceStart(
 *                               DWORD argc
 *                               LPTSTR *argv
 *                              );
 * ARGUMENTS:
 *   DWORD argc             Number of arguments
 *   LPTSTR *argv           Argument data
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *   This function is called by `StartServiceCtrlDispatcher'.  It registers
 *   the service and updates its status information before and after calling
 *   the main worker loop (htpd_loop()).
 *
 */
void WINAPI htpServiceStart(DWORD argc, LPTSTR *argv) {
	htpServiceStat.dwServiceType = SERVICE_WIN32;
	htpServiceStat.dwCurrentState = SERVICE_START_PENDING;
	htpServiceStat.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
	htpServiceStat.dwWin32ExitCode = 0;
	htpServiceStat.dwServiceSpecificExitCode = 0;
	htpServiceStat.dwCheckPoint = 0;
	htpServiceStat.dwWaitHint = 0;

	htpServiceStat_handle = RegisterServiceCtrlHandler(svcName, htpServiceControl); 

	if (htpServiceStat_handle == (SERVICE_STATUS_HANDLE) 0) {
		fprintf(stderr, "Could not register service: %i\n", (int) GetLastError());
		return;
	}
	SetServiceStatus(htpServiceStat_handle, &htpServiceStat);

	htpServiceStat.dwCurrentState = SERVICE_RUNNING;
	htpServiceStat.dwCheckPoint = 0;
	htpServiceStat.dwWaitHint = 0;
	SetServiceStatus(htpServiceStat_handle, &htpServiceStat);

	htpd_loop();

	htpServiceStat.dwCurrentState = SERVICE_STOPPED;
	htpServiceStat.dwCheckPoint = 0;
	htpServiceStat.dwWaitHint = 0;
	SetServiceStatus(htpServiceStat_handle, &htpServiceStat);

	return;
}
#endif /* _USE_WIN32_ */

/*
 * SYNOPSIS:
 *   static int daemon_init(void);
 *
 * ARGUMENTS:
 *   (none)
 *
 * RETURN VALUE:
 *   This function returns `DAEMON_RET_FAILURE' or `DAEMON_RET_SUCCESS'.
 *   If it was able to successfully launch as its own set of processes
 *   SUCCESS is returned, otherwise FAILURE is returned.
 *
 * NOTES:
 *   This is the primary daemonizing routine.  Under win32 it creates a
 *   service (svcName) and sets its initialization function to the 
 *   service start function (htpServiceStart).
 *
 */
static int daemon_init(void) {
	/* Default return value is failure, indicating that caller should
	   continue on. */
	int retval = DAEMON_RET_FAILURE;
#ifndef _USE_WIN32_
#ifdef HAVE_FORK
	FILE *pidfile_fp = NULL;
	pid_t pid;

	close(STDIN_FILENO);
	close(STDOUT_FILENO);
	close(STDERR_FILENO);

#ifdef HAVE_SETSID
	setsid();
#endif

	pid = fork();

	if (pid > 0) {
		/* Write the child PID to a file so we can read it to
		   shutdown. */
		pidfile_fp = fopen(HTPD_PIDFILE, "w");
		if (pidfile_fp != NULL) {
			fprintf(pidfile_fp, "%li\n", (long) pid);
			fclose(pidfile_fp);
		}

		/* Tell the parent process that we have created children, indicating that
		   it should terminate. */
		return(DAEMON_RET_SUCCESS);
	}

	if (pid < 0) {
		/* On a fork error, terminate everyone immediately. */
		exit(EXIT_FAILURE);
	}

	/* The child returns FAILURE to indicate that it should not terminate, but
	   rather carry on processing. */
#endif
#else
	DWORD win32Error;
	SERVICE_TABLE_ENTRY serviceTable[] = {
		{svcName, htpServiceStart},
		{ NULL, NULL}
	};

	if (!StartServiceCtrlDispatcher(serviceTable)) {
		win32Error = GetLastError();
		switch (win32Error) {
			case ERROR_FAILED_SERVICE_CONTROLLER_CONNECT:
				/* Print no error here because we're not being
				   run as a service yet.  This will happen
				   later.  CreateService() will cause a new
				   process to be created where this will
				   succeed. */
				break;
			default:
				fprintf(stderr, "Could not start service dispatcher: %i\n", (int) win32Error);
				break;
		}
	} else {
		/* We were able to start the "Control Dispatcher" so we
		   indicate success.  This should tell the caller to
		   terminate since we'll be starting our own set of
		   processes. */
		retval = DAEMON_RET_SUCCESS;
	}
#endif
	return(retval);
}

/*
 * SYNOPSIS:
 *   static int daemon_stop(void);
 *
 * ARGUMENTS:
 *   (none)
 *
 * RETURN VALUE:
 *   Upon sucessfully stopping the running daemon, 0 is returned.  Otherwise
 *   -1 is returned.
 *
 * NOTES:
 *   This function uses the Service Control functions on Win32.
 *   This function uses a pid file (HTPD_PIDFILE) on POSIX platforms.
 *
 */
static int daemon_stop(void) {
	int retval = -1;
#ifndef _USE_WIN32_
	FILE *pidfile_fp = NULL;
	char buf[128];
	pid_t pid;
	int killret, unlinkret;

	pidfile_fp = fopen(HTPD_PIDFILE, "r");
	if (pidfile_fp == NULL) {
		fprintf(stderr, "Service is not running.\n");
		return(-1);
	}

	fgets(buf, sizeof(buf), pidfile_fp);
	pid = strtol(buf, NULL, 10);

	if (pid <= 0) {
		fprintf(stderr, "Invalid process ID: %li\n", (long) pid);
		return(-1);
	}

	unlinkret = unlink(HTPD_PIDFILE);
	if (unlinkret < 0) {
		fprintf(stderr, "Error removing pid file (\"%s\"): ", HTPD_PIDFILE);
		perror("unlink");
	}

	killret = kill(pid, 1);
	if (killret < 0) {
		perror("kill");
		return(-1);
	}

	retval = 0;
#else
	/* Open a connection to the SCM if there isn't one already. */
	if (manager == NULL) {
		manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
		if (manager == NULL) {
			fprintf(stderr, "Could not contact service manager: %i\n", (int) GetLastError());
			return(-1);
		}
	}

	/* Connect to our service. */
	service = OpenService(manager, svcName, SERVICE_ALL_ACCESS);
	if (service == NULL) {
		fprintf(stderr, "Could not open service: %i\n", (int) GetLastError());
		return(-1);
	}

	/* Ask ourselves to stop through the SCM. */
	if (!ControlService(service, SERVICE_CONTROL_STOP, &htpServiceStat)) {
		if (GetLastError() != ERROR_SERVICE_NOT_ACTIVE) {
			fprintf(stderr, "Could not stop service: %i\n", (int) GetLastError());
			return(-1);
		}
	}

	fprintf(stderr, "Service stopped.\n");

	retval = 0;
#endif
	return(retval);
}

/*
 * SYNOPSIS:
 *   static int daemon_remove(void);
 *
 * ARGUMENTS:
 *   (none)
 *
 * RETURN VALUE:
 *   Upon sucessfully removing the (possibly running) daemon from startup, 0
 *   is returned.  Otherwise -1 is returned.
 *
 * NOTES:
 *   This function only does something useful on win32 right now.
 *
 */
static int daemon_remove(void) {
	int retval = -1;
	int stop_ret;

	stop_ret = daemon_stop();

	if (stop_ret < 0) {
		return(stop_ret);
	}
#ifndef _USE_WIN32_

#else
	if (manager == NULL) {
		manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
		if (manager == NULL) {
			fprintf(stderr, "Could not contact service manager: %i\n", (int) GetLastError());
			return(-1);
		}
	}

	service = OpenService(manager, svcName, SERVICE_ALL_ACCESS);
	if (service == NULL) {
		fprintf(stderr, "Could not open service: %i\n", (int) GetLastError());
		return(-1);
	}

	if (!DeleteService(service)) {
		fprintf(stderr, "Could not delete service: %i\n", (int) GetLastError());
		return(-1);
	}

	fprintf(stderr, "Service deleted.\n");

	retval = 0;
#endif
	return(retval);
}

/*
 * SYNOPSIS:
 *   static void daemon_start(
 *                            int argc,
 *                            char **argv
 *                           );
 *
 * ARGUMENTS:
 *   int argc               Number of arguments
 *   char *argv             Argument data
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *
 */
static void daemon_start(int argc, char **argv) {
#ifdef _USE_WIN32_
	/* Create a Win32 Service. */
	/* Most of this was taken from "tinc" (src/process.c) */
#ifdef NO_WIN32_NT4
	/* Windows NT 4.0 lacks the `ChangeServiceConfig2' call. */
	SERVICE_DESCRIPTION description = {"Hypertext Time Protocol Daemon (HTPD)"};
#endif
	char svcCommand[8192], *svcptr;
	int argind;

	/*
	 * This whole mess is to construct the "lpBinaryPathName" which needs
	 * quotes and all kinds of other non-sense.
	 */
	svcptr = svcCommand;

	strcpy(svcptr, "\"");
	svcptr += strlen(svcptr);  /* This is not the same as svcptr++. */

	/* If the full path wasn't specified, assume it's in the current directory. */
	if (strchr(argv[0], '\\') == NULL) {
		GetCurrentDirectory(sizeof(svcCommand) - (svcptr - svcCommand) - 1, svcptr);
		svcptr += strlen(svcptr);
		strncat(svcptr, "\\", sizeof(svcCommand) - (svcptr - svcCommand) - 1);
		svcptr += strlen(svcptr);
	}
	strncat(svcptr, argv[0], sizeof(svcCommand) - (svcptr - svcCommand) - 1);
	svcptr += strlen(svcptr);
	strncat(svcptr, "\"", sizeof(svcCommand) - (svcptr - svcCommand) - 1);
	svcptr += strlen(svcptr);

	for (argind = 1; argind < argc; argind++) {
		strncat(svcptr, " \"", sizeof(svcCommand) - (svcptr - svcCommand) - 1);
		svcptr += strlen(svcptr);

		strncat(svcptr, argv[argind], sizeof(svcCommand) - (svcptr - svcCommand) - 1);
		svcptr += strlen(svcptr);

		strncat(svcptr, "\"", sizeof(svcCommand) - (svcptr - svcCommand) - 1);
		svcptr += strlen(svcptr);
	}

	manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
	if (manager == NULL) {
		fprintf(stderr, "Could not contact service manager: %i\n", (int) GetLastError());
		exit(EXIT_FAILURE);
	}

	/*
         * This will ultimately spawn another set of processes so we may exit.
	 */
	service = CreateService(manager, svcName, svcName, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, svcCommand, "NDIS", NULL, NULL, NULL, NULL);

	if (service == NULL) {
		if (GetLastError() == ERROR_SERVICE_EXISTS) {
			service = OpenService(manager, svcName, SERVICE_ALL_ACCESS);
		}

		if (service == NULL) {
			fprintf(stderr, "Could not create service: %i\n", (int) GetLastError());
			exit(EXIT_FAILURE);
		}
	}

#ifdef NO_WIN32_NT4
	/* Windows NT 4.0 lacks this call. */
	ChangeServiceConfig2(service, SERVICE_CONFIG_DESCRIPTION, &description);
#endif

	if (!StartService(service, 0, NULL)) {
		fprintf(stderr, "Could not start service: %i\n", (int) GetLastError());
		exit(EXIT_FAILURE);
	} else {
		printf("Service started.\n");
	}


	exit(EXIT_SUCCESS);
#endif
	return;
}

int set_proxy(const char *shortvar, const char *var, const char *arguments, const char *value, lc_flags_t flags, void *extra) {
	char *host = NULL, *portstr = NULL;
	char *real_value = NULL, *end_ptr = NULL;
	size_t hostlen = 0;
	int port = -1;

	if (value == NULL) {
		fprintf(stderr, "Error: Argument required.\n");
		return(LC_CBRET_ERROR);
	}

	real_value = strstr(value, "://");
	if (real_value != NULL) {
		value = real_value + 3;
	}

	hostlen = strlen(value) + 1;
	if (hostlen == 1) {
		fprintf(stderr, "Error: Argument required.\n");
		return(LC_CBRET_ERROR);
	}

	host = malloc(hostlen);
	if (host == NULL) {
		fprintf(stderr, "Error: Could not allocate memory.\n");
		return(LC_CBRET_ERROR);
	}
	memcpy(host, value, hostlen);

	end_ptr = strchr(host, '/');
	if (end_ptr != NULL) {
		*end_ptr = '\0';
	}

	portstr = strchr(host, ':');
	if (portstr != NULL) {
		portstr[0] = '\0';

		portstr++;
		port = atoi(portstr);
		if (port <= 0) {
			fprintf(stderr, "Error: Invalid port specified: %s:%s.\n", host, portstr);
			free(host);
			return(LC_CBRET_ERROR);
		}
	} else {
		port = 8080;
	}

	http_proxy = host;
	http_proxy_port = port;

	return(LC_CBRET_OKAY);
}

int add_server(const char *shortvar, const char *var, const char *arguments, const char *value, lc_flags_t flags, void *extra) {
	char *host = NULL, *portstr = NULL;
	char *real_value = NULL, *end_ptr = NULL;
	size_t hostlen = 0;
	int port = -1;
	unsigned int  timechk = 0;

	if (value == NULL) {
		fprintf(stderr, "Error: Argument required.\n");
		return(LC_CBRET_ERROR);
	}

	real_value = strstr(value, "://");
	if (real_value != NULL) {
		value = real_value + 3;
	}

	hostlen = strlen(value) + 1;
	if (hostlen == 1) {
		fprintf(stderr, "Error: Argument required.\n");
		return(LC_CBRET_ERROR);
	}

	host = malloc(hostlen);
	if (host == NULL) {
		fprintf(stderr, "Error: Could not allocate memory.\n");
		return(LC_CBRET_ERROR);
	}
	memcpy(host, value, hostlen);

	end_ptr = strchr(host, '/');
	if (end_ptr != NULL) {
		*end_ptr = '\0';
	}

	portstr = strchr(host, ':');
	if (portstr != NULL) {
		portstr[0] = '\0';

		portstr++;
		port = atoi(portstr);
		if (port <= 0) {
			fprintf(stderr, "Error: Invalid port specified: %s:%s.\n", host, portstr);
			free(host);
			return(LC_CBRET_ERROR);
		}
	} else {
		port = 80;
	}

	for (timechk = 0; timechk < timeind; timechk++) {
		if (strcasecmp(timeservers[timechk].host, host) == 0 && timeservers[timechk].port == port) {
			return(LC_CBRET_OKAY);
		}
	}

	timeservers[timeind].host = host;
	timeservers[timeind].port = port;

	timeind++;

	return(LC_CBRET_OKAY);
}

int print_help(const char *shortvar, const char *var, const char *arguments, const char *value, lc_flags_t flags, void *extra) {
	printf("htpd version " PACKAGE_VERSION "\n");
	printf("Usage: htpd [-rkh] [-M max] [-m min] [-P proxy] [-H host [-H host [...]]]\n");
	printf("   -r        Remove service from startup.\n");
	printf("   -k        Stop service.\n");
	printf("   -h        This help.\n");
	printf("   -M max    Maximum time between clock updates.\n");
	printf("   -m min    Minimum time between clock updates.\n");
	printf("   -P proxy  Proxy to send data through.\n");
	printf("   -H host   Host to query for time, can be in the format of:\n");
	printf("             hostname[:port].   Default port is 80.\n");
	exit(EXIT_SUCCESS);
}

int htp_remove_svc(const char *shortvar, const char *var, const char *arguments, const char *value, lc_flags_t flags, void *extra) {
	if (daemon_remove() < 0) {
		exit(EXIT_FAILURE);
	}

	exit(EXIT_SUCCESS);
}

int htp_stop_svc(const char *shortvar, const char *var, const char *arguments, const char *value, lc_flags_t flags, void *extra) {
	if (daemon_stop() < 0) {
		exit(EXIT_FAILURE);
	}

	exit(EXIT_SUCCESS);
}

int main(int argc, char *argv[]) {
	int lc_p_ret = 0;

	htp_init();

	lc_register_callback("Host", 'H', LC_VAR_STRING, add_server, NULL);
	lc_register_callback("ProxyHost", 'P', LC_VAR_STRING, set_proxy, NULL);
	lc_register_callback(NULL, 'h', LC_VAR_NONE, print_help, NULL);
	lc_register_callback("Remove", 'r', LC_VAR_NONE, htp_remove_svc, NULL);
	lc_register_callback("Stop", 'k', LC_VAR_NONE, htp_stop_svc, NULL);
	lc_register_var("MaxSleep", LC_VAR_LONG, &maxsleeptime, 'M');
	lc_register_var("MinSleep", LC_VAR_LONG, &minsleeptime, 'm');
	lc_p_ret = lc_process(argc, argv, "htpd", LC_CONF_SPACE, SYSCONFDIR "/htpd.conf");
	if (lc_p_ret < 0) {
		fprintf(stderr, "Error processing configuration information: %s.\n", lc_geterrstr());
		return(EXIT_FAILURE);
	}

	totaltimeservers = timeind;

	if (totaltimeservers == 0) {
		fprintf(stderr, "No servers specified.\n");
		return(EXIT_FAILURE);
	}

	/*
	 * If daemon initialization went well, we're no longer needed.
	 */
	if (daemon_init() == DAEMON_RET_SUCCESS) {
		return(EXIT_SUCCESS);
	}

	daemon_start(argc, argv);

#ifdef HAVE_OPENLOG
	openlog("htpd", LOG_PID, LOG_DAEMON);
#endif

	return(htpd_loop());
}
