/*
    xd -- yet another heXDump with Japanese support

    revision history:
    	0.0: Oct. 20, 2010 by Dai ISHIJIMA
	1.0: Aug.  3, 2018 (aka. xd2)
	1.1: Sep. 16, 2023 (aka, xd3)
*/

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

#ifdef WINNT
#include <io.h>
#include <fcntl.h>
#define NEED_FSETBIN
#endif

#ifndef HAVE_FSETBIN
#define fsetbin(fp) setmode(fileno(fp), O_BINARY)
#endif

#include "codeconv.h"
#include "ucs2jis.h"

#define YES 1
#define NO  0

#define NDUMP 16
#define BUFLEN ((NDUMP) * 2)

#define SJIS 's'
#define EUC  'e'
#define UTF8 'w'
#define UCS2 'u'
#define JIS  'j'
#define NONE 'n'
#define UNKNOWN (EOF)

#ifndef CODING
#ifdef WINNT
#define CODING SJIS
#else
#define CODING UTF8
#endif
#endif

#define EOS '\0'

#define ESC 27

#define RANKSIZ 8

#define shift --argc; ++argv
char *prog;


/* 入力バッファを EOF でクリアする */
void initbuf(int len, int *buf)
{
    int i;

    for (i = 0; i < len; i++) {
	buf[i] = EOF;
    }
}


/* バッファに文字を取り込む */
int readbuf(FILE *fp, int len, int *buf)
{
    int ch;
    int i;

    for (i = 0; i < len / 2; i++) {	/* バッファの半分を前にコピー */
	buf[i] = buf[i + len / 2];
    }
    for (; i < len; i++) {		/* 後半は文字なし */
	buf[i] = EOF;
    }
    for (i = 0; i < len; i++) {		/* 文字なしの位置を検出 */
	if (buf[i] == EOF) {
	    break;
	}
    }
    /* バッファに文字を読み込む */
    while ((i < len) && ((ch = getc(fp)) != EOF)) {
	buf[i] = ch;
	++i;
    }
    for (; i < len; i++) {		/* 読めない部分は EOF に */
	buf[i] = EOF;
    }
    return(buf[0]);			/* 先頭の文字が EOF なら終了 */
}


/* 16進数でのダンプ */
void dumphex(int len, int *buf)
{
    int i;

    for (i = 0; i < len; i++) {
	if (buf[i] == EOF) {
	    printf("   ");
	}
	else {
	    printf("%02x ", buf[i]);
	}
	if (i == (len / 2) - 1) {
	    printf(" ");
	}
    }
}


/* SJISで文字を出力 */
void sjisput(int len, int ch)
{
    int sjis;

    if (len == 1) { /* いわゆる半角 */
	if ((' ' <= lobyte(ch)) && (lobyte(ch) <= '~')) {
	    printf("%c", lobyte(ch));
	}
	else if ((0xa1 <= lobyte(ch)) && (lobyte(ch) <= 0xdf)) {
	    /* 半角カタカナ */
	    printf("%c", ch);
	}
	else {
	    printf(".");
	}
    }
    else if (len == 2) {
	sjis = jtos(ch);
	printf("%c%c", hibyte(sjis), lobyte(sjis));
    }
}


/* EUC-JPで文字を出力 */
void eucput(int len, int ch)
{
    int euc;

    if (len == 1) { /* いわゆる半角 */
	if ((' ' <= lobyte(ch)) && (lobyte(ch) <= '~')) {
	    printf("%c", lobyte(ch));
	}
	else if ((0xa1 <= lobyte(ch)) && (lobyte(ch) <= 0xdf)) {
	    /* 半角カタカナ */
	    printf("%c", 0x8e);
	    printf("%c", lobyte(ch));
	}
	else {
	    printf(".");
	}
    }
    else if (len == 2) {
	euc = jtoe(ch);
	printf("%c%c", hibyte(euc), lobyte(euc));
    }
}


/* UTF-8で文字を出力 */
void utf8put(int len, int ch)
{
    int ucs;
    char *utf8;
    
    if (len == 1) { /* いわゆる半角 */
	if ((' ' <= lobyte(ch)) && (lobyte(ch) <= '~')) {
	    printf("%c", lobyte(ch));
	}
	else if ((0xa1 <= lobyte(ch)) && (lobyte(ch) <= 0xdf)) {
	    ucs = jis2ucs(ch);
	    if (ucs > 0) {
		utf8 = ucs2utf8(ucs);
		while (*utf8 != EOS) {
		    printf("%c", *utf8);
		    ++utf8;
		}
	    }
	    else {
		printf(".");
	    }
	}
	else {
	    printf(".");
	}
    }
    else if (len == 2) {
	ucs = jis2ucs(ch);
	if (ucs > 0) {
	    utf8 = ucs2utf8(ucs);
	    while (*utf8 != EOS) {
		printf("%c", *utf8);
		++utf8;
	    }
	}
	else {
	    printf("..");
	}
    }
}


/* JIS X0201, X208の文字を変換して出力 */
void mbput(int len, int ch, int ocode)
{
    if (ocode == SJIS) {
	sjisput(len, ch);
    }
    else if (ocode == EUC) {
	eucput(len, ch);
    }
    else if (ocode == UTF8) {
	utf8put(len, ch);
    }
    else {
	if (len > 1) {
	    if ((' ' <= hibyte(ch)) && (hibyte(ch) <= '~')) {
		printf("%c", hibyte(ch));
	    }
		else {
		    printf(".");
		}
	}
	if ((' ' <= lobyte(ch)) && (lobyte(ch) <= '~')) {
	    printf("%c", lobyte(ch));
	}
	else {
	    printf(".");
	}
    }
}


/* 入力がSJISの場合の出力処理 (検証も兼ねる) */
int mbsjis(int *buf, int *code, int outcode)
{
    int mblen;		/* 出力したマルチバイト文字の長さ */
    int jischar;	/* JIS X 0201 or 0208 の文字コード */

    mblen = 0;
    if ((*code == SJIS) && (iskana(*buf))) {
	/* SJIS確定時に半角カナ */
	mbput(1, *buf, outcode);
	mblen = 1;
    }
    else if (issjis(*buf)) {
	jischar = stoj(hilo(*buf, *(buf + 1)));
	if (validjis(jischar)) {	/* 変換してJISならSJIS確定 */
	    *code = SJIS;
	    mbput(2, jischar, outcode);
	    mblen = 2;
	}
	else {
	    *code = UNKNOWN;
	    mblen = 0;
	}
    }
    return(mblen);
}


/* 入力がEUCの場合の出力処理 */
int mbeuc(int *buf, int *code, int outcode)
{
    int mblen;
    int jischar;

    mblen = 0;
    if ((*code == EUC) && (*buf == 0x8e)) {
	if (iskana(*(buf + 1))) {
	    mbput(1, *(buf + 1), outcode);
	    mblen = 2;
	}
	else {
	    *code = UNKNOWN;
	    mblen = 0;
	}
    }
    else {
	jischar = etoj(hilo(*buf, *(buf + 1)));
	if (validjis(jischar)) {
	    *code = EUC;
	    mbput(2, jischar, outcode);
	    mblen = 2;
	}
	else {
	    *code = UNKNOWN;
	    mblen = 0;
	}
    }
    return(mblen);
}


/* 入力がUTF-8の場合の出力処理 */
int mbutf8(int *buf, int *code, int outcode)
{
    int mblen;
    int jischar;

    mblen = 0;
    if (*buf >= 0xc0) {
	jischar = utoj(buf, &mblen);
	if ((*code == UTF8) && (iskana(jischar))) {
	    mbput(1, jischar, outcode);
	}
	else {
	    if (validjis(jischar)) {
		*code = UTF8;
		mbput(2, jischar, outcode);
		if (mblen > 2) {
		    printf(" ");
		}
	    }
	    else {
		mblen = 0;
		*code = UNKNOWN;
	    }
	}
    }
    else {
	mblen = 0;
	*code = UNKNOWN;
    }
    return(mblen);
}


/* マルチバイト文字の出力 */
int mbdump(int *buf, char *coderank, int *code, int outcode)
{
    int mblen;
    
    mblen = 0;
    /* 入力文字コードが確定していたら、それに従って出力 */
    if (*code == SJIS) {
	mblen = mbsjis(buf, code, outcode);
    }
    else if (*code == EUC) {
	mblen = mbeuc(buf, code, outcode);
    }
    else if (*code == UTF8) {
	mblen = mbutf8(buf, code, outcode);
    }
    if ((*code == UNKNOWN) && (mblen == 0)) {
	/* 文字コードがわからないときは、序列に従って検証 */
	while (*coderank != EOS) {
	    if (*coderank == NONE) {
		mblen = 0;
		break;
	    }
	    else if (*coderank == SJIS) {
		mblen = mbsjis(buf, code, outcode);
	    }
	    else if (*coderank == EUC) {
		mblen = mbeuc(buf, code, outcode);
	    }
	    else if (*coderank == UTF8) {
		mblen = mbutf8(buf, code, outcode);
	    }
	    if (mblen > 0) {
		break;
	    }
	    ++coderank;
	}
    }
    return(mblen);
}


/* 2バイトJISコードの文字を出力 */
void jisput(int hi, int lo, int outcode)
{
    int jis;

    jis = (hi << 8) | lo;
    if (validjis(jis)) {
	mbput(2, jis, outcode);
    }
    else {
	if ((' ' <= hi) && (hi <= '~')) {
	    printf("%c", hi);
	}
	else {
	    printf(".");
	}
	if ((' ' <= lo) && (lo <= '~')) {
	    printf("%c", lo);
	}
    }
}


/* ISO-2022-JP のエスケープシーケンス処理 */
int escape(int *buf, int *i, int *ofs)
{
    int code;

    if (buf[*i + 1] == '$') {
	code = JIS;
	++*i;
	printf("%c", buf[*i]);
	++*i;
	printf("%c", buf[*i]);
	if (NDUMP <= *i) {
	    *ofs = *i - NDUMP;
	}
    }
    else if (buf[*i + 1] == '(') {
	code = UNKNOWN;
	++*i;
	printf("%c", buf[*i]);
	++*i;
	printf("%c", buf[*i]);
	if (NDUMP <= *i) {
	    *ofs = *i - NDUMP;
	}
    }
    return(code);
}


/* 7bitめが0の場合・表示可能文字の場合 */
int bit7off(int *buf, int i, int code, int outcode)
{
    if (code == JIS) {
	if ((' ' <= buf[i + 1]) && (buf[i + 1] <= '~')) {
	    jisput(buf[i], buf[i + 1], outcode);
	    ++i;
	}
    }
    else {
	printf("%c", buf[i]);
    }
    return(i);
}


/* 文字のダンプ処理 */
void dumpchar(int len, int *buf, char *coderank, int outcode)
{
    static int ofs = 0;
    int i;
    int mblen;
    static int code = UNKNOWN;

    for (i = 0; i < ofs; i++) {
	printf(" ");
    }
    for (; i < len; i++) {
	if (buf[i] == EOF) {
	    printf(" ");
	}
	else if ((buf[i] == ESC) && (*coderank != NONE)) {
	    printf(".");
	    code = escape(buf, &i, &ofs);
	}
	else if (buf[i] < ' ') {
	    printf(".");
	    code = UNKNOWN;
	}
	else if ((' ' <= buf[i]) && (buf[i] <= '~')) {
	    i = bit7off(buf, i, code, outcode);
	}
	else if (buf[i] == 0x7f) {
	    printf(".");
	}
	else if (code != NONE) {
	    mblen = mbdump(&buf[i], coderank, &code, outcode);
	    if (mblen > 1) {
		i += (mblen - 1);
	    }
	    if (mblen == 0) {
		printf(".");
	    }
	}
	else {
	    printf(".");
	}
    }
    ofs = i - len;
    fflush(stdout);
}


void dump(FILE *fp, char *coderank, int outcode)
{
    int buf[BUFLEN];
    int ofs = 0;

#ifdef NEED_FSETBIN
    fsetbin(fp);
#endif
    initbuf(BUFLEN, buf);
    while (readbuf(fp, BUFLEN, buf) != EOF) {
	printf("%04x ", ofs);
	dumphex(NDUMP, buf);
	printf("  ");
	dumpchar(NDUMP, buf, coderank, outcode);
	printf("\n");
	ofs +=  NDUMP;
    }
}


void usecode(char *coderank, int code)
{
    while (*coderank != EOS) {
	if (*coderank == code) {
	    break;
	}
	++coderank;
    }
    if (*coderank == EOS) {
	*coderank = code;
	++coderank;
	*coderank = EOS;
    }
}


void initrank(char *coderank)
{
    int i;

    for (i = 0; i < RANKSIZ; i++) {
	coderank[i] = EOS;
    }
}


void usage()
{
    fprintf(stderr, "Usage: %s [-[seun]]\n", prog);
}


int main(int argc, char *argv[])
{
    char coderank[RANKSIZ];
    char outcode;
    FILE *fp;

    prog = *argv;
    shift;
    initrank(coderank);
    outcode = CODING;
    ucs2init();
    init2ucs();
    while ((argc > 0) && (argv[0][0] == '-') && (argv[0][1] != EOS)) {
	if (argv[0][1] == 's') {
	    usecode(coderank, SJIS);
	}
	else if (argv[0][1] == 'e') {
	    usecode(coderank, EUC);
	}
	else if (argv[0][1] == 'w') {
	    usecode(coderank, UTF8);
	}
	else if (argv[0][1] == 'u') {
	    usecode(coderank, UTF8);
	}
	else if (argv[0][1] == 'n') {
	    usecode(coderank, NONE);
	}
	else if (argv[0][1] == 'j') {
	    usecode(coderank, JIS);
	}
	else if (argv[0][1] == 'S') {
	    outcode = SJIS;
	}
	else if (argv[0][1] == 'E') {
	    outcode = EUC;
	}
	else if (argv[0][1] == 'U') {
	    outcode = UTF8;
	}
	else if (argv[0][1] == 'W') {
	    outcode = UTF8;
	}
	else {
	    usage();
	    exit(0);
	}
	shift;
    }
    if (coderank[0] == EOS) {
	usecode(coderank, CODING);
	usecode(coderank, SJIS);
	usecode(coderank, EUC);
	usecode(coderank, UTF8);
    }
    if (argc > 0) {
	while (argc > 0) {
	    if (argv[0][0] == '-') {
		dump(stdin, coderank, outcode);
	    }
	    else {
		if ((fp = fopen(*argv, "r")) == NULL) {
		    fprintf(stderr, "%s: can't open %s\n", prog, *argv);
		    exit(1);
		}
		dump(fp, coderank, outcode);
		fclose(fp);
	    }
	    shift;
	}
    }
    else {
	dump(stdin, coderank, outcode);
    }
    exit(0);
}

/* Local Variables: */
/* compile-command:"cc -DCODING=EUC -Wall -o xd xd.c codeconv.c ucs2jis.c" */
/* End: */
