/******************************************************************************/
/*! @file inkf
    @brief yet another nkf with international language support
    @author Masashi Astro Tachibana
    @date 2002-2018
 ******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "apolloron.h"

using namespace apolloron;


const char *INKF_PROGNAME = "inkf";
const char *INKF_BRIEF    = "Yet Another Network Kanji Filter";
const char *INKF_VERSION  = "1.2.1";
const char *INKF_COPY     = "Copyright (C) 2002-2018 Masashi Astro Tachibana";
const char *INKF_DEF_OUT  = "inkf.out";

typedef enum {
    MIME_NONE,
    MIME_BASE64,
    MIME_QUOTED_PRINTABLE,
    MIME_BASE64_FULL,
    MIME_QUOTED_PRINTABLE_FULL,
    MIME_AUTODETECT
} TMIME;

typedef enum {
    LINE_MODE_NOCONVERSION = 0,
    LINE_MODE_LF = 1,
    LINE_MODE_CRLF = 2,
    LINE_MODE_CR = 3
} TLineMode;

const char *RETURN_STR[] = {
    "\n",   // default retuen code
    "\n",   // LF
    "\r\n", // CRLF
    "\r"    // CR
};

typedef struct {
    bool flag_show_help;
    bool flag_show_version;
    bool flag_guess;
    bool flag_overwrite;
    bool flag_html_to_plain;
    bool flag_hankaku_ascii;
    bool flag_zenkaku_ascii;
    bool flag_hankaku_katakana;
    bool flag_zenkaku_katakana;
    bool flag_hiragana;
    bool flag_katakana;
    bool flag_midi;
    char input_charset[32];
    char output_charset[32];
    TMIME mime_decode;
    TMIME mime_encode;
    TLineMode line_mode;
    char **input_filenames;
    const char *output_filename;
} TOption;


bool set_option(TOption &option, int argc, const char **argv);
void guess(const String &str, const char *input_charset, FILE *fpout);
void convert(String &str, const TOption &option, FILE *fpout);
void show_help();
void show_version();
void show_copy();


int main(int argc, char *argv[]) {
    TOption option;
    int retval = 0;
    FILE *fpin = stdin, *fpout = stdout;
    String tmp_str;
    char buf[4096 + 1], *p;
    int i, j, l;

    // set option
    if (!set_option(option, argc, (const char **)argv)) {
        fprintf(stderr, "Invalid parameter(s).\n");
        retval = -1;
        goto main_exit;
    }

    // show help
    if (option.flag_show_help) {
        show_help();
        show_version();
        show_copy();
        goto main_exit;
    }

    // show version
    if (option.flag_show_version) {
        show_version();
        show_copy();
        goto main_exit;
    }

    // open output stream
    if (option.output_filename != NULL && option.output_filename[0] != '\0') {
        fpout = fopen(option.output_filename, "wb");
        if (fpout == (FILE *)NULL) {
            fprintf(stderr, "Cannot open the output file '%s'.\n", option.output_filename);
        }
    }

    // conversion
    if (option.input_filenames == (char **)NULL) {

        // read from stdin
        if (option.mime_decode == MIME_NONE && option.mime_encode == MIME_NONE &&
                !option.flag_guess && !option.flag_html_to_plain && !option.flag_midi) {
            while (!feof(fpin)) {
                tmp_str = "";
                while (!feof(fpin)) {
                    l = fread(buf, 1, 4096, fpin);
                    buf[l] = '\0';
                    p = strrchr(buf, '\n');
                    if (p != NULL) {
                        j = p - buf + 1;
                        tmp_str.add(buf, j);
                        convert(tmp_str, option, fpout);
                        tmp_str = buf + j;
                    } else {
                        tmp_str.add(buf);
                    }
                }
                if (0 < tmp_str.len()) {
                    convert(tmp_str, option, fpout);
                }
            }
        } else {
            while (!feof(fpin)) {
                l = fread(buf, 1, 4096, fpin);
                if (l <= 0) {
                    continue;
                }
                buf[l] = '\0';
                tmp_str.add(buf);
            }
            if (option.flag_guess) {
                guess(tmp_str, option.input_charset, fpout);
            } else if (option.flag_midi) {
                tmp_str = tmp_str.toMIDI();
                fwrite(tmp_str.c_str(), 1, tmp_str.binaryLength(), fpout);
            } else if (0 < tmp_str.len()) {
                convert(tmp_str, option, fpout);
            }
        }
    } else {
        bool charset_autodetect_pre;
        charset_autodetect_pre = false;

        if (!strcasecmp(option.input_charset, "AUTODETECT")) {
            charset_autodetect_pre = true;
        }

        // read from file(s) or specified URL
        for (i = 0; option.input_filenames[i] != NULL; i++) {
            if (charset_autodetect_pre) {
                strcpy(option.input_charset, "AUTODETECT");
            }
#if __OPENSSL == 1
            if (!strncasecmp(option.input_filenames[i], "http://", 7) ||
                    !strncasecmp(option.input_filenames[i], "https://", 8)) {
#else
            if (!strncasecmp(option.input_filenames[i], "http://", 7)) {
#endif
                HTTPClient hc;
                tmp_str = hc.getURL(option.input_filenames[i]);
                strncpy(option.input_charset, hc.getOrigCharset().c_str(), 31);
                option.input_charset[31] = '\0';
            } else {
                tmp_str.loadFile(option.input_filenames[i]);
            }
            if ((option.output_filename == NULL || option.output_filename[0] == '\0') &&
                    option.flag_overwrite) {
                fpout = fopen(option.input_filenames[i], "wb");
                if (fpout == (FILE *)NULL) {
                    fprintf(stderr, "Cannot open the output file '%s'.\n",
                            option.input_filenames[i]);
                    continue;
                }
            }
            if (option.flag_guess) {
                guess(tmp_str, option.input_charset,  fpout);
                break;
            } else if (option.flag_midi) {
                tmp_str = tmp_str.toMIDI();
                fwrite(tmp_str.c_str(), 1, tmp_str.binaryLength(), fpout);
                if (!((option.output_filename == NULL || option.output_filename[0] == '\0') &&
                    option.flag_overwrite)) {
                    break;
                }
            } else if (0 < tmp_str.len()) {
                convert(tmp_str, option, fpout);
            }
            if ((option.output_filename == NULL || option.output_filename[0] == '\0') &&
                    option.flag_overwrite) {
                fclose(fpout);
                fpout = (FILE *)NULL;
            }
        }
    }

    // close output stream
    if (option.output_filename != NULL && option.output_filename[0] != '\0' &&
            fpout != (FILE *)NULL) {
        fclose(fpout);
    }

main_exit:
    if (option.input_filenames != (char **)NULL) {
        delete [] option.input_filenames;
    }

    tmp_str.clear();

    return retval;
}


bool set_option(TOption &option, int argc, const char **argv) {
    bool o_param_found = false;
    int input_filenames_max = 0;
    int i, j;

    // set default values
    option.flag_show_help = false;
    option.flag_show_version = false;
    option.flag_guess = false;
    option.flag_overwrite = false;
    option.flag_html_to_plain = false;
    option.flag_hankaku_ascii = false;
    option.flag_zenkaku_ascii = false;
    option.flag_hankaku_katakana = false;
    option.flag_zenkaku_katakana = false;
    option.flag_hiragana = false;
    option.flag_katakana = false;
    option.flag_midi = false;
    strcpy(option.input_charset, "AUTODETECT");
    option.output_charset[0] = '\0';
    option.mime_decode = MIME_NONE;
    option.mime_encode = MIME_NONE;
    option.line_mode = LINE_MODE_NOCONVERSION;
    option.input_filenames = (char **)NULL; // default is stdin
    option.output_filename = NULL; // default is stdout

    for (i = 1; i < argc; i++) {
        if (argv[i][0] == '-' && argv[i][1] == '-') {
            if (!strcasecmp(argv[i], "--help")) {
                option.flag_show_help = true;
                break;
            }
            if (!strcasecmp(argv[i], "--version")) {
                option.flag_show_version = true;
                break;
            }
            if (!strcasecmp(argv[i], "--guess")) {
                option.flag_guess = true;
            } else if (!strncasecmp(argv[i], "--ic=", 5)) {
                strncpy(option.input_charset, argv[i]+5, 31);
                option.input_charset[31] = '\0';
            } else if (!strncasecmp(argv[i], "--oc=", 5)) {
                strncpy(option.output_charset, argv[i]+5, 31);
                option.output_charset[31] = '\0';
            } else if (!strcasecmp(argv[i], "--overwrite")) {
                option.flag_overwrite = true;
            } else if (!strcasecmp(argv[i], "--html-to-plain")) {
                option.flag_html_to_plain = true;
            } else if (!strcasecmp(argv[i], "--hankaku-ascii")) {
                option.flag_hankaku_ascii = true;
            } else if (!strcasecmp(argv[i], "--zenkaku-ascii")) {
                option.flag_zenkaku_ascii = true;
            } else if (!strcasecmp(argv[i], "--hankaku-katakana")) {
                option.flag_hankaku_katakana = true;
            } else if (!strcasecmp(argv[i], "--zenkaku-katakana")) {
                option.flag_zenkaku_katakana = true;
            } else if (!strcasecmp(argv[i], "--hiragana")) {
                option.flag_hiragana = true;
            } else if (!strcasecmp(argv[i], "--katakana")) {
                option.flag_katakana = true;
            } else if (!strcasecmp(argv[i], "--midi")) {
                option.flag_midi = true;
            } else {
                return false; // invalid parameter
            }
        } else if (argv[i][0] == '-') {
            for (j = 1; argv[i][j] != '\0'; j++) {
                switch (argv[i][j]) {
                    case 'v':
                        option.flag_show_version = true;
                        return true;
                        break;
                    case 'V':
                        option.flag_show_help = true;
                        return true;
                        break;
                    case 'g':
                        option.flag_guess = true;
                        break;
                    case 'j':
                        strcpy(option.output_charset, "ISO-2022-JP");
                        break;
                    case 's':
                        strcpy(option.output_charset, "SHIFT_JIS");
                        break;
                    case 'e':
                        strcpy(option.output_charset, "EUC-JP");
                        break;
                    case 'w':
                        strcpy(option.output_charset, "UTF-8");
                        break;
                    case 'k':
                        strcpy(option.output_charset, "EUC-KR");
                        break;
                    case 'c':
                        strcpy(option.output_charset, "GB2312");
                        break;
                    case 't':
                        strcpy(option.output_charset, "BIG5");
                        break;
                    case 'J':
                        strcpy(option.input_charset, "ISO-2022-JP");
                        break;
                    case 'S':
                        strcpy(option.input_charset, "SHIFT_JIS");
                        break;
                    case 'E':
                        strcpy(option.input_charset, "EUC-JP");
                        break;
                    case 'W':
                        strcpy(option.input_charset, "UTF-8");
                        break;
                    case 'K':
                        strcpy(option.input_charset, "EUC-KR");
                        break;
                    case 'C':
                        strcpy(option.input_charset, "GB2312");
                        break;
                    case 'T':
                        strcpy(option.input_charset, "BIG5");
                        break;
                    case 'm':
                        option.mime_decode = MIME_AUTODETECT;
                        if (argv[i][j + 1] == 'B') {
                            option.mime_decode = MIME_BASE64;
                            j++;
                        } else if (argv[i][j + 1] == 'Q') {
                            option.mime_decode = MIME_QUOTED_PRINTABLE;
                            j++;
                        } else if (argv[i][j + 1] == 'N') {
                            option.mime_decode = MIME_AUTODETECT;
                            j++;
                        } else if (argv[i][j + 1] == '0') {
                            option.mime_decode = MIME_NONE;
                            j++;
                        }
                        break;
                    case 'M':
                        option.mime_encode = MIME_BASE64_FULL;
                        if (argv[i][j + 1] == 'B') {
                            option.mime_encode = MIME_BASE64;
                            j++;
                        } else if (argv[i][j + 1] == 'Q') {
                            option.mime_encode = MIME_QUOTED_PRINTABLE;
                            j++;
                        }
                        break;
                    case 'Z':
                        option.flag_hankaku_ascii = true;
                        break;
                    case 'O':
                        o_param_found = true;
                        break;
                    case 'L':
                        option.line_mode = LINE_MODE_NOCONVERSION;
                        if (argv[i][j + 1] == 'u') {
                            option.line_mode = LINE_MODE_LF;
                            j++;
                        } else if (argv[i][j + 1] == 'w') {
                            option.line_mode = LINE_MODE_CRLF;
                            j++;
                        } else if (argv[i][j + 1] == 'm') {
                            option.line_mode = LINE_MODE_CR;
                            j++;
                        }
                        break;
                    case 'b': // ignore bufferd option
                    case 'u': // ignore unbufferd option
                        break;
                    default:
                        return false; // invalid parameter
                        break;
                }
            }
        } else {
            j = argc - i - (o_param_found?1:0);
            if (0 < j) {
                option.input_filenames = new char * [j + 1];
            }
            for (j = i; j < argc; j++) {
                if (o_param_found && j == argc - 1) {
                    option.output_filename = argv[j];
                } else if (argv[j][0] != '\0') {
                    option.input_filenames[input_filenames_max] = (char *)argv[j];
                    option.input_filenames[input_filenames_max + 1] = NULL;
                    input_filenames_max++;
                }
            }
            i = argc;
            break;
        }
    }

    if (input_filenames_max == 0 && option.input_filenames != (char **)NULL) {
        delete [] option.input_filenames;
        option.input_filenames = (char **)NULL;
    }

    if (option.output_filename != NULL && option.output_filename[0] == '\0') {
        option.output_filename = NULL;
    }

    if (o_param_found && option.output_filename == NULL) {
        option.output_filename = INKF_DEF_OUT;
    }

    return true;
}


void guess(const String &str, const char *input_charset, FILE *fpout) {
    const char *charcode;
    const char *retcode;

    if (input_charset != NULL && !strncasecmp(input_charset, "AUTODETECT", 10)) {
        charcode = str.detectCharSet();

        // for compatibility with nkf 2.x
        if (!strcmp(charcode, "CP932")) {
            charcode = "Shift_JIS";
        } else if (!strcmp(charcode, "CP949")) {
            charcode = "EUC-KR";
        } else if (!strcmp(charcode, "CP950")) {
            charcode = "BIG5";
        } else if (!strcmp(charcode, "GB18030")) {
            charcode = "GB2312";
        }
    } else {
        charcode = input_charset;
    }

    retcode = "LF";
    if (0 <= str.search(RETURN_STR[LINE_MODE_CRLF])) {
        retcode = "CRLF";
    } else if (0 <= str.search(RETURN_STR[LINE_MODE_CR])) {
        retcode = "CR";
    }

    fprintf(fpout, "%s (%s)\n", charcode, retcode);
}


void convert(String &str, const TOption &option, FILE *fpout) {
    if (option.mime_decode == MIME_BASE64) {
        str = str.decodeBASE64();
    } else if (option.mime_decode == MIME_QUOTED_PRINTABLE) {
        str = str.decodeQuotedPrintable();
    } else if (option.mime_decode == MIME_AUTODETECT) {
        if (0 <= str.search("=?")) {
            if (!strncasecmp(option.input_charset, "AUTODETECT", 10)) {
                str = str.decodeMIME(option.input_charset, "");
            } else {
                str = str.decodeMIME(option.input_charset, option.input_charset);
            }
        } else if (str[0] != '=') {
            str = str.decodeBASE64();
        } else {
            str = str.decodeQuotedPrintable();
        }
    }
    str = str.strconv(option.input_charset, option.output_charset);
    if (option.flag_html_to_plain) {
        str = str.convertHTMLToPlain(option.output_charset, option.output_charset);
    }
    if (option.flag_hankaku_ascii) {
        str = str.changeWidth("a", option.output_charset, option.output_charset);
    } else if (option.flag_zenkaku_ascii) {
        str = str.changeWidth("A", option.output_charset, option.output_charset);
    }
    if (option.flag_hiragana) {
        str = str.changeWidth("HI", option.output_charset, option.output_charset);
    } else if (option.flag_katakana) {
        str = str.changeWidth("J", option.output_charset, option.output_charset);
    }
    if (option.flag_hankaku_katakana) {
        str = str.changeWidth("k", option.output_charset, option.output_charset);
    } else if (option.flag_zenkaku_katakana) {
        str = str.changeWidth("K", option.output_charset, option.output_charset);
    }
    if (option.line_mode != LINE_MODE_NOCONVERSION) {
        str = str.changeReturnCode(RETURN_STR[option.line_mode]);
    }
    if (option.mime_encode == MIME_BASE64) {
        str = str.encodeBASE64();
    } else if (option.mime_encode == MIME_QUOTED_PRINTABLE) {
        str = str.encodeQuotedPrintable();
    } else if (option.mime_encode == MIME_BASE64_FULL) {
        str = str.encodeMIME(option.output_charset,
                option.output_charset, 72, RETURN_STR[option.line_mode], 'B');
    } else if (option.mime_encode == MIME_QUOTED_PRINTABLE_FULL) {
        str = str.encodeMIME(option.output_charset,
                option.output_charset, 72, RETURN_STR[option.line_mode], 'Q');
    }

    fwrite(str.c_str(), 1, str.len(), fpout);
}


void show_help() {
    fprintf(stderr, 
      "Usage:  %s -[flags] [--] [in file/in URL] .. [out file for -O flag]\n"
      " j/s/e/w/k/c/t  Specify output encoding ISO-2022-JP, Shift_JIS, EUC-JP, UTF-8,\n"
      "                EUC-KR, GB2312, BIG5\n"
      " J/S/E/W/K/C/T  Specify input encoding ISO-2022-JP, Shift_JIS, EUC-JP, UTF-8,\n"
      "                EUC-KR, GB2312, BIG5\n"
      " m[BQN0]        MIME decode [B:base64,Q:quoted,N:non-strict,0:no decode]\n"
      " M[BQ]          MIME encode [B:base64 Q:quoted]\n"
      " Z              Convert JISX0208 Alphabet to ASCII\n"
      " O              Output to File (DEFAULT '%s')\n"
      " L[uwm]         Line mode u:LF w:CRLF m:CR (DEFAULT noconversion)\n"
      " --ic=<encoding>        Specify the input encoding\n"
      " --oc=<encoding>        Specify the output encoding\n"
      "  *supported encoding is as follows:\n"
      "   ISO-2022-JP\n"
      "   Shift_JIS (CP932)\n"
      "   EUC-JP\n"
      "   ISO-8859-[1,2,3,4,5,6,7,8,9,10,13,14,15,16]\n"
      "   KOI8-[R,U]\n"
      "   CP1251\n"
      "   CP1252\n"
      "   BIG5 (CP950)\n"
      "   GB2312 (GBK,GB18030)\n"
      "   EUC-KR (CP949)\n"
      "   UTF-8\n"
      "   UTF-7\n"
      "   UTF-7-IMAP\n"
      "   AUTODETECT\n"
      "   AUTODETECT_JP\n"
      " --hiragana --katakana  Hiragana/Katakana Conversion\n"
      " --zenkaku-ascii        Convert ASCII to JISX0208 Alphabet\n"
      " --hankaku-ascii        Convert JISX0208 Alphabet to ASCII\n"
      " --zenkaku-katakana     Convert Hankaku Kana to JISX0208 Katakana\n"
      " --hankaku-katakana     Convert JISX0208 Katakana to Hankaku Kana\n"
      " --html-to-plain        Convert HTML to Plain Text\n"
      " -g --guess     Guess the input code\n"
      " --midi         Create MIDI object from MML like music sequencial text\n"
      " --overwrite    Overwrite original listed files by filtered result\n"
      " -v --version   Print the version\n"
      " --help/-V      Print this help / configuration\n"
      , INKF_PROGNAME, INKF_DEF_OUT);
}


void show_version() {
    fprintf(stderr, "%s - %s Version %s\n", INKF_PROGNAME, INKF_BRIEF, INKF_VERSION);
}


void show_copy() {
    fprintf(stderr, "%s\n", INKF_COPY);
}
