/* BlinkenLib
 * version 0.5.2 date 2006-05-10
 * Copyright 2004-2006 Stefan Schuermans <1stein@schuermans.info>
 * Copyleft: GNU public license - http://www.gnu.org/copyleft/gpl.html
 * a blinkenarea.org project
 */
#include "globals.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef DISABLE_NETWORK
#include <SDL_net.h>
#endif // DISABLE_NETWORK
#include "BlinkenConstants.h"
#include "BlinkenFrame.h"
#include "BlinkenMovie.h"
#include "Tools.h"

struct sBlinkenMovie {
	Uint32 height;
	Uint32 width;
	Uint32 channels;
	Uint32 maxval;
	Uint32 infoCnt;
	char * * * pppInfos;
	Uint32 frameCnt;
	stBlinkenFrame * * ppFrames;
};

stBlinkenMovie * BlinkenMovieNew( Uint32 height, Uint32 width, Uint32 channels, Uint32 maxval ) {
	stBlinkenMovie * pMovie;

	if( height < BlinkenHeightMin ) height = BlinkenHeightMin;
	if( height > BlinkenHeightMax ) height = BlinkenHeightMax;
	if( width < BlinkenWidthMin ) width = BlinkenWidthMin;
	if( width > BlinkenWidthMax ) width = BlinkenWidthMax;
	if( channels < BlinkenChannelsMin ) channels = BlinkenChannelsMin;
	if( channels > BlinkenChannelsMax ) channels = BlinkenMaxvalMax;
	if( maxval < BlinkenMaxvalMin ) maxval = BlinkenMaxvalMin;
	if( maxval > BlinkenMaxvalMax ) maxval = BlinkenMaxvalMax;

	pMovie = (stBlinkenMovie *)malloc( sizeof( stBlinkenMovie ) );
	if( pMovie == NULL )
		return NULL;

	pMovie->height = height;
	pMovie->width = width;
	pMovie->channels = channels;
	pMovie->maxval = maxval;
	pMovie->infoCnt = 0;
	pMovie->pppInfos = (char * * *)malloc2D( 0, 2, sizeof( char * ) );
	if( pMovie->pppInfos == NULL ) {
		free( pMovie );
		return NULL;
	}
	pMovie->frameCnt = 0;
	pMovie->ppFrames = (stBlinkenFrame * *)malloc1D( 0, sizeof( stBlinkenFrame * ) );
	if( pMovie->ppFrames == NULL ) {
		free( pMovie->pppInfos );
		free( pMovie );
		return NULL;
	}

	return pMovie;
}

stBlinkenMovie * BlinkenMovieClone( stBlinkenMovie * pSrcMovie ) {
	stBlinkenMovie * pMovie;
	stBlinkenFrame * pFrame;
	Uint32 i;

	pMovie = BlinkenMovieNew( pSrcMovie->height, pSrcMovie->width, pSrcMovie->channels, pSrcMovie->maxval );
	if( pMovie == NULL )
		return NULL;

	for( i = 0; i < pSrcMovie->infoCnt; i++ )
		BlinkenMovieAppendInfo( pMovie, pSrcMovie->pppInfos[i][0], pSrcMovie->pppInfos[i][1] );

	for( i = 0; i < pSrcMovie->frameCnt; i++ ) {
		pFrame = BlinkenFrameClone( pSrcMovie->ppFrames[i] );
		if( BlinkenMovieAppendFrame( pMovie, pFrame ) != 0 )
			BlinkenFrameFree( pFrame );
	}

	return pMovie;
}

void BlinkenMovieFree( stBlinkenMovie * pMovie ) {
	Uint32 i;

	if( pMovie == NULL )
		return;

	for( i = 0; i < pMovie->infoCnt; i++ ) {
		free( pMovie->pppInfos[i][0] );
		free( pMovie->pppInfos[i][1] );
	}
	free( pMovie->pppInfos );

	for( i = 0; i < pMovie->frameCnt; i++ )
		BlinkenFrameFree( pMovie->ppFrames[i] );
	free( pMovie->ppFrames );

	free( pMovie );
}

Uint32 BlinkenMovieGetHeight( stBlinkenMovie * pMovie ) {
	if( pMovie == NULL )
		return 0;

	return pMovie->height;
}

Uint32 BlinkenMovieGetWidth( stBlinkenMovie * pMovie ) {
	if( pMovie == NULL )
		return 0;

	return pMovie->width;
}

Uint32 BlinkenMovieGetChannels( stBlinkenMovie * pMovie ) {
	if( pMovie == NULL )
		return 0;

	return pMovie->channels;
}

Uint32 BlinkenMovieGetMaxval( stBlinkenMovie * pMovie ) {
	if( pMovie == NULL )
		return 0;

	return pMovie->maxval;
}

Uint32 BlinkenMovieGetDuration( stBlinkenMovie * pMovie ) {
	Uint32 i, duration;

	if( pMovie == NULL )
		return 0;

	duration = 0;
	for( i = 0; i < pMovie->frameCnt; i++ )
		duration += BlinkenFrameGetDuration( pMovie->ppFrames[i] );
	return duration;
}

Uint32 BlinkenMovieGetInfoCnt( stBlinkenMovie * pMovie ) {
	if( pMovie == NULL )
		return 0;

	return pMovie->infoCnt;
}

char * BlinkenMovieGetInfoType( stBlinkenMovie * pMovie, Uint32 infoNo ) {
	if( pMovie == NULL || pMovie->infoCnt < 1 )
		return (char *)"";

	if( infoNo >= pMovie->infoCnt ) infoNo = pMovie->infoCnt - 1;
	return pMovie->pppInfos[infoNo][0];
}

char * BlinkenMovieGetInfoData( stBlinkenMovie * pMovie, Uint32 infoNo ) {
	if( pMovie == NULL || pMovie->infoCnt < 1 )
		return (char *)"";

	if( infoNo >= pMovie->infoCnt ) infoNo = pMovie->infoCnt - 1;
	return pMovie->pppInfos[infoNo][1];
}

void BlinkenMovieSetInfo( stBlinkenMovie * pMovie, Uint32 infoNo, const char * pInfoType, char * pInfoData ) {
	char * pType, * pData;

    // QUESTION: Why do we use the exact same string TWICE?

	if( pMovie == NULL || infoNo >= pMovie->infoCnt )
		return;

    // Allocate Memory - strdup doesn't seem to be available everywhere, at least not with the
    // same prototype, so use malloc/strcpy instead

	//pType = strdup( pInfoType );
	pType = (char*)malloc(strlen(pInfoType)+1);
	if( pType == NULL ) {
		return; // If we can't allocate a few bytes, we may be in BIG trouble!
	}
	strcpy(pType, pInfoType);

	//pData = strdup( pInfoData );
	pData = (char*)malloc(strlen(pInfoType)+1);
	if( pData == NULL ) {
		free( pType );
		return; // If we can't allocate a few bytes, we may be in BIG trouble!
	}
	strcpy(pData, pInfoType);


	free( pMovie->pppInfos[infoNo][0] );
	pMovie->pppInfos[infoNo][0] = pType;
	free( pMovie->pppInfos[infoNo][1] );
	pMovie->pppInfos[infoNo][1] = pData;
}

void BlinkenMovieInsertInfo( stBlinkenMovie * pMovie, Uint32 infoNo, const char * pInfoType, char * pInfoData ) {
	char * * * pppNewInfos, * pType, * pData;
	Uint32 i;

	if( pMovie == NULL || infoNo > pMovie->infoCnt )
		return;

	pppNewInfos = (char * * *)malloc2D( pMovie->infoCnt + 1, 2, sizeof( char * ) );
	if( pppNewInfos == NULL )
		return;

	pType = strdup( pInfoType );
	if( pType == NULL ) {
		free( pppNewInfos );
		return;
	}
	pData = strdup( pInfoData );
	if( pData == NULL ) {
		free( pppNewInfos );
		free( pType );
		return;
	}

	for( i = 0; i < infoNo; i++ ) {
		pppNewInfos[i][0] = pMovie->pppInfos[i][0];
		pppNewInfos[i][1] = pMovie->pppInfos[i][1];
	}

	pppNewInfos[infoNo][0] = pType;
	pppNewInfos[infoNo][1] = pData;

	for( i = infoNo; i < pMovie->infoCnt; i++ ) {
		pppNewInfos[i+1][0] = pMovie->pppInfos[i][0];
		pppNewInfos[i+1][1] = pMovie->pppInfos[i][1];
	}

	free( pMovie->pppInfos );
	pMovie->pppInfos = pppNewInfos;
	pMovie->infoCnt++;
}

void BlinkenMovieAppendInfo( stBlinkenMovie * pMovie, const char * pInfoType, char * pInfoData ) {
	if( pMovie == NULL )
		return;

	BlinkenMovieInsertInfo( pMovie, pMovie->infoCnt, pInfoType, pInfoData );
}

void BlinkenMovieDeleteInfo( stBlinkenMovie * pMovie, Uint32 infoNo ) {
	char * * * pppNewInfos;
	Uint32 i;

	if( pMovie == NULL || infoNo >= pMovie->infoCnt )
		return;

	pppNewInfos = (char * * *)malloc2D( pMovie->infoCnt - 1, 2, sizeof( char * ) );
	if( pppNewInfos == NULL )
		return;

	for( i = 0; i < infoNo; i++ ) {
		pppNewInfos[i][0] = pMovie->pppInfos[i][0];
		pppNewInfos[i][1] = pMovie->pppInfos[i][1];
	}

	free( pMovie->pppInfos[infoNo][0] );
	free( pMovie->pppInfos[infoNo][1] );

	for( i = infoNo; i < pMovie->infoCnt - 1; i++ ) {
		pppNewInfos[i][0] = pMovie->pppInfos[i+1][0];
		pppNewInfos[i][1] = pMovie->pppInfos[i+1][1];
	}

	free( pMovie->pppInfos );
	pMovie->pppInfos = pppNewInfos;
	pMovie->infoCnt--;
}

void BlinkenMovieDeleteInfos( stBlinkenMovie * pMovie ) {
	char * * * pppNewInfos;
	Uint32 i;

	if( pMovie == NULL )
		return;

	pppNewInfos = (char * * *)malloc2D( 0, 2, sizeof( char * ) );
	if( pppNewInfos == NULL )
		return;

	for( i = 0; i < pMovie->infoCnt; i++ ) {
		free( pMovie->pppInfos[i][0] );
		free( pMovie->pppInfos[i][1] );
	}

	free( pMovie->pppInfos );
	pMovie->pppInfos = pppNewInfos;
	pMovie->infoCnt = 0;
}

Uint32 BlinkenMovieGetFrameCnt( stBlinkenMovie * pMovie ) {
	if( pMovie == NULL )
		return 0;

	return pMovie->frameCnt;
}

stBlinkenFrame * BlinkenMovieGetFrame( stBlinkenMovie * pMovie, Uint32 frameNo ) {
	if( pMovie == NULL || pMovie->frameCnt < 1 )
		return NULL;

	if( frameNo >= pMovie->frameCnt ) frameNo = pMovie->frameCnt - 1;
	return pMovie->ppFrames[frameNo];
}

void BlinkenMovieSetFrame( stBlinkenMovie * pMovie, Uint32 frameNo, stBlinkenFrame * pFrame ) {
	if( pMovie == NULL || frameNo >= pMovie->frameCnt )
		return;

	BlinkenFrameResize( pFrame, pMovie->height, pMovie->width, pMovie->channels, pMovie->maxval );
	pMovie->ppFrames[frameNo] = pFrame;
}

Sint32 BlinkenMovieInsertFrame( stBlinkenMovie * pMovie, Uint32 frameNo, stBlinkenFrame * pFrame ) {
	stBlinkenFrame * * ppNewFrames;
	Uint32 i;

	if( pMovie == NULL || frameNo > pMovie->frameCnt )
		return -1;

	ppNewFrames = (stBlinkenFrame * *)malloc1D( pMovie->frameCnt + 1, sizeof( stBlinkenFrame * ) );
	if( ppNewFrames == NULL )
		return -1;

	for( i = 0; i < frameNo; i++ )
		ppNewFrames[i] = pMovie->ppFrames[i];

	BlinkenFrameResize( pFrame, pMovie->height, pMovie->width, pMovie->channels, pMovie->maxval );
	ppNewFrames[frameNo] = pFrame;

	for( i = frameNo; i < pMovie->frameCnt; i++ )
		ppNewFrames[i+1] = pMovie->ppFrames[i];

	free( pMovie->ppFrames );
	pMovie->ppFrames = ppNewFrames;
	pMovie->frameCnt++;
	return 0;
}

Sint32 BlinkenMovieAppendFrame( stBlinkenMovie * pMovie, stBlinkenFrame * pFrame ) {
	if( pMovie == NULL )
		return -1;

	return BlinkenMovieInsertFrame( pMovie, pMovie->frameCnt, pFrame );
}

void BlinkenMovieDeleteFrame( stBlinkenMovie * pMovie, Uint32 frameNo ) {
	stBlinkenFrame * * ppNewFrames;
	Uint32 i;

	if( pMovie == NULL || frameNo >= pMovie->frameCnt )
		return;

	ppNewFrames = (stBlinkenFrame * *)malloc1D( pMovie->frameCnt - 1, sizeof( stBlinkenFrame * ) );
	if( ppNewFrames == NULL )
		return;

	for( i = 0; i < frameNo; i++ )
		ppNewFrames[i] = pMovie->ppFrames[i];

	BlinkenFrameFree( pMovie->ppFrames[frameNo] );

	for( i = frameNo; i < pMovie->frameCnt - 1; i++ )
		ppNewFrames[i] = pMovie->ppFrames[i+1];

	free( pMovie->ppFrames );
	pMovie->ppFrames = ppNewFrames;
	pMovie->frameCnt--;
}

void BlinkenMovieDeleteFrames( stBlinkenMovie * pMovie ) {
	stBlinkenFrame * * ppNewFrames;
	Uint32 i;

	if( pMovie == NULL )
		return;

	ppNewFrames = (stBlinkenFrame * *)malloc1D( 0, sizeof( stBlinkenFrame * ) );
	if( ppNewFrames == NULL )
		return;

	for( i = 0; i < pMovie->frameCnt; i++ )
		BlinkenFrameFree( pMovie->ppFrames[i] );

	free( pMovie->ppFrames );
	pMovie->ppFrames = ppNewFrames;
	pMovie->frameCnt = 0;
}

void BlinkenMovieResize( stBlinkenMovie * pMovie, Uint32 height, Uint32 width, Uint32 channels, Uint32 maxval ) {
	Uint32 i;

	if( pMovie == NULL )
		return;

	if( height < BlinkenHeightMin ) height = BlinkenHeightMin;
	if( height > BlinkenHeightMax ) height = BlinkenHeightMax;
	if( width < BlinkenWidthMin ) width = BlinkenWidthMin;
	if( width > BlinkenWidthMax ) width = BlinkenWidthMax;
	if( channels < BlinkenChannelsMin ) channels = BlinkenChannelsMin;
	if( channels > BlinkenChannelsMax ) channels = BlinkenMaxvalMax;
	if( maxval < BlinkenMaxvalMin ) maxval = BlinkenMaxvalMin;
	if( maxval > BlinkenMaxvalMax ) maxval = BlinkenMaxvalMax;

	pMovie->height = height;
	pMovie->width = width;
	pMovie->channels = channels;
	pMovie->maxval = maxval;

	for( i = 0; i < pMovie->frameCnt; i++ )
		BlinkenFrameResize( pMovie->ppFrames[i], height, width, channels, maxval );
}

void BlinkenMovieScale( stBlinkenMovie * pMovie, Uint32 height, Uint32 width ) {
	Uint32 i;

	if( pMovie == NULL )
		return;

	if( height < BlinkenHeightMin ) height = BlinkenHeightMin;
	if( height > BlinkenHeightMax ) height = BlinkenHeightMax;
	if( width < BlinkenWidthMin ) width = BlinkenWidthMin;
	if( width > BlinkenWidthMax ) width = BlinkenWidthMax;

	pMovie->height = height;
	pMovie->width = width;

	for( i = 0; i < pMovie->frameCnt; i++ )
		BlinkenFrameScale( pMovie->ppFrames[i], height, width );
}

char * BlinkenMovieToString( stBlinkenMovie * pMovie ) {
	char * * strs, * str, * ptr;
	Uint32 i, size;

	if( pMovie == NULL )
		return NULL;

	strs = (char * *)malloc1D( pMovie->frameCnt, sizeof( char * ) );
	if( strs == NULL )
		return NULL;

	for( i = 0; i < pMovie->frameCnt; i++ ) {
		strs[i] = BlinkenFrameToString( pMovie->ppFrames[i] );
		if( strs[i] == NULL ) {
			for( i--; i >= 0; i-- ) /* FIXME: Two nested for loops with the same loop variable? (apart from: i is unsigned, that will underflow... */
				free( strs[i] );
			free( strs );
			return NULL;
		}
	}

	size = 128;
	for( i = 0; i < pMovie->infoCnt; i++ )
		size += strlen( pMovie->pppInfos[i][0] ) + strlen( pMovie->pppInfos[i][1] ) + 8;
	for( i = 0; i < pMovie->frameCnt; i++ )
		size += strlen( strs[i] ) + 32;

	str = (char *)malloc( size );
	if( str == NULL ) {
		for( i = 0; i < pMovie->frameCnt; i++ )
			free( strs[i] );
		free( strs );
		return NULL;
	}

	ptr = str;

	sprintf( ptr, "BlinkenMovie %ux%u-%u/%u\n", pMovie->width, pMovie->height, pMovie->channels, pMovie->maxval );
	ptr += strlen( ptr );

	for( i = 0; i < pMovie->infoCnt; i++ ) {
		sprintf( ptr, "%s = %s\n", pMovie->pppInfos[i][0], pMovie->pppInfos[i][1] );
		ptr += strlen( ptr );
	}

	for( i = 0; i < pMovie->frameCnt; i++ ) {
		sprintf( ptr, "frame %u\n%s", i, strs[i] );
		ptr += strlen( ptr );
		free( strs[i] );
	}
	free( strs );

	return str;
}

stBlinkenMovie * BlinkenMovieLoadBlm( char * pFilename ) {
	FILE * pFile;
	stBlinkenMovie * pMovie;
	stBlinkenFrame * pFrame;
	Uint32 width, height, y, x, duration;
	Sint32 chr;
	char infoType[256], infoData[1024], pixel[2];

	if( pFilename == NULL )
		return NULL;

  //open file
	pFile = fopen( pFilename, "rt" );
	if( pFile == NULL )
		return NULL;

  //read magic and size
	if( fscanf( pFile, " # BlinkenLights Movie %ux%u", &width, &height ) != 2 ) {
		fclose( pFile );
		return NULL;
	}

  //allocate a new movie
	pMovie = BlinkenMovieNew( height, width, 1, 1 );
	if( pMovie == NULL ) {
		fclose( pFile );
		return NULL;
	}

  //no frame yet
	pFrame = NULL;
	y = 0;

  //read frames
	while( ! feof( pFile ) ) {
    //skip rest of previous line (including newline)
		while( (chr = fgetc( pFile )) != '\n' && chr != EOF );

    //info line
		if( fscanf( pFile, " # %255[A-Za-z0-9] %*[=:] %1023[^\n]", infoType, infoData ) == 2 ) {
			BlinkenMovieAppendInfo( pMovie, infoType, infoData );
		}

    //start of frame
		else if( fscanf( pFile, " @ %u", &duration ) == 1 ) {
      //create new frame and append it to movie
			pFrame = BlinkenFrameNew( height, width, 1, 1, duration );
			if( pFrame != NULL ) {
				BlinkenFrameClear( pFrame );
				if( BlinkenMovieAppendFrame( pMovie, pFrame ) != 0 ) {
					BlinkenFrameFree( pFrame );
					pFrame = NULL;
				}
				y = 0;
			}
		}

    //data line
		else if( fscanf( pFile, "%1[01]", pixel ) == 1 ) {
			if( pFrame != NULL ) {
				for( x = 0; ; x++ ) {
					BlinkenFrameSetPixel( pFrame, y, x, 0, (unsigned char)(pixel[0] == '1' ? 1 : 0) ); //set pixel
					if( fscanf( pFile, "%1[01]", pixel ) != 1 ) //read next pixel
						break;
				}
				y++; //next row
			}
		}

	} //while( ! feof( pFile ) )

  //close file
	fclose( pFile );

	return pMovie;
}

stBlinkenMovie * BlinkenMovieLoadBmm( char * pFilename ) {
	FILE * pFile;
	stBlinkenMovie * pMovie;
	stBlinkenFrame * pFrame;
	Uint32 width, height, y, x, duration, val;
	Sint32 chr;
	char infoType[256], infoData[1024], pixel[8];

	if( pFilename == NULL )
		return NULL;

  //open file
	pFile = fopen( pFilename, "rt" );
	if( pFile == NULL )
		return NULL;

  //read magic and size
	if( fscanf( pFile, " # BlinkenMini Movie %ux%u", &width, &height ) != 2 ) {
		fclose( pFile );
		return NULL;
	}

  //allocate a new movie
	pMovie = BlinkenMovieNew( height, width, 1, 255 );
	if( pMovie == NULL ) {
		fclose( pFile );
		return NULL;
	}

  //no frame yet
	pFrame = NULL;
	y = 0;

  //read frames
	while( ! feof( pFile ) ) {
    //skip rest of previous line (including newline)
		while( (chr = fgetc( pFile )) != '\n' && chr != EOF );

    //info line
		if( fscanf( pFile, " # %255[A-Za-z0-9] %*[=:] %1023[^\n]", infoType, infoData ) == 2 ) {
			BlinkenMovieAppendInfo( pMovie, infoType, infoData );
		}

    //start of frame
		else if( fscanf( pFile, " @ %u", &duration ) == 1 ) {
      //create new frame and append it to movie
			pFrame = BlinkenFrameNew( height, width, 1, 255, duration );
			if( pFrame != NULL ) {
				BlinkenFrameClear( pFrame );
				if( BlinkenMovieAppendFrame( pMovie, pFrame ) != 0 ) {
					BlinkenFrameFree( pFrame );
					pFrame = NULL;
				}
				y = 0;
			}
		}

    //data line
		else if( fscanf( pFile, "%7[0-9A-FXa-fx]", pixel ) == 1 ) {
			if( pFrame != NULL ) {
				for( x = 0; ; x++ ) {
					if( sscanf( pixel, "%u", &val ) != 1 ) //convert pixel to number
						break;
					BlinkenFrameSetPixel( pFrame, y, x, 0, (unsigned char)val ); //set pixel
					fscanf( pFile, "%*[ \t]" ); //kill space
					if( fscanf( pFile, "%7[0-9A-FXa-fx]", pixel ) != 1 ) //read next pixel
						break;
				}
				y++; //next row
			}
		}

	} //while( ! feof( pFile ) )

  //close file
	fclose( pFile );

	return pMovie;
}

stBlinkenMovie * BlinkenMovieLoadBml( char * pFilename ) {
	FILE * pFile;
	stBlinkenMovie * pMovie;
	stBlinkenFrame * pFrame;
	Uint32 width, height, channels, bits, maxval, chrs, y, x, c, duration, val;
	char buffer[2048], infoType[256], infoData[1024], pixelFormat[16], pixel[8], * ptr, chr;

	if( pFilename == NULL )
		return NULL;

  //open file
	pFile = fopen( pFilename, "rt" );
	if( pFile == NULL )
		return NULL;

  //no movie yet - blm tag not yet found
	pMovie = NULL;

  //no frame yet
	pFrame = NULL;
	y = 0;

  //read tags
	maxval = 0;
	while( ! feof( pFile ) ) {

    //skip to just before beginning of next tag
		fscanf( pFile, "%*[^<]" );
    //skip beginning character of next tag
		if( fgetc( pFile ) != '<' )
      //end loop (no more tags)
			break;

    //no blm tag yet
		if( pMovie == NULL ) {

      //blm tag
			if( fscanf( pFile, "blm%2047[^>]", buffer ) == 1 ) {
        //get attributes
				width = 0;
				height = 0;
				channels = 1;
				bits = 4;
				maxval = 15;
				if( (ptr = strstr( buffer, "height=\"" )) != NULL ) //height
					sscanf( ptr+8, "%u", &height );
				if( (ptr = strstr( buffer, "width=\"" )) != NULL ) //width
					sscanf( ptr+7, "%u", &width );
				if( (ptr = strstr( buffer, "channels=\"" )) != NULL ) //channels
					sscanf( ptr+10, "%u", &channels );
				if( (ptr = strstr( buffer, "bits=\"" )) != NULL ) //bits
					sscanf( ptr+6, "%u", &bits );
				maxval = (1 << bits) - 1; //maxval

        //allocate a new movie
				pMovie = BlinkenMovieNew( height, width, channels, maxval );
				if( pMovie == NULL ) {
					fclose( pFile );
					return NULL;
				}

        //get number of characters per channel
				chrs = (bits + 3) >> 2;
        //get fscanf formart string for reading a pixel
				sprintf( pixelFormat, "%%%d[0-9A-Fa-f]", chrs > 4 ? 5 : chrs ); //read max 5 chars (2 already in use by prefix)
        //initialize pixel buffer with hex prefix
				strcpy( pixel, "0x" );
			}

		} //if( pMovie == NULL )

    //blm tag was already found
		else { //if( pMovie == NULL )
      //title tag
			if( fscanf( pFile, "title>%2047[^<]", buffer ) == 1 ) {
        //add info to movie
				BlinkenMovieAppendInfo( pMovie, "title", buffer );
			}

      //description tag
			else if( fscanf( pFile, "description>%2047[^<]", buffer ) == 1 ) {
        //check if generic info
				if( sscanf( buffer, "%255[A-Za-z0-9] %*[=:] %1023[^\n]", infoType, infoData ) == 2 )
          //add info to movie
					BlinkenMovieAppendInfo( pMovie, infoType, infoData );
				else
          //add info to movie
					BlinkenMovieAppendInfo( pMovie, "description", buffer );
			}

      //creator tag
			else if( fscanf( pFile, "creator>%2047[^<]", buffer ) == 1 ) {
        //add info to movie
				BlinkenMovieAppendInfo( pMovie, "creator", buffer );
			}

      //author tag
			else if( fscanf( pFile, "author>%2047[^<]", buffer ) == 1 ) {
        //add info to movie
				BlinkenMovieAppendInfo( pMovie, "author", buffer );
			}

      //email tag
			else if( fscanf( pFile, "email>%2047[^<]", buffer ) == 1 ) {
        //add info to movie
				BlinkenMovieAppendInfo( pMovie, "email", buffer );
			}

      //url tag
			else if( fscanf( pFile, "url>%2047[^<]", buffer ) == 1 ) {
        //add info to movie
				BlinkenMovieAppendInfo( pMovie, "url", buffer );
			}

      //frame tag
					else if( fscanf( pFile, "frame%2047[^>]", buffer ) == 1 ) {
        //get attributes
						duration = 0;
						if( (ptr = strstr( buffer, "duration=\"" )) != NULL ) //duration
							sscanf( ptr+10, "%u", &duration );
        //create new frame and append it to movie
						pFrame = BlinkenFrameNew( height, width, channels, maxval, duration );
						if( pFrame != NULL ) {
							BlinkenFrameClear( pFrame );
							if( BlinkenMovieAppendFrame( pMovie, pFrame ) != 0 ) {
								BlinkenFrameFree( pFrame );
								pFrame = NULL;
							}
							y = 0;
						}
					}

      //row tag
					else if( fscanf( pFile, "row%c", &chr ) == 1 && chr == '>' ) {
						if( pFrame != NULL ) {
          //parse row
							for( x = 0; x < width; x++ ) {
								for( c = 0; c < channels; c++ ) {
              //read next pixel (one channel of pixel)
									if( fscanf( pFile, pixelFormat, pixel+2 ) != 1 ) {
										x = width; //also terminate x loop
										break;
									}
              //convert pixel (one channel of pixel) to number
									if( sscanf( pixel, "%x", &val ) != 1 ) {
										x = width; //also terminate x loop
										break;
									}
              //set pixel (one channel of pixel)
									BlinkenFrameSetPixel( pFrame, y, x, c, (unsigned char)val );
								}
							}
							y++; //next row
						}
					}

		} //if( pMovie == NULL ) ... else

	} //while( ! feof( pFile ) )

  //close file
	fclose( pFile );

	return pMovie;
}

stBlinkenMovie * BlinkenMovieLoadBbm( char * pFilename ) {
	FILE * pFile;
	stBlinkenMovie * pMovie;
	stBlinkenFrame * pFrame;
	unsigned char header[24], subHeader[6], frameStartMarker[4];
	unsigned long headerMagic, headerFrameCnt, headerDuration, headerFramePtr;
	unsigned short headerHeight, headerWidth, headerChannels, headerMaxval;
	unsigned long subHeaderMagic, frameStartMarkerMagic;
	unsigned short subHeaderSize;
	unsigned char * pInfoHeader, * pInfoHeaderX, * pFrameData;
	Uint32 len, duration, y, x, c, i;

	if( pFilename == NULL )
		return NULL;

  //open file
	pFile = fopen( pFilename, "rb" );
	if( pFile == NULL )
		return NULL;

  //read header
	if( fread( header, 1, 24, pFile ) != 24 ) {
		fclose( pFile );
		return NULL;
	}
	headerMagic = (unsigned long)header[0] << 24 | (unsigned long)header[1] << 16 | (unsigned long)header[2] << 8 | (unsigned long)header[3];
	headerHeight = (unsigned short)header[4] << 8 | (unsigned short)header[5];
	headerWidth = (unsigned short)header[6] << 8 | (unsigned short)header[7];
	headerChannels = (unsigned short)header[8] << 8 | (unsigned short)header[9];
	headerMaxval = (unsigned short)header[10] << 8 | (unsigned short)header[11];
	headerFrameCnt = (unsigned long)header[12] << 24 | (unsigned long)header[13] << 16 | (unsigned long)header[14] << 8 | (unsigned long)header[15];
	headerDuration = (unsigned long)header[16] << 24 | (unsigned long)header[17] << 16 | (unsigned long)header[18] << 8 | (unsigned long)header[19];
	headerFramePtr = (unsigned long)header[20] << 24 | (unsigned long)header[21] << 16 | (unsigned long)header[22] << 8 | (unsigned long)header[23];
  //check magic
	if( headerMagic != 0x23542666 ) {
		fclose( pFile );
		return NULL;
	}

  //allocate a new movie
	pMovie = BlinkenMovieNew( headerHeight, headerWidth, headerChannels, headerMaxval );
	if( pMovie == NULL ) {
		fclose( pFile );
		return NULL;
	}

  //read subheaders
	while( ftell( pFile ) + 6 <= (long)headerFramePtr ) {
		if( fread( subHeader, 1, 6, pFile ) != 6 ) {
			BlinkenMovieFree( pMovie );
			fclose( pFile );
			return NULL;
		}
		subHeaderMagic = (unsigned long)subHeader[0] << 24 | (unsigned long)subHeader[1] << 16 | (unsigned long)subHeader[2] << 8 | (unsigned long)subHeader[3];
		subHeaderSize = (unsigned short)subHeader[4] << 8 | (unsigned short)subHeader[5];

    //header fits into gap to frame start
		if( subHeaderSize >= 6 && ftell( pFile ) + subHeaderSize - 6 <= (long)headerFramePtr ) {
      //info header
			if( subHeaderMagic == 0x696E666F ) { //'i' 'n' 'f' 'o'
        //read rest of info header
				pInfoHeader = (unsigned char *)malloc( subHeaderSize - 6 );
				if( pInfoHeader == NULL ) {
					BlinkenMovieFree( pMovie );
					fclose( pFile );
					return NULL;
				}
				if( fread( pInfoHeader, 1, subHeaderSize - 6, pFile ) != (unsigned short)(subHeaderSize - 6) ) {
					free( pInfoHeader );
					BlinkenMovieFree( pMovie );
					fclose( pFile );
					return NULL;
				}
        //parse information
				if( (pInfoHeaderX = (unsigned char *)memchr( pInfoHeader, 0, subHeaderSize - 6 )) != NULL ) {
					pInfoHeaderX++;
					len = pInfoHeaderX - pInfoHeader;
					if( memchr( pInfoHeaderX, 0, subHeaderSize - 6 - len ) != NULL )
						BlinkenMovieAppendInfo( pMovie, (char *)pInfoHeader,  (char *)pInfoHeaderX );
				}
				free( pInfoHeader );
			}

      //unknown subHeader
			else
        //skip
				fseek( pFile, subHeaderSize - 6, SEEK_CUR );

		} //if( ftell( pFile ) ...
	} //while( ftell( pFile ) ...

  //seek to start of frames
	fseek( pFile, headerFramePtr, SEEK_SET );

  //read frame start marker
	if( fread( frameStartMarker, 1, 4, pFile ) != 4 ) {
		BlinkenMovieFree( pMovie );
		fclose( pFile );
		return NULL;
	}
	frameStartMarkerMagic = (unsigned long)frameStartMarker[0] << 24 | (unsigned long)frameStartMarker[1] << 16 | (unsigned long)frameStartMarker[2] << 8 | (unsigned long)frameStartMarker[3];
	if( frameStartMarkerMagic != 0x66726D73 ) { //'f' 'r' 'm' 's'
		BlinkenMovieFree( pMovie );
		fclose( pFile );
		return NULL;
	}

  //allocate buffer for frame data
	len = 2 + headerHeight * headerWidth * headerChannels;
	pFrameData = (unsigned char *)malloc( len );
	if( pFrameData == NULL ) {
		BlinkenMovieFree( pMovie );
		fclose( pFile );
		return NULL;
	}

  //read frames
	for( ; ; ) {
    //read frame
		if( fread( pFrameData, 1, len, pFile ) != (Uint32)len )
			break;
		duration = (unsigned short)pFrameData[0] << 8 | (unsigned short)pFrameData[1];
    //build frame and append it to movie
		pFrame = BlinkenFrameNew( headerHeight, headerWidth, headerChannels, headerMaxval, duration );
		if( pFrame == NULL )
			break;
		i = 2;
		for( y = 0; y < headerHeight; y++ )
			for( x = 0; x < headerWidth; x++ )
				for( c = 0; c < headerChannels; c++, i++ )
					BlinkenFrameSetPixel( pFrame, y, x, c, pFrameData[i] );
		if( BlinkenMovieAppendFrame( pMovie, pFrame ) != 0 ) {
			BlinkenFrameFree( pFrame );
			pFrame = NULL;
		}

	} //for( ; ; )

  //free buffer for frame data
	free( pFrameData );

  //close file
	fclose( pFile );

	return pMovie;
}

stBlinkenMovie * BlinkenMovieLoad( char * pFilename ) {
	Uint32 len;

	if( pFilename == NULL )
		return NULL;

	len = strlen( pFilename );
	if( len > 4 && strcmp( pFilename + len - 4, ".blm" ) == 0 )
		return BlinkenMovieLoadBlm( pFilename );
	if( len > 4 && strcmp( pFilename + len - 4, ".bmm" ) == 0 )
		return BlinkenMovieLoadBmm( pFilename );
	if( len > 4 && strcmp( pFilename + len - 4, ".bml" ) == 0 )
		return BlinkenMovieLoadBml( pFilename );
	if( len > 4 && strcmp( pFilename + len - 4, ".bbm" ) == 0 )
		return BlinkenMovieLoadBbm( pFilename );
	return NULL;
}

Sint32 BlinkenMovieSaveBlm( stBlinkenMovie * pMovie, char * pFilename ) {
	stBlinkenMovie * pOutMovie;
	FILE * pFile;
	Uint32 i, y, x;

	if( pMovie == NULL || pFilename == NULL )
		return -1;

  //convert movie to suitable format
	pOutMovie = BlinkenMovieClone( pMovie );
	if( pOutMovie == NULL )
		return -1;
	BlinkenMovieResize( pOutMovie, pOutMovie->height, pOutMovie->width, 1, 1 );

  //open file
	pFile = fopen( pFilename, "wt" );
	if( pFile == NULL ) {
		BlinkenMovieFree( pOutMovie );
		return -1;
	}

  //write header line
	fprintf( pFile, "# BlinkenLights Movie %ux%u\n", pOutMovie->width, pOutMovie->height );

  //write information lines
	for( i = 0; i < pOutMovie->infoCnt; i++ )
		fprintf( pFile, "# %s = %s\n", pOutMovie->pppInfos[i][0], pOutMovie->pppInfos[i][1] );

  //write frames
	for( i = 0; i < pOutMovie->frameCnt; i++ ) {
		fprintf( pFile, "\n@%u\n", BlinkenFrameGetDuration( pOutMovie->ppFrames[i] ) );
		for( y = 0; y < pOutMovie->height; y++ ) {
			for( x = 0; x < pOutMovie->width; x++ ) {
				if( BlinkenFrameGetPixel( pOutMovie->ppFrames[i], y, x, 0 ) != 0 )
					fprintf( pFile, "1" );
				else
					fprintf( pFile, "0" );
			}
			fprintf( pFile, "\n" );
		}
	}

  //close file
	fclose( pFile );

  //free copied movie
	BlinkenMovieFree( pOutMovie );

  //success
	return 0;
}

Sint32 BlinkenMovieSaveBmm( stBlinkenMovie * pMovie, char * pFilename ) {
	stBlinkenMovie * pOutMovie;
	FILE * pFile;
	Uint32 i, y, x;

	if( pMovie == NULL || pFilename == NULL )
		return -1;

  //convert movie to suitable format
	pOutMovie = BlinkenMovieClone( pMovie );
	if( pOutMovie == NULL )
		return -1;
	BlinkenMovieResize( pOutMovie, pOutMovie->height, pOutMovie->width, 1, 255 );

  //open file
	pFile = fopen( pFilename, "wt" );
	if( pFile == NULL ) {
		BlinkenMovieFree( pOutMovie );
		return -1;
	}

  //write header line
	fprintf( pFile, "# BlinkenMini Movie %ux%u\n", pOutMovie->width, pOutMovie->height );

  //write information lines
	for( i = 0; i < pOutMovie->infoCnt; i++ )
		fprintf( pFile, "# %s = %s\n", pOutMovie->pppInfos[i][0], pOutMovie->pppInfos[i][1] );

  //write frames
	for( i = 0; i < pOutMovie->frameCnt; i++ ) {
		fprintf( pFile, "\n@%u\n", BlinkenFrameGetDuration( pOutMovie->ppFrames[i] ) );
		for( y = 0; y < pOutMovie->height; y++ ) {
			fprintf( pFile, "0x%02X", BlinkenFrameGetPixel( pOutMovie->ppFrames[i], y, 0, 0 ) );
			for( x = 1; x < pOutMovie->width; x++ )
				fprintf( pFile, " 0x%02X", BlinkenFrameGetPixel( pOutMovie->ppFrames[i], y, x, 0 ) );
			fprintf( pFile, "\n" );
		}
	}

  //close file
	fclose( pFile );

  //free copied movie
	BlinkenMovieFree( pOutMovie );

  //success
	return 0;
}

Sint32 BlinkenMovieSaveBml( stBlinkenMovie * pMovie, char * pFilename ) {
	stBlinkenMovie * pOutMovie;
	FILE * pFile;
	Uint32 bits, val, i, y, x, c;

	if( pMovie == NULL || pFilename == NULL )
		return -1;

  //convert movie to suitable format
	pOutMovie = BlinkenMovieClone( pMovie );
	if( pOutMovie == NULL )
		return -1;
	val = pOutMovie->maxval; //get number of bits
	for( bits = 0; val != 0; val >>= 1, bits++ );
	BlinkenMovieResize( pOutMovie, pOutMovie->height, pOutMovie->width, pOutMovie->channels, (1 << bits) - 1 );

  //open file
	pFile = fopen( pFilename, "wt" );
	if( pFile == NULL ) {
		BlinkenMovieFree( pOutMovie );
		return -1;
	}

  //write header line
	fprintf( pFile, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" );

  //write blm start tag
	fprintf( pFile, "<blm width=\"%u\" height=\"%u\" bits=\"%u\" channels=\"%u\">\n",
					 pOutMovie->width, pOutMovie->height, bits, pOutMovie->channels );

  //write information lines
	fprintf( pFile, "\t<header>\n" );
	for( i = 0; i < pOutMovie->infoCnt; i++ ) {
		if( strcmp( pOutMovie->pppInfos[i][0], "title" ) == 0 )
			fprintf( pFile, "\t\t<title>%s</title>\n", pOutMovie->pppInfos[i][1] );
		else if( strcmp( pOutMovie->pppInfos[i][0], "description" ) == 0 )
			fprintf( pFile, "\t\t<description>%s</description>\n", pOutMovie->pppInfos[i][1] );
		else if( strcmp( pOutMovie->pppInfos[i][0], "creator" ) == 0 )
			fprintf( pFile, "\t\t<creator>%s</creator>\n", pOutMovie->pppInfos[i][1] );
		else if( strcmp( pOutMovie->pppInfos[i][0], "author" ) == 0)
			fprintf( pFile, "\t\t<author>%s</author>\n", pOutMovie->pppInfos[i][1] );
		else if( strcmp( pOutMovie->pppInfos[i][0], "email" ) == 0)
			fprintf( pFile, "\t\t<email>%s</email>\n", pOutMovie->pppInfos[i][1] );
		else if( strcmp( pOutMovie->pppInfos[i][0], "url" ) == 0)
			fprintf( pFile, "\t\t<url>%s</url>\n", pOutMovie->pppInfos[i][1] );
		else
			fprintf( pFile, "\t\t<description>%s: %s</description>\n", pOutMovie->pppInfos[i][0], pOutMovie->pppInfos[i][1] );
	}
	fprintf( pFile, "\t</header>\n" );

  //write frames
	for( i = 0; i < pOutMovie->frameCnt; i++ ) {
		fprintf( pFile, "\n\t<frame duration=\"%u\">\n", BlinkenFrameGetDuration( pOutMovie->ppFrames[i] ) );
		for( y = 0; y < pOutMovie->height; y++ ) {
			fprintf( pFile, "\t\t<row>" );
			for( x = 0; x < pOutMovie->width; x++ )
				for( c = 0; c < pOutMovie->channels; c++ )
					fprintf( pFile, bits > 4 ? "%02X" : "%01X",
									 BlinkenFrameGetPixel( pOutMovie->ppFrames[i], y, x, c ) );
			fprintf( pFile, "</row>\n" );
		}
		fprintf( pFile, "\t</frame>\n" );
	}

  //write blm end tag
	fprintf( pFile, "</blm>\n" );

  //close file
	fclose( pFile );

  //free copied movie
	BlinkenMovieFree( pOutMovie );

  //success
	return 0;
}

Sint32 BlinkenMovieSaveBbm( stBlinkenMovie * pMovie, char * pFilename ) {
	unsigned char * pFrameData;
	FILE * pFile;
	unsigned char header[24], infoHeader[6], framePointer[4], frameStartMarker[4];
	Uint32 duration, len, len0, len1, i, j, y, x, c, val;
	long pos;

	if( pMovie == NULL || pFilename == NULL )
		return -1;

  //allocate frame data buffer
	pFrameData = (unsigned char *)malloc( 2 + pMovie->height * pMovie->width * pMovie->channels );
	if( pFrameData == NULL )
		return -1;

  //open file
	pFile = fopen( pFilename, "wb" );
	if( pFile == NULL ) {
		free( pFrameData );
		return -1;
	}

  //write header
	header[0] = 0x23; //magic
	header[1] = 0x54;
	header[2] = 0x26;
	header[3] = 0x66;
	header[4] = (unsigned char)(pMovie->height >> 8);
	header[5] = (unsigned char)pMovie->height;
	header[6] = (unsigned char)(pMovie->width >> 8);
	header[7] = (unsigned char)pMovie->width;
	header[8] = (unsigned char)(pMovie->channels >> 8);
	header[9] = (unsigned char)pMovie->channels;
	header[10] = (unsigned char)(pMovie->maxval >> 8);
	header[11] = (unsigned char)pMovie->maxval;
	header[12] = (unsigned char)(pMovie->frameCnt >> 24);
	header[13] = (unsigned char)(pMovie->frameCnt >> 16);
	header[14] = (unsigned char)(pMovie->frameCnt >> 8);
	header[15] = (unsigned char)pMovie->frameCnt;
	duration = 0;
	for( i = 0; i < pMovie->frameCnt; i++ )
		duration += BlinkenFrameGetDuration( pMovie->ppFrames[i] );
	header[16] = (unsigned char)(duration >> 24);
	header[17] = (unsigned char)(duration >> 16);
	header[18] = (unsigned char)(duration >> 8);
	header[19] = (unsigned char)duration;
	header[20] = 0; //frame pointer is written later
	header[21] = 0;
	header[22] = 0;
	header[23] = 0;
	fwrite( header, 1, 24, pFile );

  //write information
	for( i = 0; i < pMovie->infoCnt; i++ ) {
		len0 = strlen( pMovie->pppInfos[i][0] );
		if( len0 > 32760 )
			len0 = 32760;
		len1 = strlen( pMovie->pppInfos[i][1] );
		if( len1 > 32760 )
			len1 = 32760;
		len = 8 + len0 + len1;
		infoHeader[0] = 0x69; //'i'
		infoHeader[1] = 0x6E; //'n'
		infoHeader[2] = 0x66; //'f'
		infoHeader[3] = 0x6F; //'o'
		infoHeader[4] = (unsigned char)(len >> 8);
		infoHeader[5] = (unsigned char)len;
		fwrite( infoHeader, 1, 6, pFile );
		fwrite( pMovie->pppInfos[i][0], 1, len0, pFile );
		fwrite( "\0", 1, 1, pFile );
		fwrite( pMovie->pppInfos[i][1], 1, len1, pFile );
		fwrite( "\0", 1, 1, pFile );
	}

  //write frame pointer
	pos = ftell( pFile );
	framePointer[0] = (unsigned char)(pos >> 24);
	framePointer[1] = (unsigned char)(pos >> 16);
	framePointer[2] = (unsigned char)(pos >> 8);
	framePointer[3] = (unsigned char)pos;
	fseek( pFile, 20, SEEK_SET );
	fwrite( framePointer, 1, 4, pFile );
	fseek( pFile, pos, SEEK_SET );

  //write frame start marker
	frameStartMarker[0] = 0x66; //'f'
	frameStartMarker[1] = 0x72; //'r'
	frameStartMarker[2] = 0x6D; //'m'
	frameStartMarker[3] = 0x73; //'s'
	fwrite( frameStartMarker, 1, 4, pFile );

  //write frames
	for( i = 0; i < pMovie->frameCnt; i++ ) {
		val = BlinkenFrameGetDuration( pMovie->ppFrames[i] );
		pFrameData[0] = (unsigned char)(val >> 8);
		pFrameData[1] = (unsigned char)val;
		for( j = 2, y = 0; y < pMovie->height; y++ )
			for( x = 0; x < pMovie->width; x++ )
				for( c = 0; c < pMovie->channels; c++, j++ )
					pFrameData[j] = BlinkenFrameGetPixel( pMovie->ppFrames[i], y, x, c );
		fwrite( pFrameData, 1, j, pFile );
	}

  //free frame data buffer
	free( pFrameData );

  //close file
	fclose( pFile );

  //success
	return 0;
}

Sint32 BlinkenMovieSave( stBlinkenMovie * pMovie, char * pFilename ) {
	Uint32 len;

	if( pMovie == NULL || pFilename == NULL )
		return -1;

	len = strlen( pFilename );
	if( len > 4 && strcmp( pFilename + len - 4, ".blm" ) == 0 )
		return BlinkenMovieSaveBlm( pMovie, pFilename );
	if( len > 4 && strcmp( pFilename + len - 4, ".bmm" ) == 0 )
		return BlinkenMovieSaveBmm( pMovie, pFilename );
	if( len > 4 && strcmp( pFilename + len - 4, ".bml" ) == 0 )
		return BlinkenMovieSaveBml( pMovie, pFilename );
	if( len > 4 && strcmp( pFilename + len - 4, ".bbm" ) == 0 )
		return BlinkenMovieSaveBbm( pMovie, pFilename );
	return -1;
}

#ifndef DISABLE_NETWORK
void BlinkenMovieSend( stBlinkenMovie * pMovie, UDPsocket locBLPsd, UDPpacket *locBLPp,etBlinkenProto proto ) {
	//udp socket must be "connected"
	Uint32 i, len;
	char buffer[65536]; //64kB is more tham maximum UDP size

	for( i = 0; i < pMovie->frameCnt; i++ ) {
		len = BlinkenFrameToNetwork( pMovie->ppFrames[i], proto, buffer, sizeof( buffer ) );
		if( len > 0 ) {
      //send( udpSocket, buffer, len, 0 );
			// Send packet
			memcpy(locBLPp->data, &buffer, len);
			locBLPp->len = len;
			SDLNet_UDP_Send(locBLPsd, -1, locBLPp);
			printf("packet send\n");
		}
		SDL_Delay( BlinkenFrameGetDuration( pMovie->ppFrames[i] ) );
	}
}
/*
stBlinkenMovie * BlinkenMovieReceive( UDPsocket BLPsd, UDPpacket *BLPp, Uint32 timeout, etBlinkenProto * pProto )
*/
#endif // DISABLE_NETWORK
