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

#define MAX_SUBS 2048
typedef unsigned char md5sum[16];

struct sub_info {
    int x, y, w, h;
};

static SDL_Surface * black_white(SDL_Surface * t_in_black, SDL_Surface * t_in_white);
static int check_picture(SDL_Surface * surface, md5sum * md5sums, int nsubs);
static void output_frame(SDL_Surface * surface, struct sub_info * sub_infos, int cur_sub);

int main(int argc, char ** argv) {
    FILE * index;
    SDL_Surface * in_white, * in_black, * out;
    int nframes, nsubs = 0, i, j, * frames;
    char fname[128];
    md5sum md5sums[MAX_SUBS];
    struct sub_info sub_infos[MAX_SUBS];
    
    if (argc != 2) {
	fprintf(stderr, "Usage: %s <number>\n", argv[0]);
	exit(-1);
    }
    
    nframes = atoi(argv[1]);
    
    frames = (int *) malloc(sizeof(int) * nframes);
    
    if (!(index = fopen("index.lua", "w"))) {
	fprintf(stderr, "Unable to create file 'index.lua'\n");
	exit(-1);
    }
    
    for (i = 0; i < nframes; i++) {
	printf("Reading frame %i\n", i);
	sprintf(fname, "black-%04i.bmp", i);
	in_white = SDL_LoadBMP(fname);
	sprintf(fname, "white-%04i.bmp", i);
	in_black = SDL_LoadBMP(fname);
	
	out = black_white(in_black, in_white);
	
	SDL_FreeSurface(in_white);
	SDL_FreeSurface(in_black);
	
	if ((frames[i] = check_picture(out, md5sums, nsubs)) < 0) {
	    printf("New sub, number: %i\n", nsubs);
	    output_frame(out, sub_infos, nsubs);
	    frames[i] = nsubs;
	    nsubs++;
	} else {
	    printf("Same as number: %i\n", frames[i]);
	}
	
	SDL_FreeSurface(out);
    }
    
    fprintf(index, "sub_pos = {\n");
    
    for (i = 0; i < nsubs; i++) {
	struct sub_info * s;
	s = sub_infos + i;
	if (!s->w)
	    continue;
	fprintf(index, "    [%i] = { [\"x\"] = %i, [\"y\"] = %i, [\"w\"] = %i, [\"h\"] = %i },\n", i, s->x, s->y, s->w, s->h);
    }
    
    fprintf(index, "}\n\nframes = {\n");

    for (i = 0; i < nsubs; i++) {
	struct sub_info * s;
	s = sub_infos + i;
	if (!s->w)
	    continue;
	for (j = 0; j < nframes; j++) {
	    if (frames[j] == i) {
		fprintf(index, "    [%i] = %i,\n", j, i);
	    }
	}
    }
    
    fprintf(index, "}\n");
    
    fclose(index);
    
    free(frames);
    
    return 0;
}

static int check_line_h(SDL_Surface * surface, int y) {
    int x;
    uint32 * p = surface->pixels;
    
    for (x = 0; x < surface->w; x++) {
	if (p[x + y * surface->w])
	    return 1;
    }
    
    return 0;
};

static int check_line_v(SDL_Surface * surface, int x) {
    int y;
    uint32 * p = surface->pixels;
    
    for (y = 0; y < surface->h; y++) {
	if (p[x + y * surface->w])
	    return 1;
    }
    
    return 0;
}

static void output_frame(SDL_Surface * surface, struct sub_info * sub_infos, int cur_sub) {
    struct sub_info * s = sub_infos + cur_sub;
    int i, x, y;
    uint32 * p = surface->pixels;
    FILE * raw;
    char fname[128];
    
    s->x = 0;
    s->y = 0;
    s->w = surface->w;
    s->h = surface->h;
    
    i = s->h - 1;
    while(s->h) {
	if (check_line_h(surface, i))
	    break;
	(s->h)--;
	i--;
    }
    
    i = 0;
    while (s->h) {
	if (check_line_h(surface, i))
	    break;
	(s->h)--;
	(s->y)++;
	i++;
    }

    i = s->w - 1;
    while(s->w) {
	if (check_line_v(surface, i))
	    break;
	(s->w)--;
	i--;
    }
    
    i = 0;
    while (s->w) {
	if (check_line_v(surface, i))
	    break;
	(s->w)--;
	(s->x)++;
	i++;
    }
    
    if (!s->h) {
	printf("Frame is empty.\n");
	return;
    }
    
    printf("Frame info: %i-%i, %i-%i, dumping...\n", s->x, s->y, s->h, s->w);

    sprintf(fname, "sub-%i.rgba", cur_sub);
    
    if (!(raw = fopen(fname, "wb"))) {
	fprintf(stderr, "Unable to open output file '%s'\n", fname);
	exit(-1);
    }
    
    for (y = s->y; y < (s->y + s->h); y++) {
	for (x = s->x; x < (s->x + s->w); x++) {
	    fwrite(p + x + y * surface->w, 4, 1, raw);
	}
    }
    
    fclose(raw);
}

static int are_md5sums_equals(md5sum a, md5sum b) {
    int i;
    
    for (i = 0; i < 16; i++) {
	if (a[i] != b[i])
	    return 0;
    }
    return 1;
}

static void set_md5sum(md5sum d, const md5sum s) {
    int i;
    
    for (i = 0; i < 16; i++) {
	d[i] = s[i];
    }
}

static int check_picture(SDL_Surface * surface, md5sum * md5sums, int n) {
    md5_context ctx;
    md5sum cur_md5sum;
    int i;
    
    md5_starts(&ctx);
    md5_update(&ctx, (uint8 *) surface->pixels, surface->w * surface->h * 4);
    md5_finish(&ctx, cur_md5sum);
    
    for (i = 0; i < n; i++) {
	if (are_md5sums_equals(md5sums[i], cur_md5sum))
	    return i;
    }
    
    if (n == MAX_SUBS) {
	fprintf(stderr, "Too much subs.\n");
	exit(-1);
    }
    
    set_md5sum(md5sums[n], cur_md5sum);
    
    return -1;
}

static SDL_Surface * black_white(SDL_Surface * t_in_black, SDL_Surface * t_in_white) {
    int x, y, w, h;
    double A, S, Rb, Gb, Bb, Rw, Gw, Bw, Cb, Cw, DR, DG, DB, Rs, Gs, Bs, ts;
    unsigned char * white, * black, * mix;
    SDL_PixelFormat f;
    SDL_Surface * out, * in_white, * in_black;

    out = SDL_CreateRGBSurface(SDL_SWSURFACE, 1, 1, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
    
    f = *(out->format);
    SDL_FreeSurface(out);
    
    in_white = SDL_ConvertSurface(t_in_white, &f, SDL_SWSURFACE);
    in_black = SDL_ConvertSurface(t_in_black, &f, SDL_SWSURFACE);
    
    w = in_white->w;
    h = in_white->h;
    
    out = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
    
    white = in_white->pixels;
    black = in_black->pixels;
    mix = out->pixels;
    
    for (y = 0; y < h; y++) {
        for (x = 0; x < w; x++) {
            Rw = white[(x + y * w) * 4 + 0];
            Gw = white[(x + y * w) * 4 + 1];
            Bw = white[(x + y * w) * 4 + 2];

            Rb = black[(x + y * w) * 4 + 0];
            Gb = black[(x + y * w) * 4 + 1];
            Bb = black[(x + y * w) * 4 + 2];
            
            DR = Rw - Rb;
            DG = Gw - Gb;
            DB = Bw - Bb;
            
            if ((DR < 0) || (DG < 0) || (DB < 0)) {
		ts = Rw; Rw = Rb; Rb = ts;
		ts = Gw; Gw = Gb; Gb = ts;
		ts = Bw; Bw = Bb; Bb = ts;
                DR = Rw - Rb;
	        DG = Gw - Gb;
    		DB = Bw - Bb;
        	if ((DR < 0) || (DG < 0) || (DB < 0)) {
            	    fprintf(stderr, "Picture is incoherent.\n");
            	    exit(-1);
		}
            }
            
            if ((DR >= DG) && (DR >= DB)) {
                Cb = Rb;
                Cw = Rw;
            } else if ((DG >= DR) && (DG >= DB)) {
                Cb = Gb;
                Cw = Gw;
            } else {
                Cb = Bb;
                Cw = Bw;
	    }
            
            A = Cb - Cw + 255;
            S = (Cb - Cw + 255) / 255;
            
            if (A == 0) {
                Rs = Gs = Bs = 0;
            } else {
                Rs = Rb / S;
                Gs = Gb / S;
                Bs = Bb / S;
            }
            
            mix[(x + y * w) * 4 + 0] = (unsigned char) (Rs + 0.5);
            mix[(x + y * w) * 4 + 1] = (unsigned char) (Gs + 0.5);
            mix[(x + y * w) * 4 + 2] = (unsigned char) (Bs + 0.5);
            mix[(x + y * w) * 4 + 3] = (unsigned char) (A + 0.5);
        }
    }
    
    SDL_FreeSurface(in_white);
    SDL_FreeSurface(in_black);
    
    return out;
}

