/* 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;
}