/*---------------------------------------------------------------------------*
  Project:  NitroSDK - tools - ntrcomp
  File:     ntrcomp_main.c

  Copyright 2003,2004 Nintendo.  All rights reserved.

  These coded instructions, statements, and computer programs contain
  proprietary information of Nintendo of America Inc. and/or Nintendo
  Company Ltd., and are protected by Federal copyright law.  They may
  not be disclosed to third parties or copied or duplicated in any form,
  in whole or in part, without the prior written consent of Nintendo.

  $Log: ntrcomp_main.c,v $
  Revision 1.10  2004/05/07 02:48:48  takano_makoto
  small modification

  Revision 1.9  2004/03/05 08:42:13  takano_makoto
  changed output file Header, agb ->nitro

  Revision 1.8  2004/03/05 03:18:31  takano_makoto
  small modification: one variable change to long from int

  Revision 1.7  2004/03/04 11:25:06  takano_makoto
  small modification

  Revision 1.6  2004/03/04 10:26:53  takano_makoto
  added function that make output file name automatically

  Revision 1.5  2004/03/04 09:12:09  takano_makoto
  added CVS Header

  $NoKeywords: $
 *---------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
#include "nitroCompLib.h"

// #pragma comment(lib, "nitroCompLib.lib") /* When using a DLL */

#define uint    unsigned int
#define ulong   unsigned long
#define uchar   unsigned char

//---------------------------------------------------------------------------
// Type definitions
//---------------------------------------------------------------------------
// Compression type
typedef enum {
    CONV_TYPE_NON,          // No conversion
    CONV_TYPE_DIFF,         // Difference filter
    CONV_TYPE_RUNLENGTH,    // Run length encode
    CONV_TYPE_LZ77,         // LZ77 compression
    CONV_TYPE_HUFFMAN       // Huffman compression
} t_conv_type;

// Compression data information structure
typedef struct {
    t_conv_type type;    // Compression type
    uint  opt;           // Compression parameter
    ulong src_size;      // Data size before compression
    ulong dest_size;     // Data size after compression
    uchar *src_buf;      // Data buffer for before compression
    uchar *dest_buf;     // Data buffer for after compression
    uchar header_flg:1;  // Flag for whether to append header information
    uchar textout_flg:1; // Flag for whether to output text
} t_conv_data;


//---------------------------------------------------------------------------
// Prototype declaration
//---------------------------------------------------------------------------
static void usage(const char *cmd_string);
static int parse_opt(int argc, char* argv[], t_conv_data *cnv_dat, char **in_fname
                     , char **out_fname);
static int load_file(const char *fname, t_conv_data *cnv_dat);
static int save_file(const char *fname, const t_conv_data *cnv_dat);
static int convert_data(t_conv_data *cnv_dat);
static int fwrite_text(const uchar *buf, ulong size, FILE *fp, const char *name);
static char* cut_fname(char *path);
static char* make_output_fname(const char *input_fname, const t_conv_data *cnv_dat);

//---------------------------------------------------------------------------
// Main function
//---------------------------------------------------------------------------
int main(int argc, char* argv[]) {
    t_conv_data cnv_dat;
    char *in_fname, *out_fname;

    // Initialize data
    memset(&cnv_dat, 0, sizeof(cnv_dat));

    // Parse command options
    if (parse_opt(argc, argv, &cnv_dat, &in_fname, &out_fname) != 0) {
        exit(1);
    }

    // Load input file to buffer
    if (load_file(in_fname, &cnv_dat) != 0) {
        exit(1);
    }

    // Compress file
    if (convert_data(&cnv_dat) != 0) {
        exit(1);
    }

    // Write to output file from buffer
    if (save_file(out_fname, &cnv_dat) != 0) {
        exit(1);
    }

    fprintf(stdout, "convert %s(%ldbyte) to %s(%ldbyte)\n"
            , in_fname, cnv_dat.src_size, out_fname, cnv_dat.dest_size);

    free(cnv_dat.src_buf);
    free(cnv_dat.dest_buf);

    return 0;
}

//---------------------------------------------------------------------------
// USAGE:
//---------------------------------------------------------------------------
static void usage(const char *cmd_string) {
    fprintf(stderr, "Usage: %s <-d|r|l|h [option]> [-t] [-H] [-o outputFile] <inputFile> \n", cmd_string);
    fprintf(stderr, "\t-t                   output C format text.\n");
    fprintf(stderr, "\t-H                   Raw data header\n");
    fprintf(stderr, "\t-d BitSize(8|16)     Differential filter.\n");
    fprintf(stderr, "\t-r                   Runlength encode.\n");
    fprintf(stderr, "\t-l Search            LZ77      encode.\n");
    fprintf(stderr, "\t-h BitSize(4|8)      Huffman   encode.\n");
    fprintf(stderr, "\t-o outputFile        Specify the output file name.\n");
}


//---------------------------------------------------------------------------
// Get a pointer to the file name portion from the path character string. Delete the extension.
// @param path  path
// @return pointer to the file name
//---------------------------------------------------------------------------
static char* cut_fname(char *path) {
    char *search_tmp;

    if (path == NULL) {
        return NULL;
    }

    if ((search_tmp = strrchr(path, '/')) != NULL) {
        path = (search_tmp + 1);
    }
    if ((search_tmp = strrchr(path, '\\')) != NULL) {
        path = (search_tmp + 1);
    }
    if ((search_tmp = strrchr(path, '.')) != NULL) {
        *search_tmp = '\0';
    }
    return path;
}

//---------------------------------------------------------------------------
// Function that generates the output file name from the input file name.
// @param input_fname Input file name
// @param cnv_dat Conversion information
// @return Pointer to output file name
//---------------------------------------------------------------------------
static char* make_output_fname(const char *input_fname, const t_conv_data *cnv_dat) {
    char *out_fname, *edit_tmp;

    if (input_fname == NULL) {
        return NULL;
    }

    // Secure memory region
    if (( out_fname = (char *) malloc(strlen(input_fname) + 0x10) ) == NULL) {
        fprintf(stderr, "internal err: malloc fail!\n");
        exit(1);
    }
    
    strcpy(out_fname, input_fname);
    edit_tmp = cut_fname(out_fname);

    // Append compression method to file name
    switch (cnv_dat->type) {
    case CONV_TYPE_NON:
        break;
    case CONV_TYPE_DIFF:
        strcat(edit_tmp, "_DIFF");
        break;
    case CONV_TYPE_RUNLENGTH:
        strcat(edit_tmp, "_RL");
        break;
    case CONV_TYPE_LZ77:
        strcat(edit_tmp, "_LZ");
        break;
    case CONV_TYPE_HUFFMAN:
        strcat(edit_tmp, "_HUFF");
        break;
    }
    // Append extension
    if (cnv_dat->textout_flg) {
        strcat(edit_tmp, ".c");
    } else {
        strcat(edit_tmp, ".bin");
    }
    return out_fname;
}

//---------------------------------------------------------------------------
// Parse command parameters
// @param argc Number of parameters
// @param argv Parameter array
// @retval cnv_dat Compression information data
// @retval in_fname Input file name
// @retval out_fname Output file name
// @return 0 File write succeeded
//        -1 File write error
//---------------------------------------------------------------------------

static int parse_opt(int argc, char* argv[], t_conv_data *cnv_dat,
                 char **in_fname, char **out_fname)
{
    int i = 1;
    t_conv_type type;
    int opt_num;
    char *cmd_string;

    // Get command name
    cmd_string = cut_fname(argv[0]);

    // Initialize status
    cnv_dat->type = CONV_TYPE_NON;
    cnv_dat->header_flg = 0;
    cnv_dat->textout_flg = 0;
    *out_fname = NULL;

    // Parse options
    while (i < argc && argv[i][0] == '-') {
        if (strlen(argv[i]) != 2) {
            usage(cmd_string);
            return -1;
        }

        switch (argv[i][1]) {
        case 't':
        // t option process (argument 0)
            if (cnv_dat->textout_flg) {
                usage(cmd_string);
                return -1;
            }
            cnv_dat->textout_flg = 1;
            break;

        case 'H':
        // H option process (argument 0)
            if (cnv_dat->header_flg) {
                usage(cmd_string);
                return -1;
            }
            cnv_dat->header_flg = 1;
            break;
        case 'o':
        // o option (argument 1)
            if (*out_fname != NULL) {
                usage(cmd_string);
                return -1;
            }
            if (++i >= argc) {
                usage(cmd_string);
                return -1;
            }
            *out_fname = argv[i];
            break;

        case 'd':
        // d option process (argument 1)
            type = CONV_TYPE_DIFF;
            opt_num = 1;
            goto common;
        case 'r':
        // r option process (argument 0)
            type = CONV_TYPE_RUNLENGTH;
            opt_num = 0;
            goto common;
        case 'l':
        // l option process (argument 1)
            type = CONV_TYPE_LZ77;
            opt_num = 1;
            goto common;
        case 'h':
        // h option process (argument 1)
            type = CONV_TYPE_HUFFMAN;
            opt_num = 1;
            goto common;
        common:
        // Common process for specific part of compression method
            if (cnv_dat->type != CONV_TYPE_NON) {
                usage(cmd_string);
                return -1;
            }
            cnv_dat->type = type;

            if (opt_num == 0) {
                break;
            }

            // If there are arguments
            if (++i >= argc) {
                usage(cmd_string);
                return -1;
            }
            if (argv[i][0] == '-') {
                usage(cmd_string);
                return -1;
            }
            cnv_dat->opt = atoi(argv[i]);

            if (cnv_dat->opt == 0) {
                usage(cmd_string);
                return -1;
            }
            break;

        default:
            usage(cmd_string);
            return -1;
        }
        i++;
    }

    if (cnv_dat->type == CONV_TYPE_NON) {
        usage(cmd_string);
        return -1;
    }

    // Get input file name
    if (i != argc - 1) {
        usage(cmd_string);
        return -1;
    };

    *in_fname = argv[i];

    // Autogenerate output file name
    if (*out_fname == NULL) {
        *out_fname = make_output_fname(*in_fname, cnv_dat);
    }

    if (strcmp(*in_fname, *out_fname) == 0) {
        fprintf(stderr, "ERR: output file %s is same as input file\n", *out_fname);
        return -1;
    }

    return 0;
}



//---------------------------------------------------------------------------
// Function to load file name
//
// @param fname Input file name
// @retval Pointer to compression information structure
// @return 0 File read succeeded
//        -1 File read error
//---------------------------------------------------------------------------

static int load_file(const char *fname, t_conv_data *cnv_dat) {
    FILE *in_file;

    // Open file
    if ((in_file = fopen(fname, "rb")) == NULL) {
        fprintf(stderr, "ERR: could not open file: %s\n", fname);
        return -1;
    }

    // Get file size
    fseek(in_file, 0, SEEK_END);
    if ((cnv_dat->src_size = ftell(in_file)) < 0) {
        fprintf(stderr, "ERR: file error\n");
        return -1;
    }

    // Load input file to buffer
    if ((cnv_dat->src_buf = (uchar *) malloc(cnv_dat->src_size)) == NULL) {
        fprintf(stderr, "ERR: memory exhausted\n");
        return -1;
    }

    fseek(in_file, 0, SEEK_SET);
    if (fread(cnv_dat->src_buf, 1, cnv_dat->src_size, in_file) != cnv_dat->src_size) {
        fprintf(stderr, "ERR: read error\n");
        return -1;
    }

    fclose(in_file);

    return 0;
}


//---------------------------------------------------------------------------
// Function to write to output file
//
// @param fname Output file name
// @retval Pointer to the compression information structure
// @return 0 File write succeeded
//        -1 File write error
//---------------------------------------------------------------------------

static int save_file(const char *fname, const t_conv_data *cnv_dat) {
    FILE *out_file;

    // Check write data
    if (cnv_dat->dest_buf == NULL || cnv_dat->dest_size < 0) {
        fprintf(stderr, "ERR: convert error\n");
        return -1;
    }

    // Open file
    if ((out_file = fopen(fname, "wb")) == NULL) {
        fprintf(stderr, "ERR: could not open file: %s\n", fname);
        return -1;
    }

    // Write the buffer to the output file.
    if (cnv_dat->textout_flg) {
        // Text output
        if (fwrite_text(cnv_dat->dest_buf, cnv_dat->dest_size, out_file, fname)
             != cnv_dat->dest_size)
        {
            fprintf(stderr, "ERR: write error\n");
            return -1;
        }
    } else {
        // Binary output
        if (fwrite(cnv_dat->dest_buf, 1, cnv_dat->dest_size, out_file)
            != cnv_dat->dest_size)
        {
            fprintf(stderr, "ERR: write error\n");
            return -1;
        }
    }

    fclose(out_file);

    return 0;
}

//---------------------------------------------------------------------------
// C language format text output
// @param buf Data buffer
// @param size Data size
// @param fp Output file pointer
// @param name Constant label name
// @return 0 File write succeeded
//        -1 File write error
//---------------------------------------------------------------------------

static int fwrite_text(const uchar *buf, ulong size, FILE *fp, const char *name) {
    char *const_name;
    ulong i;

    if (fp == NULL) {
        return -1;
    }
    if (buf == NULL) {
        return -1;
    }
    if (name == NULL) {
        return -1;
    }

    // Decide constant name
    if (( const_name = (char *) malloc( strlen(name) ) ) == NULL) {
        fprintf(stderr, "internal err: malloc fail!\n");
        return -1;
    }
    
    strcpy(const_name, name);
    const_name = cut_fname(const_name);

    // Write data
    if (fprintf(fp, "#include <nitro.h>\n\n") < 0) {
        return -1;
    }
    if (fprintf(fp, "const u8 %s[0x%lX] = {", const_name, size) < 0) {
        return -1;
    }

    for (i = 0; i < size; i++) {
        if (i % 0x10 == 0) {
            if (fprintf(fp, "\n") < 0) {
                return -1;
            }
        }
        if (fprintf(fp, "0x%02X,", *buf) < 0) {
            return -1;
        }
        buf++;
    }

    if (fprintf(fp, "\n};\n") < 0) {
        return -1;
    }

    free(const_name);
    
    return i;
}


//---------------------------------------------------------------------------
// Data conversion
// @retval cnv_dat Conversion information data
// @return 0 Conversion succeeded
//        -1 Conversion failed
//---------------------------------------------------------------------------

static int convert_data(t_conv_data *cnv_dat) {
    char cmd_str[16];
    t_conv_data *dat;

    dat = cnv_dat; // For shortening the variable name

    // Generate the command string
    switch (cnv_dat->type) {
    case CONV_TYPE_DIFF:
    // Difference filter
        if (cnv_dat->opt != 8 && cnv_dat->opt != 16) {
            fprintf(stderr, "ERR: invalid DIFF parameter %d\n", cnv_dat->opt);
            return -1;
        };
        sprintf(cmd_str, "d%d", cnv_dat->opt);
        break;

    case CONV_TYPE_RUNLENGTH:
    // Run length encoding
        strcpy(cmd_str, "r");
        break;

    case CONV_TYPE_LZ77:
    // LZ77 compression
        if (cnv_dat->opt < 2 || cnv_dat->opt > 255) {
            fprintf(stderr, "ERR: invalid LZ77 parameter %d\n", cnv_dat->opt);
            return -1;
        };
        sprintf(cmd_str, "l%d", cnv_dat->opt);
        break;

    case CONV_TYPE_HUFFMAN:
    // Huffman compression
        if (cnv_dat->opt != 4 && cnv_dat->opt != 8) {
            fprintf(stderr, "ERR: invalid HUFFMAN parameter %d\n", cnv_dat->opt);
            return -1;
        };
        sprintf(cmd_str, "h%d", cnv_dat->opt);
        break;

    default:
        fprintf(stderr, "ERR: invalid convert type\n");
        return -1;
    }

    // Secure buffer region for file after compression
    dat->dest_buf = nitroCompMalloc(dat->src_size);

    // Data compression
    dat->dest_size = nitroCompress(dat->src_buf, dat->src_size, dat->dest_buf,
                                   cmd_str, dat->header_flg);

#if 0
    // Check expansion of compressed data
    {
        char* decomp_buf;
        ulong decomp_size;

        decomp_buf = nitroCompMalloc(dat->src_size);
        decomp_size = nitroDecompress(dat->dest_buf, dat->dest_size, decomp_buf, -1);

        if (matchingCheck(dat->src_buf, dat->src_size, decomp_buf, decomp_size) == 0) {
            fprintf(stderr, "ERR: data convert error\n");
            return -1;
        }
    }
#endif

    return 0;
}

