#include <windows.h>
#include <direct.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>

bool g_bRecurse, g_bShortFN, g_bVerbose, g_bNoQuote, g_bDirOnly, g_bDoBoth, g_bChDir;
char listwild[MAX_PATH], wildpath[MAX_PATH];
char cmd[1024];
int cmdend=0;

void Usage()
{ puts("Usage: do [opt] list|wild cmdline\n"
       "Options:\n"
       "-r\t- Recurse through subdirectories and force do to assume wildcard.\n"
       "-s\t- Convert long filenames to short ones before processing.\n"
       "-v\t- Verbose mode. (Echo command lines to stdout)\n"
       "-n\t- Does not add quotation marks to command line. (but @q always does)\n"
       "-d\t- Processes directories instead of files when using a wildcard\n"
       "-b\t- Process both directories and files (higher precedence than -d)\n"
       "  \t  (when using listfiles do always processes both directories and files)\n"
       "-c\t- Causes do to change directories when recursing\n\n"
       "By default, it is assumed that you are using a wildcard if the first parameter\n"
       "contains a ? or a *. Using -r forces it to use a wildcard match.\n"
       "Either way, this program performs the given command line on a group of files\n"
       "If you use a list file, it reads each line of the file and uses it as the\n"
       "filename. Or you can specify a wildcard (ie c:\\temp\\*.* or just *.*, but not\n"
       "c:\\html??\\*.* as all the wildcards have to be after the last slash, if any).\n"
       "For each filename found, it parses the cmdline parameter(s), replacing the\n"
       "following tokens with the corresponding part. Quotation marks are added for LFN\n"
       "compatibility, unless -n is specified as an option. For the examples, the file\n"
       "used is subdir\\test.mp3 or test.mp3.\n"
       "[press any key to continue.]");
  getch();
  puts("\nToken:\t  Replacement:  \tExample:\n"
       "@f\t- \"path\\filename.ext\"\t\"subdir\\test.mp3\"\n"
       "@p\t- \"path              \t\"subdir\n"
       "@u\t- \"path\\filename    \t\"subdir\\test\n"
       "@n\t- filename          \ttest\n"
       "@q\t- quotation mark    \t\"\n"
       "@@\t- 'at' sign         \t@\n"
       "\nExamples of use:\n"
       "do -r -v *.mp3 l3dec @f @u.wav@q -wav\n"
       "   l3dec \"subdir\\test.mp3\" \"subdir\\test.wav\" -wav\n"
       "do -v list upx -9 @f\n"
       "   upx -9 \"subdir\\test.mp3\"\t(reads filenames from the file 'list')\n"
       "do -rn \"dumb cache.its\" del @f\n"
       "   del subdir\\dumb cache.its\t(but LFNs generally won't work without quotes)\n"
       "do -rd Debug deltree /Y @f\n"
       "   deltree /Y subdir\\Debug   \t(deletes all the subdirs named Debug)\n");
  exit(1);
}

void DoSplit(char const *file, char **p, char **u, char **n, int *plen, int *ulen, int *nlen)
{ static char pathfile[1024], path[1024];
  int len=0;
  char const *bks, *ext;

  bks = strrchr(file, '\\');
  if(bks)
  { bks++;
    len = bks-file;
    *plen = len-1;
    memcpy(path, file, len-1);
    memcpy(pathfile, file, len);
  }
  else bks=file, *plen=0;
  path[len-1] = pathfile[len] = 0;

  ext = strrchr(bks, '.');
  if(ext)
  { int flen = *nlen = ext-bks;
    memcpy(pathfile+len, bks, flen);
    pathfile[*ulen = len+flen] = 0;
  }
  else
  { int nl = *nlen = strlen(file)-len;
    *ulen = len+nl;
    strcpy(pathfile+len, file+len);
  }

  *p = path;
  *u = pathfile;
  *n = pathfile+len;
}

void ProcessFile(char const *fullfile)
{ char final[1024], *token, *p_op=0, *u_op=0, *n_op=0;
  int  fpos=0, cpos=0, len, plen, ulen, nlen;

  while(true)
  { token = strchr(cmd+cpos, '@');
    if(token)
    { int glen = token-cmd-cpos;
      char key = toupper(*(token+1));
      memcpy(final+fpos, cmd+cpos, glen);
      cpos+=glen+2;
      fpos+=glen;
      switch(key)
      { case 'F':
          if(!g_bNoQuote) final[fpos++] = '\"';
          len = strlen(fullfile);
          memcpy(final+fpos, fullfile, len);
          fpos+=len;
          if(!g_bNoQuote) final[fpos++] = '\"';
          break;

        case 'P':
          if(!g_bNoQuote) final[fpos++] = '\"';
          if(!p_op) DoSplit(fullfile, &p_op, &u_op, &n_op, &plen, &ulen, &nlen);
          memcpy(final+fpos, p_op, plen);
          fpos+=plen;
          break;
          
        case 'U':
          if(!g_bNoQuote) final[fpos++] = '\"';
          if(!u_op) DoSplit(fullfile, &p_op, &u_op, &n_op, &plen, &ulen, &nlen);
          memcpy(final+fpos, u_op, ulen);
          fpos+=ulen;
          break;

        case 'N':
          if(!n_op) DoSplit(fullfile, &p_op, &u_op, &n_op, &plen, &ulen, &nlen);
          memcpy(final+fpos, n_op, nlen);
          fpos+=nlen;
          break;

        case 'Q':
          final[fpos++] = '\"';
          break;

        case '@':
          final[fpos++] = '@';
          break;

        default:
          printf("Error in '%s'\n", cmd);
          len = token-cmd + sizeof("Error in '");
          while(len--) putchar(' ');
          puts("^");
          exit(1);
      }
    }
    else
    { len = strlen(cmd+cpos);
      memcpy(final+fpos, cmd+cpos, len);
      fpos+=len;
      break;
    }
  }
  final[fpos]=0;

  if(g_bVerbose) puts(final);
  system(final);
}

void ProcessFiles(char const *path)
{ WIN32_FIND_DATA data;
  char fullpath[MAX_PATH];
  int  pathlen;
  HANDLE handle;
  
  strcpy(fullpath, path);
  pathlen = strlen(fullpath);
  if(pathlen)
  { fullpath[pathlen++] = '\\';
    fullpath[pathlen] = 0;
  }

  if(g_bRecurse)
  { strcpy(fullpath+pathlen, "*.*");
    handle=FindFirstFile(fullpath, &data);
    if(handle != INVALID_HANDLE_VALUE)
    { for(;;)
      { if(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY &&
           data.cFileName[0] != '.')
        { if(g_bShortFN && data.cAlternateFileName[0])
            strcpy(fullpath+pathlen, data.cAlternateFileName);
          else strcpy(fullpath+pathlen, data.cFileName);
          ProcessFiles(fullpath);
        }
        if(!FindNextFile(handle, &data)) break;
      }
      FindClose(handle);
    }
  }
  if(g_bChDir)
  { if(g_bVerbose) printf("Switching to %s\n", path);
    _chdir(path);
  }
  strcpy(fullpath+pathlen, listwild);
  handle=FindFirstFile(fullpath, &data);
  if(handle != INVALID_HANDLE_VALUE)
  { for(;;)
    { if(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
      { if((!g_bDirOnly && !g_bDoBoth) || data.cFileName[0] == '.') goto Next;
      }
      else if(g_bDirOnly && !g_bDoBoth) goto Next;
      if(g_bChDir)
      { if(g_bShortFN && data.cAlternateFileName[0])
          ProcessFile(data.cAlternateFileName);
        else ProcessFile(data.cFileName);
      }
      else
      { if(g_bShortFN && data.cAlternateFileName[0])
          strcpy(fullpath+pathlen, data.cAlternateFileName);
        else strcpy(fullpath+pathlen, data.cFileName);
        ProcessFile(fullpath);
      }
      Next:
      if(!FindNextFile(handle, &data)) break;
    }
    FindClose(handle);
  }
}

int main(int argc, char *argv[])
{
  int i;

  if(argc < 3) Usage();
  { bool gotopts=false, done=false;
    for(i=1;i<argc && !done;i++)
    { strcpy(cmd, argv[i]);
      if(cmd[0] == '-')
      { int i,len;
        if(gotopts) Usage();
        _strupr(cmd);
        len = strlen(cmd);
        for(i=1;i<len;i++)
        { if(isspace(cmd[i])) break;
          switch(cmd[i])
          { case 'R': g_bRecurse = true; break;
            case 'S': g_bShortFN = true; break;
            case 'V': g_bVerbose = true; break;
            case 'N': g_bNoQuote = true; break;
            case 'D': g_bDirOnly = true; break;
            case 'B': g_bDoBoth  = true; break;
            case 'C': g_bChDir   = true; break;
            default:
              Usage();
          }
        }
      }
      else
      { strcpy(listwild, argv[i]);
        gotopts = done = true;
      }
    }
  }
  for(;i<argc;i++)
  { strcpy(cmd+cmdend, argv[i]);
    cmdend+=strlen(argv[i]);
    if(i != argc-1) cmd[cmdend++] = ' ';
  }
  cmd[cmdend] = 0;
  if(cmd[0] == 0) Usage();

  if(!g_bRecurse && !strchr(listwild, '?') && !strchr(listwild, '*'))
  { char longfile[MAX_PATH];
    FILE *in = fopen(listwild, "r");
    if(!in)
    { puts("Error opening list file!");
      return 1;
    }
    if(g_bShortFN)
    { while(fgets(longfile, MAX_PATH, in))
      { longfile[strlen(longfile)-1] = 0;
        int len=GetShortPathName(longfile, listwild, MAX_PATH);
        ProcessFile(listwild);
      }
    }
    else
    { while(fgets(listwild, MAX_PATH, in))
      { listwild[strlen(listwild)-1] = 0;
        ProcessFile(listwild);
      }
    }
    fclose(in);
  }
  else
  { char *back = strrchr(listwild, '\\');
    if(!back) ProcessFiles("");
    else
    { memcpy(wildpath, listwild, back-listwild);
      wildpath[back-listwild+1] = 0;
      memcpy(listwild, back+1, strlen(back+1)+1);
      ProcessFiles(wildpath);
    }
  }

  return 0;
}
