#ifndef DISABLE_NETWORK
#include "globals.h"

#include <SDL_net.h>
#include <string.h>
#include "mcufstream.h"
#include <BlinkenLib.h>
#include "fgobjects.h"
#include "errorhandler.h"
#include "engine.h"
#include "levelhandler.h"
#include "gameengine.h"
#include "monstersprites.h"

// global
MCUF_Frame currentMCUFFrame;
Uint32 currentMCUFFrameNum;
Uint32 currentMCUFMovieFrameNum;

// more or less local
UDPsocket MCUFsd;
IPaddress MCUFsrvadd;
UDPpacket *MCUFp;
Uint32 mcufLastTick;
Uint32 mcufLastFrameTick;

stBlinkenMovie * pMcufMovie;


Uint32 BlinkenColorizerSolid(Uint32 step, Uint32 channels, Uint32 y, Uint32 x, Uint32 c, Uint32 maxval /* max 254 */)
{
  Uint32 substep, updown, chan;
  step %= channels * 2 * maxval;
  substep = step % maxval;
  updown = (step / maxval) % 2;
  chan = (step / maxval) / 2;
  if( updown == 0 ) {
    if( c == chan )
      return maxval+1;
    else if( c == (chan + 1) % channels )
      return substep;
    else
      return 0;
  } else {
    if( c == chan )
      return (maxval + 1) - substep;
    else if( c == (chan + 1) % channels )
      return (maxval + 1);
    else
      return 0;
  }
  y = x = 0; // keep compiler quiet
}

Uint32 BlinkenColorizerRainbow( Uint32 step, Uint32 channels, Uint32 y, Uint32 x, Uint32 c, Uint32 maxval )
{
  return BlinkenColorizerSolid( step + ((x + y)), channels, y, x, c, maxval );
}

Uint32 BlinkenRainbowColorStep = 0;

void initMCUFSocket() {
	// Reset frame number
	currentMCUFFrameNum = 0;

	// hardcode fixed values of frame
	currentMCUFFrame.magic[0] = (char) 0x23;
	currentMCUFFrame.magic[1] = (char) 0x54;
	currentMCUFFrame.magic[2] = (char) 0x26;
	currentMCUFFrame.magic[3] = (char) 0x66;

	currentMCUFFrame.width[0] = (char) 0x00;
	currentMCUFFrame.width[1] = (char) 0x12;
	currentMCUFFrame.height[0] = (char) 0x00;
	currentMCUFFrame.height[1] = (char) 0x08;
	currentMCUFFrame.maxval[0] = (char) 0x00;
	currentMCUFFrame.maxval[1] = (char) 0xff;
	currentMCUFFrame.channels[0] = (char) 0x00;
	currentMCUFFrame.channels[1] = (char) 0x03;
	
	// Open UDP Port
	// Get the socket

#ifdef MCUFRANDOM_SOURCEPORT
	if (!(MCUFsd = SDLNet_UDP_Open(0))) {
#else
	if (!(MCUFsd = SDLNet_UDP_Open(mcufport))) {
#endif
		fprintf(stderr, "SDLNet_UDP_Open: %s\n", SDLNet_GetError());
		exit(EXIT_FAILURE);
	}

	//   Resolve server name
	if (SDLNet_ResolveHost(&MCUFsrvadd, mcufhost, mcufport)) {
		fprintf(stderr, "SDLNet_ResolveHost(%s %d): %s\n", mcufhost, mcufport, SDLNet_GetError());
		exit(EXIT_FAILURE);
	}

	/* Allocate memory for the packet */
	if (!(MCUFp = SDLNet_AllocPacket(sizeof(currentMCUFFrame)))) {
		fprintf(stderr, "SDLNet_AllocPacket: %s\n", SDLNet_GetError());
		exit(EXIT_FAILURE);
	}

}

void deInitMCUFSocket() {
	SDLNet_FreePacket(MCUFp);
	SDLNet_UDP_Close(MCUFsd);
}

void initMCUFFrames() {
	mcufLastTick = BS_GetTicks() + 10;
}

void deInitMCUFFrames() {
	// Nothing to do
}

void sendMCUFFrame() {
	// Max 50 FPS
	Uint32 tick = BS_GetTicks();
	if(mcufLastTick > tick) {
		return;
	}
	mcufLastTick = tick + 20;

	// Configure the packet
	MCUFp->address.host = MCUFsrvadd.host;  /* Set the destination host */
	MCUFp->address.port = MCUFsrvadd.port;  /* And destination port */

	// --- HERE, we calculate the pixel data

	// set bg: all off
	clearMCUFFrame();
	
	BlinkenRainbowColorStep++;
	
	showMCUFTiles();
	showMCUFFgObjects();
	showMCUFPixels();
	showMCUFMonster();

	// paint kate
	currentMCUFFrame.pixels[0 + (MCUFKATE_X + (MCUFKATE_Y * MCUFSCR_W))*MCUFSCR_CHANS] = (char) 0xff;
	currentMCUFFrame.pixels[1 + (MCUFKATE_X + (MCUFKATE_Y * MCUFSCR_W))*MCUFSCR_CHANS] = (char) 0xd2;
	currentMCUFFrame.pixels[2 + (MCUFKATE_X + (MCUFKATE_Y * MCUFSCR_W))*MCUFSCR_CHANS] = (char) 0x00;

	Sint32 tailX = 0;
	Sint32 tailY = 0;
	if ((spritevx*spritevx)+(spritevy*spritevy) > 9) {
		if (spritevx > 2) {
			tailX = -1;
		}
		if (spritevx < -2) {
			tailX = 1;
		}
		if (spritevy > 2) {
			tailY = -1;
		}
		if (spritevy < -1) {
			tailY = 1;
		}	
	}
	if (currentMCUFFrame.pixels[0 + (MCUFKATE_X + tailX + ((MCUFKATE_Y + tailY) * MCUFSCR_W))*MCUFSCR_CHANS] == 0
			&& currentMCUFFrame.pixels[1 + (MCUFKATE_X + tailX + ((MCUFKATE_Y + tailY) * MCUFSCR_W))*MCUFSCR_CHANS] == 0
			&& currentMCUFFrame.pixels[2 + (MCUFKATE_X + tailX + ((MCUFKATE_Y + tailY) * MCUFSCR_W))*MCUFSCR_CHANS] == 0
		) {
		currentMCUFFrame.pixels[0 + (MCUFKATE_X + tailX + ((MCUFKATE_Y + tailY) * MCUFSCR_W))*MCUFSCR_CHANS] = 0x40;
		currentMCUFFrame.pixels[1 + (MCUFKATE_X + tailX + ((MCUFKATE_Y + tailY) * MCUFSCR_W))*MCUFSCR_CHANS] = 0x34;
		currentMCUFFrame.pixels[2 + (MCUFKATE_X + tailX + ((MCUFKATE_Y + tailY) * MCUFSCR_W))*MCUFSCR_CHANS] = 0x00;		
	}

	// --- end of "calculate the pixel data"

	// Send packet
	memcpy(MCUFp->data, &currentMCUFFrame, sizeof(currentMCUFFrame));
	MCUFp->len = sizeof(currentMCUFFrame);
	SDLNet_UDP_Send(MCUFsd, -1, MCUFp);

}

void clearMCUFFrame() {
	for(Uint32 mcuf_i = 0; mcuf_i < 144*MCUFSCR_CHANS; mcuf_i++) {
		currentMCUFFrame.pixels[mcuf_i] = 0x00;
	}
}

void showMCUFTiles() {
	Sint32 xpos;
	Sint32 ypos;
	Uint32 xoffs=(Uint32)round(spritex+MCUF_XOFFSET)/TILESIZE;
	Uint32 yoffs=(Uint32)round(spritey+MCUF_YOFFSET)/TILESIZE;
	
	DISPLAYLIST *tthis = lhandle.dlist;
	while(tthis) {
		xpos = (Uint32)round(tthis->x+TILESIZE/2)/TILESIZE - xoffs + MCUFKATE_X;
		ypos = (Uint32)round(tthis->y)/TILESIZE - yoffs + MCUFKATE_Y;
		if(xpos >= MCUFSCR_M && xpos < MCUFSCR_W && ypos >= MCUFSCR_M && ypos < MCUFSCR_H) {
			currentMCUFFrame.pixels[0 + (xpos + (ypos * MCUFSCR_W)) * MCUFSCR_CHANS] = BlinkenColorizerRainbow(BlinkenRainbowColorStep, 3, tthis->y, tthis->x, 0, 0x42);
			currentMCUFFrame.pixels[1 + (xpos + (ypos * MCUFSCR_W)) * MCUFSCR_CHANS] = BlinkenColorizerRainbow(BlinkenRainbowColorStep, 3, tthis->y, tthis->x, 1, 0x42);
			currentMCUFFrame.pixels[2 + (xpos + (ypos * MCUFSCR_W)) * MCUFSCR_CHANS] = BlinkenColorizerRainbow(BlinkenRainbowColorStep, 3, tthis->y, tthis->x, 2, 0x42);
		}
		tthis = tthis->next;
	}
}

void showMCUFFgObjects() {
	Sint32 xpos, xpos_to;
	Sint32 ypos, ypos_to;
	Uint32 xoffs=(Uint32)round(spritex+MCUF_XOFFSET)/TILESIZE;
	Uint32 yoffs=(Uint32)round(spritey)/TILESIZE;
	char switchcolor = (char) 0x42;
	if (BS_GetTicks() % 2300 > 1200) {
		switchcolor = (char) 0xaa;
	}
	char monstercolor = (char) 0x9c;
	if (BS_GetTicks() % 300 > 150) {
		monstercolor = (char) 0xff;
	}

	for(Uint32 i = 0; i < MAX_FGOBJECTS; i++) {
		if(!fgObjs[i]) {
			continue;
		}
		SDL_Surface *tempsurface = getFGSurface(fgObjs[i]->gfxobj);
		xpos = (Uint32)round(fgObjs[i]->x+TILESIZE/2)/TILESIZE - xoffs + MCUFKATE_X;
		xpos_to =  (Uint32)round(tempsurface->w/TILESIZE) + xpos - 1;
		ypos = (Uint32)round(fgObjs[i]->y)/TILESIZE - yoffs + MCUFKATE_Y;
		ypos_to = (Uint32)round(tempsurface->h/TILESIZE) + ypos - 1;

		if(xpos<MCUFSCR_W && xpos_to>MCUFSCR_W && xpos>0)
			xpos_to=MCUFSCR_W-1;
		if(xpos<0 && xpos_to<MCUFSCR_W && xpos_to>0)
			xpos=0;
		if(ypos<MCUFSCR_H && ypos_to>MCUFSCR_H && ypos>0)
			ypos_to=MCUFSCR_H-1;
		if(ypos>MCUFSCR_H && ypos_to<MCUFSCR_H && ypos_to>0)
			ypos=MCUFSCR_H-1;
		if(ypos<0 && ypos_to<MCUFSCR_H && ypos_to>0)
			ypos=0;
		if(fgObjs[i]->isVisible && xpos_to < MCUFSCR_W && ypos_to < MCUFSCR_H && xpos >= 0 && ypos >= 0) {

			// draw switches or other non-blocking, non-killing and non-pixel Objects
			for (Sint32 x = xpos; x <= xpos_to; x++) {
				for (Sint32 y = ypos; y <= ypos_to; y++) {
					currentMCUFFrame.pixels[0 + (x + (y * MCUFSCR_W)) * MCUFSCR_CHANS] = (char) 0x90;
					currentMCUFFrame.pixels[1 + (x + (y * MCUFSCR_W)) * MCUFSCR_CHANS] = (char) 0xdc;
					currentMCUFFrame.pixels[2 + (x + (y * MCUFSCR_W)) * MCUFSCR_CHANS] = (char) 0xff;
				}
			}

			// draw blocking-Objects as Wall-Tiles
			if(fgObjs[i]->isBlocking) {
				for (Sint32 x = xpos; x <= xpos_to; x++) {
					for (Sint32 y = ypos; y <= ypos_to; y++) {
						currentMCUFFrame.pixels[0 + (x + (y * MCUFSCR_W)) * MCUFSCR_CHANS] = BlinkenColorizerRainbow(BlinkenRainbowColorStep, 3, fgObjs[i]->y, fgObjs[i]->x, 0, 0x42);
						currentMCUFFrame.pixels[1 + (x + (y * MCUFSCR_W)) * MCUFSCR_CHANS] = BlinkenColorizerRainbow(BlinkenRainbowColorStep, 3, fgObjs[i]->y, fgObjs[i]->x, 1, 0x42);
						currentMCUFFrame.pixels[2 + (x + (y * MCUFSCR_W)) * MCUFSCR_CHANS] = BlinkenColorizerRainbow(BlinkenRainbowColorStep, 3, fgObjs[i]->y, fgObjs[i]->x, 2, 0x42);
					}
				}
			}

			// draw pixel-Objects as Pixels
			if(fgObjs[i]->isPixel) {
				for (Sint32 x = xpos; x <= xpos_to; x++) {
					for (Sint32 y = ypos; y <= ypos_to; y++) {
						currentMCUFFrame.pixels[0 + (x + (y * MCUFSCR_W)) * MCUFSCR_CHANS] = (char)blinkbright;
						currentMCUFFrame.pixels[1 + (x + (y * MCUFSCR_W)) * MCUFSCR_CHANS] = (char)blinkbright;
						currentMCUFFrame.pixels[2 + (x + (y * MCUFSCR_W)) * MCUFSCR_CHANS] = (char)blinkbright;
					}
				}
			}

			// draw Elevator-Objects
			if(fgObjs[i]->isElevator) {
				for (Sint32 x = xpos; x <= xpos_to; x+=2) {
					for (Sint32 y = ypos; y <= ypos_to; y++) {
						currentMCUFFrame.pixels[0 + (x + (y * MCUFSCR_W)) * MCUFSCR_CHANS] = switchcolor;
						currentMCUFFrame.pixels[1 + (x + (y * MCUFSCR_W)) * MCUFSCR_CHANS] = switchcolor;
						currentMCUFFrame.pixels[2 + (x + (y * MCUFSCR_W)) * MCUFSCR_CHANS] = 0x0a;
					}
				}
			}

			// draw killing-Objects as Monsters
			if(fgObjs[i]->isKilling) {
				for (Sint32 x = xpos; x <= xpos_to; x++) {
					for (Sint32 y = ypos; y <= ypos_to; y++) {
						currentMCUFFrame.pixels[0 + (x + (y * MCUFSCR_W)) * MCUFSCR_CHANS] = monstercolor;
						currentMCUFFrame.pixels[1 + (x + (y * MCUFSCR_W)) * MCUFSCR_CHANS] = 0x00;
						currentMCUFFrame.pixels[2 + (x + (y * MCUFSCR_W)) * MCUFSCR_CHANS] = 0x00;
					}
				}
			}

		}
	}
}

void showMCUFPixels() {
	Sint32 xpos;
	Sint32 ypos;
	Uint32 xoffs=(Uint32)round(spritex+MCUF_XOFFSET)/TILESIZE;
	Uint32 yoffs=(Uint32)round(spritey+MCUF_YOFFSET)/TILESIZE;

	DISPLAYLIST *tthis = lhandle.pixels;
	while(tthis) {
		xpos = (Uint32)round(tthis->x+TILESIZE/2)/TILESIZE - xoffs + MCUFKATE_X;
		ypos = (Uint32)round(tthis->y)/TILESIZE - yoffs + MCUFKATE_Y;
		if(tthis->type != PIXELTYPE_NONE && xpos >= MCUFSCR_M && xpos < MCUFSCR_W && ypos >= MCUFSCR_M && ypos < MCUFSCR_H) {

			// draw "#" and "P"
			if(tthis->type == PIXELTYPE_PIXEL || tthis->type == PIXELTYPE_POWER) {
						currentMCUFFrame.pixels[0 + (xpos + (ypos * MCUFSCR_W)) * MCUFSCR_CHANS] = (char)blinkbright;
						currentMCUFFrame.pixels[1 + (xpos + (ypos * MCUFSCR_W)) * MCUFSCR_CHANS] = (char)blinkbright;
						currentMCUFFrame.pixels[2 + (xpos + (ypos * MCUFSCR_W)) * MCUFSCR_CHANS] = (char)blinkbright;
			}

			// draw "+" and "~"
			if(tthis->type == PIXELTYPE_RESPAWN) {
				currentMCUFFrame.pixels[0 + (xpos + (ypos * MCUFSCR_W)) * MCUFSCR_CHANS] = (char) 0x33;
				currentMCUFFrame.pixels[1 + (xpos + (ypos * MCUFSCR_W)) * MCUFSCR_CHANS] = (char) 0x00;
				currentMCUFFrame.pixels[2 + (xpos + (ypos * MCUFSCR_W)) * MCUFSCR_CHANS] = (char) 0xFF;
			}
			
			// draw "+" and "~"
			if(tthis->type == PIXELTYPE_EXIT) {
				if(allowedToExit) {
					currentMCUFFrame.pixels[0 + (xpos + (ypos * MCUFSCR_W)) * MCUFSCR_CHANS] = (char) 0x00;
					currentMCUFFrame.pixels[1 + (xpos + (ypos * MCUFSCR_W)) * MCUFSCR_CHANS] = (char) 0xFF;
					currentMCUFFrame.pixels[2 + (xpos + (ypos * MCUFSCR_W)) * MCUFSCR_CHANS] = (char) 0x00;
				} else {
					currentMCUFFrame.pixels[0 + (xpos + (ypos * MCUFSCR_W)) * MCUFSCR_CHANS] = (char) 0xFF;
					currentMCUFFrame.pixels[1 + (xpos + (ypos * MCUFSCR_W)) * MCUFSCR_CHANS] = (char) 0x00;
					currentMCUFFrame.pixels[2 + (xpos + (ypos * MCUFSCR_W)) * MCUFSCR_CHANS] = (char) 0x00;
				}
			}

			// draw "$" and "*"
			if(tthis->type == PIXELTYPE_POINTS || tthis->type == PIXELTYPE_LIVE) {
				currentMCUFFrame.pixels[0 + (xpos + (ypos * MCUFSCR_W)) * MCUFSCR_CHANS] = (char) 0xfc;
				currentMCUFFrame.pixels[1 + (xpos + (ypos * MCUFSCR_W)) * MCUFSCR_CHANS] = (char) 0xff;
				currentMCUFFrame.pixels[2 + (xpos + (ypos * MCUFSCR_W)) * MCUFSCR_CHANS] = (char) 0x0a;
			}

		}
		tthis = tthis->next;
	}

}

void showMCUFMonster() {
	Sint32 xpos;
	Sint32 ypos;
	Uint32 xoffs=(Uint32)round(spritex+MCUF_XOFFSET)/TILESIZE;
	Uint32 yoffs=(Uint32)round(spritey+MCUF_YOFFSET)/TILESIZE;
	char color = (char) 0x9c;

	if (BS_GetTicks() % 300 > 150) {
		color = (char) 0xff;
	}

	MONSTERS *tthis = lhandle.monsters;
	while(tthis) {
		if(tthis->isAlive) {
			xpos = (Uint32)round(tthis->monsterx+TILESIZE/2)/TILESIZE - xoffs + MCUFKATE_X;
			ypos = (Uint32)ceil(tthis->monstery)/TILESIZE - yoffs + MCUFKATE_Y;
			if(xpos >= MCUFSCR_M && xpos < MCUFSCR_W && ypos >= MCUFSCR_M  && ypos < MCUFSCR_H) {
				currentMCUFFrame.pixels[0 + (xpos + (ypos * MCUFSCR_W)) * MCUFSCR_CHANS] = color;
				currentMCUFFrame.pixels[1 + (xpos + (ypos * MCUFSCR_W)) * MCUFSCR_CHANS] = (char) 0x00;
				currentMCUFFrame.pixels[2 + (xpos + (ypos * MCUFSCR_W)) * MCUFSCR_CHANS] = (char) 0x00;
			}
		}
		tthis = tthis->next;
	}
}

void initMCUFMovie(const char* filename) {
	currentMCUFMovieFrameNum = 0;
	mcufLastFrameTick = 0;
	char fullfname[MAX_FNAME_LENGTH];
	/*char infname[MAX_FNAME_LENGTH];
	sprintf(fullfname, "%s", configGetPath(filename));*/
	sprintf(fullfname, "%s", filename);
	pMcufMovie = BlinkenMovieLoad(fullfname);
	if ((pMcufMovie) == NULL)
		printf("%s not found\n",fullfname);
	mcufLastTick = BS_GetTicks();
}

bool showMCUFMovie() {
	Uint32 tick = BS_GetTicks();
	if(mcufLastTick > tick) {
		return false;
	}
	// Configure the packet
	MCUFp->address.host = MCUFsrvadd.host;  /* Set the destination host */
	MCUFp->address.port = MCUFsrvadd.port;  /* And destination port */
	Uint32 len, framescount;
	
	//write pMcufMovie on top
	framescount = BlinkenMovieGetFrameCnt(pMcufMovie);
	if (pMcufMovie!=NULL && currentMCUFMovieFrameNum<=framescount) {
		stBlinkenFrame * pFrame = 0;
		while(mcufLastTick <= tick && currentMCUFMovieFrameNum<=framescount) {
			pFrame = BlinkenMovieGetFrame(pMcufMovie, currentMCUFMovieFrameNum++);
			mcufLastTick += BlinkenFrameGetDuration(pFrame);
		}
		len = BlinkenFrameToNetwork( pFrame, BlinkenProtoMcuf , (char *)&currentMCUFFrame, sizeof( currentMCUFFrame ) );
		if( len > 0 ) {
			//send( udpSocket, buffer, len, 0 );
			// Send packet
			memcpy(MCUFp->data, &currentMCUFFrame, len);
			MCUFp->len = len;
			SDLNet_UDP_Send(MCUFsd, -1, MCUFp);
		}

	} else {
		return false;
	}

	return true; // true = movie still running, false = movie finished*/
}

void deInitMCUFMovie() {
	// This MUST stop the movie, no matter if it's finished or still running
	BlinkenMovieFree(pMcufMovie);
}
#endif // DISABLE_NETWORK

