#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

/* returns true if '%' occurs exactly once within pattern */
bool valid_pattern(const char* pattern) {
   size_t count = 0;
   while (*pattern) {
      if (*pattern == '%') {
	 ++count;
	 if (count == 2) return false;
      }
      ++pattern;
   }
   return count == 1;
}

/* extract the stem, i.e. the part matched by '%' in pattern
   from the given filename;
   return 0 if the filename does not match the pattern */
char* extract_stem(const char* pattern, const char* filename) {
   const char* pp = pattern; const char* fp = filename;
   while (*pp && *pp != '%' && *fp == *pp) {
      ++pp; ++fp;
   }
   if (*pp != '%') return 0;
   const char* pp2 = pp + strlen(pp) - 1;
   const char* fp2 = fp + strlen(fp) - 1;
   while (*pp2 == *fp2 && pp2 > pp && fp2 > fp) {
      --pp2; --fp2;
   }
   if (pp2 != pp) return 0;
   size_t stem_len = fp2 + 1 - fp;
   char* stem = malloc(stem_len + 1);
   if (!stem) return 0;
   strncpy(stem, fp, stem_len);
   stem[stem_len] = 0;
   return stem;
}

/* generate string from the pattern by substituting '%' by the stem;
   a null pointer is returned in case of errors */
char* apply_stem(const char* pattern, const char* stem) {
   size_t len = strlen(pattern) - 1 + strlen(stem);
   char* s = malloc(len+1);
   if (!s) return 0;
   char* cp = s;
   while (*pattern && *pattern != '%') {
      *cp++ = *pattern++;
   }
   ++pattern; /* skip '%' */
   while (*stem) {
      *cp++ = *stem++;
   }
   while (*pattern) {
      *cp++ = *pattern++;
   }
   *cp++ = 0;
   return s;
}

int main(int argc, char** argv) {
   const char* cmdname = *argv++; --argc;
   const char* usage = "Usage: %s delimiter command ... delimiter "
      "in-pattern out-pattern {file}\n";
   if (argc < 6) {
      fprintf(stderr, usage, cmdname); exit(1);
   }
   const char* delimiter = *argv++; --argc;
   char** command = argv++; --argc;
   while (argc > 0 && strcmp(*argv, delimiter) != 0) {
      --argc; ++argv;
   }
   if (argc == 0) {
      fprintf(stderr, "%s: terminating delimiter '%s' not found\n", cmdname,
	 delimiter);
      fprintf(stderr, usage, cmdname); exit(1);
   }
   *argv++ = 0; --argc;
   if (argc < 3) {
      fprintf(stderr, "%s; too few arguments left: %d\n", cmdname, argc);
      fprintf(stderr, usage, cmdname); exit(1);
   }
   const char* inpattern = *argv++; --argc;
   const char* outpattern = *argv++; --argc;
   if (!valid_pattern(inpattern) || !valid_pattern(outpattern)) {
      fprintf(stderr, "%s: patterns must include exactly one '%%'\n",
	 cmdname);
      exit(1);
   }
   char** files = argv; size_t file_count = argc;
   char* stem[argc]; size_t stem_index = 0;
   while (argc > 0) {
      stem[stem_index] = extract_stem(inpattern, *argv);
      if (!stem[stem_index]) {
	 fprintf(stderr, "%s: %s does not match '%s'\n",
	    cmdname, *argv, inpattern);
	 exit(1);
      }
      --argc; ++argv; ++stem_index;
   }

   pid_t pid[file_count];
   for (size_t i = 0; i < file_count; ++i) {
      pid[i] = fork();
      if (pid[i] < 0) {
	 perror("fork"); exit(1);
      }
      if (pid[i] == 0) {
	 int fdin = open(files[i], O_RDONLY);
	 if (fdin < 0) {
	    perror(files[i]); exit(1);
	 }
	 if (dup2(fdin, 0) < 0 || close(fdin) < 0) {
	    perror("dup2 or close"); exit(1);
	 }
	 char* outfile = apply_stem(outpattern, stem[i]);
	 free(stem[i]); /* no longer needed */
	 int fdout = open(outfile, O_WRONLY|O_CREAT|O_TRUNC, 0666);
	 if (fdout < 0) {
	    perror(outfile); exit(1);
	 }
	 if (dup2(fdout, 1) < 0 || close(fdout) < 0) {
	    perror("dup2 or close"); exit(1);
	 }
	 execvp(command[0], command);
	 perror(command[0]);
	 exit(1);
      }
   }
   unsigned int exit_code = 0;
   for (size_t i = 0; i < file_count; ++i) {
      int stat;
      if (waitpid(pid[i], &stat, 0) < 0) {
	 perror("wait"); exit(1);
      }
      if (!WIFEXITED(stat) || WEXITSTATUS(stat) != 0) {
	 fprintf(stderr, "%s: %s terminated not successfuly for %s\n",
	    cmdname, command[0], files[i]);
	 ++exit_code;
      }
   }
   exit(exit_code);
}
