/*
* Slice
* v 1.0 (2007-03-28)
* File splitter
*
* Copyright 2006 Zach Scrivena
* Email: zachscrivena@gmail.com
* Webpage: http://zs.freeshell.org/
*
* Splits a file into several 'slices' that can be easily concatenated
* to recover the original file. Free software written in C.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/


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

#define BUFFER_SIZE 1048576 // 1 Mb
#define STRING_SIZE 256


/* Function prototypes */
void strcombine(char *target, char *source1, char *source2, char *source3);
long long int getFileSize(char *filename);
int fileExists(char *filename);
void printNumComma(long long l);
void printUsage();


/* Main entry point for Slice program */
int main(int argc, char *argv[])
{
    FILE *fp_in;  // input file pointer
    FILE *fp_out; // output file pointer
    char *command; // first command-line argument ("command")
    char *file_in;  // second command-line argument ("file")
    char *file_out; // output file name
    long long commandInteger; // integer associated with the "command"
    long long size;    // input file size in bytes
    long long sliceSize;     // size of each slice (except the last slice) in bytes
    long long lastSliceSize; // size of the last slice in bytes
    long long numSlices; // number of slices
    long long sliceIndex;
    long long thisSliceSize;
    long long bytesWritten;
    long long totalBytesWritten;
    int numDigits;
    int bytesToWrite;
    int bytesRead;
    char formatString[STRING_SIZE];
    char buffer[BUFFER_SIZE];
    char stringNumSlices[STRING_SIZE];
    char stringSliceIndex[STRING_SIZE];
    long int sliceStartTime;
    long int sliceDuration;
    char indicator[] = "/-\\|";
    int indicatorMaxIndex = strlen(indicator) - 1;
    int indicatorIndex;

    // program settings
    int showProgress = 1; // show progress by default
    int overwriteOutputFiles = 0;  // do not overwrite output files by default

    // temp vars
    char a[STRING_SIZE];
    int i;

    printf("\nSlice 1.0    Copyright 2007 Zach Scrivena    2007-03-28");

    /* Process command-line arguments */

    if (argc == 1)
    {
        printUsage();
        exit(0);
    }
    else if (argc < 3)
    {
        printf("\n\nERROR: Insufficient number of arguments.\nTo display help, run Slice without any command-line arguments.\n");
        exit(1);
    }

    // correct number of arguments supplied
    command = argv[1];        // first argument
    file_in = argv[argc - 1]; // last argument

    // extract the integer part of the command
    if ((commandInteger = strtoll(command, (char **) NULL, 10)) <= 0)
    {
        printf("\n\nERROR: Invalid command %s.\nTo display help, run Slice without any command-line arguments.\n", command);
        exit(1);
    }

    // process switches
    for (i = 2; i < argc - 1; i++)
    {
        if (strcmp("-s", argv[i]) == 0)
        {
            // suppress progress indicator
            showProgress = 0;
        }
        else if (strcmp("-o", argv[i]) == 0)
        {
            // overwrite output files
            overwriteOutputFiles = 1;
        }
        else
        {
            // invalid switch
            printf("\n\nERROR: Invalid switch %s.\nTo display help, run Slice without any command-line arguments.\n", argv[i]);
            exit(1);
        }
    }

    // check if input file exists
    if (!fileExists(file_in))
    {
        printf("\n\nERROR: Input file %s does not exist.\nSlice aborted.\n", file_in);
        exit(1);
    }

    // print file to be sliced
    printf("\n\nFile to be sliced: %s ", file_in);

    // get size of input file
    if ((size = getFileSize(file_in)) == -1)
    {
        printf("\nSlice aborted.\n");
        exit(1);
    }

    printf("(");
    printNumComma(size);
    printf(" bytes)");

    /* Compute slice sizes */

    if (command[strlen(command) - 1] == 'x')
    {
        // split file into specified number of slices
        sliceSize = size / commandInteger;
        numSlices = commandInteger;
    }
    else
    {
        // split file into slices not exceeding specified size in bytes
        sliceSize = commandInteger;
        numSlices = (size + commandInteger - 1) / commandInteger;
    }

    if (numSlices < 1 ||
       sliceSize < 1)
    {
        printf("\n\nERROR: Unable to slice file to given specification.\nTry relaxing the specification.\nSlice aborted.\n");
        exit(1);
    }

    if (numSlices == 1)
    {
        printf("\n\nNothing to do; original file already satisfies specification.\n");
        exit(0);
    }

    if (sliceSize > size)
    {
        sliceSize = size;
    }

    // compute size of the last slice
    lastSliceSize = size - (numSlices - 1) * sliceSize;

    // display size of slices
    printf("\nCreating ");
    printNumComma(numSlices);
    printf(" slices ");

    if (lastSliceSize == sliceSize)
    {
        printf("(");
        printNumComma(numSlices);
        printf(" x ");
        printNumComma(sliceSize);
        printf(" bytes)");
    }
    else
    {
        printf("(");
        printNumComma(numSlices - 1);
        printf(" x ");
        printNumComma(sliceSize);
        printf(" bytes + 1 x ");
        printNumComma(lastSliceSize);
        printf(" bytes)");
    }

    if (showProgress) printf("...");

    fflush(stdout);

    /* Allocate memory for output file names */

    // compute number of digits required to display the maximum slice index
    numDigits = sprintf(a, "%lld", numSlices);

    // create format string used in creating the file extension of each slice
    sprintf(formatString, "%%0%dd", numDigits);

    // allocate memory for the name of the output files
    if ((file_out = (char *) malloc((strlen(file_in) + 1 + numDigits) * sizeof(char))) == NULL)
    {
        printf("\n\nERROR: Insufficient memory to create name of the output files.\nSlice aborted.\n");
        exit(1);
    }

    // string representation of the number of the slices
    sprintf(stringNumSlices, formatString, numSlices);

    /* Check if the output files already exist */
    if (!overwriteOutputFiles)
    {
        for (sliceIndex = 1; sliceIndex <= numSlices; sliceIndex++)
        {
            // string representation of the slice index
            sprintf(stringSliceIndex, formatString, sliceIndex);

            // create the name of the output file, e.g. work.dat.001
            strcombine(file_out, file_in, ".", stringSliceIndex);

            if (fileExists(file_out))
            {
                char stringOne[STRING_SIZE]; // string representation of number 1 (e.g. "0001")
                sprintf(stringOne, formatString, 1);

                printf("\n\nERROR: Output file %s already exists.", file_out);

                strcombine(file_out, file_in, ".", stringOne);
                printf("\nPlease ensure that files");
                printf("\n    %s, through", file_out);
                strcombine(file_out, file_in, ".", stringNumSlices);
                printf("\n    %s\ndo not already exist.", file_out);

                printf("\nSlice aborted.\n");
                exit(1);
            }
        }
    }

    // start timer
    sliceStartTime = time(NULL);

    /* Open the input file for binary read-only */

    if ((fp_in = fopen(file_in, "rb")) == 0)
    {
        printf("\n\nERROR: Unable to open file for reading.\nSlice aborted.\n", file_in);
        exit(1);
    }


    /* Write output files */

    if (showProgress) printf("\nWriting slice ");

    totalBytesWritten = 0;

    // for progress indicator /-\|
    indicatorIndex = 0;

    for (sliceIndex = 1; sliceIndex <= numSlices; sliceIndex++)
    {
        // string representation of the slice index
        sprintf(stringSliceIndex, formatString, sliceIndex);

        // create the name of the output file, e.g. work.dat.001
        strcombine(file_out, file_in, ".", stringSliceIndex);

        // print slice status
        if (showProgress)
        {
            if (sliceIndex > 1) for (i = 0; i < (2 * numDigits + 25); i++) printf("\b"); // erase previously printed line
            printf("%s of %s: %3d%% - [Total %3d%%]",
                stringSliceIndex,
                stringNumSlices,
                0,
                (int) (100.0 * totalBytesWritten / size)); // e.g. "001 of 123:   0% - [Total   0%]"
        }
        else
        {
            printf(".");
        }
        fflush(stdout);

        // open output file for binary write-only
        if ((fp_out = fopen(file_out, "wb")) == NULL)
        {
            printf("\n\nERROR: Unable to create output file %s.\nSlice aborted.\n", file_out);
            exit(1);
        }

        // get size of the current slice (output file)
        thisSliceSize = (sliceIndex == numSlices) ? lastSliceSize : sliceSize;

        /* Read input file and write the current slice */

        // reset total bytes written for the current slice
        bytesWritten = 0;

        while (bytesWritten < thisSliceSize)
        {
            if (thisSliceSize - bytesWritten < BUFFER_SIZE)
            {
                bytesToWrite = (int) (thisSliceSize - bytesWritten);
            }
            else
            {
                bytesToWrite = BUFFER_SIZE;
            }

            // read bytes from input file into the buffer
            bytesRead = fread(buffer, 1, bytesToWrite, fp_in);

            if (bytesRead != bytesToWrite)
            {
                printf("\n\nERROR: Unable to read input file.\nSlice aborted.\n");
                exit(1);
            }

            // write bytes from buffer to the current slice (output file)
            fwrite(buffer, 1, bytesRead, fp_out);

            // increment total bytes written for the current slice
            bytesWritten += bytesToWrite;

            // update progress bar
            if (showProgress)
            {
                indicatorIndex++;
                if (indicatorIndex > indicatorMaxIndex) indicatorIndex = 0;
                printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"); // erase previously printed line
                printf("%3d%% %c [Total %3d%%]",
                    (int) (100.0 * bytesWritten / thisSliceSize),
                    indicator[indicatorIndex],
                    (int) (100.0 * (totalBytesWritten + bytesWritten) / size));
                fflush(stdout);
            }
        }

        // close the current slice (output file)
        fclose(fp_out);

        // update total bytes written
        totalBytesWritten += thisSliceSize;
    }

    // close the input file
    fclose(fp_in);

    // free previously allocated memory
    free(file_out);

    // done!
    sliceDuration = time(NULL) - sliceStartTime;
    if (sliceDuration <= 0) sliceDuration = 1;

    if (showProgress)
    {
        if (sliceIndex > 1) for (i = 0; i < (2 * numDigits + 25); i++) printf("\b"); // erase previously printed line
        printf("%s of %s: %3d%%   [Total %3d%%]", stringNumSlices, stringNumSlices, 100, 100); // e.g. "123 of 123: 100%   [Total 100%]"
    }

    printf("\nSlice is done!");

    printf("\nAverage slicing speed: ");
    printNumComma((long long int) (size / sliceDuration));
    printf(" bytes/second");

    printf("\n\nTo recover the original file, simply concatenate the file slices sequentially\nin binary mode. For example,");
    printf("\n  Windows: copy  /b  file.txt.1 + file.txt.2 + file.txt.3  file.txt");
    printf("\n  Unix   : cat  file.txt.1 file.txt.2 file.txt.3  >  file.txt");
    printf("\n");

    // successful termination
    return 0;
}


/* Concatenate multiple strings (e.g. target = source1 + source2 + source3) */
void strcombine(char *target, char *source1, char *source2, char *source3)
{
    strcpy(target, source1);
    strcat(target, source2);
    strcat(target, source3);
}


/* Get size in bytes of the specified file */
long long int getFileSize(char *filename)
{
    struct stat statBuffer; // file attributes

    if (stat(filename, &statBuffer) != 0)
    {
        printf("\n\nERROR: Unable to obtain attributes of file %s.", filename);
        return -1;
    }

    return (long long int) statBuffer.st_size;
}


/* Check if specified file exists */
int fileExists(char *filename)
{
    struct stat statBuffer; // file attributes
    return (stat(filename, &statBuffer) == 0) ? 1 : 0;
}


/* Prints a given long long int as a comma-grouped numeral */
void printNumComma(long long num)
{
    char stringNoComma[STRING_SIZE]; // string representation of num without commas
    char stringComma[STRING_SIZE];   // string representation of num with commas
    int stringNoComma_len;
    int i;
    int j;

    sprintf(stringNoComma, "%lld", num);
    stringNoComma_len = strlen(stringNoComma);

    j = 0;
    for (i = 0; i < stringNoComma_len; i++)
    {
        stringComma[j++] = stringNoComma[i];

        if (((stringNoComma_len - i) % 3 == 1) && (i < stringNoComma_len - 1))
        {
            stringComma[j++] = ','; // insert a comma
        }
    }

    stringComma[j] = '\0'; // null-terminate the string
    printf("%s", stringComma);
}


/* Prints usage for Slice */
void printUsage()
{
    //RULER---00000000011111111112222222222333333333344444444445555555555666666666677777777778
    //RULER---12345678901234567890123456789012345678901234567890123456789012345678901234567890
    printf("\n" \
          "\nSplits a file into several 'slices' that can be easily concatenated to recover" \
          "\nthe original file. By default, Slice displays a progress indicator, and will" \
          "\nnot overwrite existing output files." \
           "\n" \
           "\nUSAGE:   slice  [command]  <switches>  [\"File\"]" \
           "\n" \
           "\n [command] is a positive integer followed possibly by the character 'x'." \
           "\n   With the 'x', the integer gives the total number of slices;" \
           "\n   without the 'x', the integer gives the maximum size of each slice in bytes." \
           "\n" \
           "\n <Switches>:" \
           "\n   -s   Suppress progress indicator (improves speed)" \
           "\n   -o   Overwrite existing output files" \
           "\n" \
           "\n [\"File\"] is the name of the file to be split." \
           "\n" \
           "\nEXAMPLES:" \
           "\n  slice 5x work.dat       Split the file into 5 slices." \
           "\n  slice 123456 work.dat   Split the file into slices that are at most" \
           "\n                            123456 bytes each." \
           "\n  slice 5x -s work.dat    Split the file into 5 slices, and suppress the" \
           "\n                            progress indicator." \
           "\n  slice 5x -o work.dat    Split the file into 5 slices, and automatically" \
           "\n                            overwrite existing output files." \
           "\n\n");
}


/*
* Compile notes:
* This source file has been compiled successfully using Cygwin's GCC 3.4.4
*/