// TILEMAPP, basic tilemap tool by CNGSOFT //
// since 20181013-1414 until 20181014-0055 //
// supertiles: '190930-1200 - '190130-0940 //
// h/v flipping: '200803-1120 //

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define WIN32_LEAN_AND_MEAN 1
#include <windows.h>
//#include <wingdi.h>

unsigned char tilemap[1<<14],suptile[1<<14],clipbrd[1<<14],special[1<<8];
int winlenx,winleny,winmulx,winmuly,winmulz,windivz;
int maplenx,mapleny,map_x_x,map_x_y,map_y_x,map_y_y;
int dibposx,dibposy,diblenx,dibleny;
int dib_x_x,dib_x_y,dib_y_x,dib_y_y;
int dibmaxx,dibmaxy,suplenx,supleny;
int sup_x_x,sup_x_y,sup_y_x,sup_y_y;
int suptiles=0,suptile0=0,clip_x=0,clip_y=0;
int winposx=0,winposy=0,mapposx=0,mapposy=0,selposx=0,selposy=0;
FILE *f; int scanfail=0,dirty=0;

inline int minimum(int x,int y) { return x<y?x:y; }
inline int maximum(int x,int y) { return x>y?x:y; }
inline int within(int x,int y,int z) { return x<=y&&y<=z; }

char filemapname[256];
int filemapload(void)
{
	if (f=fopen(filemapname,"rb"))
	{
		int i=fread(tilemap,1,maplenx*mapleny,f);
		fclose(f);
		while (i>0)
			tilemap[--i]&=suptiles;
	}
	return !!f;
}
int filemapsave(void)
{
	if (!dirty)
		return -1;
	if (f=fopen(filemapname,"wb"))
	{
		fwrite(tilemap,1,maplenx*mapleny,f);
		fclose(f);
	}
	return !!f;
}

HBITMAP tilebm; HDC tiledc=0;
void wndproc_supertile(HDC hdc,int x,int y,int z,int a)
{
	z*=suplenx*supleny;
	int xx,yy,zz,lx=diblenx*winmulx*winmulz/windivz,ly=dibleny*winmuly*winmulz/windivz;
	for (yy=0;yy<supleny;++yy)
		for (xx=0;xx<suplenx;++xx)
		{
			zz=suptile[z++];
			BitBlt(hdc,
				(((x*suplenx+xx)*sup_x_x)+((y*supleny+yy)*sup_y_x))*lx,
				(((x*suplenx+xx)*sup_x_y)+((y*supleny+yy)*sup_y_y))*ly,
				lx,ly,tiledc,
				(dibposx+(zz%dibmaxx)*dib_x_x+(zz/dibmaxx)*dib_x_y)*winmulx*winmulz/windivz,
				(dibposy+(zz%dibmaxx)*dib_y_x+(zz/dibmaxx)*dib_y_y)*winmuly*winmulz/windivz,
				a);//,diblenx,dibleny
		}
}
void wndproc_cursor(HDC hdc,int x,int y,int z)
{
	int lx=diblenx*winmulx*(suplenx*sup_x_x+supleny*sup_y_x)*winmulz/windivz;
	int ly=dibleny*winmuly*(suplenx*sup_x_y+supleny*sup_y_y)*winmulz/windivz;
	int tx=(((x)*sup_x_x)+((y)*sup_y_x))*lx;
	int ty=(((x)*sup_x_y)+((y)*sup_y_y))*ly;
	int sx=tx+lx,sy=ty+ly,wx=lx/(z?4:1),wy=ly/(z?1:4);
	Rectangle(hdc,tx,ty,tx+wx,ty+wy);
	Rectangle(hdc,sx-wx,sy-wy,sx,sy);
}
char str[256];
LRESULT CALLBACK wndproc(HWND hwnd,UINT msg,WPARAM wparam,LPARAM lparam)
{
	int x,y,z;
	int minx=minimum(mapposx,selposx),miny=minimum(mapposy,selposy),maxx=maximum(mapposx,selposx),maxy=maximum(mapposy,selposy);
	switch(msg)
	{
		case WM_CLOSE:
			DestroyWindow(hwnd);
			break;
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
		case WM_PAINT:
			{
				PAINTSTRUCT ps; RECT rt;
				HDC hdc=BeginPaint(hwnd,&ps);
				if (!tiledc)
					tiledc=CreateCompatibleDC(hdc),SelectObject(tiledc,tilebm);
				//HGDIOBJ context=SelectObject(tiledc,tilebm);
				for (y=0;y<winleny;++y)
					for (x=0;x<winlenx;++x)
						z=tilemap[(y+winposy)*maplenx+x+winposx],
						wndproc_supertile(hdc,2+x,y,z,((within(minx,x+winposx,maxx)&&within(miny,y+winposy,maxy))^!!special[z])?NOTSRCCOPY:SRCCOPY);
				wndproc_cursor(hdc,2+mapposx-winposx,mapposy-winposy,0);
				for (y=0;y<winleny;++y)
					wndproc_supertile(hdc,1,y,0,SRCCOPY);
				z=suptile0-(winleny-1)/2;
				for (y=0;y<winleny;++y,++z)
					z&=suptiles,wndproc_supertile(hdc,0,y,z,special[z]?NOTSRCCOPY:SRCCOPY);
				wndproc_cursor(hdc,0,(winleny-1)/2,1);
				//SelectObject(tiledc,context);
				//DeleteDC(tiledc);
				EndPaint(hwnd,&ps);
			}
			break;
		case WM_KEYDOWN:
			{
				int GUI_CONTROL=GetKeyState(VK_CONTROL)<0,GUI_SHIFT=GetKeyState(VK_SHIFT)<0,q=0;
				switch (wparam)
				{
					case VK_F1:
						MessageBox(hwnd,
							"Up\t\\\nDown\tMove cursor\nLeft\t(+shift: select area)\nRight\t/\n"
							"Prior\t\\\nNext\tMove cursor quickly\nHome\t(+shift: select area)\nEnd\t/\n"
							"Space\tPut tile on cursor on area\n"
							"Return\tGet tile from cursor\n"
							"^Up\t\\\n^Left\tSelect prev. tile\n"
							"^Down\t\\\n^Right\tSelect next tile\n"
							"^Prior\t\\\n^Home\tSelect prev. tile quickly\n"
							"^Next\t\\\n^End\tSelect next tile quickly\n"
							"^C\tCopy area to clipboard\n"
							"^X\tCut area to clipboard\n"
							"^V\tPaste area from clipboard\n"
							"^H\tReplace tiles\n\t(+shift: swap tiles)\n"
							"^M\tFlip area horizontally\n\t(+shift: flip vertically)\n"
							"^R\tReload map\n"
							"^S\tSave map\n"
							"Escape\tSave mapand exit\n"
						,"TILEMAPP",MB_OK);
						break;
					case VK_ESCAPE:
						DestroyWindow(hwnd);
						break;
					case VK_UP:
						if (GUI_CONTROL)
							--suptile0;
						else
							q=1,mapposx-=map_x_y,mapposy-=map_y_y;
						break;
					case VK_DOWN:
						if (GUI_CONTROL)
							++suptile0;
						else
							q=1,mapposx+=map_x_y,mapposy+=map_y_y;
						break;
					case VK_LEFT:
						if (GUI_CONTROL)
							--suptile0;
						else
							q=1,mapposx-=map_x_x,mapposy-=map_y_x;
						break;
					case VK_RIGHT:
						if (GUI_CONTROL)
							++suptile0;
						else
							q=1,mapposx+=map_x_x,mapposy+=map_y_x;
						break;
					case VK_PRIOR:
						if (GUI_CONTROL)
							suptile0-=16;
						else
							q=1,mapposx-=16*map_x_y,mapposy-=16*map_y_y;
						break;
					case VK_NEXT:
						if (GUI_CONTROL)
							suptile0+=16;
						else
							q=1,mapposx+=16*map_x_y,mapposy+=16*map_y_y;
						break;
					case VK_HOME:
						if (GUI_CONTROL)
							suptile0-=16;
						else
							q=1,mapposx-=16*map_x_x,mapposy-=16*map_y_x;
						break;
					case VK_END:
						if (GUI_CONTROL)
							suptile0+=16;
						else
							q=1,mapposx+=16*map_x_x,mapposy+=16*map_y_x;
						break;
					case VK_RETURN:
						suptile0=tilemap[mapposy*maplenx+mapposx];
						break;
					case VK_SPACE:
						for (y=minimum(mapposy,selposy);y<=maxy;++y)
							for (x=minimum(mapposx,selposx);x<=maxx;++x)
								tilemap[y*maplenx+x]=suptile0;
						q=dirty=1;
						break;
					case 'C':
					case 'X':
						if (GUI_CONTROL)
						{
							dirty=(wparam=='X');
							clip_y=maxy-miny+1;
							clip_x=maxx-minx+1;
							for (y=0;y<clip_y;++y)
								for (x=0;x<clip_x;++x)
								{
									clipbrd[y*clip_x+x]=tilemap[(miny+y)*maplenx+minx+x];
									if (dirty)
										tilemap[(miny+y)*maplenx+minx+x]=suptile0;
								}
						}
						break;
					case 'V':
						if (GUI_CONTROL)
						{
							dirty=(clip_y&&clip_x);
							for (y=0;y<clip_y;++y)
								if (miny+y<mapleny)
									for (x=0;x<clip_x;++x)
										if (minx+x<maplenx)
											tilemap[(miny+y)*maplenx+minx+x]=clipbrd[y*clip_x+x];
						}
						break;
					case 'H': q=1; // replace / swap
						if (GUI_CONTROL&&(suptile0!=(z=tilemap[mapposy*maplenx+mapposx])))
						{
							dirty=1;
							for (x=0;x<sizeof(tilemap);++x)
								if (tilemap[x]==z)
									tilemap[x]=suptile0;
								else if (GUI_SHIFT&&(tilemap[x]==suptile0))
									tilemap[x]=z;
							if (GUI_SHIFT)
								suptile0=z;
						}
						break;
					case 'M': // flip H/V
						if (GUI_CONTROL)
						{
							if (GUI_SHIFT) // flip V
							{
								if (dirty=(miny!=maxy))
								{
									int gapy=maxy+miny;
									for (x=minx;x<=maxx;++x)
										for (y=(miny+maxy+1)/2;y<=maxy;++y)
										{
											z=tilemap[y*maplenx+x];
											tilemap[y*maplenx+x]=tilemap[(gapy-y)*maplenx+x];
											tilemap[(gapy-y)*maplenx+x]=z;
										}
								}
							}
							else // flip H
							{
								if (dirty=(minx!=maxx))
								{
									int gapx=maxx+minx;
									for (y=miny;y<=maxy;++y)
										for (x=(minx+maxx+1)/2;x<=maxx;++x)
										{
											z=tilemap[y*maplenx+x];
											tilemap[y*maplenx+x]=tilemap[y*maplenx+gapx-x];
											tilemap[y*maplenx+gapx-x]=z;
										}
								}
							}
						}
						break;
					case 'R':
						filemapload();
						dirty=0;
						break;
					case 'S':
						filemapsave();
						dirty=0;
						break;
					default:
						q=-1;
						return DefWindowProc(hwnd,msg,wparam,lparam);
						break;
				}
				if (mapposx<0) mapposx=0;
				if (mapposx>=maplenx) mapposx=maplenx-1;
				if (mapposy<0) mapposy=0;
				if (mapposy>=mapleny) mapposy=mapleny-1;
				//mapposx=(mapposx+maplenx)%maplenx;
				//mapposy=(mapposy+mapleny)%mapleny;
				suptile0&=suptiles;
				if (mapposx<winposx) winposx=mapposx;
				if (mapposy<winposy) winposy=mapposy;
				if (mapposx>=winposx+winlenx) winposx=mapposx-winlenx+1;
				if (mapposy>=winposy+winleny) winposy=mapposy-winleny+1;
				if (q>0)
					if (!GUI_SHIFT)
						selposx=mapposx,selposy=mapposy;
				InvalidateRect(hwnd,0,0);//UpdateWindow(hwnd);
				if (q>=0)
					sprintf(str,"%04i:%04i=%03i | %03i",mapposx,mapposy,tilemap[mapposy*maplenx+mapposx],suptile0);
			}
			break;
		default:
			return DefWindowProc(hwnd,msg,wparam,lparam);
	}
	if (*str)
		SetWindowText(hwnd,str),*str=0;
	return 0;
}

int scanfile(void)
{
	int i=0,c,s=1;
	if (!scanfail&&(c=fgetc(f))!=EOF)
	{
		while (c==' ')
			c=fgetc(f);
		while (c=='-')
			s=-s,c=fgetc(f);
		while (c>='0'&&c<='9')
		{
			i=i*10+(c-'0');
			c=fgetc(f);
		}
		while (c==' ')
			c=fgetc(f);
		if (c!=EOF&&c!=','&&c!=10)
			while ((c=fgetc(f))!=EOF&&c!=10)
				{}
	}
	else
		scanfail=1;
	return i*s;
}

char cannotopen[]="error: cannot open %s!\n",str1[256],str2[256],str3[256];
char caption[]="TILEMAPP",scratch[256];

#define usagebox(...) (sprintf(scratch,__VA_ARGS__),MessageBox(0,scratch,caption,MB_ICONINFORMATION))
#define errorbox(...) (sprintf(scratch,__VA_ARGS__),MessageBox(0,scratch,caption,MB_ICONSTOP))

int main(int argc,char *argv[])
{
	char *s;
	int i,x,y,z;
	if (argc<4||argc>5||!strchr(argv[2],'.'))
		return usagebox("usage: %s <file.dib|-> <file.til> <file.map|-> [file.mpp|-]\n",argv[0]),1;
	if (!strcmp(argv[1],"-"))
		strcpy(str1,argv[2]),strcpy(strrchr(str1,'.'),".dib"),argv[1]=str1;
	if (!strcmp(argv[3],"-"))
		strcpy(str2,argv[2]),strcpy(strrchr(str2,'.'),".map"),argv[3]=str2;
	if (argc>4&&!strcmp(argv[4],"-"))
		strcpy(str3,argv[2]),strcpy(strrchr(str3,'.'),".mpp"),argv[4]=str3;
	if (!(tilebm=LoadImage(NULL,s=argv[1],IMAGE_BITMAP,0,0,LR_LOADFROMFILE)))
		return errorbox(cannotopen,s),1;
	if (!(f=fopen(s=argv[2],"r")))
		return errorbox(cannotopen,s),1;
	winlenx=scanfile(); winleny=scanfile();
	winmulx=scanfile(); winmuly=scanfile(); winmulz=scanfile(); windivz=scanfile();
	maplenx=scanfile(); mapleny=scanfile();
	map_x_x=scanfile(); map_x_y=scanfile(); map_y_x=scanfile(); map_y_y=scanfile();
	dibposx=scanfile(); dibposy=scanfile(); diblenx=scanfile(); dibleny=scanfile();
	dib_x_x=scanfile(); dib_x_y=scanfile(); dib_y_x=scanfile(); dib_y_y=scanfile();
	dibmaxx=scanfile(); dibmaxy=scanfile();
	suplenx=scanfile(); supleny=scanfile();
	sup_x_x=scanfile(); sup_x_y=scanfile(); sup_y_x=scanfile(); sup_y_y=scanfile();
	printf("map: %ix%i [%ix%i]\n",maplenx,mapleny,suplenx,supleny);
	z=y=x=0;
	while (!scanfail&&(i=scanfile())<256&&z<256)
	{
		if (special[z]=(i<0)) i+=128;
		suptile[z++]=i;
		if (++x>=suplenx)
		{
			x=0;
			if (++y>=supleny)
				y=0,++suptiles;
		}
	}
	if (!scanfail)
	{
		if (argc>4) // read supertile data
		{
			if (!(f=fopen(s=argv[3],"rb")))
				return errorbox(cannotopen,s),1;
			suptiles=fread(suptile,1,sizeof(suptile),f)/suplenx/supleny;
			fclose(f);
		}
		else // supertile file isn't a map
		{
			int oldx=winlenx*suplenx*winmulz,oldy=winleny*supleny*winmulz;
			winlenx=suplenx*1;
			winleny=supleny*8; // at least 8 supertiles
			maplenx=suplenx*1;
			mapleny=supleny*scanfile(); // 256
			suplenx=supleny=1;
			while (winlenx*winmulz<oldx&&winleny*winmulz<oldy)
				++winmulz;
			for (z=0;z<256;++z)
				suptile[z]=z;
			suptiles=dibmaxx*dibmaxy;
		}
	}
	z=1;
	while (z<suptiles&&z<255)
		z<<=1;
	suptiles=z-1; // bit mask (255, 127, 63...) rather than amount (256, 128, 64...)
	fclose(f);
	printf("tiles: %ix%i ; supertiles: %ix%i\n",diblenx,dibleny,suplenx,supleny);
	strcpy(filemapname,argv[argc-1]);
	if (!filemapload())
		return errorbox(cannotopen,s),1;
	BITMAP bm;
	GetObject(tilebm,sizeof(bm),&bm);
	x=bm.bmWidth*winmulx*winmulz/windivz; y=bm.bmHeight*winmuly*winmulz/windivz;
	tilebm=CopyImage(tilebm,IMAGE_BITMAP,x,y,LR_COPYDELETEORG);
	// start of window
	#define WS_MYWINDOW (WS_OVERLAPPEDWINDOW&~WS_MAXIMIZEBOX&~WS_SIZEBOX)
	WNDCLASSEX wc; HWND hwnd; MSG msg;
	wc.cbSize=sizeof(WNDCLASSEX);
	wc.style=0;
	wc.lpfnWndProc=wndproc;
	wc.cbClsExtra=wc.cbWndExtra=0;
	wc.hInstance=GetModuleHandle(0);
	wc.hIcon=wc.hIconSm=LoadIcon(NULL,IDI_APPLICATION);
	wc.hCursor=LoadCursor(NULL,IDC_ARROW);
	wc.hbrBackground=0;//(HBRUSH)(COLOR_WINDOW+0);
	wc.lpszMenuName=NULL;
	wc.lpszClassName=argv[0];
	RegisterClassEx(&wc);
	// window runtime
	RECT desired; desired.left=desired.top=0;
	desired.right=((((2+winlenx)*suplenx)*sup_x_x)+((winleny*supleny)*sup_y_x))*diblenx*winmulx*winmulz/windivz;
	desired.bottom=((((2+winlenx)*suplenx)*sup_x_y)+((winleny*supleny)*sup_y_y))*dibleny*winmuly*winmulz/windivz;
	AdjustWindowRect(&desired,WS_MYWINDOW,0);//WS_OVERLAPPEDWINDOW
	if (hwnd=CreateWindowEx(0,argv[0],caption,WS_MYWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,desired.right-desired.left,desired.bottom-desired.top,NULL,NULL,wc.hInstance,NULL))
	{
		ShowWindow(hwnd,SW_SHOWDEFAULT),UpdateWindow(hwnd);
		while (GetMessage(&msg,NULL,0,0)>0)
			TranslateMessage(&msg),DispatchMessage(&msg);
	}
	// end of window
	if (!filemapsave())
		return errorbox(cannotopen,filemapname),1;
	return 0;
}

#ifndef __argc
	extern int __argc; extern char **__argv; // GCC5's -std=gnu99 does NOT define them by default, despite being part of STDLIB.H and MSVCRT!
#endif
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { return main(__argc,__argv); }
