/*
 * The dhcpd-pools has BSD 2-clause license which also known as "Simplified
 * BSD License" or "FreeBSD License".
 *
 * Copyright 2006- Sami Kerola. 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 AND CONTRIBUTORS ``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 AND CONTRIBUTORS OR
 * CONTRIBUTORS 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.
 *
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing
 * official policies, either expressed or implied, of Sami Kerola.
 */

/*! \file dhcpd-pools.c
 * \brief The main(), and core initialization.
 */

#include <config.h>
#include <stdlib.h>
#include <string.h>
#include <err.h>
#include <errno.h>
#include <getopt.h>
#include <stdbool.h>
#include <stdio.h>
#include <limits.h>

#include "close-stream.h"
#include "closeout.h"
#include "defaults.h"
#include "dhcpd-pools.h"
#include "progname.h"
#include "xalloc.h"

/* Global variables */
int prefix_length[2][NUM_OF_PREFIX];
struct configuration_t config;
struct shared_network_t *shared_networks;
unsigned int num_shared_networks;
struct range_t *ranges;
unsigned int num_ranges;
struct leases_t *leases;
unsigned int RANGES;

/* Function pointers */
int (*parse_ipaddr) (const char *restrict src, union ipaddr_t *restrict dst);
void (*copy_ipaddr) (union ipaddr_t *restrict dst, const union ipaddr_t *restrict src);
const char *(*ntop_ipaddr) (const union ipaddr_t *ip);
double (*get_range_size) (const struct range_t *r);
int (*xstrstr) (const char *__restrict str);
int (*ipcomp) (const union ipaddr_t *restrict a, const union ipaddr_t *restrict b);
int (*leasecomp) (const struct leases_t *restrict a, const struct leases_t *restrict b);
int (*output_analysis) (void);
void (*add_lease) (union ipaddr_t *ip, enum ltype type);
struct leases_t *(*find_lease) (union ipaddr_t *ip);

/*! \brief Start of execution.  Parse options, and call other other
 * functions one after another.  At the moment adding threading support
 * would be difficult, but there does not seem to be valid reason to
 * consider that.  Overall the analysis already quick enough even without
 * making it parallel.
 *
 * \return Return value indicates success or fail or analysis, unless
 * either --warning or --critical options are in use, which makes the
 * return value in some cases to match with Nagios expectations about
 * alarming. */
int main(int argc, char **argv)
{
	int i, sorts = 0;
	int option_index = 0;
	char const *tmp;
	struct range_t *tmp_ranges;
	enum {
		OPT_WARN = CHAR_MAX + 1,
		OPT_CRIT,
		OPT_MINSIZE
	};
	int ret_val;

	static struct option const long_options[] = {
		{"config", required_argument, NULL, 'c'},
		{"leases", required_argument, NULL, 'l'},
		{"format", required_argument, NULL, 'f'},
		{"sort", required_argument, NULL, 's'},
		{"reverse", no_argument, NULL, 'r'},
		{"output", required_argument, NULL, 'o'},
		{"limit", required_argument, NULL, 'L'},
		{"version", no_argument, NULL, 'v'},
		{"help", no_argument, NULL, 'h'},
		{"warning", required_argument, NULL, OPT_WARN},
		{"critical", required_argument, NULL, OPT_CRIT},
		{"minsize", required_argument, NULL, OPT_MINSIZE},
		{NULL, 0, NULL, 0}
	};

	atexit(close_stdout);
	set_program_name(argv[0]);
	/* FIXME: These allocations should be fully dynamic, e.g., grow
	 * if needed.  */
	config.dhcpdconf_file = xmalloc(sizeof(char) * MAXLEN);
	config.dhcpdlease_file = xmalloc(sizeof(char) * MAXLEN);
	config.output_file = xmalloc(sizeof(char) * MAXLEN);
	/* Make sure string has zero length if there is no
	 * command line option */
	config.output_file[0] = '\0';
	/* Alarming defaults. */
	config.warning = ALARM_WARN;
	config.critical = ALARM_CRIT;
	/* File location defaults */
	strncpy(config.dhcpdconf_file, DHCPDCONF_FILE, MAXLEN - 1);
	strncpy(config.dhcpdlease_file, DHCPDLEASE_FILE, MAXLEN - 1);
	tmp = OUTPUT_LIMIT;
	config.output_limit[0] = (*tmp - '0');
	tmp++;
	config.output_limit[1] = (*tmp - '0');
	config.fullhtml = false;
	/* Make sure some output format is selected by default */
	strncpy(config.output_format, OUTPUT_FORMAT, (size_t)1);
	/* Default sort order is by IPs small to big */
	config.reverse_order = false;
	config.backups_found = false;
	/* Parse command line options */
	while (1) {
		int c;

		c = getopt_long(argc, argv, "c:l:f:o:s:rL:vh", long_options, &option_index);
		if (c == EOF)
			break;
		switch (c) {
		case 'c':
			/* config file */
			strncpy(config.dhcpdconf_file, optarg, MAXLEN - 1);
			break;
		case 'l':
			/* lease file */
			strncpy(config.dhcpdlease_file, optarg, MAXLEN - 1);
			break;
		case 'f':
			/* Output format */
			strncpy(config.output_format, optarg, (size_t)1);
			break;
		case 's':
			/* Output sorting option */
			sorts = strlen(optarg);
			if (5 < sorts) {
				warnx("main: only first 5 sort orders will be used");
				strncpy(config.sort, optarg, (size_t)5);
				sorts = 5;
			} else
				strncpy(config.sort, optarg, (size_t)sorts);
			for (i = 0; i < sorts; i++)
				field_selector(config.sort[i]);
			break;
		case 'r':
			/* What ever sort in reverse order */
			config.reverse_order = true;
			break;
		case 'o':
			/* Output file */
			strncpy(config.output_file, optarg, MAXLEN - 1);
			break;
		case 'L':
			/* Specification what will be printed */
			for (i = 0; i < 2; i++) {
				if (optarg[i] >= '0' && optarg[i] < '8')
					config.output_limit[i] = optarg[i] - '0';
				else {
					clean_up();
					errx(EXIT_FAILURE,
					     "main: output mask `%s' is illegal", optarg);
				}
			}
			break;
		case OPT_WARN:
			strcpy(config.output_format, "a");
			config.warning = strtod_or_err(optarg, "illegal argument");
			break;
		case OPT_CRIT:
			strcpy(config.output_format, "a");
			config.critical = strtod_or_err(optarg, "illegal argument");
			break;
		case OPT_MINSIZE:
			config.minsize = strtod_or_err(optarg, "illegal argument");
			break;
		case 'v':
			/* Print version */
			print_version();
		case 'h':
			/* Print help */
			usage(EXIT_SUCCESS);
		default:
			errx(EXIT_FAILURE, "Try `%s --help' for more information.", program_name);
		}
	}
	/* Output function selection */
	switch (config.output_format[0]) {
	case 't':
		output_analysis = output_txt;
		break;
	case 'a':
		output_analysis = output_alarming;
		break;
	case 'h':
		output_analysis = output_html;
		break;
	case 'H':
		output_analysis = output_html;
		config.fullhtml = true;
		break;
	case 'x':
		output_analysis = output_xml;
		break;
	case 'X':
		output_analysis = output_xml;
		break;
	case 'j':
		output_analysis = output_json;
		break;
	case 'J':
		output_analysis = output_json;
		break;
	case 'c':
		output_analysis = output_csv;
		break;
	default:
		clean_up();
		errx(EXIT_FAILURE, "main: unknown output format `%c'", config.output_format[0]);
	}
	/* Do the job */
	prepare_memory();
	set_ipv_functions(VERSION_UNKNOWN);
	parse_config(true, config.dhcpdconf_file, shared_networks);
	parse_leases();
	prepare_data();
	do_counting();
	tmp_ranges = xmalloc(sizeof(struct range_t) * num_ranges);
	if (sorts != 0)
		mergesort_ranges(ranges, num_ranges, tmp_ranges);
	if (config.reverse_order == true)
		flip_ranges(ranges, tmp_ranges);
	free(tmp_ranges);
	ret_val = output_analysis();
	clean_up();
	return (ret_val);
}

/*! \brief Run time initialization. Global allocations, counter
 * initializations, etc are here.
 * FIXME: This function should return void. */
int prepare_memory(void)
{
	config.dhcp_version = VERSION_UNKNOWN;
	RANGES = 64;
	num_ranges = num_shared_networks = 0;
	shared_networks = xmalloc(sizeof(struct shared_network_t) * SHARED_NETWORKS);
	ranges = xmalloc(sizeof(struct range_t) * RANGES);
	/* First shared network entry is all networks */
	shared_networks->name = xstrdup("All networks");
	shared_networks->used = 0;
	shared_networks->touched = 0;
	shared_networks->backups = 0;
	return 0;
}
