#define VERSION "2.0"

#define BUFFSIZE 512 /* Fragment size unit */

/* #define PC */

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

#ifdef PC
#include <conio.h>
#endif

enum EXIT_VALUE { SUCCESS, ERROR, ABORT };
enum CONDITION { MUST, NEEDNT };

void usage(void);
void span(char *infname, char *outfname, char *lastoutfname, char *outfext);
void bind(char *infname, char *outfname, char *lastinfname, char *infext);
void askdisk(char *type);
void incrdisk(char *ext);
char opensource(char *name, char condition);
char opentarget(char *name);
void readsource(unsigned bytes);
char writetarget(unsigned bytes, char condition);

#define minimum(a, b) (a < b ? a : b)

unsigned char buff[BUFFSIZE];
int infh, outfh;
long infbytesleft;
short disk = 0;

void usage(void)
{
    printf("Usage:\n"
           "      span -s [source file] [target path]\n"
           "      span -b [target file] [source path]\n\n"
           "Examples:\n"
           "      span -s c:/temp/dummy.zip a:/\n"
           "      span -b c:/temp/dummy.zip a:/\n");
    exit(ERROR);
}

int main(int argc, char **argv)
{
    char name[FILENAME_MAX], lastname[FILENAME_MAX], *basename, spanit = 0;
    int extpos;
#ifdef PC
    char *tmp;
#endif

    printf("Span v%s. Copyright (c) 1996-1997 Konstantinos Tampouris, "
           "Infinity Labs.\n\n", VERSION);
#ifdef PC
    if (!_USE_LFN)
    {
        printf("No long filename support found\n");
        return(ERROR);
    }
#endif
    if (argc != 4) usage();
    if (!stricmp(argv[1], "-s"))
        spanit = 1;
    else if (stricmp(argv[1], "-b"))
        usage();
#ifdef PC
    if ((basename = strrchr(argv[2], '/')) < (tmp = strrchr(argv[2], '\\')))
        basename = tmp;
    if (basename)
#else
    if (basename = strrchr(argv[2], '/'))
#endif
        basename++;
    else
        basename = argv[2];
    extpos = strlen(basename) + strlen(argv[3]) + 1;
    if (extpos + 4 > FILENAME_MAX)
    {
        printf("%s file name too big\n", (spanit ? "Targer" : "Source"));
        return(ERROR);
    }
    sprintf(name, "%s%s.000", argv[3], basename);
    sprintf(lastname, "%s%s.end", argv[3], basename);
    if (spanit)
        span(argv[2], name, lastname, &name[extpos]);
    else
        bind(name, argv[2], lastname, &name[extpos]);
    return SUCCESS;
}

void span(char *infname, char *outfname, char *lastoutfname, char *outfext)
{
    unsigned bytes = 0;
    long outfsize;

    if (!opensource(infname, MUST)) exit(ERROR);
    while (1)
    {
        askdisk("target");
        remove(lastoutfname);
        if (!opentarget(outfname)) continue;
        while (infbytesleft)
        {
            if (!bytes) readsource(bytes = minimum(BUFFSIZE, infbytesleft));
            if (!writetarget(bytes, NEEDNT)) break;
            infbytesleft -= bytes;
            bytes = 0;
        }
        outfsize = tell(outfh);
        close(outfh);
        if (!outfsize)
        {
            remove(outfname);
            continue;
        }
        if (!infbytesleft) break;
        incrdisk(outfext);
    }
    close(infh);
    if (!disk) *(lastoutfname - outfname + outfext - 1) = 0;
    rename(outfname, lastoutfname);
}

void bind(char *infname, char *outfname, char *lastinfname, char *infext)
{
    char lastdisk = 0;
    unsigned bytes;

    if (!opentarget(outfname)) exit(ERROR);
    while (1)
    {
        askdisk("source");
        if (!opensource(infname, NEEDNT))
            if (opensource(lastinfname, NEEDNT))
                lastdisk = 1;
            else
                continue;
        while (infbytesleft)
        {
            readsource(bytes = minimum(BUFFSIZE, infbytesleft));
            writetarget(bytes, MUST);
            infbytesleft -= bytes;
        }
        close(infh);
        if (lastdisk) break;
        incrdisk(infext);
    }
    close(outfh);
}

void askdisk(char *type)
{
    char c;

    printf("Insert disk #%hd in the %s drive and press [ENTER] - "
           "Press [ESC] to quit", disk + 1, type);
    fflush(stdout);
    while ((c = getch()) != '\r')
        if (c == '\e') /* ESC */
        {
            printf("\n");
            exit(ABORT);
        }
    printf("\n");
}

void incrdisk(char *ext)
{
    if (disk == 999)
    {
        printf("More than 1000 diskettes needed - Abort\n");
        exit(ERROR);
    }
    sprintf(ext, "%03hd", ++disk);
}

char opensource(char *name, char condition)
{
    if ((infh = open(name, O_RDONLY | O_BINARY)) < 0)
    {
        if (condition == MUST) printf("Error opening source file\n");
        return 0;
    }
    lseek(infh, 0, SEEK_END);
    if ((infbytesleft = tell(infh)) < 0)
    {
        close(infh);
        printf("Error obtaining source file's size\n");
        return 0;
    }
    lseek(infh, 0, SEEK_SET);
    return 1;
}

char opentarget(char *name)
{
    outfh = open(name, O_WRONLY | O_BINARY | O_TRUNC | O_CREAT
#ifdef PC
    , S_IWUSR);
#else
    );
#endif
    if (outfh < 0)
    {
        printf("Error opening target file\n");
        return 0;
    }
    return 1;
}

void readsource(unsigned bytes)
{
    if (read(infh, buff, bytes) != bytes)
    {
        printf("Error reading source file\n");
        exit(ERROR);
    }
}

char writetarget(unsigned bytes, char condition)
{
    if (write(outfh, buff, bytes) != bytes)
    {
        if (condition == NEEDNT) return 0;
        printf("Error writing target file\n");
        exit(ERROR);
    }
    return 1;
}
