
#include "texturestack.h"
#include "byte_swapping.h"
#include "psd_read.h"
#include "RevengeResources.h"
#include <XDraw.h>
#include <XWindow.h>
#include <stdlib.h>
#include <XTextControls.h>
#include "TBitmapView.h"
#include <XFile.h>
#include <XMessages.h>
#include <XDialog.h>
#include <XSlideControl.h>
#include <XAppMenu.h>
#include <XDataUtil.h>
#include <XPopup.h>
#include "jpeg.h"

enum { x_size = 144, y_size = 200, x_offset = 8, y_offset = 8 };
stacked_texture *TtxstEditForm::st_clip = 0;

TtxstEditForm::TtxstEditForm( tag_entry *t ): TTagEditForm( t )
{
	edit = 0;
	stacks = 0;
}


TtxstEditForm::~TtxstEditForm()
{
	dispose_stackset( stacks );
	stacks = 0;
}


void TtxstEditForm::CommitToTag()
{
	if (!stacks) return;
	save_stackset( fEntry, stacks );
}


BEGINDISPATCH(TtxstEditForm,TTagEditForm)
	DISPATCHTABLE(KXGGetMenuStatus,TtxstEditForm::DoGetMenuStatus)
	DISPATCHTABLE(KXGDoMenuCommand,TtxstEditForm::DoMenuCommand)
ENDDISPATCH


long TtxstEditForm::DoGetMenuStatus( long arg, void *ptr )
{
	if (!edit) return -1;
	
	XGSMenuStatus *e = (XGSMenuStatus *)ptr;
	XGPtrSet<stacked_texture> sel;
	int i;
	
	switch (arg) {
		case KMenuSplitRGBA: {
				edit->GetSelection( &sel );
				e->enable = false;
				for (i = 0; i < sel.Size(); ++i) {
					if (sel[i]->channels == 4) {
						e->enable = true;
						break;
					}
				}
				return 0;
			} break;
		case KMenuMergeRGBA: {
				edit->GetSelection( &sel );
				e->enable = false;
				if (sel.Size() == 2) {
					if ((sel[0]->channels == 3 && sel[1]->channels == 1) ||
						(sel[1]->channels == 3 && sel[0]->channels == 1)) {
						if (sel[0]->width == sel[1]->width &&
							sel[0]->height == sel[1]->height) {
							e->enable = true;
						}
					}
				}
				return 0;
			} break;
		case KMenuRecompress: {
				edit->GetSelection( &sel );
				e->enable = sel.Size() > 0;
				return 0;
			} break;
		case KXGEditCut:
		case KXGEditCopy:
		case KXGEditClear:
				e->enable = (edit->fSelection.Size() > 0);
				return 0;
		case KXGEditPaste:
				e->enable = st_clip;
				return 0;
		default:
			break;
	}
	return -1;
}


struct recompress_texture_data {
	stacked_texture *tex;
	void *raw;
	int max_quality;
	bool more_coming;
	bool use_for_all;
	unsigned char out_format;
	int out_jpeg_quality;
};

static void preview_jpeg( XGDialog *dlog, recompress_texture_data *rtd, int quality )
{
	TBitmapView *bmp = dynamic_cast<TBitmapView *>(dlog->FindViewByID( 1 ));
	stacked_texture *tex = rtd->tex;
	byte *buf;
	int comp_size;
	
	if (quality > rtd->max_quality) {
		quality = rtd->max_quality;
	}
	
	if (quality < 0) {
		buf = (byte *)(rtd->raw);
		comp_size = 0;
	} else {
		comp_size = tex->width * tex->height * tex->channels * 3;
		buf = (byte *)malloc( comp_size );
		compress_jpeg( buf, &comp_size, rtd->raw, tex->width, tex->height, tex->channels, quality );
		decompress_jpeg( buf + comp_size, buf, comp_size, tex->width, tex->height, tex->channels, false );
	}
	
	switch (tex->channels) {
		case 1:
			bmp->SetBitmapAlpha( buf + comp_size, tex->width, tex->height );
			break;
		case 3:
			bmp->SetBitmapRGB( buf + comp_size, tex->width, tex->height );
			break;
		case 4:
			bmp->SetBitmapRGBA( buf + comp_size, tex->width, tex->height );
			break;
	}
	bmp->InvalView();
	
	if (buf != rtd->raw) {
		free( buf );
	}
	
	int raw_size = tex->width * tex->height * tex->channels;
	if (comp_size == 0) {
		comp_size = raw_size;
	}
	char info[100];
	sprintf( info, "%d bytes (%d%%)", comp_size, (comp_size * 100 + (raw_size >> 1)) / raw_size );
	dlog->SetText( 2, info );
	dlog->FindViewByID( 2 )->UpdateView();
	bmp->UpdateView();
}

static long RecompProc( XGDialog *dlog, void *data, long msg, long arg, void * /*parg*/ )
{
	recompress_texture_data *rtd = (recompress_texture_data *)data;
	if (!rtd) return -1;
	stacked_texture *tex = rtd->tex;
	if (!tex) return -1;
	TBitmapView *bmp = dynamic_cast<TBitmapView *>(dlog->FindViewByID( 1 ));
	XGSlideControl *slide = dynamic_cast<XGSlideControl *>(dlog->FindViewByID( 2002 ));
	
	switch (msg) {
		case KXGDialogMessage:
			switch (arg) {
				case KXGDMInitDialog: {
						rtd->raw = malloc( tex->width * tex->height * tex->channels );
						if (tex->format == 1) {
							decompress_jpeg( rtd->raw, tex->compressed_data, tex->compressed_length, tex->width, tex->height, tex->channels, true );
							rtd->max_quality = estimate_jpeg_quality( tex->compressed_data, tex->compressed_length, tex->width, tex->height, tex->channels ) + 1;
							if (rtd->max_quality > 100) {
								rtd->max_quality = 100;
							}
							dlog->SetValue( 2001, rtd->max_quality );
							slide->SetMinValue( 1 );
							slide->SetMaxValue( rtd->max_quality );
							slide->SetValue( rtd->max_quality );
							dlog->SetValue( 1000, false );
							dlog->SetValue( 2000, true );
							dlog->SetEnabled( 2001, true );
							dlog->SetEnabled( 2002, true );
							preview_jpeg( dlog, rtd, rtd->max_quality );
						} else {
							memcpy( rtd->raw, tex->compressed_data, tex->width * tex->height * tex->channels );
							rtd->max_quality = 100;
							slide->SetMinValue( 1 );
							slide->SetMaxValue( 100 );
							slide->SetValue( 95 );
							dlog->SetValue( 2001, 95 );
							dlog->SetValue( 1000, true );
							dlog->SetValue( 2000, false );
							dlog->SetEnabled( 2001, false );
							dlog->SetEnabled( 2002, false );
							preview_jpeg( dlog, rtd, -1 );
						}
						if (rtd->more_coming) {
							dlog->FindViewByID( 3000 )->ShowView();
							dlog->SetValue( 2001, rtd->use_for_all );
						} else {
							dlog->FindViewByID( 3000 )->HideView();
						}
					} break;
				default:
					break;
			} break;
		case KXGKeyClick:
			switch (arg) {
				case 2001: {	// jpeg quality
						if (dlog->GetValue( 2000 )) {
							int q = dlog->GetValue( arg );
							if (q < 1) q = 1; else if (q > 100) q = 100;
							dlog->SetValue( 2002, q );
							preview_jpeg( dlog, rtd, q );
						}
					} break;
			} break;
		case KXGSliderValue:
			switch (arg) {
				case 2002: {	// jpeg qulaity
						if (dlog->GetValue( 2000 )) {
							int q = dlog->GetValue( arg );
							if (q < 1) q = 1; else if (q > rtd->max_quality) q = rtd->max_quality;
							dlog->SetValue( 2001, q );
							preview_jpeg( dlog, rtd, q );
							dlog->FindViewByID( 2001 )->UpdateView();
						}
					} break;
			} break;
		case KXGControlClick:
			switch (arg) {
				case 1000:
					dlog->SetValue( 1000, true );
					dlog->SetValue( 2000, false );
					dlog->SetEnabled( 2001, false );
					dlog->SetEnabled( 2002, false );
					preview_jpeg( dlog, rtd, -1 );
					break;
				case 2000:
					dlog->SetValue( 1000, false );
					dlog->SetValue( 2000, true );
					dlog->SetEnabled( 2001, true );
					dlog->SetEnabled( 2002, true );
					{
						int q = dlog->GetValue( 2001 );
						if (q < 1) q = 1; else if (q > 100) q = 100;
						dlog->SetValue( 2002, q );
						preview_jpeg( dlog, rtd, q );
					}
					break;
				case 3000:	// use for all
					rtd->use_for_all = !rtd->use_for_all;
					dlog->SetValue( 3000, rtd->use_for_all );
					break;
				case 3001:	// recompress
					dlog->EndDialog( 1 );
					if (dlog->GetValue( 1000 )) {
						rtd->out_jpeg_quality = -1;
					} else {
						int q = dlog->GetValue( 2001 );
						if (q < 1) q = 1; else if (q > 100) q = 100;
						rtd->out_jpeg_quality = q;
					}
					rtd->use_for_all = dlog->GetValue( 3000 );
					if (rtd->raw) {
						free( rtd->raw );
						rtd->raw = 0;
					}
					return 0;
				case 3002:	// cancel
					dlog->EndDialog( 0 );
					if (rtd->raw) {
						free( rtd->raw );
						rtd->raw = 0;
					}
					return 0;
			} break;
		default:
			break;
	}
	
	return -1;
}

inline void SetRGBPoint( byte *rgb, int width, int height, int x, int y, int r, int g, int b )
{
	x = x & (width - 1);
	y = y & (height - 1);
	rgb += (y * width + x) * 3;
	rgb[0] = r;
	rgb[1] = g;
	rgb[2] = b;
}

void DrawRGBLine( byte *rgb, int width, int height, int x1, int y1, int x2, int y2, int r, int g, int b )
{
	int dx = x2 - x1;
	int dy = y2 - y1;
	int x, y;
	
	if (abs(dx) > abs(dy)) {
		if (dx > 0) {
			for (x = x1; x <= x2; ++x) {
				y = y1 + ((x - x1) * dy) / dx;
				SetRGBPoint( rgb, width, height, x, y, r, g, b );
			}
		} else {
			for (x = x1; x >= x2; --x) {
				y = y1 + ((x - x1) * dy) / dx;
				SetRGBPoint( rgb, width, height, x, y, r, g, b );
			}
		}
	} else {
		if (dy > 0) {
			for (y = y1; y <= y2; ++y) {
				x = x1 + ((y - y1) * dx) / dy;
				SetRGBPoint( rgb, width, height, x, y, r, g, b );
			}
		} else if (dy < 0) {
			for (y = y1; y >= y2; --y) {
				x = x1 + ((y - y1) * dx) / dy;
				SetRGBPoint( rgb, width, height, x, y, r, g, b );
			}
		} else {
			SetRGBPoint( rgb, width, height, x1, y1, r, g, b );
		}
	}
}

float dtext( XGDialog *d, int i )
{
	char buf[128];
	d->FindViewByID(i)->GetTitle( buf );
	return atof( buf );
}

static void SetTexInfoBitmap( XGDialog *d, stacked_texture *t )
{
	byte *raw = (byte *)malloc( t->width * t->height * t->channels );
	if (t->format == 1) {
		decompress_jpeg( raw, t->compressed_data, t->compressed_length, t->width, t->height, t->channels, false );
	} else {
		memcpy( raw, t->compressed_data, t->width * t->height * t->channels );
	}
	
	byte *rgb;
	if (t->channels == 3) {
		rgb = raw;
	} else {
		rgb = (byte *)malloc( t->width * t->height * 3 );
		byte *in = raw;
		byte *out = rgb;
		int count = t->height * t->width;
		if (t->channels == 1) {
			for (int i = 0; i < count; ++i) {
				int p = *in++;
				out[0] = p;
				out[1] = p;
				out[2] = p;
				out += 3;
			}
		} else if (t->channels == 4) {
			for (int i = 0; i < count; ++i) {
				out[0] = in[0];
				out[1] = in[1];
				out[2] = in[2];
				in += 4;
				out += 3;
			}
		}
		free( raw );
	}
	
	if (t->channels > 1 && dynamic_cast<XGPopControl *>(d->FindViewByID(100))->GetValue() == 1) {
		
		float o_x = dtext( d, 103 ) * 4.0;
		float o_y = dtext( d, 104 ) * 4.0;
		float dux = dtext( d, 106 ) * 4.0;
		float duy = dtext( d, 107 ) * 4.0;
		float dvx = dtext( d, 109 ) * 4.0;
		float dvy = dtext( d, 110 ) * 4.0;
		float s = t->width / ( dux*dux + duy*duy );
		dux *= s;
		duy *= s;
		s = t->height / ( dvx*dvx + dvy*dvy );
		dvx *= s;
		dvy *= s;
		byte *paint = (byte *)malloc( 512 * 512 * 3 );
		byte *out2 = paint;
		int u_mask = t->width - 1;
		int v_mask = t->height - 1;
		for (int y = 0; y < 512; ++y) {
			for (int x = 0; x < 512; ++x) {
				int u, v;
				u = (int)(((y - o_y) * duy) + ((x - o_x) * dux)) & u_mask;
				v = (int)(((y - o_y) * dvy) + ((x - o_x) * dvx)) & v_mask;
				int offset = (v * t->width + u) * 3;
				out2[0] = rgb[offset+0];
				out2[1] = rgb[offset+1];
				out2[2] = rgb[offset+2];
				out2 += 3;
			}
		}
		
		s = t->width / ( dux*dux + duy*duy );
		dux *= s;
		duy *= s;
		s = t->height / ( dvx*dvx + dvy*dvy );
		dvx *= s;
		dvy *= s;
		DrawRGBLine( paint, 512, 512, o_x, o_y, o_x + dux + dvx, o_y + duy + dvy, 128, 0, 0 );
		DrawRGBLine( paint, 512, 512, o_x + dux, o_y + duy, o_x + dvx, o_y + dvy, 128, 0, 0 );
		DrawRGBLine( paint, 512, 512, o_x + dux, o_y + duy, o_x + dux + dvx, o_y + duy + dvy, 0, 255, 0 );
		DrawRGBLine( paint, 512, 512, o_x + dvx, o_y + dvy, o_x + dux + dvx, o_y + duy + dvy, 0, 255, 0 );
		DrawRGBLine( paint, 512, 512, o_x, o_y, o_x + dux, o_y + duy, 255, 0, 0 );
		DrawRGBLine( paint, 512, 512, o_x, o_y, o_x + dvx, o_y + dvy, 0, 0, 255 );
		SetRGBPoint( paint, 512, 512, o_x, o_y, 255, 255, 255 );
		SetRGBPoint( paint, 512, 512, o_x + 1, o_y, 0, 0, 0 );
		SetRGBPoint( paint, 512, 512, o_x - 1, o_y, 0, 0, 0 );
		SetRGBPoint( paint, 512, 512, o_x, o_y - 1, 0, 0, 0 );
		SetRGBPoint( paint, 512, 512, o_x, o_y + 1, 0, 0, 0 );
		
		TBitmapView *bmp = dynamic_cast<TBitmapView *>(d->FindViewByID( 1 ));
		if (bmp) {
			bmp->SetBitmapRGB( paint, 512, 512 );
			bmp->InvalView();
		}
		free( paint );
	} else {
		if (d->GetValue( 101 )) {
			int x = d->GetValue( 103 );
			int y = d->GetValue( 104 );
			int dux = d->GetValue( 106 );
			int duy = d->GetValue( 107 );
			int dvx = d->GetValue( 109 );
			int dvy = d->GetValue( 110 );
			DrawRGBLine( rgb, t->width, t->height, x, y, x + dux + dvx, y + duy + dvy, 128, 0, 0 );
			DrawRGBLine( rgb, t->width, t->height, x + dux, y + duy, x + dvx, y + dvy, 128, 0, 0 );
			DrawRGBLine( rgb, t->width, t->height, x + dux, y + duy, x + dux + dvx, y + duy + dvy, 0, 128, 128 );
			DrawRGBLine( rgb, t->width, t->height, x + dvx, y + dvy, x + dux + dvx, y + duy + dvy, 0, 128, 128 );
			DrawRGBLine( rgb, t->width, t->height, x, y, x + dux, y + duy, 255, 255, 0 );
			DrawRGBLine( rgb, t->width, t->height, x, y, x + dvx, y + dvy, 255, 0, 255 );
			SetRGBPoint( rgb, t->width, t->height, x, y, 255, 255, 255 );
			SetRGBPoint( rgb, t->width, t->height, x + 1, y, 0, 0, 0 );
			SetRGBPoint( rgb, t->width, t->height, x - 1, y, 0, 0, 0 );
			SetRGBPoint( rgb, t->width, t->height, x, y - 1, 0, 0, 0 );
			SetRGBPoint( rgb, t->width, t->height, x, y + 1, 0, 0, 0 );
		}
		
		TBitmapView *bmp = dynamic_cast<TBitmapView *>(d->FindViewByID( 1 ));
		if (bmp) {
			bmp->SetBitmapRGB( rgb, t->width, t->height );
			bmp->InvalView();
		}
	}
	
	free( rgb );
}

static void SetTexInfoVis( XGDialog *d, stacked_texture *t, XGPopControl *p )
{
	int logo = -1;	// -1 == optional, 0 == force off, 1 == force on
	int layer = 0;
	
	if (t->channels == 1) {
		switch (p->GetValue()) {
			case 0:	// detail mask
				logo = 0;
				layer = 1;
				break;
			case 1:	// team color
				logo = -1;
				layer = 1;
				break;
			case 2:	// logo
				logo = 1;
				layer = 0;
				break;
			case 3: // fullbright
				logo = 0;
				layer = 0;
				break;
			case 4: // reflection
				logo = -1;
				layer = 0;
				break;
		}
	} else {
		switch (p->GetValue()) {
			case 0: // base colormap
				logo = -1;
				layer = 0;
				break;
			case 1: // details
				logo = 1;
				layer = 1;
				break;
		}
	}
	
	if (logo == -1) {
		logo = d->GetValue( 101 );
		d->FindViewByID( 101 )->ShowView();
	} else {
		d->SetValue( 101, logo );
		d->FindViewByID( 101 )->HideView();
	}
	
	for (int i = 102; i <= 110; ++i) {
		if (logo) {
			d->FindViewByID( i )->ShowView();
		} else {
			d->FindViewByID( i )->HideView();
		}
	}
	
	for (int i = 111; i <= 112; ++i) {
		if (layer) {
			d->FindViewByID( i )->ShowView();
		} else {
			d->FindViewByID( i )->HideView();
		}
	}
	
	SetTexInfoBitmap( d, t );
}

static char *TextFloat( double value, int precision = 3 )
{
	static char buffer[256];
	
	sprintf( buffer, "%.*f", precision, value );
	char *s = buffer + strlen(buffer) - 1;
	while (s > buffer && *s == '0' && s[-1] != '.') {
		*s = 0;
		--s;
	}
	return buffer;
}


static short GetFix( XGDialog *d, int item, int scale )
{
	char buf[256];
	d->FindViewByID( item )->GetTitle( buf );
	double value = atof( buf );
	return (short)(value * 16384.0 / scale);
}

struct texinfodata
{
	texture_stack_head *head;
	stacked_texture *tex;
};


static long TextureInfoProc( XGDialog *dlog, void *data, long msg, long arg, void * /*parg*/ )
{
	texinfodata *infodata = (texinfodata *)data;
	stacked_texture *tex = infodata->tex;
	if (!tex) return -1;
	texture_stack_head *head = infodata->head;
	if (!head) return -1;
	TBitmapView *bmp = dynamic_cast<TBitmapView *>(dlog->FindViewByID( 1 ));
	XGPopControl *pop = dynamic_cast<XGPopControl *>(dlog->FindViewByID( 100 ));
	static bool changed = false;
	
	switch (msg) {
		case KXGDialogMessage:
			switch (arg) {
				case KXGDMInitDialog: {
						changed = false;
						if (tex->channels == 1) {
							pop->DeleteAll();
							pop->Insert( 0, "Terrain Detail Mask" );
							pop->Insert( 1, "Skin Team Color Mask" );
							pop->Insert( 2, "Skin Logo Mask" );
							pop->Insert( 3, "Skin Glow Mask" );
							pop->Insert( 4, "Skin Reflection Mask" );
							switch (tex->use) {
								case txst_use_detailmask:
									pop->SetValue( 0 );
									break;
								default:
								case txst_use_skinblend:
									pop->SetValue( 1 );
									break;
								case txst_use_logomask:
									pop->SetValue( 2 );
									break;
								case txst_use_fullbrightmask:
									pop->SetValue( 3 );
									break;
								case txst_use_reflectmask:
									pop->SetValue( 4 );
									break;
							}
						} else {
							pop->DeleteAll();
							pop->Insert( 0, "Base Colormap" );
							pop->Insert( 1, "Detail Texture" );
							switch (tex->use) {
								default:
								case txst_use_basecolor:
									pop->SetValue( 0 );
									break;
								case txst_use_details:
									pop->SetValue( 1 );
									break;
							}
						}
						if (tex->use == txst_use_details) {
							if ((tex->decal_ux | tex->decal_uy) && (tex->decal_vx | tex->decal_vy)) {
								dlog->SetValue( 101, true );
							#if 0
								dlog->SetText( 103, TextFloat( tex->decal_corner_x * (128.0f / 16384.0) ) );
								dlog->SetText( 104, TextFloat( tex->decal_corner_y * (128.0f / 16384.0) ) );
								dlog->SetText( 106, TextFloat( tex->decal_ux * (128.0f / 16384.0) ) );
								dlog->SetText( 107, TextFloat( tex->decal_uy * (128.0f / 16384.0) ) );
								dlog->SetText( 109, TextFloat( tex->decal_vx * (128.0f / 16384.0) ) );
								dlog->SetText( 110, TextFloat( tex->decal_vy * (128.0f / 16384.0) ) );
							#else
								dlog->SetText( 103, TextFloat( tex->decal_corner_x * (tex->width / 16384.0) ) );
								dlog->SetText( 104, TextFloat( tex->decal_corner_y * (tex->height / 16384.0) ) );
								dlog->SetText( 106, TextFloat( tex->decal_ux * (tex->width / 16384.0) ) );
								dlog->SetText( 107, TextFloat( tex->decal_uy * (tex->height / 16384.0) ) );
								dlog->SetText( 109, TextFloat( tex->decal_vx * (tex->width / 16384.0) ) );
								dlog->SetText( 110, TextFloat( tex->decal_vy * (tex->height / 16384.0) ) );
							#endif
							} else {
								dlog->SetValue( 101, false );
								dlog->SetText( 103, TextFloat( 0.25 * (128.0f / 1.0) ) );
								dlog->SetText( 104, TextFloat( 0.25 * (128.0f / 1.0) ) );
								dlog->SetText( 106, TextFloat( 0.5 * (128.0f / 1.0) ) );
								dlog->SetText( 107, TextFloat( 0.0 * (128.0f / 1.0) ) );
								dlog->SetText( 109, TextFloat( 0.0 * (128.0f / 1.0) ) );
								dlog->SetText( 110, TextFloat( 0.5 * (128.0f / 1.0) ) );
							}
						} else {
							if ((tex->decal_ux | tex->decal_uy) && (tex->decal_vx | tex->decal_vy)) {
								dlog->SetValue( 101, true );
								dlog->SetText( 103, TextFloat( tex->decal_corner_x * (tex->width / 16384.0) ) );
								dlog->SetText( 104, TextFloat( tex->decal_corner_y * (tex->height / 16384.0) ) );
								dlog->SetText( 106, TextFloat( tex->decal_ux * (tex->width / 16384.0) ) );
								dlog->SetText( 107, TextFloat( tex->decal_uy * (tex->height / 16384.0) ) );
								dlog->SetText( 109, TextFloat( tex->decal_vx * (tex->width / 16384.0) ) );
								dlog->SetText( 110, TextFloat( tex->decal_vy * (tex->height / 16384.0) ) );
							} else {
								dlog->SetValue( 101, false );
								dlog->SetText( 103, TextFloat( 0.25 * (tex->width / 1.0) ) );
								dlog->SetText( 104, TextFloat( 0.25 * (tex->height / 1.0) ) );
								dlog->SetText( 106, TextFloat( 0.5 * (tex->width / 1.0) ) );
								dlog->SetText( 107, TextFloat( 0.0 * (tex->height / 1.0) ) );
								dlog->SetText( 109, TextFloat( 0.0 * (tex->width / 1.0) ) );
								dlog->SetText( 110, TextFloat( 0.5 * (tex->height / 1.0) ) );
							}
						}
						SetTexInfoVis( dlog, tex, pop );
						dlog->SetValue( 112, tex->layer );
						for (int i = 0; i < NUMBER_OF_TEXTURESTACK_FLAGS; ++i)
						{
							dlog->SetValue( 301+i, head->flags & (1 << i) );
						}
					} break;
				default:
					break;
			} break;
		case KXGKeyClick:
			if (arg >= 103 && arg <= 110) {
				SetTexInfoBitmap( dlog, tex );
				return 0;
			}
			break;
		case KXGPopValue:
			switch (arg) {
				case 100:	// file popup
					SetTexInfoVis( dlog, tex, pop );
					return 0;
				default:
					break;
			} break;
		case KXGControlClick:
			switch (arg) {
				case 101: // team logo
					dlog->SetValue( arg, !dlog->GetValue( arg ) );
					SetTexInfoVis( dlog, tex, pop );
					return 0;
				case 201: { // recompress
						recompress_texture_data rtd;
						rtd.more_coming = false;
						rtd.use_for_all = false;
						rtd.tex = tex;
						bool ok = XGDialog::ModalDialog( KWindowRecompressTexture, RecompProc, &rtd );
						if (ok) {
							recompress_texture( tex, rtd.out_jpeg_quality );
							changed = true;
						}
					} return 0;
				case 202: // cancel
					dlog->EndDialog( changed ? 1 : 0 );
					return 0;
				case 203: // ok
					if (tex->channels == 1) {
						switch (dlog->GetValue( 100 )) {
							case 0:
								tex->use = txst_use_detailmask;
								break;
							default:
							case 1:
								tex->use = txst_use_skinblend;
								break;
							case 2:
								tex->use = txst_use_logomask;
								break;
							case 3:
								tex->use = txst_use_fullbrightmask;
								break;
							case 4:
								tex->use = txst_use_reflectmask;
								break;
						}
					} else {
						switch (dlog->GetValue( 100 )) {
							default:
							case 0:
								tex->use = txst_use_basecolor;
								break;
							case 1:
								tex->use = txst_use_details;
								break;
						}
					}
					tex->layer = dlog->GetValue( 112 );
					if (tex->use == txst_use_detailmask) {
						tex->decal_corner_x = GetFix( dlog, 103, 128 );
						tex->decal_corner_y = GetFix( dlog, 104, 128 );
						tex->decal_ux = GetFix( dlog, 106, 128 );
						tex->decal_uy = GetFix( dlog, 107, 128 );
						tex->decal_vx = GetFix( dlog, 109, 128 );
						tex->decal_vy = GetFix( dlog, 110, 128 );
					} else if (dlog->GetValue( 101 )) {
						tex->decal_corner_x = GetFix( dlog, 103, tex->width );
						tex->decal_corner_y = GetFix( dlog, 104, tex->height );
						tex->decal_ux = GetFix( dlog, 106, tex->width );
						tex->decal_uy = GetFix( dlog, 107, tex->height );
						tex->decal_vx = GetFix( dlog, 109, tex->width );
						tex->decal_vy = GetFix( dlog, 110, tex->height );
					} else {
						tex->decal_corner_x = 0;
						tex->decal_corner_y = 0;
						tex->decal_ux = 0;
						tex->decal_uy = 0;
						tex->decal_vx = 0;
						tex->decal_vy = 0;
					}
					head->flags = 0;
					dlog->EndDialog( 1 );
					for (int i = 0; i < NUMBER_OF_TEXTURESTACK_FLAGS; ++i)
					{
						head->flags |= ((dlog->GetValue( 301+i ) != 0) << i);
					}
					return 0;
				case 301:
					dlog->SetValue( arg, !dlog->GetValue(arg) );
					return 0;
			} break;
		default:
			break;
	}
	
	return -1;
}


void TtxstEditForm::DeleteSelected()
{
	XGPtrSet<stacked_texture> sel;
	edit->GetSelection( &sel );
	edit->ClearSelection();
	for (int i = 0; i < sel.Size(); ++i) {
		int stacknum, stacklevel;
		find_texture_in_stack( stacks, sel[i], &stacknum, &stacklevel );
		if (stacknum >= 0) {
			remove_from_texturestack( stacks, stacknum, sel[i], false );
			dispose_stacked_texture( sel[i] );
		}
	}
	compact_stackset( stacks );
	RebuildEditorContents();
}


void TtxstEditForm::CopySelected()
{
	XGPtrSet<stacked_texture> sel;
	int i;
	stacked_texture *tex, *next;
	
	edit->GetSelection( &sel );
	for (tex = st_clip; tex; tex = next) {
		next = tex->next;
		dispose_stacked_texture( tex );
	}
	st_clip = 0;
	
	next = 0;
	for (i = 0; i < sel.Size(); ++i) {
		tex = duplicate_stacked_texture( sel[i] );
		if (next) {
			LLAFTER( next, tex );
		} else {
			LLADD( st_clip, tex );
		}
		next = tex;
	}
}


void TtxstEditForm::Paste()
{
	if (!st_clip) return;
	XGPtrSet<stacked_texture> sel;
	if (edit->fDropStackLevel < 0) {
		insert_texturestack_in_set( stacks, edit->fDropStackNum );
		edit->fDropStackLevel = 0;
	}
	for (stacked_texture *tex = st_clip; tex; tex = tex->next) {
		stacked_texture *copy = duplicate_stacked_texture( tex );
		insert_into_texturestack( stacks, edit->fDropStackNum, edit->fDropStackLevel++, copy );
		sel += copy;
	}
	compact_stackset( stacks );
	edit->SetSelection( sel );
	RebuildEditorContents();
}


long TtxstEditForm::DoMenuCommand( long arg, void * /* ptr */ )
{
	if (!edit) return -1;
	XGPtrSet<stacked_texture> sel;
	stacked_texture *tex;
	
	int i;
	
	switch (arg) {
		case KMenuSplitRGBA: {
				edit->GetSelection( &sel );
				XGPtrSet<stacked_texture> newsel;
				for (i = 0; i < sel.Size(); ++i) {
					tex = sel[i];
					if (tex->channels != 4) continue;
					newsel += tex;
					stacked_texture *alpha = (stacked_texture *)malloc( sizeof(stacked_texture) );
					memset( alpha, 0, sizeof(stacked_texture) );
					tex->channels = 3;
					delete tex->thumbnail;
					tex->thumbnail = 0;
					alpha->channels = 1;
					alpha->format = tex->format;
					alpha->use = txst_use_skinblend;
					alpha->layer = tex->layer;
					alpha->width = tex->width;
					alpha->height = tex->height;
					switch (tex->format) {
						case 0: {
								tex->compressed_length = 3 * tex->width * tex->height;
								alpha->compressed_length = 1 * tex->width * tex->height;
								byte (*out_rgb)[3] = (byte (*)[3])malloc( tex->compressed_length );
								byte *out_a = (byte *)malloc( alpha->compressed_length );
								const byte (*in_rgba)[4] = (const byte (*)[4])tex->compressed_data;
								for (int y = 0; y < tex->height; ++y) {
									for (int x = 0; x < tex->width; ++x) {
										out_rgb[y * tex->width + x][0] = in_rgba[y * tex->width + x][0];
										out_rgb[y * tex->width + x][1] = in_rgba[y * tex->width + x][1];
										out_rgb[y * tex->width + x][2] = in_rgba[y * tex->width + x][2];
										out_a[y * tex->width + x] = in_rgba[y * tex->width + x][3];
									}
								}
								free( (void *)in_rgba );
								tex->compressed_data = out_rgb;
								alpha->compressed_data = out_a;
							} break;
						case 1: {
								unsigned char *in_rgba = (unsigned char *)(tex->compressed_data);
								int rgb_len = (in_rgba[0] << 16) + (in_rgba[1] << 8) + (in_rgba[2]);
								int alpha_len = tex->compressed_length - 3 - rgb_len;
								tex->compressed_length = rgb_len;
								alpha->compressed_length = alpha_len;
								tex->compressed_data = malloc( rgb_len );
								alpha->compressed_data = malloc( alpha_len );
								memcpy( (void *)tex->compressed_data, in_rgba + 3, rgb_len );
								memcpy( (void *)alpha->compressed_data, in_rgba + 3 + rgb_len, alpha_len );
								free( in_rgba );
							} break;
					}
					LLAFTER( tex, alpha );
					newsel += alpha;
				}
				edit->SetSelection( newsel );
				edit->InvalView();
				stacks->modified = true;
			} return 0;
		case KMenuMergeRGBA: {
				edit->GetSelection( &sel );
				stacked_texture *rgb_tex;
				stacked_texture *a_tex;
				rgb_tex = sel[0];
				if (rgb_tex->channels != 3) {
					a_tex = rgb_tex;
					rgb_tex = sel[1];
				} else {
					a_tex = sel[1];
				}
				if (rgb_tex->channels != 3 || a_tex->channels != 1 || rgb_tex->width != a_tex->width || rgb_tex->height != a_tex->height) return 0;
				if (rgb_tex->format == 0) {
					if (a_tex->format != 0) {
						recompress_texture( a_tex, -1 );
					}
					byte *in_rgb = (byte *)rgb_tex->compressed_data;
					byte *in_a = (byte *)a_tex->compressed_data;
					int count = a_tex->width * a_tex->height;
					byte *rgba = (byte *)malloc( count * 4 );
					byte *out_rgba = rgba;
					for (int i = 0; i < count; ++i) {
						out_rgba[0] = in_rgb[0];
						out_rgba[1] = in_rgb[1];
						out_rgba[2] = in_rgb[2];
						in_rgb += 3;
						out_rgba[3] = in_a[0];
						in_a += 1;
						out_rgba += 4;
					}
					free( (void *)rgb_tex->compressed_data );
					rgb_tex->compressed_data = rgba;
					rgb_tex->compressed_length = 4 * count;
					
				} else {
					if (a_tex->format == 0) {
						int q = estimate_jpeg_quality( rgb_tex->compressed_data, rgb_tex->compressed_length,
													rgb_tex->width, rgb_tex->height, 3 );
						recompress_texture( a_tex, q );
					}
					byte *rgba = (byte *)malloc( 3 + rgb_tex->compressed_length + a_tex->compressed_length );
					rgba[0] = rgb_tex->compressed_length >> 16;
					rgba[1] = rgb_tex->compressed_length >> 8;
					rgba[2] = rgb_tex->compressed_length >> 0;
					memcpy( rgba + 3, rgb_tex->compressed_data, rgb_tex->compressed_length );
					memcpy( rgba + 3 + rgb_tex->compressed_length, a_tex->compressed_data, a_tex->compressed_length );
					free( (void *)rgb_tex->compressed_data );
					rgb_tex->compressed_data = rgba;
					rgb_tex->compressed_length = 3 + rgb_tex->compressed_length + a_tex->compressed_length;
				}
				rgb_tex->channels = 4;
				int stackNum, stackLevel;
				find_texture_in_stack( stacks, a_tex, &stackNum, &stackLevel );
				remove_from_texturestack( stacks, stackNum, a_tex, true );
				dispose_stacked_texture( a_tex );
				delete rgb_tex->thumbnail;
				rgb_tex->thumbnail = 0;
				edit->InvalView();
			} return 0;
		case KMenuRecompress: {
				edit->GetSelection( &sel );
				if (sel.Size() < 1) break;
				recompress_texture_data rtd;
				for (i = 0; i < sel.Size(); ++i) {
					rtd.more_coming = i < sel.Size() - 1;
					rtd.use_for_all = rtd.more_coming && i == 0;
					rtd.tex = sel[i];
					bool ok = XGDialog::ModalDialog( KWindowRecompressTexture, RecompProc, &rtd );
					if (!ok) break;
					recompress_texture( rtd.tex, rtd.out_jpeg_quality );
					if (rtd.use_for_all) {
						while (++i < sel.Size()) {
							recompress_texture( sel[i], rtd.out_jpeg_quality );
						}
					}
					edit->InvalElements( sel );
				}
			} return 0;
		case KXGEditCopy:
			CopySelected();
			return 0;
		case KXGEditCut:
			CopySelected();
			DeleteSelected();
			return 0;
		case KXGEditClear:
			DeleteSelected();
			return 0;
		case KXGEditPaste:
			Paste();
			return 0;
		default:
			break;
	}
	return -1;
}


long TtxstEditForm::DoKeyEvent( long arg, void * )
{
	short mods;
	short c;

	mods = GETHIWORD(arg);
	c = GETLOWORD(arg);
	if (mods & KSpecialKey) {
		if (c == KKeyDelete) {
			DoMenuCommand( KXGEditClear, 0 );
			return 0;
		} else {
			return -1;
		}
	} else {
		if (c == 0x08) {
			DoMenuCommand( KXGEditClear, 0 );
			return 0;
		} else {
			return -1;
		}
	}
	return -1;
}


void TtxstEditForm::ReloadTag()
{
	dispose_stackset( stacks );
	stacks = load_stackset( fEntry );
	RebuildEditorContents();
}


bool TtxstEditForm::NeedCommit()
{
	return stacks && stacks->modified;
}


void TtxstEditForm::RebuildEditorContents()
{
	edit = dynamic_cast<TtxstEditView *>(GetWindow()->FindViewByID(1));
	if (!edit) return;
	if (!stacks) return;
	edit->stacks = stacks;
	
	XGPtrSet<stacked_texture> newsel;
	
	for (int i = 0; i < stacks->used_stacks; ++i) {
		for (stacked_texture *tex = stacks->stacks[i].textures; tex; tex = tex->next) {
			if (edit->IsSelected(tex)) {
				newsel += tex;
			}
		}
	}
	edit->SetSelection( newsel );
	
	edit->SetScrollLimits( 0, stacks->used_stacks * y_size, y_size );
	edit->InvalView();
}


#pragma mark -


TtxstEditView::TtxstEditView( XGView *parent, XGArgStream &stream ): XGView( parent, stream ), XGDropper( this )
{
	stacks = 0;
	fDropStackNum = 0;
	fDropStackLevel = -1;
	fDrawDropPoint = false;
}


TtxstEditView::~TtxstEditView()
{
}



void TtxstEditView::DoDrawView( XRect bounds )
{
	if (!stacks) return;
	
//	XRect content = GetContentRect();
//	SectRect( &content
	
	XGDraw draw(this);
	draw.SetFont(XGFont::LoadFont(1));
	int lh = draw.GetFontHeight();
	
	draw.EraseRect( bounds );
	
	long x_scroll, y_scroll, step_scroll;
	GetScrollParams( &x_scroll, &y_scroll, &step_scroll );
	
	long low = y_scroll + bounds.top / step_scroll;
	long high = y_scroll + (bounds.bottom + step_scroll - 1) / step_scroll;
	if (low < 0) low = 0;
	if (high >= stacks->used_stacks) high = stacks->used_stacks - 1;
	int y = y_offset + (low - y_scroll) * step_scroll;
	while (low <= high) {
		int x = x_offset;
		for (stacked_texture *tex = stacks->stacks[low].textures; tex; tex = tex->next, x += x_size) {
			if (!tex->thumbnail) {
				make_texture_thumbnail( tex );
			}
			
			int w = tex->thumbnail->GetWidth();
			int h = tex->thumbnail->GetHeight();
			XRect r;
			r.left = x + (x_size/2) - (w / 2);
			r.top = y + (x_size/2) - (h / 2);
			r.right = r.left + w;
			r.bottom = r.top + h;
			
			if (fSelection.HasMember(tex)) {
				InsetRect( &r, -4, -4 );
				draw.FillRect( r, KXGColorHilite );
				InsetRect( &r, -1, -1 );
				draw.SetForeColor( KXGColorBlack );
				draw.FrameRect( r );
				InsetRect( &r, 4, 4 );
				draw.FrameRect( r );
			} else {
				InsetRect( &r, -1, -1 );
				//draw.FrameRect( r );
			}
			
			draw.DrawBitmap( tex->thumbnail, r.left+1, r.top+1 );
			
			r.bottom += 3;
			
			static const char *chan_name[] = { "empty", "alpha", "lum/alpha", "rgb", "rgba" };
			char buf[100];
			sprintf( buf, "%s %d x %d", chan_name[tex->channels], tex->width, tex->height );
			draw.MoveTo( x + 8, r.bottom + 1 * lh );
			draw.DrawString( buf );
			
			switch (tex->format) {
				case 0:
					strcpy( buf, "raw data" );
					break;
				case 1:
					strcpy( buf, "jpeg" );
					break;
				default:
					sprintf( buf, "format %d", tex->format );
					break;
			}
			int rawlen = tex->width * tex->height * tex->channels;
			sprintf( buf + strlen(buf), ", %.1f k (%d%%)", tex->compressed_length / 1024.0,
							(tex->compressed_length * 100 + (rawlen / 2)) / rawlen );
			draw.MoveTo( x + 8, r.bottom + 2 * lh );
			draw.DrawString( buf );
			
			switch (tex->use) {
				case txst_use_basecolor:
					sprintf( buf, "base texture" );
					break;
				case txst_use_detailmask:
					sprintf( buf, "detail mask %d", tex->layer );
					break;
				case txst_use_details:
					sprintf( buf, "detail texture %d", tex->layer );
					break;
				case txst_use_skinblend:
					sprintf( buf, "skin modifier %d", tex->layer );
					break;
				case txst_use_logomask:
					sprintf( buf, "logo mask" );
					break;
				case txst_use_fullbrightmask:
					sprintf( buf, "glow mask" );
					break;
				case txst_use_reflectmask:
					sprintf( buf, "reflect mask" );
					break;
				default:
					sprintf( buf, "use %d layer %d", tex->use, tex->layer );
					break;
			}
			draw.MoveTo( x + 8, r.bottom + 3 * lh );
			draw.DrawString( buf );
		}
		y += step_scroll;
		++low;
	}
	
	if (fDrawDropPoint) DrawDropPoint( true );
}

void TtxstEditView::InvalElements( const XGPtrSet<stacked_texture> &elems )
{
	if (!stacks) return;
	
	long x_scroll, y_scroll, step_scroll;
	GetScrollParams( &x_scroll, &y_scroll, &step_scroll );
	XRect bounds = GetContentRect();
	long low = y_scroll + bounds.top / step_scroll;
	long high = y_scroll + (bounds.bottom + step_scroll - 1) / step_scroll;
	if (low < 0) low = 0;
	if (high >= stacks->used_stacks) high = stacks->used_stacks - 1;
	int y = y_offset + (low - y_scroll) * step_scroll;
	while (low <= high) {
		int x = x_offset;
		for (stacked_texture *tex = stacks->stacks[low].textures; tex; tex = tex->next, x += x_size) {
			if (elems.HasMember(tex)) {
				XRect r;
				r.top = y;
				r.left = x;
				r.bottom = r.top + step_scroll;
				r.right = r.left + x_size;
				InvalView(&r);
			}
		}
		y += step_scroll;
		++low;
	}
}


void TtxstEditView::SetSelection( const XGPtrSet<stacked_texture> &elems )
{
	InvalElements( elems ^ fSelection );
	fSelection = elems;
}

void TtxstEditView::ClearSelection()
{
	InvalElements( fSelection );
	fSelection.Empty();
}


bool TtxstEditView::DoMouseDown(XPoint where, short flags)
{
	long x_scroll, y_scroll, step_scroll;
	GetScrollParams( &x_scroll, &y_scroll, &step_scroll );
	
	int stacknum = (where.v - y_offset) / step_scroll + y_scroll;
	int stacklevel = (where.h - x_offset) / x_size;
	stacked_texture *tex = get_texture( stacks, stacknum, stacklevel );
	if (!tex) {
		ClearSelection();
		return false;
	}
	if (flags & KKeyShift) {
		InvalElements( tex );
		if (IsSelected(tex)) {
			fSelection -= tex;
			return false;
		} else {
			fSelection += tex;
		}
	} else {
		if (!IsSelected(tex)) {
			SetSelection(tex);
		}
	}
	UpdateView();
	FindDrop( where, &fDropStackNum, &fDropStackLevel );
	
	if (tex && IsSelected( tex )) {
		if (flags & KDoubleClick) {
			texinfodata infodata;
			infodata.tex = tex;
			infodata.head = &(stacks->stacks[stacknum]);
			bool ok = XGDialog::ModalDialog( KWindowTextureUsage, TextureInfoProc, &infodata );
			// dialog proc updates tex on ok, so we don't have to do anything
			if (ok) {
				stacks->modified = true;
				InvalElements( tex );
			}
			
		} else if (XGDragger::Wait( this, where )) {
			XGDragger drag( this, false /* internal */);	
			
			int i;
			stacked_texture **dragset = new stacked_texture *[fSelection.Size()];
			for (i = 0; i < fSelection.Size(); ++i) {
				dragset[i] = fSelection[i];
			}
			{
				stacked_texture *dragged = tex;
				XRect r;
				int num, level;
				int w = dragged->thumbnail->GetWidth();
				int h = dragged->thumbnail->GetHeight();
				find_texture_in_stack( stacks, dragged, &num, &level );
				r.top = y_offset + num * y_size - y_scroll * step_scroll + (x_size/2) - (w/2);
				r.left = x_offset + level * x_size - x_scroll * step_scroll + (x_size/2) - (h/2);
				r.right = r.left + w;
				r.bottom = r.top + h;
				drag.AddObject( 1, 'txst', dragset, sizeof(stacked_texture *) * fSelection.Size(), &r );
			}
		#if 0
			for (i = 0; i < fSelection.Size(); ++i) {
				stacked_texture *dragged = dragset[i];
				XRect r;
				int num, level;
				int w = dragged->thumbnail->GetWidth();
				int h = dragged->thumbnail->GetHeight();
				find_texture_in_stack( stacks, dragged, &num, &level );
				r.top = y_offset + num * y_size - y_scroll * step_scroll + (x_size/2) - (w/2);
				r.left = x_offset + level * x_size - x_scroll * step_scroll + (x_size/2) - (h/2);
				r.right = r.left + w;
				r.bottom = r.top + h;
				drag.AddObject( i + 1, 'txst', &dragset[i], sizeof(stacked_texture *), &r );
			}
		#endif
			drag.Track();
			delete[] dragset;
		}
	}
	return false;
}


bool TtxstEditView::Accept(long /* modifiers */, long /* itemRef */, long flavor )
{
	if (flavor == KDragFlavorFile || flavor == 'txst') return true;
	
	return false;
}


void TtxstEditView::Track(XPoint where, long /* modifiers */)
{
	int drop_x, drop_y;
	FindDrop( where, &drop_y, &drop_x );
	if (drop_x != fDropStackLevel || drop_y != fDropStackNum) {
		DrawDropPoint( false );
		fDropStackNum = drop_y;
		fDropStackLevel = drop_x;
		DrawDropPoint( true );
	}
}


void TtxstEditView::Enter(XPoint where, long mods, bool hilite)
{
	XGDropper::Enter(where,mods,hilite);
	fDropStackNum = -1;
	fDrawDropPoint = true;
	Track(where,0);
}

void TtxstEditView::Leave()
{
	DrawDropPoint(false);
	fDrawDropPoint = false;
	XGDropper::Leave();
}


void TtxstEditView::BeginDrop(XPoint where, long /* mods */)
{
	FindDrop( where, &fDropStackNum, &fDropStackLevel );
	fSelection.Empty();
}


static stacked_texture *new_stacked_texture( int channels, int use, int layer, int width, int height )
{
	stacked_texture *tex = (stacked_texture *)malloc( sizeof(stacked_texture) );
	tex->next = tex->prev = 0;
	tex->compressed_data = 0;
	tex->compressed_length = 0;
	tex->thumbnail = 0;
	tex->channels = channels;
	tex->format = 0;
	tex->use = use;
	tex->layer = layer;
	tex->width = width;
	tex->height = height;
	tex->decal_corner_x = 0;
	tex->decal_corner_y = 0;
	tex->decal_ux = tex->decal_uy = 0;
	tex->decal_vx = tex->decal_vy = 0;
	return tex;
}


static stacked_texture *new_mask_texture( const byte *data, int layer, int xoff, int yoff, int in_w, int in_h, int w, int h )
{
	stacked_texture *tex = new_stacked_texture( 1, txst_use_skinblend, layer, w, h );
	tex->compressed_length = w * h;
	byte *buf = (byte *)malloc( w * h );
	tex->compressed_data = buf;
	tex->format = 0;
	memset( buf, 0, w * h );
	for (int y = 0; y < in_h; ++y) {
		for (int x = 0; x < in_w; ++x) {
			buf[xoff + x + (yoff + y) * w] = data[y * in_w + x];
		}
	}
	return tex;
}


static bool memnchr( byte *data, byte chr, int count )
{
	for (int i = 0; i < count; ++i) {
		if (data[i] != chr) return true;
	}
	return false;
}


int TtxstEditView::AddFile( int stacknum, int level, XGFileSpecifier &fs )
{
	int added = 0;
	char ext[4];
	fs.GetExtension( ext, 3 );
	
	if (strcmp(ext,"psd") == 0) {
		XGFile f;
		f.Open( &fs, false );
		long size = f.GetEOF();
		void *buf = malloc( size );
		if (!buf) return 0;
		
		if (f.Read( size, buf ) != size) {
			free(buf);
			return 0;
		}
		
		PSDImage_t psd;
		int err = PSDReadFromByteStream( &psd, (byte *)buf, 0 );
		free( buf );
		if (err != PSD_OK) return 0;
		if (psd.Width & (psd.Width-1)) {
			FreePSDImage( &psd );
			return 0;
		}
		if (psd.Height & (psd.Height-1)) {
			FreePSDImage( &psd );
			return 0;
		}
		int w = psd.Width;
		int h = psd.Height;
		int maskcount = 0;
		for (int layer = 0; layer < psd.NumLayers; ++layer) {
			stacked_texture *tex;
			if (1 || layer == 0) {
				if (psd.Layers[layer].RGB) {
					if (psd.Layers[layer].Alpha && memnchr( psd.Layers[layer].Alpha, 255, psd.Layers[layer].Width * psd.Layers[layer].Height )) {
						tex = new_stacked_texture( 4, txst_use_basecolor, 0, w, h );
						tex->compressed_length = 4 * w * h;
						byte *buf = (byte *)malloc( tex->compressed_length );
						tex->compressed_data = buf;
						for (int y = 0; y < h; ++y) {
							int yy = y;
							if (yy < psd.Layers[layer].Top) yy = psd.Layers[layer].Top;
							else if (yy >= psd.Layers[layer].Bottom) yy = psd.Layers[layer].Bottom - 1;
							for (int x = 0; x < w; ++x) {
								int xx = x;
								if (xx < psd.Layers[layer].Left) xx = psd.Layers[layer].Left;
								else if (xx >= psd.Layers[layer].Right) xx = psd.Layers[layer].Right - 1;
								byte *color = psd.Layers[layer].RGB + (((xx - psd.Layers[layer].Left) + (yy - psd.Layers[layer].Top) * psd.Layers[layer].Width) * 3);
								buf[(x+y*w)*4+0] = color[0];
								buf[(x+y*w)*4+1] = color[1];
								buf[(x+y*w)*4+2] = color[2];
								int alpha;
								if (x < psd.Layers[layer].Left || x >= psd.Layers[layer].Right || y < psd.Layers[layer].Top || y >= psd.Layers[layer].Bottom) {
									alpha = 0;
								} else {
									alpha = psd.Layers[layer].Alpha[(x - psd.Layers[layer].Left) + (y - psd.Layers[layer].Top) * psd.Layers[layer].Width];
								}
								buf[(x+y*w)*4+3] = alpha;
							}
						}
						insert_into_texturestack( stacks, stacknum, level + added, tex );
						fSelection += tex;
						++added;
						if (psd.Layers[layer].Mask && memnchr( psd.Layers[layer].Mask, 255, psd.Layers[layer].MaskWidth * psd.Layers[layer].MaskHeight )) {
							tex = new_mask_texture( psd.Layers[layer].Mask, maskcount++,
														psd.Layers[layer].MaskLeft, psd.Layers[layer].MaskTop,
														psd.Layers[layer].MaskWidth, psd.Layers[layer].MaskHeight, w, h );
							insert_into_texturestack( stacks, stacknum, level + added, tex );
							fSelection += tex;
							++added;
						}
					} else if (psd.Layers[layer].Mask && memnchr( psd.Layers[layer].Mask, 255, psd.Layers[layer].MaskWidth * psd.Layers[layer].MaskHeight )) {
						tex = new_stacked_texture( 4, txst_use_basecolor, 0, w, h );
						tex->compressed_length = 4 * w * h;
						byte *buf = (byte *)malloc( tex->compressed_length );
						tex->compressed_data = buf;
						for (int y = 0; y < h; ++y) {
							int yy = y;
							if (yy < psd.Layers[layer].Top) yy = psd.Layers[layer].Top;
							else if (yy >= psd.Layers[layer].Bottom) yy = psd.Layers[layer].Bottom - 1;
							for (int x = 0; x < w; ++x) {
								int xx = x;
								if (xx < psd.Layers[layer].Left) xx = psd.Layers[layer].Left;
								else if (xx >= psd.Layers[layer].Right) xx = psd.Layers[layer].Right - 1;
								byte *color = psd.Layers[layer].RGB + (((xx - psd.Layers[layer].Left) + (yy - psd.Layers[layer].Top) * psd.Layers[layer].Width) * 3);
								buf[(x+y*w)*4+0] = color[0];
								buf[(x+y*w)*4+1] = color[1];
								buf[(x+y*w)*4+2] = color[2];
								int alpha;
								if (x < psd.Layers[layer].MaskLeft || x >= psd.Layers[layer].MaskRight || y < psd.Layers[layer].MaskTop || y >= psd.Layers[layer].MaskBottom) {
									alpha = 0;
								} else {
									alpha = psd.Layers[layer].Mask[(x - psd.Layers[layer].MaskLeft) + (y - psd.Layers[layer].MaskTop) * psd.Layers[layer].MaskWidth];
								}
								buf[(x+y*w)*4+3] = alpha;
							}
						}
						insert_into_texturestack( stacks, stacknum, level + added, tex );
						fSelection += tex;
						++added;
					} else {
						tex = new_stacked_texture( 3, txst_use_basecolor, 0, w, h );
						tex->compressed_length = 3 * w * h;
						byte *buf = (byte *)malloc( tex->compressed_length );
						tex->compressed_data = buf;
						for (int y = 0; y < h; ++y) {
							int yy = y;
							if (yy < psd.Layers[layer].Top) yy = psd.Layers[layer].Top;
							else if (yy >= psd.Layers[layer].Bottom) yy = psd.Layers[layer].Bottom - 1;
							for (int x = 0; x < w; ++x) {
								int xx = x;
								if (xx < psd.Layers[layer].Left) xx = psd.Layers[layer].Left;
								else if (xx >= psd.Layers[layer].Right) xx = psd.Layers[layer].Right - 1;
								byte *color = psd.Layers[layer].RGB + (((xx - psd.Layers[layer].Left) + (yy - psd.Layers[layer].Top) * psd.Layers[layer].Width) * 3);
								buf[(x+y*w)*3+0] = color[0];
								buf[(x+y*w)*3+1] = color[1];
								buf[(x+y*w)*3+2] = color[2];
							}
						}
						insert_into_texturestack( stacks, stacknum, level + added, tex );
						fSelection += tex;
						++added;
					}
				} else {
					if (psd.Layers[layer].Mask && memnchr( psd.Layers[layer].Mask, 255, psd.Layers[layer].MaskWidth * psd.Layers[layer].MaskHeight )) {
						tex = new_mask_texture( psd.Layers[layer].Mask, maskcount++,
													psd.Layers[layer].MaskLeft, psd.Layers[layer].MaskTop,
													psd.Layers[layer].MaskWidth, psd.Layers[layer].MaskHeight, w, h );
						insert_into_texturestack( stacks, stacknum, level + added, tex );
						fSelection += tex;
						++added;
					} else if (psd.Layers[layer].Alpha && memnchr( psd.Layers[layer].Alpha, 255, psd.Layers[layer].Width * psd.Layers[layer].Height )) {
						tex = new_mask_texture( psd.Layers[layer].Alpha, maskcount++,
													psd.Layers[layer].Left, psd.Layers[layer].Top,
													psd.Layers[layer].Width, psd.Layers[layer].Height, w, h );
						insert_into_texturestack( stacks, stacknum, level + added, tex );
						fSelection += tex;
						++added;
					}
				}
			} else {
				if (psd.Layers[layer].Mask && memnchr( psd.Layers[layer].Mask, 255, psd.Layers[layer].MaskWidth * psd.Layers[layer].MaskHeight )) {
					tex = new_mask_texture( psd.Layers[layer].Mask, maskcount++,
												psd.Layers[layer].MaskLeft, psd.Layers[layer].MaskTop,
												psd.Layers[layer].MaskWidth, psd.Layers[layer].MaskHeight, w, h );
					insert_into_texturestack( stacks, stacknum, level + added, tex );
					++added;
				} else if (psd.Layers[layer].Alpha && memnchr( psd.Layers[layer].Alpha, 255, psd.Layers[layer].Width * psd.Layers[layer].Height )) {
					tex = new_mask_texture( psd.Layers[layer].Alpha, maskcount++,
												psd.Layers[layer].Left, psd.Layers[layer].Top,
												psd.Layers[layer].Width, psd.Layers[layer].Height, w, h );
					insert_into_texturestack( stacks, stacknum, level + added, tex );
					fSelection += tex;
					++added;
				}
			}
		}
		FreePSDImage( &psd );
	}
	return added;
}


bool TtxstEditView::Drop(XPoint /*where*/, long mods, long /*itemRef*/, long flavor, void * dataptr, long datalen)
{
	if (fDropStackNum < 0) return false;
	if (fDropStackNum > stacks->used_stacks) fDropStackNum = stacks->used_stacks;
	int count;
	switch (flavor) {
		case KDragFlavorFile: {
				XGFileSpecifier fs;
				XGDropper::MakeFileSpecFromDropData( fs, dataptr, datalen );
				if (fDropStackLevel < 0) {
					insert_texturestack_in_set( stacks, fDropStackNum );
					count = AddFile( fDropStackNum, 0, fs );
					fDropStackNum += 1;
					return (count > 0);
				} else {
					count = AddFile( fDropStackNum, fDropStackLevel, fs );
					fDropStackLevel += count;
					return (count > 0);
				}
			} break;
		case 'txst': {
				count = datalen / sizeof( stacked_texture * );
				for (int i = 0; i < count; ++i) {
					int num, level;
					stacked_texture *tex = ((stacked_texture **)dataptr)[i];
					find_texture_in_stack( stacks, tex, &num, &level );
					bool copy = ((mods & KKeyDragCopyMask) == KKeyDragCopy);
					if (num == -1 || level == -1) {
						copy = true;
					}
					if (copy) {
						tex = duplicate_stacked_texture( tex );
						if (!tex) return true;
					} else {
						if (num == fDropStackNum && level < fDropStackLevel) {
							--fDropStackLevel;
						}
						remove_from_texturestack( stacks, num, tex, false );
					}
					if (fDropStackLevel < 0) {
						insert_texturestack_in_set( stacks, fDropStackNum );
						fDropStackLevel = 0;
					}
					insert_into_texturestack( stacks, fDropStackNum, fDropStackLevel, tex );
					++fDropStackLevel;
					fSelection += tex;
				}
				return true;
			} break;
	}
	return false;
}


void TtxstEditView::EndDrop(XPoint /*where*/, long /* mods */)
{
	fDrawDropPoint = false;
	compact_stackset( stacks );
	SetScrollLimits( 0, stacks->used_stacks * y_size, y_size );
	InvalView();
}


void TtxstEditView::FindDrop( XPoint where, int *num, int *level )
{
	enum { v_slop = 35 };
	int drop_x, drop_y;
	
	long scroll_x, scroll_y, scroll_step;
	
	GetScrollParams( &scroll_x, &scroll_y, &scroll_step );
	where.h += scroll_x * scroll_step;
	where.v += scroll_y * scroll_step;
	
	if (where.v > y_offset + stacks->used_stacks * y_size) {
		drop_x = -1;
		drop_y = stacks->used_stacks;
	} else if (where.v < y_offset) {
		drop_x = -1;
		drop_y = 0;
	} else if ((where.v - y_offset + v_slop) % y_size < 2 * v_slop) {
		drop_x = -1;
		drop_y = (where.v - y_offset + v_slop) / y_size;
	} else {
		drop_x = (where.h - x_offset + x_size / 2) / x_size;
		drop_y = (where.v - y_offset) / y_size;
		if (drop_x < 0) {
			drop_x = 0;
		} else {
			int j = 0;
			stacked_texture *tex = stacks->stacks[drop_y].textures;
			while (tex) {
				++j;
				tex = tex->next;
			}
			if (drop_x > j) drop_x = j;
		}
	}
	
	*num = drop_y;
	*level = drop_x;
}


void TtxstEditView::DrawDropPoint( bool show )
{
	enum { insert_bar_width = 4, side_inset = 2 };
	
	if (fDropStackNum < 0) return;
	
	XGDraw draw( this );
	XRect content = GetContentRect();
	long scroll_x, scroll_y, scroll_step;
	GetScrollParams( &scroll_x, &scroll_y, &scroll_step );
	
	XRect r;
	if (fDropStackLevel < 0) {
		r.left = content.left;
		r.right = content.right;
		r.top = y_offset + fDropStackNum * y_size - scroll_y * scroll_step - (insert_bar_width / 2);
		r.bottom = r.top + insert_bar_width;
	} else {
		r.left = x_offset + fDropStackLevel * x_size - scroll_x * scroll_step - (insert_bar_width / 2);
		r.right = r.left + insert_bar_width;
		r.top = y_offset + fDropStackNum * y_size - scroll_y * scroll_step;
		r.bottom = r.top + y_size;
	}
	InsetRect( &content, side_inset, side_inset );
	if (SectRect( &content, &r, &r )) {
		Color color = show ? RGB(0,0,0) : RGB(255,255,255);
		draw.FillRect( r, color );
	}
}



#pragma mark -



byte_swap_code swap_txst_tagheader[] =
{
	_begin_bs_array, 1,
	
	// unsigned short count;
	_2byte,
	
	// unsigned short padding;
	2,
	
	_end_bs_array
	
	// count * txst_stackrefs follow
};


byte_swap_code swap_txst_stackref[] =
{
	_begin_bs_array, 1,
	
	//unsigned offset;	// from start of tag
	_4byte,
	
	//unsigned length;
	_4byte,
	
	// unsigned short flags
	_2byte,
	
	//unsigned short usage_chance;
	_2byte,
	
	_end_bs_array
};


byte_swap_code swap_txst_stackheader[] =
{
	_begin_bs_array, 1,
	
	// unsigned short count;
	_2byte,
	
	// unsigned short padding;
	2,
	
	_end_bs_array
	
	// count * txst_texheaders follow
};


byte_swap_code swap_txst_texheader[] =
{
	_begin_bs_array, 1,
	
	//	unsigned offset;	// from start of stack
	_4byte,
	
	//	unsigned length;
	_4byte,
	
	//	unsigned char channels;	// 0=empty (decal only), 1=alpha, 3=rgb, 4=rgba
	_byte,
	
	//	unsigned char format;	// right now only 0=raw 8-bit
	_byte,
	
	//	unsigned char use;
	_byte,
	
	//	unsigned char layer;	// for skinblends and detailmasks, 0-based
	_byte,
	
	//	unsigned short width, height;
	_2byte, _2byte,
	
	//	signed short decal_corner_x;
	_2byte,
	
	//	signed short decal_corner_y;
	_2byte,
	
	//	signed short decal_ux, decal_uy;
	_2byte, _2byte,
	
	//	signed short decal_vx, decal_vy;
	_2byte, _2byte,
	
	_end_bs_array
	
	// count * txst_texheaders follow
};


void edit_texturestack( tag_entry *entry )
{
	RegisterView(TtxstEditView);
	TtxstEditForm *form = new TtxstEditForm( entry );
	form->AttachWindow( KWindowTextureStack );
}


void defaultnew_texturestack( tag_entry *e )
{
	txst_tagheader h = { 0, 0 };
	set_entry_data( e, &h, sizeof(h), false );
}


static void mipdown( byte *pix, int &width, int &height, int channels )
{
	int y, x, c, v;
	if (width > 1) {
		if (height > 1) {
			for (y = 0; y < height >> 1; ++y) {
				for (x = 0; x < width >> 1; ++x) {
					for (c = 0; c < channels; ++c) {
						v = pix[(y * 2 * width + x * 2) * channels + c] +
							pix[(y * 2 * width + x * 2 + 1) * channels + c] +
							pix[((y * 2 + 1) * width + x * 2) * channels + c] +
							pix[((y * 2 + 1) * width + x * 2 + 1) * channels + c];
						pix[(y * (width>>1) + x) * channels + c] = (v+2) >> 2;
					}
				}
			}
			width >>= 1;
			height >>= 1;
		} else {
			for (x = 0; x < width >> 1; ++x) {
				for (c = 0; c < channels; ++c) {
					v = pix[(x * 2) * channels + c] +
						pix[(width + x * 2 + 1) * channels + c];
					pix[x * channels + c] = (v+1) >> 1;
				}
			}
			width >>= 1;
		}
	} else {
		if (height > 1) {
			for (y = 0; y < height >> 1; ++y) {
				for (c = 0; c < channels; ++c) {
					v = pix[(y * 2 * width) * channels + c] +
						pix[((y * 2 + 1) * width) * channels + c];
					pix[(y * (width>>1)) * channels + c] = (v+1) >> 1;
				}
			}
			height >>= 1;
		}
	}
}

void make_texture_thumbnail( stacked_texture *tex )
{
	if (!tex || !tex->compressed_data) return;
	if (tex->thumbnail) {
		delete tex->thumbnail;
		tex->thumbnail = 0;
	}
	
	int width = tex->width, height = tex->height;
	byte *pix = (byte *)malloc( width * height * tex->channels );
	if (!pix) return;
	if (tex->format == 1) {
		decompress_jpeg( pix, tex->compressed_data, tex->compressed_length, width, height, tex->channels, false );
	} else {
		memcpy( pix, tex->compressed_data, width * height * tex->channels );
	}
	while (width > 128 || height > 128) {
		mipdown( pix, width, height, tex->channels );
	}
	tex->thumbnail = new XGBitmap();
	if (!tex->thumbnail) {
		free(pix);
		return;
	}
	switch (tex->channels) {
		case 1:
			tex->thumbnail->SetBitmapAlpha( pix, width, height );
			break;
		case 3:
			tex->thumbnail->SetBitmapRGB( pix, width, height );
			break;
		case 4:
			tex->thumbnail->SetBitmapRGBA( pix, width, height );
			break;
	}
	free(pix);
}


void compact_stackset( stack_set *ss )
{
	int in, out;
	for (in = 0, out = 0; in < ss->used_stacks; ++in) {
		if (ss->stacks[in].textures) {
			ss->stacks[out] = ss->stacks[in];
			++out;
		} else {
			ss->modified = true;
		}
	}
	ss->used_stacks = out;
	memset( ss->stacks + out, 0, sizeof(texture_stack_head) * (ss->avail_stacks - out) );
}

stack_set *load_stackset( tag_entry *e )
{
	txst_tagheader th;
	txst_stackref sr;
	txst_stackheader sh;
	txst_texheader tex;
	
	if (!e || get_entry_type(e) != 'txst') return 0;
	byte *raw = (byte *)get_entry_data( e );
	if (!raw) return 0;
	
	byte_swap_move_be( "txst_tagheader", &th, raw, sizeof(th), 1, swap_txst_tagheader );
	
	stack_set *ss = new_empty_stackset( th.count + 20 );
	if (!ss) return 0;
	
	txst_stackref *insr = (txst_stackref *)(raw + sizeof(th));
	for (int i = 0; i < th.count; ++i) {
		byte_swap_move_be( "txst_stackref", &sr, insr + i, sizeof(sr), 1, swap_txst_stackref );
		
		unsigned flags = sr.flags;
		unsigned usage_chance = sr.usage_chance;
				
		byte *stack = raw + sr.offset;
		byte_swap_move_be( "txst_stackheader", &sh, stack, sizeof(sh), 1, swap_txst_stackheader );
		txst_texheader *intex = (txst_texheader *)(stack + sizeof(sh));
		for (int j = 0; j < sh.count; ++j) {
			byte_swap_move_be( "txst_texheader", &tex, intex + j, sizeof(tex), 1, swap_txst_texheader );
			stacked_texture *st = (stacked_texture *)( malloc(sizeof(stacked_texture) ));
			if (!st) {
				dispose_stackset( ss );
				return 0;
			}
			void *buf;
			st->compressed_data = buf = malloc(tex.length);
			st->compressed_length = tex.length;
			insert_into_texturestack( ss, i, j, st );
			if (!buf) {
				dispose_stackset( ss );
				return 0;
			}
			memcpy( buf, stack + tex.offset, tex.length );
			st->thumbnail = 0;
			st->channels = tex.channels;
			st->format = tex.format;
			st->use = tex.use;
			st->layer = tex.layer;
			st->width = tex.width;
			st->height = tex.height;
			st->decal_corner_x = tex.decal_corner_x;
			st->decal_corner_y = tex.decal_corner_y;
			st->decal_ux = tex.decal_ux;
			st->decal_uy = tex.decal_uy;
			st->decal_vx = tex.decal_vx;
			st->decal_vy = tex.decal_vy;
		}
		ss->stacks[i].flags = flags;
		ss->stacks[i].usage_chance = usage_chance;
	}
	
	compact_stackset( ss );
	
	ss->modified = false;
	ss->entry = e;
	
	return ss;
}


void save_stackset( tag_entry *entry, stack_set *ss )
{
	txst_tagheader taghead;
	txst_stackref stackref;
	txst_stackheader stackhead;
	txst_texheader texhead;
	
	int i;
	stacked_texture *tex;
	
	if (!entry || !ss || !ss->stacks) return;
	int total = sizeof(txst_tagheader) + ss->used_stacks * sizeof(txst_stackref) + ss->used_stacks * sizeof(txst_stackheader);
	for (i = 0; i < ss->used_stacks; ++i) {
		for (tex = ss->stacks[i].textures; tex; tex = tex->next) {
			if (!tex->compressed_data || tex->compressed_length <= 0) return;
			total += sizeof(txst_texheader) + tex->compressed_length;
		}
	}
	byte *buf = (byte *)malloc( total );
	if (!buf) return;
	
	taghead.count = ss->used_stacks;
	byte_swap_move_be( "txst_tagheader", buf, &taghead, sizeof(taghead), 1, swap_txst_tagheader );
	
	unsigned off1 = sizeof(txst_tagheader);
	unsigned off2 = off1 + ss->used_stacks * sizeof(txst_stackref);
	
	for (i = 0; i < ss->used_stacks; ++i) {
		int stacktot = sizeof(txst_stackheader);
		int stackcount = 0;
		for (tex = ss->stacks[i].textures; tex; tex = tex->next) {
			stackcount += 1;
			stacktot += sizeof(txst_texheader) + tex->compressed_length;
		}
		stackref.offset = off2;
		stackref.length = stacktot;
		stackref.flags = ss->stacks[i].flags;
		stackref.usage_chance = ss->stacks[i].usage_chance;
		byte_swap_move_be( "txst_stackref", buf + off1, &stackref, sizeof(stackref), 1, swap_txst_stackref );
		off1 += sizeof(stackref);
		
		unsigned off3 = 0;
		stackhead.count = stackcount;
		byte_swap_move_be( "txst_stackheader", buf + off2, &stackhead, sizeof(stackhead), 1, swap_txst_stackheader );
		off3 += sizeof(stackhead);
		
		unsigned off4 = off3 + stackcount * sizeof(txst_texheader);
		for (tex = ss->stacks[i].textures; tex; tex = tex->next) {
			texhead.offset = off4;
			texhead.length = tex->compressed_length;
			texhead.channels = tex->channels;
			texhead.format = tex->format;
			texhead.use = tex->use;
			texhead.layer = tex->layer;
			texhead.width = tex->width;
			texhead.height = tex->height;
			texhead.decal_corner_x = tex->decal_corner_x;
			texhead.decal_corner_y = tex->decal_corner_y;
			texhead.decal_ux = tex->decal_ux;
			texhead.decal_uy = tex->decal_uy;
			texhead.decal_vx = tex->decal_vx;
			texhead.decal_vy = tex->decal_vy;
			byte_swap_move_be( "txst_texheader", buf + off2 + off3, &texhead, sizeof(texhead), 1, swap_txst_texheader );
			off3 += sizeof(texhead);
			
			memcpy( buf + off2 + off4, tex->compressed_data, tex->compressed_length );
			off4 += tex->compressed_length;
		}
		if (off3 != sizeof(txst_stackheader) + stackcount * sizeof(txst_texheader) || off4 != stacktot) {
			free( buf );
			return;
		}
		off2 += stacktot;
	}
	
	if (off1 != sizeof(txst_tagheader) + ss->used_stacks * sizeof(txst_stackref) || off2 != total) {
		free( buf );
		return;
	}
	
	set_entry_data( entry, buf, total, true );
	ss->modified = false;
	ss->entry = entry;
}


void dispose_stackset( stack_set *ss )
{
	if (!ss) return;
	
	if (ss->stacks) {
		for (int i = 0; i < ss->used_stacks; ++i) {
			for (stacked_texture *tex = ss->stacks[i].textures, *next; tex; tex = next) {
				next = tex->next;
				dispose_stacked_texture( tex );
			}
		}
		free( ss->stacks );
	}
	
	free( ss );
}



stack_set *new_empty_stackset( int maxstacks )
{
	stack_set *ss = (stack_set *)malloc(sizeof(stack_set));
	if (!ss) return 0;
	ss->entry = 0;
	ss->used_stacks = 0;
	ss->avail_stacks = maxstacks;
	ss->modified = true;
	ss->stacks = (texture_stack_head *)calloc( maxstacks, sizeof(texture_stack_head) );
	if (!ss->stacks) {
		dispose_stackset( ss );
		return 0;
	}
	return ss;
}


void insert_texturestack_in_set( stack_set *ss, int index )
{
	if (!ss) return;
	ss->modified = true;
	if (index >= ss->avail_stacks || ss->used_stacks + 1 > ss->avail_stacks) {
		int newcount = ss->used_stacks + 1;
		if (newcount <= index) newcount = index + 1;
		newcount += 20;
		texture_stack_head *newstacks = (texture_stack_head*)calloc( newcount, sizeof(texture_stack_head) );
		if (!newstacks) {
			return;
		}
		memcpy( newstacks, ss->stacks, sizeof(texture_stack_head) * ss->used_stacks );
		free( ss->stacks );
		ss->stacks = newstacks;
		ss->avail_stacks = newcount;
	}
	if (index < ss->used_stacks) {
		memmove( ss->stacks + index + 1, ss->stacks + index, sizeof(texture_stack_head) * (ss->used_stacks - index) );
		++ss->used_stacks;
	} else {
		memset( ss->stacks + ss->used_stacks, 0, sizeof(texture_stack_head) * (index - ss->used_stacks) );
		ss->used_stacks = index + 1;
	}
	ss->stacks[index].textures = 0;
	ss->stacks[index].flags = 0;
	ss->stacks[index].usage_chance = 1;
}


int insert_into_texturestack( stack_set *ss, int index, int pos, stacked_texture *tex )
{
	if (!ss || !tex) return -1;
	ss->modified = true;
	if (index >= ss->used_stacks) {
		insert_texturestack_in_set( ss, index );
		if (index >= ss->used_stacks) {
			return -1;
		}
	}
	if (ss->stacks[index].textures == 0 || pos == 0) {
		LLADD( ss->stacks[index].textures, tex );
		return 0;
	}
	int step = 1;
	stacked_texture *last = ss->stacks[index].textures;
	while (step != pos && last->next) {
		last = last->next;
		++step;
	}
	LLAFTER( last, tex );
		
	return step;
}


void remove_from_texturestack( stack_set *ss, int index, stacked_texture *tex, bool collapse_set )
{
	if (!ss || !ss->stacks || !tex || index >= ss->used_stacks) return;
	ss->modified = true;
	for (stacked_texture *scan = ss->stacks[index].textures; scan; scan = scan->next) {
		if (scan == tex) {
			LLDEL( ss->stacks[index].textures, tex );
			if (collapse_set && !ss->stacks[index].textures) {
				memmove( ss->stacks + index, ss->stacks + index + 1, sizeof(texture_stack_head) * (ss->used_stacks - index - 1) );
				ss->used_stacks -= 1;
			}
		}
	}
}


#define PIECESIZE 128

stack_set *import_psd_colormap( XGFileSpecifier *fs )
{
	XGFile f;
	f.Open( fs, false );
	long size = f.GetEOF();
	void *buf = malloc( size );
	if (!buf) return 0;
	
	if (f.Read( size, buf ) != size) {
		free(buf);
		return 0;
	}
	
	PSDImage_t psd;
	int err = PSDReadFromByteStream( &psd, (byte *)buf, 0 );
	free( buf );
	if (err != PSD_OK) return 0;
	
	int xparts = (psd.Width + PIECESIZE - 2 - 1 - 2) / (PIECESIZE - 2);
	int yparts = (psd.Height + PIECESIZE - 2 - 1 - 2) / (PIECESIZE - 2);
	int xoffset = -1;
	int yoffset = -1;
	if (xparts * (PIECESIZE - 2) < psd.Width) {
		xoffset += (psd.Width - xparts * (PIECESIZE - 2)) / 2;
	}
	if (yparts * (PIECESIZE - 2) < psd.Height) {
		yoffset += (psd.Height - yparts * (PIECESIZE - 2)) / 2;
	}
	stack_set *ss = new_empty_stackset( xparts * yparts );
	if (!ss) {
		FreePSDImage( &psd );
		return 0;
	}
	
	Image_t *image = PSDMakeImageFromLayer( psd.Layers + 0, PSD_FLAG_RGB );
	for (int y = 0; y < yparts; ++y) {
		for (int x = 0; x < xparts; ++x) {
			stacked_texture *tex = (stacked_texture *)malloc(sizeof(stacked_texture));
			if (!tex) {
				dispose_stackset( ss );
				return 0;
			}
			int complength = 3 * PIECESIZE * PIECESIZE;
			byte *out = (byte *)malloc( complength );
			tex->compressed_data = out;
			if (!tex->compressed_data) {
				dispose_stackset( ss );
				return 0;
			}
			tex->compressed_length = complength;
			tex->thumbnail = 0;
			tex->channels = 3;
			tex->format = 0;
			tex->use = txst_use_basecolor;
			tex->layer = 0;
			tex->width = PIECESIZE;
			tex->height = PIECESIZE;
			tex->decal_corner_x = tex->decal_corner_y = 0;
			tex->decal_ux = tex->decal_uy = 0;
			tex->decal_vx = tex->decal_vy = 0;
			for (int yy = 0; yy < PIECESIZE; ++yy) {
				int ypix = yoffset + y * (PIECESIZE - 2) + yy;
				if (ypix < image->y) ypix = image->y;
				if (ypix >= image->y + image->height) ypix = image->y + image->height - 1;
				byte *in = image->data + ((ypix - image->y) * image->width - image->x) * 3;
				for (int xx = 0; xx < PIECESIZE; ++xx) {
					int xpix = xoffset + x * (PIECESIZE - 2) + xx;
					if (xpix < image->x) xpix = image->x;
					if (xpix >= image->x + image->width) xpix = image->x + image->width - 1;
					out[0] = in[xpix*3+0];
					out[1] = in[xpix*3+1];
					out[2] = in[xpix*3+2];
					out += 3;
				}
			}
//			recompress_texture( tex, 95 );
			recompress_texture( tex, 85 );
			insert_into_texturestack( ss, y * xparts + x, 0, tex );
		}
	}
	free( image->data );
	free( image );
	
	for (int i = 1, taglayer = 0; i < psd.NumLayers; ++i) {
		Image_t *image = PSDMakeImageFromLayer( psd.Layers + i, PSD_FLAG_MASK );
		if (!image) continue;
		for (int y = 0; y < yparts; ++y) {
			for (int x = 0; x < xparts; ++x) {
				stacked_texture *tex = (stacked_texture *)malloc(sizeof(stacked_texture));
				if (!tex) {
					dispose_stackset( ss );
					return 0;
				}
				int complength = PIECESIZE * PIECESIZE;
				byte *out = (byte *)malloc( complength );
				tex->compressed_data = out;
				if (!tex->compressed_data) {
					free( tex );
					dispose_stackset( ss );
					return 0;
				}
				tex->thumbnail = 0;
				tex->compressed_length = complength;
				tex->channels = 1;
				tex->format = 0;
				tex->use = txst_use_detailmask;
				tex->layer = taglayer;
				tex->width = PIECESIZE;
				tex->height = PIECESIZE;
				tex->decal_corner_x = tex->decal_corner_y = 0;
				tex->decal_ux = tex->decal_uy = 0;
				tex->decal_vx = tex->decal_vy = 0;
				int nonzero = 0;
				for (int yy = 0; yy < PIECESIZE; ++yy) {
					int ypix = yoffset + y * (PIECESIZE - 2) + yy;
					if (ypix < image->y || ypix >= image->y + image->height) {
						for (int xx = 0; xx < PIECESIZE; ++xx) {
							*out++ = 0;
						}
					} else {
						byte *in = image->data + ((ypix - image->y) * image->width - image->x) * 1;
						for (int xx = 0; xx < PIECESIZE; ++xx) {
							int xpix = xoffset + x * (PIECESIZE - 2) + xx;
							if (xpix < image->x || xpix >= image->x + image->width) {
								*out++ = 0;
							} else {
								nonzero |= in[xpix];
								*out++ = in[xpix];
							}
						}
					}
				}
				if (nonzero & 0xf8) {
					recompress_texture( tex, 85 );
					insert_into_texturestack( ss, y * xparts + x, -1, tex );
				} else {
					dispose_stacked_texture( tex );
				}
			}
		}
		++taglayer;
		free( image->data );
		free( image );
	}
		
	FreePSDImage( &psd );
	return ss;
}


stacked_texture *get_texture( stack_set *ss, int stacknum, int stacklevel )
{
	if (!ss) return 0;
	if (stacklevel < 0 || stacknum < 0 || stacknum >= ss->used_stacks) return 0;
	stacked_texture *tex = ss->stacks[stacknum].textures;
	while (tex && stacklevel--) {
		tex = tex->next;
	}
	return tex;
}


stacked_texture *duplicate_stacked_texture( const stacked_texture *tex )
{
	stacked_texture *copy = (stacked_texture *)malloc( sizeof(stacked_texture) );
	if (!copy) return 0;
	memcpy( copy, tex, sizeof(stacked_texture) );
	copy->next = copy->prev = 0;
	copy->thumbnail = 0;
	void *buf = malloc( tex->compressed_length );
	if (!buf) {
		free( copy );
		return 0;
	}
	memcpy( buf, tex->compressed_data, copy->compressed_length );
	copy->compressed_data = buf;
	return copy;
}


void dispose_stacked_texture( stacked_texture *tex )
{
	if (tex->compressed_data) {
		free( (void *)(tex->compressed_data) );
	}
	if (tex->thumbnail) {
		delete tex->thumbnail;
	}
	free( tex );
}


void recompress_texture( stacked_texture *tex, int quality )
{
	void *raw = malloc( tex->width * tex->height * tex->channels );
	if (tex->format == 1) {
		raw = malloc( tex->width * tex->height * tex->channels );
		decompress_jpeg( raw, tex->compressed_data, tex->compressed_length, tex->width, tex->height, tex->channels, true );
	} else {
		raw = (void *)(tex->compressed_data);
	}
	if (quality > 100 || quality < 1) {
		if (raw != tex->compressed_data) {
			free( (void *)(tex->compressed_data) );
			tex->compressed_data = raw;
		}
		tex->compressed_length = tex->width * tex->height * tex->channels;
		tex->format = 0;
	} else {
		int jsize = tex->width * tex->height * tex->channels * 3;
		void *jpeg = malloc( jsize );
		compress_jpeg( jpeg, &jsize, raw, tex->width, tex->height, tex->channels, quality );
		if (raw != tex->compressed_data) {
			free( raw );
		}
		if (jsize * 100 > tex->compressed_length * 95 && jsize + 1000 > tex->compressed_length) {
			free( jpeg );
			// and leave the texture alone
		} else {
			free( (void *)tex->compressed_data );
			tex->compressed_data = malloc( jsize );
			memcpy( (void *)tex->compressed_data, jpeg, jsize );
			tex->compressed_length = jsize;
			tex->format = 1;
		}
	}
}


void find_texture_in_stack( const stack_set *stacks, const stacked_texture *tex, int *outStackNum, int *outStackLevel )
{
	for (int num = 0; num < stacks->used_stacks; ++num) {
		int level = 0;
		for (const stacked_texture *scan = stacks->stacks[num].textures; scan; scan = scan->next) {
			if (tex == scan) {
				*outStackNum = num;
				*outStackLevel = level;
				return;
			}
			++level;
		}
	}
	*outStackNum = -1;
	*outStackLevel = -1;
}


