/* Copyright (c) 2003, Rene Luria aka herel <rene@luria.ch>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright notice,
 *     * this list of conditions and the following disclaimer.
 *     * 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.
 *     * Neither the name of the author nor the names of its
 *     * contributors may be used to endorse or promote products derived from
 *     * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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. */

#define __USE_POSIX2
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#ifdef HAVE_GETOPT_LONG
#include <getopt.h>
#else
extern int getopt(int argc, char * const argv[], const char *optstring);
extern char *optarg;
extern int optind;
#endif
#include <unistd.h>

#define BUFSIZE 1024
#define LISTE "liste"
#define NUMPROCS 5
#define REPL "ARG"

typedef struct
{
	int maxprocs;
	char *filename;
	int argc;
	char **argv;
} *option_t;

typedef struct
{
	int nb;
	char **replace;
} *rep_t;

static void print_help (void)
{
#ifdef HAVE_GETOPTLONG
	printf ("usage: threads [-c maxprocs|--procs=maxprocs] [-f filename|--file=filename] <command>\n");
#else
	printf ("usage: threads [-c maxprocs] [-f filename] <command>\n");
#endif
}

static option_t parse_commandline (int argc, char **argv)
{
	option_t options;
	if ((options = malloc (sizeof *options)) == NULL)
	{
		perror ("malloc");
		return NULL;
	}
	options->maxprocs = 0;
	options->filename = NULL;
	while (1)
	{
		int option;
#ifdef HAVE_GETOPTLONG
		int longindex = 0;
		static struct option long_options[] = {
			{"help", 0, 0, 'h'},
			{"file", 1, 0, 'f'},
			{"procs", 1, 0, 'c'},
			{0, 0, 0, 0}
		};
		if ((option = getopt_long (argc, argv, "hf:c:", long_options, &longindex)) == -1)
			break;
#else
		if ((option = getopt (argc, argv, "hf:c:")) == -1)
			break;
#endif
		switch (option)
		{
			case 'f':
				if ((options->filename = malloc (strlen (optarg) + 1)) == NULL)
				{
					perror ("malloc");
					return NULL;
				}
				strcpy (options->filename, optarg);
				break;
			case 'c':
				options->maxprocs = atoi (optarg);
				if (options->maxprocs == 0)
				{
					fprintf (stderr, "bad number of procs\n");
					return NULL;
				}
				break;
			case 'h':
				print_help ();
				return NULL;
				break;
			default:
				fprintf (stderr, "bad argument\n");
				return NULL;
		}
	}
	if (options->filename == NULL)
	{
		if ((options->filename = malloc (strlen (LISTE) + 1)) == NULL)
		{
			perror ("malloc");
			return NULL;
		}
		strcpy (options->filename, LISTE);
	}
	if (options->maxprocs == 0)
	{
		options->maxprocs = NUMPROCS;
	}
	if (argc - optind < 1)
	{
		fprintf (stderr, "Mauvais nombre d'arguments\n");
		return NULL;
	}
	options->argc = argc - optind;
	options->argv = argv + optind;
	return options;
}

static char *get_path (char *commande)
{
	char *path = getenv ("PATH");
	char *cur2 = path, *cur1 = path;
	char *complete = NULL;
	if (strchr (commande, '/') != NULL) /* un path absolu */
	{
		struct stat buf;
		if (stat (commande, &buf) == -1)
			fprintf (stderr, "cannot stat %s\n", commande);
		else
			complete = commande;
	}
	else
	{
		while ((cur1 = strtok (cur2, ":")))
		{
			char *filename;
			struct stat buf;
			if (cur2)
				cur2 = NULL;
			if ((filename = malloc (strlen (cur1) + 1 + strlen (commande) + 1)) == NULL)
			{
				perror ("malloc");
				break;
			}
			sprintf (filename, "%s/%s", cur1, commande);
			if (stat (filename, &buf) == -1)
			{
				free (filename);
				continue;
			}
			complete = filename;
			break;
		}
	}
	if (complete == NULL)
		fprintf (stderr, "%s n'existe pas dans le path\n", commande);
	return complete;
}

/* lance la commande d'un child */
static void do_commande (int argc, char **argv, char *replace)
{
	int i;
	for (i = 1; i < argc; i++)
	{
		char *cur;
		if ((cur = strstr (argv[i], REPL)) != NULL)
		{
			char *newargv;
			int taille = strlen (argv[i]) - strlen (REPL) + strlen (replace) + 1;
			if ((newargv = malloc (taille)) == NULL)
			{
				perror ("malloc");
				exit (EXIT_FAILURE);
			}
			memset (newargv, 0, taille);
			strcpy (newargv, argv[i]); /* la première partie */
			strncpy (newargv + (cur - argv[i]), replace, strlen (replace));
			strcpy (newargv + (cur - argv[i]) + strlen (replace), cur + strlen (REPL));
			argv[i] = newargv;
		}
	}
	if (execv (argv[0], argv) == -1)
		perror ("execv");
	exit (EXIT_SUCCESS);
}

/* launch les process pour en garder en permanence maxprocs */
static int launch_processes (int maxprocs, rep_t prep, int argc, char **argv)
{
	int ret = 0;
	int running = 0, i = 0, status;
	if (prep == NULL)
		return -1;
	while (running || !i)
	{
		int pid;
		if ((running == maxprocs) || /* soit ya déjà MAXPROCS qui tournent */
			(i == prep->nb)) /* soit tous les process ont été launchés */
		{
			wait (&status);
			running--;
		}
		if (i < prep->nb)
		{
			if ((pid = fork ()) == -1)
			{
				perror ("fork");
				ret = 1;
				break;
			}
			if (pid > 0) /* parent */
			{
				running++;
				i++;
			}
			else /* fils */
			{
				do_commande (argc, argv, prep->replace[i]);
				exit (EXIT_SUCCESS); /* doit pas revenir mais on sait jamais */
			}
		}
	}
	return ret;
}

/* lit un fichier et retourne une struct avec chaque ligne */
static rep_t get_replace (char *filename)
{
	FILE *fp;
	rep_t prep = NULL;
	char **replace = NULL;
	int nb = 0;
	if ((fp = fopen (filename, "r")) == NULL)
		perror ("fopen");
	else
	{
		char buffer[BUFSIZE];
		while (fgets (buffer, BUFSIZE, fp) != NULL)
		{
			if (buffer[strlen (buffer) - 1] == '\n') /* nique la fin de ligne */
				buffer[strlen (buffer) - 1] = '\0';
			if (nb % 10 == 0)
			{
				if ((replace = realloc (replace, (nb + 10) * (sizeof *replace))) == NULL)
				{
					perror ("realloc");
					break;
				}
			}
			if ((replace[nb] = malloc (strlen (buffer) + 1)) == NULL)
			{
				perror ("malloc");
				break;
			}
			strcpy (replace[nb], buffer);
			nb++;
		}
	}
	if (replace)
	{
		if ((prep = malloc (sizeof *prep)) == NULL)
			perror ("malloc");
		else
		{
			prep->nb = nb;
			prep->replace = replace;
		}
	}
	return prep;
}

int main (int argc, char **argv)
{
	rep_t prep;
	option_t options;
	if ((options = parse_commandline (argc, argv)) == NULL)
		return EXIT_FAILURE;
	if ((options->argv[0] = get_path (options->argv[0])) == NULL)
		return EXIT_FAILURE;
	if ((prep = get_replace (options->filename)) == NULL)
		return EXIT_FAILURE;
	if (launch_processes (options->maxprocs, prep, options->argc, options->argv))
		return EXIT_FAILURE;
	return EXIT_SUCCESS;
}
