//
//  cng2jpg
//  cng2jpg.c
//
//  Paul Knight
//  http://diplograph.net/posts/decoding_the_complete_national_geographic_images
//
//  Version History:
//    July 9, 2012 - Version 0.2.1
//      Updated header comments to include link back to the journal post.
//    July 8, 2012 - Version 0.2
//      Replaced custom copyString function with strdup(3).
//      Thanks to jauricchio for the suggestion.
//    June 23, 2012 - Version 0.1
//      Initial release.
//

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <libgen.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/syslimits.h>

static const size_t kBufferSize = 4096;

static char *gProgramName = NULL;

void setProgramName(const char *argvName)
{
    assert(!gProgramName);

    char *argvNameCopy = strdup(argvName);
    char *name = basename(argvNameCopy);
    if (!name) {
        perror("Couldn't determine program name");
        exit(EXIT_FAILURE);
    }
    gProgramName = strdup(name);
    free(argvNameCopy);
}

void printUsageAndExit(int status)
{
    assert(gProgramName);

    printf("Usage: %s [-o outputfile] inputfile\n", gProgramName);
    printf("       %s inputfile [...]\n", gProgramName);
    printf("If outputfile is not specified, inputfile with a \".jpg\" extension is assumed.\n");

    exit(status);
}

const char *fileNameWithJPGExtension(const char *inputFileName)
{
    static char *jpgFileName = NULL;
    if (jpgFileName) {
        free(jpgFileName);
        jpgFileName = NULL;
    }

    char *fileNameCopy = strdup(inputFileName);
    char *fileDirname = strdup(dirname(fileNameCopy));
    free(fileNameCopy);
    int hasDirname = (strcmp(fileDirname, "."));

    fileNameCopy = strdup(inputFileName);
    char *fileBasename = strdup(basename(fileNameCopy));
    free(fileNameCopy);

    char *extensionStart = strrchr(fileBasename, (int)'.');
    if (extensionStart == fileBasename)
        extensionStart = NULL;
    size_t fileBasenameWithoutExtensionLength = extensionStart ? (extensionStart - fileBasename) : strlen(fileBasename);
    char *fileBasenameWithoutExtension = malloc(fileBasenameWithoutExtensionLength + 1);
    bzero(fileBasenameWithoutExtension, fileBasenameWithoutExtensionLength + 1);
    strncpy(fileBasenameWithoutExtension, fileBasename, fileBasenameWithoutExtensionLength);

    size_t jpgFileNameLength;
    if (hasDirname)
        jpgFileNameLength = strlen(fileDirname) + 1 + strlen(fileBasenameWithoutExtension) + 4;
    else
        jpgFileNameLength = strlen(fileBasenameWithoutExtension) + 4;
    jpgFileName = malloc(jpgFileNameLength + 1);
    if (hasDirname)
        snprintf(jpgFileName, jpgFileNameLength + 1, "%s/%s.jpg", fileDirname, fileBasenameWithoutExtension);
    else
        snprintf(jpgFileName, jpgFileNameLength + 1, "%s.jpg", fileBasenameWithoutExtension);

    free(fileDirname);
    free(fileBasename);
    free(fileBasenameWithoutExtension);

    return jpgFileName;
}

int convertCNGFileToJPGFile(const char *inputFileName, FILE *inputFile, const char *outputFileName, FILE *outputFile)
{
    int isValidCNG = 0;
    size_t bytesRead = 0;
    unsigned char buffer[kBufferSize];

    while ((bytesRead = fread(buffer, 1, kBufferSize, inputFile))) {
        if (!isValidCNG) {
            if (bytesRead < 11 || strncmp("\xa5\xa9\xa6\xa9", (char *)(buffer + 6), 4)) {
                fprintf(stderr, "%s does not appear to be a valid CNG file\n", inputFileName);
                return 0;
            }
            isValidCNG = 1;
        }
        for (size_t i = 0; i < bytesRead; i++)
            buffer[i] ^= 0xEF;
        size_t bytesWritten = fwrite(buffer, 1, bytesRead, outputFile);
        if (bytesWritten < bytesRead) {
            fprintf(stderr, "Error writing %s\n", outputFileName);
            return 0;
        }
    }

    if (ferror(inputFile)) {
        fprintf(stderr, "Error reading %s\n", inputFileName);
        return 0;
    }

    return 1;
}

int main(int argc, char * argv[])
{
    setProgramName(argv[0]);

    int c;
    opterr = 0;
    char *outputFileNameOption = NULL;
    while ((c = getopt (argc, argv, "ho:")) != -1) {
        switch (c)
        {
            case 'h':
                printUsageAndExit(EXIT_SUCCESS);
                break;
            case 'o':
                outputFileNameOption = optarg;
                break;
            case '?':
                if (optopt == 'o') {
                    fprintf(stderr, "Option -%c requires an argument.\n", optopt);
                    printUsageAndExit(EXIT_FAILURE);
                } else if (isprint(optopt)) {
                    fprintf(stderr, "Unknown option `-%c'.\n", optopt);
                    printUsageAndExit(EXIT_FAILURE);
                } else {
                    fprintf (stderr, "Unknown option character `\\x%x'.\n", optopt);
                    printUsageAndExit(EXIT_FAILURE);
                }
                break;
            default:
                printUsageAndExit(EXIT_FAILURE);
                break;
        }
    }

    if (outputFileNameOption && argc - optind > 1) {
        fprintf(stderr, "Option -o cannot be used with multiple inputfiles.\n");
        printUsageAndExit(EXIT_FAILURE);
    }

    if (optind == argc) {
        fprintf(stderr, "No input files specified.\n");
        printUsageAndExit(EXIT_FAILURE);
    }

    // Main loop.
    for (int index = optind; index < argc; index++) {
        const char *inputFileName = argv[index];
        const char *outputFileName;
        if (outputFileNameOption)
            outputFileName = outputFileNameOption;
        else
            outputFileName = fileNameWithJPGExtension(inputFileName);

        // Verify the files are different.
        struct stat inputFileStat;
        struct stat outputFileStat;
        if (stat(inputFileName, &inputFileStat)) {
            fprintf(stderr, "Couldn't open input file %s: %s\n", inputFileName, strerror(errno));
            continue;
        }
        if (!stat(outputFileName, &outputFileStat)) {
            if (inputFileStat.st_dev == outputFileStat.st_dev && inputFileStat.st_ino == outputFileStat.st_ino) {
                fprintf(stderr, "%s and %s appear to be the same file. Skipping.\n", inputFileName, outputFileName);
                continue;
            }
        }

        // Open the files.
        FILE *inputFile = fopen(inputFileName, "r");
        if (!inputFile) {
            fprintf(stderr, "Couldn't open %s: %s\n", inputFileName, strerror(errno));
            continue;
        }
        FILE *outputFile = fopen(outputFileName, "w");
        if (!outputFile) {
            fprintf(stderr, "Couldn't open output file %s: %s\n", outputFileName, strerror(errno));
            fclose(inputFile);
            continue;
        }

        // Do the actual conversion.
        convertCNGFileToJPGFile(inputFileName, inputFile, outputFileName, outputFile);

        fclose(inputFile);
        fclose(outputFile);
    }

    return EXIT_SUCCESS;
}

