#include "c4d_general.h"
#include "c4d_memory.h"
#include "c4d_gui.h"

#include "x4d_filter.h"
#include "c4d_bitmapfilter.h"
#include "linefetchutil.h"

struct	ROW_BLOCK
{
	ROW_BLOCK	*next;

	BM_TILE	 *tile;	
	LONG			xymin;																					// first line
	LONG			xymax;																					// last line
	LONG			ref;																						// usage counter
	Bool			is_static;																			// TRUE: do not detach this tile
};

struct	ROW_ROOT
{
	BM_REF		bm;
	LONG			x1;
	LONG			y1;
	LONG			x2;
	LONG			y2;
	LONG			tile_flags;
	LONG			tile_mode;
	BM_TILE		*static_tile;
	LONG			update_flags;
	ROW_BLOCK	*root;
};


//----------------------------------------------------------------------------------------
// Create a new horizontal line fetcher object
// Function result:		line fetcher reference
// bm:								bitmap reference
// x1, x2:						minimum and maximum x coordinate (must be constant for all tiles in root)
// tile_flags:				TILE_REPEAT_BORDER, TILE_REPEAT_TILING, TILE_LAST_RESULT
// tile_mode:					TILE_BM_READ_ONLY, TILE_BM_WRITE, TILE_BM_READ_WRITE, TILE_BM_READ_SCRATCH
// static_tile:				use this parameter if all request are to be satisfied by this tile (and no other one)
//----------------------------------------------------------------------------------------
ROW_REF	new_row_fetcher( BM_REF bm, LONG x1, LONG x2, LONG tile_flags, LONG tile_mode, BM_TILE *static_tile, LONG update_flags )
{
	ROW_REF	rr;
	
	rr = (ROW_REF) GeAlloc( sizeof( ROW_ROOT ));
	if ( rr )
	{
		rr->bm = bm;
		rr->tile_flags = tile_flags;
		rr->tile_mode = tile_mode;
		rr->x1 = x1;
		rr->y1 = 0;
		rr->x2 = x2;
		rr->y2 = 0;
		rr->static_tile = static_tile;
		rr->update_flags = update_flags;
		rr->root = 0;

		if ( static_tile )
		{
			ROW_BLOCK	*curr;

			curr = (ROW_BLOCK *) GeAlloc( sizeof( ROW_BLOCK ));
			if ( curr )
			{
				curr->next = 0;
				curr->tile = static_tile;															// ###NOTE: no support for TILE_MODE_READ_SCRATCH
				curr->xymin = static_tile->ymin;
				curr->xymax = static_tile->ymax;
				curr->ref = 1;
				curr->is_static = TRUE;																// do not detach this tile!
				rr->root = curr;
			}
			else
			{
				GeFree( rr );
				rr = 0;
			}
		}
	}
	return( rr );
}

//----------------------------------------------------------------------------------------
// Delete a line fetcher object
// Function result:		-
// rr:								line fetcher reference
// discard_all:				TRUE: discard changes (is ignored for source tiles)
//----------------------------------------------------------------------------------------
void	delete_row_fetcher( ROW_REF rr, Bool discard_all )
{
	if ( rr )
	{
		while ( rr->root )																			// are there any slices that have to be returned?
		{
			ROW_BLOCK	*curr;
			
			curr = rr->root;
			rr->root = curr->next;

			if ( curr->is_static == FALSE )												// can we detach this tile?
			{
				if (( rr->tile_mode & TILE_MODE_WRITE ) && ( discard_all == FALSE ))
				{
					BfBitmapTileDetach( rr->bm, curr->tile, TRUE );
					if ( rr->update_flags & ROW_FETCH_UPDATE_VIEW )
						BfUpdateView( rr->bm );													// update document view
				}
				else
					BfBitmapTileDetach( rr->bm, curr->tile, FALSE );
			}
		
			GeFree( curr );																				// gosh!
		}
		GeFree( rr );
	}
}

//----------------------------------------------------------------------------------------
// Return the pointer to a requested horizontal line
// Function result:		pointer to the source line
// rr:								line fetcher reference
// y:									requested line
//----------------------------------------------------------------------------------------
UCHAR	*rf_get_bitmap_row( ROW_REF rr, LONG y )
{
	ROW_BLOCK	**prev;
	
	prev = &rr->root;
	while ( 1 )
	{
		ROW_BLOCK	*curr;
		BM_TILE	*tile;

		curr = *prev;
		if ( curr )																							// is there a buffered tile?
		{
			tile = curr->tile;

			if (( y >= curr->xymin ) && ( y < curr->xymax ))
			{
				curr->ref++;
				return((UCHAR *) tile->addr + ( tile->width * ( y - curr->xymin )));
			}

			if (( curr->ref == 0 ) && ( curr->is_static == FALSE ))	// is this tile not currently used and can we detach this tile?
			{
				*prev = curr->next;

				if ( rr->tile_mode & TILE_MODE_WRITE )							// return a destination tile?
				{
					BfBitmapTileDetach( rr->bm, tile, TRUE );					// destination tiles must (!!) be used in sequential order
					if ( rr->update_flags & ROW_FETCH_UPDATE_VIEW )
						BfUpdateView( rr->bm );													// update document view
				}
				else																								// it's a source tile
					BfBitmapTileDetach( rr->bm, tile, FALSE );				// this assumes that source lines are processed in a more or less sequential order

				GeFree( curr );
				continue;																						// check the next tile
			}
		}
		else																										// get a new tile
		{
			RECT32	rect;
				
			if ( rr->static_tile )																// are we not allowed to allocate a new tile?
				break;

			curr = (ROW_BLOCK *) GeAlloc( sizeof( ROW_BLOCK ));
			if ( curr )
			{
				LONG	mode;

				mode = rr->tile_mode;
				if ( mode & TILE_MODE_READ_SCRATCH )
				{
					mode &= ~TILE_MODE_READ_SCRATCH;
					mode |= TILE_MODE_READ_WRITE;											// handle this like a destination tile that will be thrown away
				}

				rect.x1 = rr->x1;
				rect.y1 = y;
				rect.x2 = rr->x2;
				rect.y2 = y + rr->bm->preferred_tile_height;				// get prefered tile height
			
				tile = BfBitmapTileGet( rr->bm, &rect, 0, 0, mode, rr->tile_flags );
				if ( tile )
				{
					curr->next = rr->root;
					curr->tile = tile;
					curr->xymin = rect.y1;
					curr->xymax = rect.y2;
					curr->ref = 1;
					curr->is_static = FALSE;
	
					rr->root = curr;
	
					return((UCHAR *) curr->tile->addr + ( curr->tile->width * ( y - curr->xymin )));
				}
				else
				{
					GeFree( curr );
					return( 0 );
				}
			}	
			else
				break;
		}
		prev = &curr->next;
	}
	return( 0 );
}

void	rf_free_bitmap_row( ROW_REF rr, LONG y )
{
	ROW_BLOCK	**prev;
	
	prev = &rr->root;
	while ( *prev )
	{
		ROW_BLOCK	*curr;

		curr = *prev;
		if (( y >= curr->xymin ) && ( y < curr->xymax ))
		{
			curr->ref--;
			if ( curr->ref == 0 )																	// do not free this tile immediately if ref is zero (wait for the next request)
			{
				*prev = curr->next;																	// unlink
				curr->next = rr->root;
				rr->root = curr;																		// make sure that this is the first tile that will be examined
			}
			break;																								// thank you very much
		}
		prev = &curr->next;
	}
}

//----------------------------------------------------------------------------------------
// Create a new vertical line fetcher object
// Function result:		line fetcher reference
// bm:								bitmap reference
// y1, y2:						minimum and maximum y coordinate (must be constant for all tiles in root)
// tile_flags:				TILE_REPEAT_BORDER, TILE_REPEAT_TILING, TILE_LAST_RESULT
// tile_mode:					TILE_BM_READ_ONLY, TILE_BM_WRITE, TILE_BM_READ_WRITE, TILE_BM_READ_SCRATCH
// static_tile:				use this parameter if all request are to be satisfied by this tile (and no other one)
//----------------------------------------------------------------------------------------
ROW_REF	new_column_fetcher( BM_REF bm, LONG y1, LONG y2, LONG tile_flags, LONG tile_mode, BM_TILE *static_tile, LONG update_flags )
{
	ROW_REF	rr;
	
	rr = (ROW_REF) GeAlloc( sizeof( ROW_ROOT ));
	if ( rr )
	{
		rr->bm = bm;
		rr->tile_flags = tile_flags;
		rr->tile_mode = tile_mode;
		rr->x1 = 0;
		rr->y1 = y1;
		rr->x2 = 0;
		rr->y2 = y2;
		rr->static_tile = static_tile;
		rr->update_flags = update_flags;
		rr->root = 0;

		if ( static_tile )
		{
			ROW_BLOCK	*curr;

			curr = (ROW_BLOCK *) GeAlloc( sizeof( ROW_BLOCK ));
			if ( curr )
			{
				curr->next = 0;
				curr->tile = static_tile;															// ###NOTE: no support for TILE_MODE_READ_SCRATCH
				curr->xymin = static_tile->xmin;
				curr->xymax = static_tile->xmax;
				curr->ref = 1;
				curr->is_static = TRUE;																// do not detach this tile!
				rr->root = curr;
			}
			else
			{
				GeFree( rr );
				rr = 0;
			}
		}
	}
	return( rr );
}

//----------------------------------------------------------------------------------------
// Delete a vertical line fetcher object
// Function result:		-
// rr:								line fetcher reference
// discard_all:				TRUE: discard changes (is ignored for source tiles)
//----------------------------------------------------------------------------------------
void	delete_column_fetcher( ROW_REF rr, Bool discard_all )
{
	delete_row_fetcher( rr, discard_all );
}

//----------------------------------------------------------------------------------------
// Return the pointer to a requested vertical line
// Function result:		pointer to the source line
// rr:								line fetcher reference
// x:									requested line
// pixel_offset:			the distance to the next pixel is returned here (in bytes)
//----------------------------------------------------------------------------------------
UCHAR	*rf_get_bitmap_column( ROW_REF rr, LONG x, LONG *pixel_offset )
{
	ROW_BLOCK	**prev;
	
	prev = &rr->root;
	while ( 1 )
	{
		ROW_BLOCK	*curr;
		BM_TILE	*tile;

		curr = *prev;
		if ( curr )																							// is there a buffered tile?
		{
			tile = curr->tile;
			if (( x >= curr->xymin ) && ( x < curr->xymax ))
			{
				curr->ref++;
				*pixel_offset = tile->width;
				return((UCHAR *) tile->addr + ((( x - curr->xymin ) * get_PX_BITS( tile->px_format )) >> 3 ));
			}

			if (( curr->ref == 0 ) && ( curr->is_static == FALSE ))	// is this tile not currently used and can we detach this tile?
			{
				*prev = curr->next;

				if ( rr->tile_mode & TILE_MODE_WRITE )							// return a destination tile?
				{
					BfBitmapTileDetach( rr->bm, tile, TRUE );					// destination tiles must (!!) be used in sequential order
					if ( rr->update_flags & ROW_FETCH_UPDATE_VIEW )
						BfUpdateView( rr->bm );													// update document view
				}
				else																								// it's a source tile
					BfBitmapTileDetach( rr->bm, tile, FALSE );				// this assumes that source lines are processed in a more or less sequential order

				GeFree( curr );
				continue;																						// check the next tile
			}
		}
		else																										// get a new tile
		{
			RECT32	rect;
				
			if ( rr->static_tile )																// are we not allowed to allocate a new tile?
				break;

			curr = (ROW_BLOCK *) GeAlloc( sizeof( ROW_BLOCK ));
			if ( curr )
			{
				LONG	mode;

				mode = rr->tile_mode;
				if ( mode & TILE_MODE_READ_SCRATCH )
				{
					mode &= ~TILE_MODE_READ_SCRATCH;
					mode |= TILE_MODE_READ_WRITE;											// handle this like a destination tile that will be thrown away
				}

				rect.x1 = x;
				rect.y1 = rr->y1;
				rect.x2 = x + rr->bm->preferred_tile_width;					// get prefered tile width
				rect.y2 = rr->y2;

				tile = BfBitmapTileGet( rr->bm, &rect, 0, 0, mode, rr->tile_flags );
				if ( tile )
				{
					curr->next = rr->root;
					curr->tile = tile;
					curr->xymin = rect.x1;
					curr->xymax = rect.x2;
					curr->ref = 1;
					curr->is_static = FALSE;
	
					rr->root = curr;
	
					*pixel_offset = tile->width;
					return((UCHAR *) tile->addr + ((( x - curr->xymin ) * get_PX_BITS( tile->px_format )) >> 3 ));
				}
				else
				{
					GeFree( curr );
					return( 0 );
				}
			}	
			else
				break;
		}
		prev = &curr->next;
	}
	return( 0 );
}

void	rf_free_bitmap_column( ROW_REF rr, LONG x )
{
	rf_free_bitmap_row( rr, x );
}
