/*
 **************************************************

  FileSplit utility program

  Written by:      Nick Shin
  e-mail address:  nshin@estss.com
  web address:     http://estss.com/NCS/

  What's it do?
  This program will split a file into any size you
  define or into any number of file you want.

  This program will also merge the files back into
  a single file.

  Who can use it?
  Anyone.  And it is available for free.
  Just give me a shout of THNX if you like/use it.

  Feel free to redistribute, modify, keep, use,
  destroy or ignore it.

  The code is so easy, I don't think I am allowed
  to copyright or copyleft such quicky code.

  But, I am the owner of this piece of code.
  Anyone taking this code and claiming it for
  themselves will be frowned upon.

 **************************************************
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef _WIN32
#define stat  _stat
#define fstat _fstat
#define BOOL  int
#endif

#define MAXBUFFER_SIZE    1024        // 1K fread() at a time
//#define MAXBUFFER_SIZE    1048576     // 1M fread() at a time
#define NUM_HASH_PRINT    30          // number of hash marks to print for eye candy

char *sourceFileName = NULL;
int  numberOfFiles = 0;
int  sizeOfFileChunks = 0;
BOOL quietFlag = 0;

int splitfile() {
  FILE  *sourcefileptr;
  FILE  *destinationptr;
  char   destinationfname[_MAX_PATH];
  char  *buffer;

  struct stat fileStatBuf;
  int    filecount = 0;
  int    result = 0;
  int    baseChunkSize = 0;
  int    lastChunkSize = 0;
  int    buffChunkSize = 0;           // for smaller fread() chunks
  int    bufxChunkSize = 0;           // for smaller fread() chunks
  int    minifileCount = 0;           // for smaller fread() chunks
  int    eyecandy = 0;                // for eye candy printing

  if (sourceFileName == NULL) {
    printf("ERROR: source filename not specified!\n");
    return 1;
  }

  if ( ( sourcefileptr = fopen( sourceFileName,"rb" ) ) == NULL) {
    printf("\nERROR: unable to open the file: \"%s\"\n", sourceFileName);
    return 1;
  }

/* The GUTS of the program */

  result = _stat( sourceFileName, &fileStatBuf );
  if( result != 0 ) {
    perror( "Problem getting information" );
    return 1;
  }
#ifdef _DEBUG
  printf("\nFile to split : %s\n", sourceFileName);
  printf("Number of files to split to = %d\n", numberOfFiles);
  printf("Size of files to split to   = %d\n", sizeOfFileChunks);

  printf("\nFile size     : %ld\n", fileStatBuf.st_size );
  printf("Drive         : %c:\n", fileStatBuf.st_dev + 'A' );
#endif

  if (numberOfFiles) {
    if (fileStatBuf.st_size%numberOfFiles) {
      baseChunkSize = fileStatBuf.st_size/numberOfFiles + 1;
      lastChunkSize = fileStatBuf.st_size - (baseChunkSize* (numberOfFiles-1));
    } else {
      baseChunkSize = fileStatBuf.st_size/numberOfFiles;
      lastChunkSize = baseChunkSize;
    }

  } else { // if (sizeOfFileChunks)
    numberOfFiles = fileStatBuf.st_size/sizeOfFileChunks;
    lastChunkSize = fileStatBuf.st_size - (sizeOfFileChunks*numberOfFiles);
    if (lastChunkSize)
      numberOfFiles++;
    baseChunkSize = sizeOfFileChunks;
  }

  // we don't want to read files more than MAXBUFFER_SIZE at a time
  if (baseChunkSize < MAXBUFFER_SIZE)
    buffChunkSize = baseChunkSize;
  else
    buffChunkSize = MAXBUFFER_SIZE;

  buffer = malloc(buffChunkSize);

  filecount = 0;  // sanity set
  while (filecount < numberOfFiles) {
    sprintf(destinationfname, "%s.%.3d", sourceFileName, filecount);         // 1000 sequence numbers
    if (!quietFlag)
      printf("Creating file %s\n", destinationfname);
    filecount++;

    if ( ( destinationptr = fopen( destinationfname,"wb" ) ) == NULL) {
      printf("\nERROR: unable to open the file: \"%s\"\n", destinationfname);
      printf("ERROR: terminating program.\n");

      free(buffer);
      fclose(sourcefileptr);
      return 1;
    }

    // the actual copy routine
    if (filecount != numberOfFiles) {
      minifileCount = baseChunkSize/buffChunkSize;
      bufxChunkSize = baseChunkSize - (buffChunkSize * minifileCount);
    } else {
      minifileCount = lastChunkSize/buffChunkSize;
      bufxChunkSize = lastChunkSize - (buffChunkSize * minifileCount);
    }
    if (!quietFlag) {
      if (minifileCount < NUM_HASH_PRINT)
        eyecandy = 0;
      else
        eyecandy = minifileCount/NUM_HASH_PRINT;
    }
    while (minifileCount--) {
      if (!quietFlag) {
        if ( (eyecandy == 0) || (minifileCount%eyecandy == 0) )
          printf("#");
      }
      fread(buffer, buffChunkSize, 1, sourcefileptr);
      fwrite(buffer, buffChunkSize, 1, destinationptr);
    }
    if (bufxChunkSize) {
      if (!quietFlag)
        printf("\n");
      fread(buffer, bufxChunkSize, 1, sourcefileptr);
      fwrite(buffer, bufxChunkSize, 1, destinationptr);
    }

    fclose(destinationptr);
  }
/* End of GUTS section */

  free(buffer);
  fclose(sourcefileptr);
  return 0;
}

int mergefiles() {
  FILE  *sourcefileptr;
  FILE  *splitfileptr;
  char   splitfilefname[_MAX_PATH];
  char  *buffer;

  struct stat fileStatBuf;
  int    filecount = 0;
  int    result = 0;
  int    fileChunkSize = 0;
  int    buffChunkSize = 0;           // for smaller fread() chunks
  int    bufxChunkSize = 0;           // for smaller fread() chunks
  int    minifileCount = 0;           // for smaller fread() chunks
  int    eyecandy = 0;                // for eye candy printing

  if (sourceFileName == NULL) {
    printf("ERROR: source filename not specified!\n");
    return 1;
  }

  if ( ( sourcefileptr = fopen( sourceFileName,"wb" ) ) == NULL) {
    printf("\nERROR: unable to open the file: \"%s\"\n", sourceFileName);
    return 1;
  }

/* The GUTS of the program */
  if (!quietFlag)
    printf("Creating file %s\n", sourceFileName);
  filecount = 0;  // sanity set
  while (result == 0) {
    sprintf(splitfilefname, "%s.%.3d", sourceFileName, filecount);         // 1000 sequence numbers

    result = _stat( splitfilefname, &fileStatBuf );
    if (result != 0)
      break;      // no more files to read in

#ifdef _DEBUG
    printf("\nFile size     : %ld\n", fileStatBuf.st_size );
    printf("Drive         : %c:\n", fileStatBuf.st_dev + 'A' );
#endif

    // we don't want to read files more than MAXBUFFER_SIZE at a time
    if (fileStatBuf.st_size < MAXBUFFER_SIZE)
      buffChunkSize = fileStatBuf.st_size;
    else
      buffChunkSize = MAXBUFFER_SIZE;

    buffer = malloc(buffChunkSize);

    if ( ( splitfileptr = fopen( splitfilefname,"rb" ) ) == NULL) {
      printf("\nERROR: unable to open the file: \"%s\"\n", splitfilefname);
      printf("ERROR: terminating program.\n");

      free(buffer);
      fclose(sourcefileptr);
      return 1;
    }

    // the actual copy routine
    minifileCount = fileStatBuf.st_size/buffChunkSize;
    bufxChunkSize = fileStatBuf.st_size - (buffChunkSize * minifileCount);

    if (!quietFlag) {
      printf("Reading file %s\n", splitfilefname);
      if (minifileCount < NUM_HASH_PRINT)
        eyecandy = 0;
      else
        eyecandy = minifileCount/NUM_HASH_PRINT;
    }

    while (minifileCount--) {
      if (!quietFlag) {
        if ( (eyecandy == 0) || (minifileCount%eyecandy == 0) )
          printf("#");
      }
      fread(buffer, buffChunkSize, 1, splitfileptr);
      fwrite(buffer, buffChunkSize, 1, sourcefileptr);
    }
    if (bufxChunkSize) {
      if (!quietFlag)
        printf("\n");
      fread(buffer, bufxChunkSize, 1, splitfileptr);
      fwrite(buffer, bufxChunkSize, 1, sourcefileptr);
    }

    fclose(splitfileptr);
    filecount++;
  }

/* End of GUTS section */

  free(buffer);
  fclose(sourcefileptr);
  return 0;
}

void usageMsg() {
  printf("\nUsage: filesplit [options] <source>\n");
  printf("\nOptions:\n");
  printf("\t/bx - split the file into (x) byte chunks\n");
  printf("\n\t/fx - split the file into (x) number of equal sized files\n");
  printf("\t      /b will be ignored when /f is used\n");
  printf("\t      the default setting is /f2\n");
  printf("\n\t/q  - quiet mode, surpress cpu wasting eye candy\n");
  printf("\n\t/mfilename - merge filename.xxx to filename\n");
  printf("\n\t/? or /h   - prints this help screen\n");
  printf("\nNote:\n  The last file of the series might be smaller\n  than the rest of the split files.\n");
  printf("  So, keep that in mind when \"equal sized files\" are created.\n");
}

int main (int argc, char *argv[]) {
  int  i = 1;
  char *ch;
  BOOL mergeFlag = 0;

  if (argc < 2) {
    usageMsg();
    exit (1);
  }

  while (i < argc) {
    if ( (argv[i][0] == '/') || (argv[i][0] == '-') ){
      switch(argv[i][1]) {
        case 'b':
          ch = &argv[i][2];
          sizeOfFileChunks = atoi(ch);
          break;
        case 'f':
          ch = &argv[i][2];
          numberOfFiles = atoi(ch);
          break;
        case 'q':
          quietFlag = 1;
          break;
        case 'm':
          mergeFlag = 1;
          break;
        case 'h':
        case '?':
          usageMsg();
          return 0;
          break;
        default:
          break;
      }
    } else {
      sourceFileName = argv[i];
    }
    i++;
  }

  if (!mergeFlag) {
    // split file
    if ( (numberOfFiles == 0) && (sizeOfFileChunks == 0) )
      numberOfFiles = 2;

    if (splitfile() != 0)
      exit (2);
  } else {
    // merge files
    if (mergefiles() != 0)
      exit (3);
  }

  return 0;
}
