/* mapview, cislo 06
	ma to jediny modul 06mview.c, podporuje to vsechny levely [1-4]

  jedinym algoritmem podporuju VSECHNY 4 typy souboru, nijak mezi nimi
  nerozlisuju, neni to potreba
  prectete si nasl. poznamky...

!!!!ve vasich vzoreccich na paletu nasobite 255 a ono se to pritom
	moduli 64 (ne 256)! jestli tedy nebudou barvy, jak jste
	ocekavali, neni to moje chyba, je to otrocke prepsani vasich vzorecku!

!!!pri rotaci palety mi BLIKA obrazovka, protoze jste nenapsali,
	jakou funkci se ceka na retrace. a bez toho muzu leda tak menit
	paletu barvu po barve, coz je pomale. hromadna zmena palety blika!!!
	pokud bych prepisoval barvy, tak by to obecne bylo pomale, kdyby bylo
	body na hranici HODNE (myslim, ze bych dokazal vymyslet mapu, kde
	polovina bodu jsou hranice --- dlouhy had). takze URCITE by se to
	meli delat zmenou palety a ne prekreslovanim bodu.

	proto je rotace palety sice defaultne zapnuta, ale neni
	hezka. DOUFAM, ZE MI NESTRHNETE BODY ZA GRAFIKU, KVULI BLIKANI,
	ZA KTERE MUZE ABSENCE CEKANI NA RETRACE OBRAZOVKY. UVAZUJTE, JAKO
	BY TO NEBLIKALO!

!!!vy pouzivate v examplech v rozporu se zadanim barvu 0, muj program na to
	ohlasi warning, ale JEDE DAL. pokud je tato barva oznacena, pak
	se oznaci i prazdne nulove pozadi a nemaze se. neosetruju to, protoze
	podle zadani tato situace NEMUZE nastat! */
int rotovat=1;

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <dos.h>

#define LENGTH 100
#define noDEBUG

typedef unsigned char Char;
typedef struct {	/* seznam vsech obdelniku */
	int x1,y1,x2,y2;
	Char index;
} TRectangle;

int count,level;
TRectangle *rect;
int active;		/* jaky je aktivni obdelnik */

Char *vram;		/* primy pristup do videoram */
Char *last;		/* posledni radek, kvuli prepisovani okraje */
FILE *log;
int rotace;

void err(Char *txt){
	printf("error: %s\n",txt);
	exit(1);
}

void MouseShow(int visible){
	asm{
		mov ax,2
		sub ax,visible
		int 33h
	}
}

void SetPalette(int num,Char r,Char g,Char b){
	asm{
		mov ax,1010h
		mov bx,num
		mov ch,g
		mov cl,b
		mov dh,r
		int 10h
	}
}

void NastavPaletu(int rotace){
	int i;
	Char (far *blok)[15][3];
	blok=malloc(45);
	for(i=0;i<15;i++){
	  (*blok)[i][0]=(i+rotace)%15*63/14;
	  (*blok)[i][1]=(i+rotace)%15*63/14;
	  (*blok)[i][2]=(i+rotace)%15*63/14;
	}
	asm{
		mov ax,1012h
		mov bx,240
		mov cx,15
		mov es,word ptr blok+2
		mov dx,word ptr blok
		int 10h
	}
	free(blok);
}

/* hardwarove funkce NO COMMENT */

void read(Char *name){
	FILE *fi;
	Char txt[LENGTH+1];
	TRectangle tmp;
	int i;

	last=(Char*)malloc(320);
	if(!last) err("not enoungh memory for the temporary array");

	fi=fopen(name,"rt");
	if(!fi) err("can not open input file");
	count=0;		/* compute the count of the rectangles */
	fgets(txt,LENGTH,fi);
	while(!feof(fi)){
		if(fscanf(fi,"%d %d %d %d %d\n",&tmp.index,
			&tmp.x1,&tmp.y1,&tmp.x2,&tmp.y2)!=5){
			break;
		}
		count++;
	}
	fseek(fi,0,SEEK_SET);
	rect=(TRectangle*)malloc(count*sizeof(TRectangle));
	if(!rect) err("not enough memory to the map");	/* allocate memory */
	fscanf(fi,"MDF/%d\n",&level);
	for(i=0;i<count;i++){	/* now read all of them */
	  fscanf(fi,"%d %d %d %d %d\n",&rect[i].index,
	    &rect[i].x1,&rect[i].y1,&rect[i].x2,&rect[i].y2);
	  if(rect[i].index<1 || rect[i].index>=240){
		printf("spatne zadane cislo barvy %d, stejne to nakreslim,\n",rect[i].index);
		printf("protoze i ve vzorovych datech byla takova chyba (ma byt rozsah 1--239)\n");
		printf("ale nedivte se, kdyz bude neco blbnout (pocitam s barvou 0 a 240--254)!\n");
		printf("zmackni ENTER, radsi ho drz, protoze se muze chyba opakovat\n");
		gets(txt);
	  }
	}
	fclose(fi);

	asm{	/* graphic mode */
		mov ax,13h
		int 10h
	}

	for(i=0;i<240;i++)	/* set the palette */
	  SetPalette(i,((i>>5)*255)/7,(((i>>2)&7)*255)/7,
	    ((i&3)*255)/3);
	NastavPaletu(0);        /* the gray scale */
	SetPalette(255,255,255,255);

	asm{		/* initialise the mouse */
		xor ax,ax
		int 33h
		mov i,ax
	}
	if(i!=-1) err("can not find mouse");

	vram=(Char*)(0xa000l<<16);	/* pointer to the videoram */
#ifdef DEBUG
	log=fopen("log.txt","at");
#endif
}

void done(){
	asm{		/* unvisible mouse */
		mov ax,3h
		int 10h
	}
	free(rect);	/* deallocates everything */
	free(last);
#ifdef DEBUG
	fclose(log);
#endif
}

void DrawBar(int i,Char color){
/* vykresli po radcich dany obdelnik, pro vetsi rychlost je to
udelano v assembleru */
  int y;
  int del=rect[i].x2-rect[i].x1+1;

  for(y=rect[i].y1;y<=rect[i].y2;y++){
	Char *vr=vram+y*320+rect[i].x1;	/* write 1 line of the bar */
	asm{
		mov cx,del
		mov al,color
		mov es,word ptr vr+2
		mov di,word ptr vr
		rep stosb
	}
  }
}

void draw(){
/* vykresli postupne vsechny obdelniky, V DANEM PORADI */
	int i;
	for(i=0;i<count;i++)
		DrawBar(i,rect[i].index);
}

int Inside(int i,int x,int y){
/* vrati, zda je bod (x,y) uvnitr obdelniku cislo i */
	return x>=rect[i].x1 && x<=rect[i].x2 &&
		y>=rect[i].y1 && y<=rect[i].y2;
}

//#define PODM(x)	(vram[idx+x]!=i && vram[idx+x]<240)
#define PODM(x)	(vram[idx+x]!=i)
  /* vraci, zda dany bod neni pozadovane barvy */
#define BARVA(x)	(240+x)
  /* prekonverteni cisla barvy na paletu */

void EnableBorder(int i1){
	const int l=-1,r=1,u=-320,d=320;
 //	const int l1=-1,r1=2,u1=-320-1,d1=2*320;
	int x,y;
	unsigned int idx;
	Char i,n;
	if(i1<0) return;
	i=i1;

/* projedu celou videoramku a obarvim okrajem ty body pozadovane barvy,
	ktere obsahuji aspon jednoho souseda jine barvy
	(jinak jsou uvnitr utvaru a my chceme vykreslit POUZE okraj)

	tohle je optimalni pro velky pocet obdelniku (afrika), protoze
	to NIJAK nezavisi na poctu utvaru a ma to konstantni casovou slozitost
	O(1) zavislou akorat tak na rozliseni, coz je 64000 bodu

	tomuto algoritmu je jedno, zpracovava-li obdelniky, vice
	napojenych obdelniku, vice oddelenych utvaru nebo utvary se derami.
	je to zcela  univerzalni... */

	/* okraje udelam az po prostredku, aby mi to neovlivnilo radkovy
	algoritmus */
	x=y=1;
	for(idx=0;idx<320;idx++)	/* nastav prvni radek */
		last[idx]=vram[idx];
	for(idx=321;idx<64000-320;idx++){	/* prostredek */
		if(vram[idx]==i && (PODM(l) || PODM(r) || PODM(u) || PODM(d)))
//			vram[idx]=BARVA((x+300-y)%15);
			n=BARVA((x+300-y)%15);
			/* nenastavuju do videoram, ale do maleho jednoradkoveho
			policka
			body vpravo a dole jsou jiste nedotcene, ale vlevo
			a nahore mohly byt zmodifikovany. proto modifikaci
			ukladam do radkoveho bufferu a pozdeji ji prepisu
			do obrazovky */
		else
			n=vram[idx];
		vram[idx-320]=last[x];	/* prekopiruj minuly bod */
		last[x]=n;
		x++;
		if(x==319){
			x=1;
			y++;
			idx+=2;
		}
	}
	for(idx=1;idx<319;idx++)	/* nastav prvni radek */
		vram[idx+199*320]=last[idx];
	for(idx=0;idx<320;idx++){
		if(vram[idx]==i){		/* horni rada */
			vram[idx]=BARVA(idx%15);
		}
		if(vram[idx+199*320]==i){	/* dolni rada */
			vram[idx+199*320]=BARVA((idx+199)%15);
		}
	}
	y=0;
	for(idx=320;idx<64000-320;idx+=320){
		if(vram[idx]==i){		/* levy sloupec */
			vram[idx]=BARVA(14-y%15);
		}
		if(vram[idx+319]==i){		/* pravy sloupec */
			vram[idx+319]=BARVA((-y+319)%15);
		}
		y++;
	}



	/* zkousel jsem to udelat v assembleru, ale nestihl jsem to odladit */
#if 0
	asm{
		mov ds,word ptr vram+2
		mov si,321
	}
nacti_bod:
	asm{
		lodsb
		cmp al,i		// je to ten bod?
		jne dalsi_bod
		mov di,si
		add di,l1
		mov ah,byte ptr ds:di
		cmp al,ah	//bod vlevo
		je dalsi_vpravo
		cmp ah,254
		jne vykresli_bod
}dalsi_vpravo:asm{
		add di,r1
		mov ah,byte ptr ds:di
		cmp al,ah	//bod vpravo
		je dalsi_nahore
		cmp ah,254
		jne vykresli_bod
}dalsi_nahore:asm{
		add di,u1
		mov ah,byte ptr ds:di
		cmp al,ah	//bod nahoru
		je dalsi_dole
		cmp ah,254
		jne vykresli_bod
}dalsi_dole:asm{
		add di,d1
		mov ah,byte ptr ds:di
		cmp al,ah	//bod dolu
		je dalsi_bod
		cmp ah,254
		je dalsi_bod
	}
vykresli_bod:
	asm{
		mov byte ptr ds:si,254
	}
dalsi_bod:
	asm{
		inc si
		cmp si,64000-320
		jb nacti_bod
	}
#endif


#if 0
	asm{
		mov ds,word ptr vram+2
		//mov si,word ptr vram
		xor si,si
		mov cx,64000
	}
zkoumej_bod:
	asm{
		lodsb
		cmp al,i		// je to ten bod?
		jne dalsi_bod
		mov di,si
		add di,l
		cmp al,byte ptr ds:di	//bod vlevo
		jne vykresli_bod
		add di,r
		cmp al,byte ptr ds:di	//bod vpravo
		jne vykresli_bod
		add di,u
		cmp al,byte ptr ds:di	//bod nahoru
		jne vykresli_bod
		add di,d
		cmp al,byte ptr ds:di	//bod dolu
		jne vykresli_bod
	}
dalsi_bod:
	asm 	loop zkoumej_bod;
vykresli_bod:
	asm{
		mov al,254		// uloz do pracovniho pole
		mov byte ptr ds:si,al
		loop zkoumej_bod
	}
#endif
}

void DisableBorder(int i){
/* prekresli ZNOVU danou barvu, cimz zrusi okraj */
	int j;
	if(i<0) return;
	for(j=0;j<count;j++)
	if(rect[j].index==i)
		DrawBar(j,i);
}

void SelectBorder(int x,int y){
/* nalezne, kam ukazuje mys, pripadne prekresli border */
	int i,last=active;
	for(i=count-1;i>=0;i--)
	if(Inside(i,x,y)){
#ifdef DEBUG
		if(rect[i].index!=active)
			fprintf(log,"inside %d/%d (%d, %d)\n",i,rect[i].index,x,y);
#endif
		active=rect[i].index;
		break;
	}
	if(i<0)
		active=-1;
#ifdef DEBUG
	if(last!=active)
		fprintf(log,"selected %d\n",active);
#endif
	if(active!=last){
		MouseShow(0);
		DisableBorder(last);
		EnableBorder(active);
		MouseShow(1);
	}
}

void interactive(){
/* testuje periodicky mys, klavesnici, pokud je to zapnuto, rotuje paletu */
	int x,y,key;
	struct time timep;
	long int cas,cas1;

	MouseShow(1);
	active=-1;
	rotace=0;
	gettime(&timep);
	cas=(((long int)timep.ti_hour*60+timep.ti_min)*60+timep.ti_sec)*100+timep.ti_hund;
	do{
		if(kbhit())
		if(getch()==0x1b)		/* escape */
			break;
		asm{			/* get the mouse state */
			mov ax,3
			int 33h
			mov x,cx
			mov y,dx
			mov key,bx
		}
		x/=2;
		if(key)
			SelectBorder(x,y);
		gettime(&timep);
		cas1=(((long int)timep.ti_hour*60+timep.ti_min)*60+timep.ti_sec)*100+timep.ti_hund;
		if(cas1-cas>5){	/* rotovat */
			cas+=5;
			rotace++;
			if(rotace==15) rotace=0;
			if(active!=-1 && rotovat)
				NastavPaletu(rotace);
		}
		delay(1);
	}while(1);
}

int main(int argc, Char **argv){
	if(argc<2) err("you must write name of the map-file");
	read(argv[1]);
	draw();
	interactive();
	done();
	return 0;
}

