/* IaMP frame/hitbox display tool
 * Developed on Linux - I don't really care about Windows support.
 *
 * written by mauve of irc.mizuumi.net #iamp
 * solid box rendering code by tropsy
 *
 * this code is unsupported.
 *
 * THIS CODE IS ALSO EXTREMELY SLOPPY
 *
 * compile with MinGW32 and libSDL
 * g++ -o framedisplayiamp framedisplayiamp.cpp -lmingw32 -lSDLmain -lSDL -lopengl32
 */

#include <SDL.h>
#include <SDL_keysym.h>
#include <GL/gl.h>
#include <GL/glext.h>

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

#include "framedisplayfont.h"

const char *character_names[11] = {
    "reimu",
    "marisa",
    "sakuya",
    "alice",
    "patchouli",
    "youmu",
    "remilia",
    "yuyuko",
    "yukari",
    "suika",
    "meiling"
};

struct node {
    int x1, y1, x2, y2;
    node *next;
};
node *start_ptr = NULL;

struct image_slot_t {
    bool valid;

    int width;
    int height;
    int pitch;
    float x_mult;
    float y_mult;

    int bpp;

    int pos;
    int length;

    unsigned char *pixels;
    GLuint tex_id;

    int tex_cycle;
};

struct rect_t {
    int x1, y1, x2, y2;
};

struct frame_t {
    struct {
	short render_frame;
	short x_offset;
	short y_offset;
	short duration;
	unsigned char identifier;
    } __attribute__ ((packed)) header;

    struct {
	short blend_mode;
	unsigned char unk2;
	unsigned char unk3;
	unsigned char unk4;
	unsigned char unk5;
	short scale;
	short unk7;
	short unk8;
	short unk9;
	short unk10;
	short unk11;
	short angle;
    } __attribute__ ((packed)) type_2;

    struct {
	short damage;
	short proration;
	short chip;
	short untech;
	short unk5;
	short unk6;
	short unk7;
	short unk8;
	short unk9;
	short unk10;
	short unk11;
	short unk12;
	short velocity_x;
	short velocity_y;
	short unk15;
	short unk16;
	short attack_type;
	int fflags;
	int aflags;

	rect_t rect;
    } __attribute__ ((packed)) data;

    int n_selfboxes;
    rect_t *selfboxes;

    int n_hitboxes;
    rect_t *hitboxes;
};

struct sequence_t {
    struct {
	int identifier;
	short unk1;
	short unk2;

	int nframes;
    } header;

    frame_t *frames;
};

struct character_t {
    int nsequences;
    sequence_t *sequences;

    unsigned long palette[256];
    unsigned long palette_alt[256];

    const char *datfilename;

    const char *name;

    int nimages;
    image_slot_t *images;
};

struct datentry_t {
    char filename[100];
    unsigned int size;
    unsigned int position;
};

struct datfile_t {
    char *filename;

    short nfiles;

    struct datentry_t *files;
};

datfile_t *dat_open(const char *filename)
{
    FILE *file = fopen(filename, "rb");
    if (!file) {
	return 0;
    }

    datfile_t *datfile = new datfile_t;
    fread(&datfile->nfiles, 2, 1, file);

    datfile->filename = strdup(filename);

    datfile->files = new datentry_t[datfile->nfiles];
    fread(datfile->files, sizeof(datentry_t), datfile->nfiles, file);

    fclose(file);

    unsigned char *data = (unsigned char *) datfile->files;
    unsigned char *end = data + (108 * datfile->nfiles);
    unsigned char xor_value = 100;
    unsigned char xor_add = 100;

    while (data < end) {
	*data++ ^= xor_value;
	xor_value += xor_add;
	xor_add += 77;
    }

    return datfile;
}

char *dat_read(datfile_t * datfile, const char *filename, int *dsize)
{
    int i;
    for (i = 0; i < datfile->nfiles; ++i) {
	if (!strcmp(datfile->files[i].filename, filename)) {
	    break;
	}
    }

    if (i == datfile->nfiles) {
	return 0;
    }

    FILE *file = fopen(datfile->filename, "rb");
    fseek(file, datfile->files[i].position, SEEK_SET);

    char *data = new char[datfile->files[i].size];
    fread(data, datfile->files[i].size, 1, file);

    fclose(file);

    *dsize = datfile->files[i].size;

    return data;
}

FILE *dat_openfile(datfile_t * datfile, const char *filename, int *dsize)
{
    int i;
    for (i = 0; i < datfile->nfiles; ++i) {
	if (!strcmp(datfile->files[i].filename, filename)) {
	    break;
	}
    }

    if (i == datfile->nfiles) {
	return 0;
    }

    FILE *file = fopen(datfile->filename, "rb");
    fseek(file, datfile->files[i].position, SEEK_SET);

    *dsize = datfile->files[i].size;

    return file;
}

void init_images(character_t * character, FILE * file, int size)
{
    unsigned char format;

    fread(&format, 1, 1, file);
    if (format != 2) {
	return;
    }

    for (int i = 0; i < 2; ++i) {
	unsigned short palette_src[256];
	unsigned short *s = palette_src;
	unsigned long *palette;

	if (i == 0) {
	    palette = character->palette;
	} else if (i == 1) {
	    palette = character->palette_alt;
	}

	fread(palette_src, 512, 1, file);
	for (int j = 0; j < 256; ++j) {
	    unsigned long v = *s++;
	    unsigned long o;

	    o = (v & 0x8000) ? 0xff000000 : 00;
	    o |= ((v & 0x7c00) << 1) >> 8;
	    o |= ((v & 0x03e0) << 6);
	    o |= ((v & 0x001f) << 3) << 16;

	    *palette++ = o;
	}
    }

    size -= 1025;

    character->nimages = 2048;
    character->images = new image_slot_t[2048];
    memset(character->images, 0, sizeof(image_slot_t) * 2048);

    int nimages = 0;
    image_slot_t *image = character->images;

    while (size > 0) {
	struct {
	    unsigned int width;
	    unsigned int height;
	    unsigned int pitch;
	    unsigned char bpp;
	    unsigned int size;
	} __attribute__ ((packed)) header;

	fread(&header, 17, 1, file);
	if (header.width > 1500 || header.height > 1500) {
	    return;
	}

	image->valid = 1;

	image->width = header.width;
	image->height = header.height;

	image->pos = ftell(file);
	image->bpp = (header.bpp & 0x30) ? 32 : 8;
	image->length = header.size;

	fseek(file, header.size, SEEK_CUR);

	size -= header.size;
	size -= 17;

	image++;
	nimages++;
    }
}

void read_pat(character_t * character, char *data)
{
    char *origdata = data;

    sequence_t *seq;

    int count1, seq_count;

    count1 = *((int *) data);
    data += 4;
    seq_count = *((int *) data);
    data += 4;

    character->nsequences = seq_count;
    character->sequences = new sequence_t[seq_count];

    memset(character->sequences, 0, seq_count * sizeof(sequence_t));

    seq = character->sequences;

    int nseq, nframe;

    nseq = 0;

    while (seq_count > 0) {
	seq_count--;
	nseq++;

	memcpy(&seq->header, data, sizeof(seq->header));
	data += sizeof(seq->header);

	int frame_count = seq->header.nframes;

	seq->frames = new frame_t[frame_count];

	memset(seq->frames, 0, sizeof(frame_t) * frame_count);

	frame_t *frame = seq->frames;

	nframe = 0;

	while (frame_count > 0) {
	    nframe++;
	    frame_count--;

	    memcpy(&frame->header, data, sizeof(frame->header));
	    data += sizeof(frame->header);

	    if (frame->header.identifier == 2) {
		memcpy(&frame->type_2, data, sizeof(frame->type_2));
		data += sizeof(frame->type_2);
	    }

	    memcpy(&frame->data, data, sizeof(frame->data));
	    data += sizeof(frame->data);

	    frame->n_selfboxes = *((int *) data);
	    data += 4;
	    if (frame->n_selfboxes > 0) {
		frame->selfboxes = new rect_t[frame->n_selfboxes];
		memcpy(frame->selfboxes, data,
		       sizeof(rect_t) * frame->n_selfboxes);
		data += sizeof(rect_t) * frame->n_selfboxes;
	    }

	    frame->n_hitboxes = *((int *) data);
	    data += 4;
	    if (frame->n_hitboxes > 0) {
		frame->hitboxes = new rect_t[frame->n_hitboxes];
		memcpy(frame->hitboxes, data,
		       sizeof(rect_t) * frame->n_hitboxes);
		data += sizeof(rect_t) * frame->n_hitboxes;
	    }


	    frame++;
	}

	seq++;
    }

    free(origdata);
}

int to_pow_2(int v)
{
    int a = 1;

    while (a < v) {
	a <<= 1;
    }

    return a;
}

static int current_tex_cycle = 1;
bool alt_colors = 0;

void flush_images(character_t * characters)
{
    for (int i = 0; i < 11; ++i) {
	character_t *ch = &characters[i];
	for (int j = 0; j < ch->nimages; ++j) {
	    image_slot_t *image = &ch->images[j];
	    if (image->tex_cycle != current_tex_cycle || !image->pixels) {
		continue;
	    }

	    if (image->bpp != 8) {
		continue;
	    }

	    glDeleteTextures(1, &image->tex_id);

	    image->pixels = 0;
	}
    }
}

void read_image(character_t * character, image_slot_t * image)
{
    FILE *file = fopen(character->datfilename, "rb");
    fseek(file, image->pos, SEEK_SET);

    int width = image->width;
    int height = image->height;
    int rwidth = to_pow_2(image->width);
    int rheight = to_pow_2(image->height);
    int count = image->width * image->height;
    int rcount = rwidth * rheight;
    int x;

    unsigned int *pixels = new unsigned int[rcount];
    memset(pixels, 0, rcount * 4);

    unsigned int *p = pixels;
    unsigned int pcount, pixel;
    image->pixels = (unsigned char *) pixels;

    if (image->bpp != 8) {
	x = 0;
	while (count > 0) {
	    fread(&pcount, 4, 1, file);
	    fread(&pixel, 4, 1, file);
	    unsigned int npixel = pixel;

	    npixel = pixel & 0xff00ff00;
	    npixel |= (pixel & 0xff0000) >> 16;
	    npixel |= (pixel & 0x0000ff) << 16;

	    while (pcount-- > 0) {
		*p++ = npixel;
		x++;
		if (x == width) {
		    p += rwidth - width;
		    x = 0;
		}
		count--;
		if (count <= 0) {
		    break;
		}
	    }
	}
    } else {
	unsigned long *pal = character->palette;
	if (alt_colors) {
	    pal = character->palette_alt;
	}
	x = 0;
	pixel = 0;
	while (count > 0) {
	    pcount = 0;
	    fread(&pcount, 1, 1, file);
	    fread(&pixel, 1, 1, file);

	    unsigned int npixel = pal[pixel];

	    while (pcount-- > 0) {
		*p++ = npixel;
		x++;
		if (x == width) {
		    p += rwidth - width;
		    x = 0;
		}
		count--;
		if (count <= 0) {
		    break;
		}
	    }
	}
    }

    fclose(file);

    GLuint tex_id;

    glGenTextures(1, &tex_id);
    image->tex_id = tex_id;

    glBindTexture(GL_TEXTURE_2D, tex_id);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, rwidth, rheight, 0, GL_RGBA,
		 GL_UNSIGNED_BYTE, pixels);

    image->x_mult = (float) width / (float) rwidth;
    image->y_mult = (float) height / (float) rheight;

    image->tex_cycle = current_tex_cycle;

    delete[]pixels;
}

node *create_box(frame_t * frame, int type)
{				// type 0 = selfboxes, 1 = hitboxes
    node *first = NULL;
    node *cur = NULL;
    int error;
    if (type == 0) {
	for (int i = 0; i < frame->n_selfboxes; ++i) {
	    node *tmp = new node;
	    tmp->next = NULL;
	    if (cur != NULL) {
		cur->next = tmp;
	    }
	    if (first == NULL) {
		first = tmp;
	    }
	    cur = tmp;
	    rect_t *rect = &frame->selfboxes[i];;
	    cur->x1 = rect->x1;
	    cur->x2 = rect->x2;
	    cur->y1 = rect->y1;
	    cur->y2 = rect->y2;
	}
    } else {
	for (int i = 0; i < frame->n_hitboxes; ++i) {
	    node *tmp = new node;
	    tmp->next = NULL;
	    if (cur != NULL) {
		cur->next = tmp;
	    }
	    if (first == NULL) {
		first = tmp;
	    }
	    cur = tmp;
	    rect_t *rect = &frame->hitboxes[i];
	    cur->x1 = rect->x1;
	    cur->x2 = rect->x2;
	    cur->y1 = rect->y1;
	    cur->y2 = rect->y2;
	}
    }
    do {
	error = 0;
	cur = first;
	while (cur->next != NULL) {
	    node *tmp = NULL;
	    do {
		if (tmp == NULL) {
		    tmp = cur->next;
		} else {
		    tmp = tmp->next;
		}
		if ((cur->x1 < tmp->x1) && (cur->x2 > tmp->x1)) {
		    node *new1 = new node;
		    node *new2 = new node;
		    new1->x1 = cur->x1;
		    new1->x2 = tmp->x1;
		    new2->x1 = tmp->x1;
		    new2->x2 = cur->x2;
		    new1->y1 = cur->y1;
		    new1->y2 = cur->y2;
		    new2->y1 = cur->y1;
		    new2->y2 = cur->y2;
		    new1->next = new2;
		    new2->next = cur->next;
		    node *tmp2 = first;
		    if (cur == first) {
			first = new1;
			delete tmp2;
		    } else {
			while (tmp2->next != cur) {
			    tmp2 = tmp2->next;
			}
			tmp2->next = new1;
			delete cur;
			cur = first;
		    }
		    error = 1;
		    break;
		}
		if ((cur->x1 < tmp->x2) && (cur->x2 > tmp->x2)) {
		    node *new1 = new node;
		    node *new2 = new node;
		    new1->x1 = cur->x1;
		    new1->x2 = tmp->x2;
		    new2->x1 = tmp->x2;
		    new2->x2 = cur->x2;
		    new1->y1 = cur->y1;
		    new1->y2 = cur->y2;
		    new2->y1 = cur->y1;
		    new2->y2 = cur->y2;
		    new1->next = new2;
		    new2->next = cur->next;
		    node *tmp2 = first;
		    if (cur == first) {
			first = new1;
			delete tmp2;
		    } else {
			while (tmp2->next != cur) {
			    tmp2 = tmp2->next;
			}
			tmp2->next = new1;
			delete cur;
			cur = first;
		    }
		    error = 1;
		    break;
		}
		if ((cur->y1 < tmp->y1) && (cur->y2 > tmp->y1)) {
		    node *new1 = new node;
		    node *new2 = new node;
		    new1->y1 = cur->y1;
		    new1->y2 = tmp->y1;
		    new2->y1 = tmp->y1;
		    new2->y2 = cur->y2;
		    new1->x1 = cur->x1;
		    new1->x2 = cur->x2;
		    new2->x1 = cur->x1;
		    new2->x2 = cur->x2;
		    new1->next = new2;
		    new2->next = cur->next;
		    node *tmp2 = first;
		    if (cur == first) {
			first = new1;
			delete tmp2;
		    } else {
			while (tmp2->next != cur) {
			    tmp2 = tmp2->next;
			}
			tmp2->next = new1;
			delete cur;
			cur = first;
		    }
		    error = 1;
		    break;
		}
		if ((cur->y1 < tmp->y2) && (cur->y2 > tmp->y2)) {
		    node *new1 = new node;
		    node *new2 = new node;
		    new1->y1 = cur->y1;
		    new1->y2 = tmp->y2;
		    new2->y1 = tmp->y2;
		    new2->y2 = cur->y2;
		    new1->x1 = cur->x1;
		    new1->x2 = cur->x2;
		    new2->x1 = cur->x1;
		    new2->x2 = cur->x2;
		    new1->next = new2;
		    new2->next = cur->next;
		    node *tmp2 = first;
		    if (cur == first) {
			first = new1;
			delete tmp2;
		    } else {
			while (tmp2->next != cur) {
			    tmp2 = tmp2->next;
			}
			tmp2->next = new1;
			delete cur;
			cur = first;
		    }
		    error = 1;
		    break;
		}
		if ((cur->x1 >= tmp->x1) && (cur->x2 <= tmp->x2)
		    && (cur->y1 >= tmp->y1) && (cur->y2 <= tmp->y2)) {
		    node *tmp2 = first;
		    if (cur == first) {
			first = first->next;
			delete tmp2;
		    } else {
			while (tmp2->next != cur) {
			    tmp2 = tmp2->next;
			}
			tmp2->next = cur->next;
			tmp2 = cur;
			cur = first;
			delete tmp2;
		    }
		    error = 1;
		    break;
		}
	    }
	    while (tmp->next != NULL);
	    if (error == 1) {
		break;
	    }
	    cur = cur->next;
	}
    }
    while (error != 0);
    return first;
}

int draw_gradient = 1;
int draw_properties = 1;
int draw_sprite = 1;
int draw_collisionbox = 1;
int draw_hitbox = 1;
int draw_damagebox = 1;
int draw_additive = 1;
int pretty_display = 1;
int draw_boxes = 1;
int realframes = 0;
int draw_axises = 1;

int position_x = 180;
int position_y = 400;

int window_width = 640;
int window_height = 480;
float window_scale = 1.0;

static GLuint font_tex_id = 0;

void init_font()
{
    unsigned char data[256 * 64];
    for (int i = 0; i < 256 * 64; ++i) {
	data[i] = 255 - header_data_cmap[header_data[i]][0];
    }

    glGenTextures(1, &font_tex_id);

    glBindTexture(GL_TEXTURE_2D, font_tex_id);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA8, 256, 64, 0, GL_ALPHA,
		 GL_UNSIGNED_BYTE, data);
}

void draw_font(int dx, int dy, const char *string)
{
    int x = dx;
    int y = dy;

    const float c_w = 10.0 / 256.0;
    const float c_h = 15.0 / 64.0;

    glBindTexture(GL_TEXTURE_2D, font_tex_id);
    glEnable(GL_TEXTURE_2D);
    glBegin(GL_QUADS);

    while (*string) {
	char ch = *string++;
	int c;

	if (ch >= 'A' && ch <= 'Z') {
	    c = ch - 'A';
	} else if (ch >= 'a' && ch <= 'z') {
	    c = 26 + ch - 'a';
	} else if (ch >= '0' && ch <= '9') {
	    c = 52 + ch - '0';
	} else if (ch == '-') {
	    c = 52 + 10;
	} else if (ch == '\n') {
	    x = dx;
	    y += 13;
	    continue;
	} else if (ch == ' ') {
	    x += 9;
	    continue;
	} else {
	    continue;
	}

	float cx = (float) (c % 25) * 10.0 / 256.0;
	float cy = (float) (c / 25) * 15.0 / 64.0;

	// i dunno
	if (c == 50 || c == 51) {
	    cy -= 1.0 / 64.0;
	}

	glTexCoord2f(cx, cy);
	glVertex2f(x, y);
	glTexCoord2f(cx + c_w, cy);
	glVertex2f(x + 10, y);
	glTexCoord2f(cx + c_w, cy + c_h);
	glVertex2f(x + 10, y + 15);
	glTexCoord2f(cx, cy + c_h);
	glVertex2f(x, y + 15);

	x += 9;
    }

    glEnd();

    glDisable(GL_TEXTURE_2D);
}

void display_bg()
{
    glBegin(GL_QUADS);
    glColor4f(0.0, 0.0, 0.1, 1.0);
    glVertex2f(0.0, 0.0);
    glVertex2f(window_width, 0.0);
    if (draw_gradient) {
	glColor4f(0.0, 0.0, 0.3, 1.0);
    }
    glVertex2f(window_width, window_height);
    glVertex2f(0.0, window_height);
    glEnd();
}

PFNGLBLENDEQUATIONEXTPROC pglBlendEquation = 0;

void display_sprite(character_t * character, frame_t * frame)
{
    if (frame->header.render_frame < 0
	|| frame->header.render_frame >= character->nimages) {
	return;
    }
    image_slot_t *image = &character->images[frame->header.render_frame];

    if (!image->pixels || image->tex_cycle != current_tex_cycle) {
	read_image(character, image);
    }

    if (image->pixels) {
	int x1, y1, x2, y2;

	glPushMatrix();

	if (frame->header.identifier == 2) {
	    if (frame->type_2.blend_mode == 1 && draw_additive) {
		if (pglBlendEquation) {
		    pglBlendEquation(GL_FUNC_ADD);
		}
		glBlendFunc(GL_SRC_ALPHA, GL_ONE);
	    } else if (frame->type_2.blend_mode == 2 && draw_additive) {
		if (pglBlendEquation) {
		    pglBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
		}
		glBlendFunc(GL_SRC_ALPHA, GL_ONE);
	    }

	    float scale = ((float) frame->type_2.scale) / 100.0;

	    x1 = -(int) ((float) frame->header.x_offset * scale);
	    y1 = -(int) ((float) frame->header.y_offset * scale);
	    x2 = x1 + (int) ((float) image->width * scale);
	    y2 = y1 + (int) ((float) image->height * scale);

	    glRotatef(frame->type_2.angle, 0.0, 0.0, 1.0);
	} else {
	    x1 = -(frame->header.x_offset);
	    y1 = -(frame->header.y_offset);
	    x2 = x1 + (image->width * 2);
	    y2 = y1 + (image->height * 2);
	}

	glBindTexture(GL_TEXTURE_2D, image->tex_id);
	glEnable(GL_TEXTURE_2D);

	glColor4f(1.0, 1.0, 1.0, 1.0);
	glBegin(GL_QUADS);
	glTexCoord2f(0.0, 0.0);
	glVertex2f(x1, y1);
	glTexCoord2f(image->x_mult, 0.0);
	glVertex2f(x2, y1);
	glTexCoord2f(image->x_mult, image->y_mult);
	glVertex2f(x2, y2);
	glTexCoord2f(0.0, image->y_mult);
	glVertex2f(x1, y2);
	glEnd();

	glDisable(GL_TEXTURE_2D);

	if (pglBlendEquation) {
	    pglBlendEquation(GL_FUNC_ADD);
	}
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	glPopMatrix();
    }
}

void display_load(character_t * characters, int n)
{
    glFinish();

    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    display_bg();

    glColor4f(0.0, 0.0, 0.0, 1.0);
    draw_font((window_width / 2) - (7 * 7) + 1, 21, "Loading");
    glColor4f(1.0, 1.0, 0.6, 1.0);
    draw_font((window_width / 2) - (7 * 7), 20, "Loading");

    if (n >= 0) {
	character_t *ch = &characters[n];
	if (ch->nsequences > 0) {
	    sequence_t *seq = &ch->sequences[0];
	    if (seq->header.nframes > 0) {
		frame_t *frame = &seq->frames[0];

		glPushMatrix();
		glTranslatef((window_width / 2), (window_height - 50),
			     0.0);
		glScalef(window_scale, window_scale, 1.0);

		display_sprite(ch, frame);

		glPopMatrix();
	    }
	}
    }


    int y = 60;
    for (int i = 0; i < n; ++i) {
	const char *name = character_names[i];

	glColor4f(0.0, 0.0, 0.0, 1.0);
	draw_font((window_width / 2) - (10 * 7) + 1, y + 1, name);
	glColor4f(1.0, 1.0, 0.6, 1.0);
	draw_font((window_width / 2) - (10 * 7), y, name);

	y += 15;
    }

    SDL_GL_SwapBuffers();
}

void display_scene(character_t * character, frame_t * frame,
		   int ch, int seq, int nframe, int fcount)
{
    glFinish();

    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    display_bg();
    if (draw_axises) {
	glColor4f(0.0, 0.8, 0.8, 1.0);
	glBegin(GL_LINES);
	glVertex2f(0.0, position_y);
	glVertex2f(window_width, position_y);
	glVertex2f(position_x, 0.0);
	glVertex2f(position_x, window_height);
	glEnd();
    }

    if (frame) {
	int off_x = position_x;
	int off_y = position_y;
	glPushMatrix();
	glTranslatef(off_x, off_y, 0.0);
	glScalef(window_scale, window_scale, 1.0);

	if (draw_sprite) {
	    display_sprite(character, frame);
	    /*
	       && character->images[frame->header.render_frame].valid) {
	       image_slot_t *image =
	       &character->images[frame->header.render_frame];

	       if (!image->pixels || image->tex_cycle != current_tex_cycle) {
	       read_image(character, image);
	       }

	       if (image->pixels) {
	       int x1, y1, x2, y2;

	       glPushMatrix();

	       if (frame->header.identifier == 2) {
	       if (frame->type_2.blend_mode == 1 && draw_additive) {
	       glBlendFunc(GL_SRC_ALPHA, GL_ONE);
	       }

	       float scale = ((float) frame->type_2.scale) / 100.0;

	       x1 = -(int) ((float) frame->header.x_offset * scale);
	       y1 = -(int) ((float) frame->header.y_offset * scale);
	       x2 = x1 + (int) ((float) image->width * scale);
	       y2 = y1 + (int) ((float) image->height * scale);

	       glRotatef(frame->type_2.angle, 0.0, 0.0, 1.0);
	       } else {
	       x1 = -(frame->header.x_offset);
	       y1 = -(frame->header.y_offset);
	       x2 = x1 + (image->width * 2);
	       y2 = y1 + (image->height * 2);
	       }

	       glBindTexture(GL_TEXTURE_2D, image->tex_id);
	       glEnable(GL_TEXTURE_2D);

	       glColor4f(1.0, 1.0, 1.0, 1.0);
	       glBegin(GL_QUADS);
	       glTexCoord2f(0.0, 0.0);
	       glVertex2f(x1, y1);
	       glTexCoord2f(image->x_mult, 0.0);
	       glVertex2f(x2, y1);
	       glTexCoord2f(image->x_mult, image->y_mult);
	       glVertex2f(x2, y2);
	       glTexCoord2f(0.0, image->y_mult);
	       glVertex2f(x1, y2);
	       glEnd();

	       glDisable(GL_TEXTURE_2D);

	       glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	       glPopMatrix();
	       }
	     */
	}
	if (pretty_display) {
	    if (draw_collisionbox) {
		glColor4f(0.8, 0.8, 0.8, 0.25);
		glBegin(GL_QUADS);
		glVertex2f(frame->data.rect.x1, frame->data.rect.y1);
		glVertex2f(frame->data.rect.x2, frame->data.rect.y1);
		glVertex2f(frame->data.rect.x2, frame->data.rect.y2);
		glVertex2f(frame->data.rect.x1, frame->data.rect.y2);
		glEnd();
	    }
	    if (draw_hitbox && frame->n_selfboxes > 0) {
		node *tmp = NULL;
		glColor4f(0.3, 1.0, 0.3, 0.3);
		do {
		    if (tmp == NULL) {
			tmp = create_box(frame, 0);
		    } else {
			node *tmp2 = tmp;
			tmp = tmp->next;
			delete tmp2;
		    }
		    glBegin(GL_QUADS);
		    glVertex2f(tmp->x1, tmp->y1);
		    glVertex2f(tmp->x2, tmp->y1);
		    glVertex2f(tmp->x2, tmp->y2);
		    glVertex2f(tmp->x1, tmp->y2);
		    glEnd();
		}
		while (tmp->next != NULL);
		delete tmp;
	    }
	    if (draw_damagebox && frame->n_hitboxes > 0) {
		node *tmp = NULL;
		glColor4f(1.0, 0.3, 0.3, 0.3);
		do {
		    if (tmp == NULL) {
			tmp = create_box(frame, 1);
		    } else {
			node *tmp2 = tmp;
			tmp = tmp->next;
			delete tmp2;
		    }
		    glBegin(GL_QUADS);
		    glVertex2f(tmp->x1, tmp->y1);
		    glVertex2f(tmp->x2, tmp->y1);
		    glVertex2f(tmp->x2, tmp->y2);
		    glVertex2f(tmp->x1, tmp->y2);
		    glEnd();
		}
		while (tmp->next != NULL);
	    }
	}
	if (draw_boxes) {
	    if (draw_collisionbox) {
		glColor4f(0.9, 0.9, 0.9, 1.0);
		glBegin(GL_LINE_LOOP);
		glVertex2f(frame->data.rect.x1, frame->data.rect.y1);
		glVertex2f(frame->data.rect.x2, frame->data.rect.y1);
		glVertex2f(frame->data.rect.x2, frame->data.rect.y2);
		glVertex2f(frame->data.rect.x1, frame->data.rect.y2);
		glEnd();
	    }
	    if (draw_hitbox && frame->n_selfboxes > 0) {
		glColor4f(0.3, 1.0, 0.3, 1.0);
		for (int i = 0; i < frame->n_selfboxes; ++i) {
		    rect_t *rect = &frame->selfboxes[i];
		    glBegin(GL_LINE_LOOP);
		    glVertex2f(rect->x1, rect->y1);
		    glVertex2f(rect->x2, rect->y1);
		    glVertex2f(rect->x2, rect->y2);
		    glVertex2f(rect->x1, rect->y2);
		    glEnd();
		}
	    }
	    if (draw_damagebox && frame->n_hitboxes > 0) {
		glColor4f(1.0, 0.3, 0.3, 1.0);
		for (int i = 0; i < frame->n_hitboxes; ++i) {
		    rect_t *rect = &frame->hitboxes[i];
		    glBegin(GL_LINE_LOOP);
		    glVertex2f(rect->x1, rect->y1);
		    glVertex2f(rect->x2, rect->y1);
		    glVertex2f(rect->x2, rect->y2);
		    glVertex2f(rect->x1, rect->y2);
		    glEnd();
		}
	    }
	}

	glPopMatrix();

	if (draw_properties == 1) {
	    char strbuf[1024];

	    sprintf(strbuf,
		    "duration %d\n\n fflags %8.8x\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n\n\n",
		    frame->header.duration, frame->data.fflags,
		    (frame->data.fflags & 1) ? "Standing" : "-",
		    (frame->data.fflags & 2) ? "Crouching" : "-",
		    (frame->data.fflags & 4) ? "Airborne" : "-",
		    (frame->data.fflags & 16) ? "Guard cancel" : "-",
		    (frame->data.fflags & 32) ? "Cancellable" : "-",
		    (frame->data.fflags & 64) ? "CH when hit" : "-",
		    (frame->data.fflags & 128) ? "Superarmor" : "-",
		    // 256, ?, 0x44488f extended superarmor, unused
		    (frame->data.fflags & 8192) ? "Melee Inv" : "-",
		    (frame->data.fflags & 16384) ? "Graze" : "-"
		    // 32768, ?, 0x444fe7 related to af 8192
		    // 65536, ?, 0x445042 related to af 16384
		    // 131072, ?, 0x445099 related to af 32768
		);

	    if (frame->n_hitboxes > 0) {
		sprintf(strbuf + strlen(strbuf),
			" aflags %8.8x\n block: %s %s %s %s\n%s\n%s\n%s\n%s\n\n",
			frame->data.aflags,
			(frame->data.aflags & 2) ? "H" : "-",
			(frame->data.aflags & 4) ? "L" : "-",
			(frame->data.aflags & 8) ? "A" : "-",
			(frame->data.aflags & 16) ? "U" : "-",
			// 32, ? 0x444863 related to ff 128/256 - ignore SA
			// only used by youmu throw
			// 64, ?
			// 128, ?
			// 256, ? 0x44499f, juggle related
			// 512, ?
			(frame->data.aflags & 1024) ? "Induces CH" : "-",
			// 8192, ? 0x444fd3 related to ff 32768
			// 16384, ? 0x44502e related to ff 65536
			// 32768, ? 0x445086 related to ff 131072
			(frame->data.
			 aflags & 262144) ? "Ungrazeable" : "-",
			(frame->data.
			 aflags & 524288) ? "Guard crush" : "-",
			// 1048576, "Always CH", unused
			(frame->data.aflags & 2097152) ? "Stagger" : "-");
		sprintf(strbuf + strlen(strbuf),
			"   Damage: %d\nSpiritDmg: %d, %d\n     Chip: %d, %d\nProration: %d\n   Untech: %d\n  Unknown: %d %d\nVelocityX: %d\nVelocityY: %d\n     Type: %d %d %d\n",
			frame->data.damage, (frame->data.damage) / 2,
			(frame->data.damage) / 4,
			frame->data.chip + (frame->data.damage / 10),
			frame->data.chip, frame->data.proration,
			frame->data.untech, frame->data.unk9,
			frame->data.unk10, frame->data.velocity_x,
			frame->data.velocity_y, frame->data.unk15,
			frame->data.unk16, frame->data.attack_type);
	    }

	    glColor4f(0.0, 0.0, 0.0, 1.0);
	    draw_font(window_width - 159, 16, strbuf);
	    glColor4f(1.0, 1.0, 1.0, 1.0);
	    draw_font(window_width - 160, 15, strbuf);
	} else if (draw_properties == 2) {
	    char strbuf[1024];
	    sprintf(strbuf,
		    " Duration: %d\n\n"
		    "   Damage: %d\nProration: %d\n"
		    "     Chip: %d\n   Untech: %d\n        5: %d\n"
		    "        6: %d\n        7: %d\n        8: %d\n"
		    "        9: %d\n       10: %d\n       11: %d\n"
		    "       12: %d\nVelocityX: %d\nVelocityY: %d\n"
		    "       15: %d\n       16: %d\n     Type: %d\n"
		    "   fflags: %8.8x\n   aflags: %8.8x\n",
		    frame->header.duration,
		    frame->data.damage,
		    frame->data.proration,
		    frame->data.chip,
		    frame->data.untech,
		    frame->data.unk5,
		    frame->data.unk6,
		    frame->data.unk7,
		    frame->data.unk8,
		    frame->data.unk9,
		    frame->data.unk10,
		    frame->data.unk11,
		    frame->data.unk12,
		    frame->data.velocity_x,
		    frame->data.velocity_y,
		    frame->data.unk15,
		    frame->data.unk16,
		    frame->data.attack_type,
		    frame->data.fflags, frame->data.aflags);

	    glColor4f(0.0, 0.0, 0.0, 1.0);
	    draw_font(window_width - 164, 16, strbuf);
	    glColor4f(1.0, 1.0, 1.0, 1.0);
	    draw_font(window_width - 165, 15, strbuf);
	}
    }

    if (character && draw_properties) {
	int current = 0;
	if (character->sequences[seq].header.nframes > 0) {
	    for (int i = 0; i < nframe; i++) {
		current +=
		    character->sequences[seq].frames[i].header.duration;
	    }
	    fcount += current;
	    for (int i = nframe;
		 i < character->sequences[seq].header.nframes; i++) {
		current +=
		    character->sequences[seq].frames[i].header.duration;;
	    }
	}

	char stringbuf[256];
	sprintf(stringbuf,
		"%-10s seq: %3d-%3d   fr: %5d-%5d   sp: %3d-%3d   ID: %3d",
		character->name, seq + 1, character->nsequences,
		fcount + 1, current, nframe + 1,
		character->sequences[seq].header.nframes,
		character->sequences[seq].header.identifier);

	glColor4f(0.0, 0.0, 0.0, 1.0);
	draw_font(window_width - 550, window_height - 20, stringbuf);
	glColor4f(1.0, 1.0, 0.6, 1.0);
	draw_font(window_width - 551, window_height - 21, stringbuf);
    }

}

void setup_opengl()
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, window_width, window_height, 0, -10, 10);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    init_font();
}

int main(int argc, char **argv)
{
    datfile_t *th075_dat;
    datfile_t *th075b_dat;

    if (!(th075_dat = dat_open("th075.dat"))) {
	printf("couldn't open th075\n");
	return -1;
    }
    if (!(th075b_dat = dat_open("th075b.dat"))) {
	printf("couldn't open th075b\n");
	return -1;
    }

    character_t chars[11];
    memset(chars, 0, sizeof(chars));

    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);

    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_Surface *surface =
	SDL_SetVideoMode(640, 480, 0, SDL_OPENGL | SDL_RESIZABLE);
    if (!surface) {
	return -1;
    }

    SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY,
			SDL_DEFAULT_REPEAT_INTERVAL);

    int ch = 0;
    int seq = 0;
    int frame = 0;
    pglBlendEquation =
	(PFNGLBLENDEQUATIONEXTPROC)
	SDL_GL_GetProcAddress("glBlendEquation");

    SDL_WM_SetCaption("framedisplay 1.5 for IaMP", 0);

    setup_opengl();
    display_load(chars, -1);

    int done = 0, render = 1, animate = 0, frame_count = 0;
    int ms_down = 0, ms_x = 0, ms_y = 0, start_x = 0, start_y = 0;

    for (int i = 0; i < 11; ++i) {
	char fname[256];
	sprintf(fname, "data\\character\\%s\\%s.pat",
		character_names[i], character_names[i]);

	int size;
	char *data = dat_read(th075b_dat, fname, &size);

	if (!data) {
	    return -1;
	}

	chars[i].name = character_names[i];

	read_pat(&chars[i], data);

	datfile_t *dat = th075_dat;
	chars[i].datfilename = "th075.dat";
	if (i == 3 || i == 10) {
	    chars[i].datfilename = "th075b.dat";
	    dat = th075b_dat;
	}
	sprintf(fname, "data\\character\\%2.2d.dat", i);
	FILE *file = dat_openfile(dat, fname, &size);
	if (!file) {
	    return -1;
	}

	init_images(&chars[i], file, size);

	fclose(file);

	display_load(chars, i);
    }

    display_scene(0, 0, ch, seq, frame, frame_count);

    SDL_GL_SwapBuffers();

    while (!done) {
	if (animate) {
	    frame_count++;
	    if (frame_count >=
		chars[ch].sequences[seq].frames[frame].header.duration) {
		frame++;
		if (frame >= chars[ch].sequences[seq].header.nframes) {
		    frame = 0;
		}

		frame_count = 0;

		render = 1;
	    }
	}
	if (render) {
	    display_scene(&chars[ch],
			  &chars[ch].sequences[seq].frames[frame],
			  ch, seq, frame, frame_count);
	    SDL_GL_SwapBuffers();

	    render = 0;
	}

	SDL_Delay(16);

	SDL_Event sdl_event;
	SDL_PumpEvents();

	while (SDL_PollEvent(&sdl_event)) {
	    switch (sdl_event.type) {
	    case SDL_KEYDOWN:
		switch (sdl_event.key.keysym.sym) {
		case SDLK_ESCAPE:
		    done = 1;
		    break;
		case SDLK_F1:
		    draw_gradient = 1 - draw_gradient;
		    render = 1;
		    break;
		case SDLK_F2:
		    draw_properties = (draw_properties + 1) % 3;
		    render = 1;
		    break;
		case SDLK_1:
		    draw_sprite = 1 - draw_sprite;
		    render = 1;
		    break;
		case SDLK_2:
		    draw_collisionbox = 1 - draw_collisionbox;
		    render = 1;
		    break;
		case SDLK_3:
		    draw_hitbox = 1 - draw_hitbox;
		    render = 1;
		    break;
		case SDLK_4:
		    draw_damagebox = 1 - draw_damagebox;
		    render = 1;
		    break;
		case SDLK_5:
		    draw_additive = 1 - draw_additive;
		    render = 1;
		    break;
		case SDLK_6:
		    pretty_display = 1 - pretty_display;
		    render = 1;
		    break;
		case SDLK_7:
		    draw_boxes = 1 - draw_boxes;
		    render = 1;
		    break;
		case SDLK_8:
		    realframes = 1 - realframes;
		    render = 1;
		    break;
		case SDLK_9:
		    draw_axises = 1 - draw_axises;
		    render = 1;
		    break;
		case SDLK_SPACE:
		    animate = 1 - animate;
		    frame_count = 0;
		    break;
		case SDLK_UP:
		case SDLK_KP8:
		    frame_count = 0;
		    seq--;
		    frame = 0;
		    if (seq < 0) {
			seq = chars[ch].nsequences - 1;
		    }
		    render = 1;
		    break;
		case SDLK_DOWN:
		case SDLK_KP2:
		    frame_count = 0;
		    seq++;
		    frame = 0;
		    if (seq >= chars[ch].nsequences) {
			seq = 0;
		    }
		    render = 1;
		    break;
		case SDLK_LEFT:
		case SDLK_KP4:
		    if (realframes == 0) {
			frame_count = 0;
			frame--;
			if (frame < 0) {
			    frame =
				chars[ch].sequences[seq].header.nframes -
				1;
			}
		    } else {
			frame_count--;
			if (frame_count < 0) {
			    frame--;
			    if (frame < 0) {
				frame =
				    chars[ch].sequences[seq].header.
				    nframes - 1;
			    }
			    frame_count =
				chars[ch].sequences[seq].frames[frame].
				header.duration - 1;
			}
		    }
		    render = 1;
		    break;
		case SDLK_RIGHT:
		case SDLK_KP6:
		    if (realframes == 0) {
			frame_count = 0;
			frame++;
			if (frame >=
			    chars[ch].sequences[seq].header.nframes) {
			    frame = 0;
			}
		    } else {
			frame_count++;
			if (frame_count >
			    chars[ch].sequences[seq].frames[frame].header.
			    duration - 1) {
			    frame++;
			    if (frame >=
				chars[ch].sequences[seq].header.nframes) {
				frame = 0;
			    }
			    frame_count = 0;
			}
		    }
		    render = 1;
		    break;
		case SDLK_PAGEUP:
		case SDLK_KP9:
		    frame_count = 0;
		    ch--;
		    seq = 0;
		    frame = 0;
		    if (ch < 0) {
			ch = 10;
		    }
		    render = 1;
		    break;
		case SDLK_PAGEDOWN:
		case SDLK_KP3:
		    frame_count = 0;
		    ch++;
		    seq = 0;
		    frame = 0;
		    if (ch >= 11) {
			ch = 0;
		    }
		    render = 1;
		    break;
		case SDLK_MINUS:
		case SDLK_KP_MINUS:
		    window_scale = window_scale / 1.1;
		    render = 1;
		    break;
		case SDLK_PLUS:
		case SDLK_EQUALS:
		case SDLK_KP_PLUS:
		    window_scale = window_scale * 1.1;
		    render = 1;
		    break;
		case SDLK_BACKSPACE:
		    window_scale = 1.0;
		    render = 1;
		    break;
		case SDLK_TAB:
		    alt_colors = 1 - alt_colors;
		    flush_images(chars);
		    render = 1;
		    break;
		default:
		    break;
		}
		break;
	    case SDL_MOUSEBUTTONDOWN:
		ms_down = 1;
		ms_x = sdl_event.button.x;
		ms_y = sdl_event.button.y;
		start_x = position_x;
		start_y = position_y;
		break;
	    case SDL_MOUSEBUTTONUP:
		ms_down = 0;
		break;
	    case SDL_MOUSEMOTION:
		if (ms_down == 1) {
		    position_x = start_x + sdl_event.motion.x - ms_x;
		    position_y = start_y + sdl_event.motion.y - ms_y;
		    render = 1;
		}
		break;
	    case SDL_VIDEOEXPOSE:
		render = 1;
		break;
	    case SDL_VIDEORESIZE:
		SDL_SetVideoMode(sdl_event.resize.w, sdl_event.resize.h, 0,
				 SDL_OPENGL | SDL_RESIZABLE);
		glViewport(0, 0, sdl_event.resize.w, sdl_event.resize.h);

		window_width = sdl_event.resize.w;
		window_height = sdl_event.resize.h;

		setup_opengl();

		current_tex_cycle++;
		break;
	    case SDL_QUIT:
		done = 1;
		break;
	    }
	}
    }

    SDL_Quit();

    return 0;
}
