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

#include <windows.h>

typedef struct {
	int width;
	int height;
} OPTION;

static int parse_args(OPTION *dst, int argc, char **argv);
static int parse_int_option(int *dst, int idx, int argc, char **argv);
static void show_usage(const char *cmd);
static void copy_basename(char *dst, const char *src);
static void gauss_filter(const char *src_path, const char *dst_path, const OPTION *opt);
static void gauss_3x3(unsigned char *dst, const unsigned char *src, int width, int height, int margin);
static void gauss_5x5(unsigned char *dst, const unsigned char *src, int width, int height, int margin);

int main(int argc, char **argv)
{
	OPTION opt;
	int i;

	i = parse_args(&opt, argc, argv);
	if ((i+1) >= argc) {
		show_usage(argv[0]);
		exit(EXIT_FAILURE);
	}
	
	for (;i+1<argc;i+=2) {
		gauss_filter(argv[i+0], argv[i+1], &opt);
	}

	return EXIT_SUCCESS;
}

static int parse_args(OPTION *dst, int argc, char **argv)
{
	int i;

	dst->width = 512;
	dst->height = 512;

	for (i=1;i<argc;i++) {

		if (argv[i][0] != '-') {
			break;
		}

		switch(argv[i][1]) {
		case 'w':
			i = parse_int_option(&(dst->width), i, argc, argv);
			break;
		case 'h':
			i = parse_int_option(&(dst->height), i, argc, argv);
			break;
		default:
			fprintf(stderr, "error - unknown option '-%c' is specified\n", argv[i][1]);
			return argc;
		}

	}

	return i;
}

static int parse_int_option(int *dst, int idx, int argc, char **argv)
{
	if (argv[idx][2]) {
		*dst = atoi(argv[idx]+2);
	} else if (idx+1 < argc) {
		idx += 1;
		*dst = atoi(argv[idx]);
	} else {
		fprintf(stderr, "error - option '-%c' requires additional parameter [integer]\n", argv[idx][1]);
		return argc;
	}

	return idx;
}

static void show_usage(const char *cmd)
{
	char name[1024];

	copy_basename(name, cmd);
	
	fprintf(stdout, "%s - add noise grayscale dat\n", name);
	fprintf(stdout, "usage: %s [options] src.dat dst.bmp [more pair..]\n", name);
	fprintf(stdout, "options:\n");
	fprintf(stdout, "  -w width  : data element width [int, default:512]\n");
	fprintf(stdout, "  -h height : data element height [int, default:512]\n");
}

static void copy_basename(char *dst, const char *src)
{
	int n;
	const char *p;
	const char *head;
	const char *tail;

	// remove directory
	p = src;
	head = src;
	tail = NULL;
	while(p[0] != 0){
		if ((p[0] == '\\') || (p[0] == '/')) {
			head = p+1;
			tail = NULL;
		} else if (p[0] == '.') {
			tail = p;
		}
		p = CharNext(p);
	}

	// copy cmd name
	if (tail == NULL) {
		tail = p;
	}

	n = tail - head;
	if(n > 1023){
		n = 1023;
	}
	
	memcpy(dst, head, n);
	dst[n] = 0;
}

unsigned char clip_u8(int val)
{
	if (val < 0) {
		return 0;
	}
	if (val > 255) {
		return 255;
	}
	return (unsigned char)val;
}

static void gauss_filter(const char *src_path, const char *dst_path, const OPTION *opt)
{
	FILE *sfd;
	FILE *dfd;
	unsigned char *src,*ptr;
	unsigned char *dst;

	int n,m,y,x;

	sfd = NULL;
	dfd = NULL;
	src = NULL;
	dst = NULL;

	sfd = fopen(src_path, "rb");
	if (sfd == NULL) {
		fprintf(stderr, "error - failed on fopen(%s) [src]\n", src_path);
		goto LAST;
	}

	n = opt->width * opt->height;
	m = (opt->width+4) * (opt->height*4);
	src = (unsigned char *)malloc(m);
	dst = (unsigned char *)malloc(n);
	if ((src == NULL) || (dst == NULL)) {
		fprintf(stderr, "error - failed on malloc(%d)\n", n);
		goto LAST;
	}

	// マージンをハードコーディング : 望ましくないことは承知の上で
	// 読み込み [左右拡張しつつ]
	ptr = src + (2 * (opt->width+4)) + 2;
	x = opt->width-1;
	for (y=0;y<opt->height;y++) {
		if (fread(ptr, 1, opt->width, sfd) != opt->width) {
			fprintf(stderr, "error - failed on fread(%d)\n", n);
		}
		ptr[ -2] = ptr[ -1] = ptr[0];
		ptr[x+2] = ptr[x+1] = ptr[x];
		ptr += (opt->width + 4);
	}
	// 上 2 ライン拡張
	x = opt->width+4;
	memcpy(src+0, src+2*x, x);
	memcpy(src+x, src+2*x, x);
	// 下 2 ライン拡張
	ptr -= 2;
	memcpy(ptr+0, ptr-x, x);
	memcpy(ptr+x, ptr-x, x);

	ptr = src + (2 * (opt->width+4)) + 2;
	// gauss_3x3(dst, ptr, opt->width, opt->height, 2);
	// フィルタが弱かったので、5x5 に広げてみる
	gauss_5x5(dst, ptr, opt->width, opt->height, 2);

	dfd = fopen(dst_path, "wb");
	if (dfd == NULL) {
		fprintf(stderr, "error - failed on fopen(%s) [dst]\n", dst_path);
		goto LAST;
	}

	if (fwrite(dst, 1, n, dfd) != n) {
		fprintf(stderr, "error - failed on fwrite(%d)\n");
		goto LAST;
	}

LAST:
	if (src != NULL) {
		free(src);
		src = NULL;
	}

	if (dst != NULL) {
		free(dst);
		dst = NULL;
	}

	if (sfd != NULL) {
		fclose(sfd);
		sfd = NULL;
	}

	if (dfd != NULL) {
		fclose(dfd);
		sfd = NULL;
	}
}

static void gauss_3x3(unsigned char *dst, const unsigned char *src, int width, int height, int margin)
{
	int x,y;
	unsigned int w;
	int src_pitch;
	const unsigned char *nxt,*pre;

	// フィルタは
	// 1 2 1
	// 2 4 2
	// 1 2 1

	src_pitch = width + (margin*2);

	pre = src - src_pitch;
	nxt = src + src_pitch;

	for (y=0;y<height;y++) {
		for (x=0;x<width;x++) {
			w  = pre[x-1]   + pre[x]*2 + pre[x+1];
			w += src[x-1]*2 + src[x]*4 + src[x+1]*2;
			w += nxt[x-1]   + nxt[x]*2 + pre[x+1];
			dst[x] = clip_u8((w+8)/16);
		}
		dst += width;
		src += src_pitch;
		pre += src_pitch;
		nxt += src_pitch;
	}
}
	
static void gauss_5x5(unsigned char *dst, const unsigned char *src, int width, int height, int margin)
{
	int x,y;
	unsigned int w;
	int src_pitch;
	const unsigned char *nxt,*pre,*nx2,*pr2;

	// フィルタは
	// 0 1 1 1 0
	// 1 2 2 2 1
	// 1 2 4 2 1
	// 1 2 2 2 1
	// 0 1 1 1 0

	src_pitch = width + (margin*2);

	pr2 = src - src_pitch*2;
	pre = src - src_pitch;
	nxt = src + src_pitch;
	nx2 = src + src_pitch*2;

	for (y=0;y<height;y++) {
		for (x=0;x<width;x++) {
			w  =            pr2[x-1]   + pr2[x]   + pr2[x+1];
			w += pre[x-2] + pre[x-1]*2 + pre[x]*2 + pre[x+1]*2 + pre[x+2];
			w += src[x-2] + src[x-1]*2 + src[x]*4 + src[x+1]*2 + src[x+2];
			w += nxt[x-2] + nxt[x-1]*2 + nxt[x]*2 + nxt[x+1]*2 + nxt[x+2];
			w +=            nx2[x-1]   + nx2[x]   + nx2[x+1];
			dst[x] = clip_u8((w+16)/32);
		}
		dst += width;
		src += src_pitch;
		pr2 += src_pitch;
		pre += src_pitch;
		nxt += src_pitch;
		nx2 += src_pitch;
	}
}
