/*
 *  Copyright (C) 2001 - 2007 Tomasz Kojm <tkojm@clamav.net>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *  MA 02110-1301, USA.
 *
 */

#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define _GNU_SOURCE
#include "getopt.h"

#include "options.h"
#include "output.h"


static int register_option(struct optstruct *opt, const char *optlong, char optshort, const struct option *options_long, const char * const *accepted_long)
{
	struct optnode *newnode;
	int i, found = 0;
	const char *longname = NULL;


    if(optshort) {
	for(i = 0; options_long[i].name; i++) {
	    if(options_long[i].val == optshort) {
		longname = options_long[i].name;
		break;
	    }
	}
    } else
	longname = optlong;

    if(!longname) {
	mprintf("!register_option: No long option for -%c\n", optshort);
	return -1;
    }

    if(accepted_long) {
	for(i = 0; accepted_long[i]; i++)
	    if(!strcmp(accepted_long[i], longname))
		found = 1;

	if(!found) {
	    if(optshort)
		mprintf("WARNING: Ignoring option --%s (-%c)\n", longname, optshort);
	    else
		mprintf("WARNING: Ignoring option --%s\n", longname);

	    return 0;
	}
    }

    newnode = (struct optnode *) malloc(sizeof(struct optnode));
    if(!newnode) {
	mprintf("!register_long_option: malloc failed\n");
	return -1;
    }

    newnode->optshort = optshort;

    if(optarg) {
	newnode->optarg = (char *) malloc(strlen(optarg) + 1);
	if(!newnode->optarg) {
	    mprintf("!register_long_option: malloc failed\n");
	    free(newnode);
	    return -1;
	}
	strcpy(newnode->optarg, optarg);
    } else
	newnode->optarg = NULL;

    newnode->optlong = (char *) malloc(strlen(longname) + 1);
    if(!newnode->optlong) {
	mprintf("ERROR: register_long_option: malloc failed\n");
	free(newnode->optarg);
	free(newnode);
	return -1;
    }
    strcpy(newnode->optlong, longname);

    newnode->next = opt->optlist;
    opt->optlist = newnode;
    return 0;
}

void opt_free(struct optstruct *opt)
{
	struct optnode *handler, *prev;

    if(!opt)
	return;

    handler = opt->optlist;
    while(handler) {
	if(handler->optarg)
	    free(handler->optarg);
	if(handler->optlong)
	    free(handler->optlong);
	prev = handler;
	handler = handler->next;
	free(prev);
    }

    if(opt->filename)
    	free(opt->filename);

    free(opt);
}

struct optstruct *opt_parse(int argc, char * const *argv, const char *getopt_short, const struct option *options_long, const char * const *accepted_long)
{
	int ret, opt_index, i, len;
	struct optstruct *opt;
	const char *longname;


    opt = (struct optstruct *) calloc(1, sizeof(struct optstruct));
    if(!opt) {
	mprintf("!opt_parse: calloc failed\n");
	return NULL;
    }

    while(1) {
	opt_index = 0;
	ret = getopt_long(argc, argv, getopt_short, options_long, &opt_index);

	if(ret == -1)
	    break;

	switch(ret) {
	    case 0:
		if(register_option(opt, options_long[opt_index].name, 0, options_long, accepted_long) == -1) {
		    opt_free(opt);
		    return NULL;
		}
		break;

    	    default:
		if(strchr(getopt_short, ret)) {
		    if(opt_index)
			longname = options_long[opt_index].name;
		    else
			longname = NULL;

		    if(register_option(opt, longname, ret, options_long, accepted_long) == -1) {
			opt_free(opt);
			return NULL;
		    }

		} else {
		    mprintf("!Unknown option passed.\n");
		    opt_free(opt);
		    return NULL;
		}
	}
    }

    if(optind < argc) {
        len = 0;

	/* count length of non-option arguments */
	for(i = optind; i < argc; i++)
	    len += strlen(argv[i]);

	len += argc - optind - 1;
	opt->filename = (char *) calloc(len + 64, sizeof(char));
	if(!opt->filename) {
	    mprintf("!opt_parse: calloc failed\n");
	    opt_free(opt);
	    return NULL;
	}

        for(i = optind; i < argc; i++) {
	    strncat(opt->filename, argv[i], strlen(argv[i]));
	    if(i != argc - 1)
		strncat(opt->filename, "\t", 1);
	}
    }

    return opt;
}

int opt_check(const struct optstruct *opt, const char *optlong)
{
	struct optnode *handler;

    if(!opt) {
	mprintf("!opt_check: opt == NULL\n");
	return 0;
    }

    handler = opt->optlist;

    while(handler) {
	if(handler->optlong && !strcmp(handler->optlong, optlong))
	    return 1;

	handler = handler->next;
    }

    return 0;
}

char *opt_arg(const struct optstruct *opt, const char *optlong)
{
	struct optnode *handler;

    if(!opt) {
	mprintf("!opt_arg: opt == NULL\n");
	return 0;
    }

    handler = opt->optlist;

    while(handler) {
	if(handler->optlong && !strcmp(handler->optlong, optlong))
	    return handler->optarg;

	handler = handler->next;
    }

    return NULL;
}

char *opt_firstarg(const struct optstruct *opt, const char *optlong, const struct optnode **optnode)
{
	const struct optnode *handler;

    if(!opt) {
	mprintf("!opt_firstarg: opt == NULL\n");
	return 0;
    }

    handler = opt->optlist;

    while(handler) {
	if(handler->optlong && !strcmp(handler->optlong, optlong)) {
	    *optnode = handler;
	    return handler->optarg;
	}
	handler = handler->next;
    }

    *optnode = NULL;
    return NULL;
}

char *opt_nextarg(const struct optnode **optnode, const char *optlong)
{
	struct optnode *handler;

    if(!optnode || !*optnode) {
	mprintf("!opt_nextarg: *optnode == NULL\n");
	return 0;
    }

    handler = (*optnode)->next;

    while(handler) {
	if(handler->optlong && !strcmp(handler->optlong, optlong)) {
	    *optnode = handler;
	    return handler->optarg;
	}
	handler = handler->next;
    }

    *optnode = NULL;
    return NULL;
}