package com.flagstone.transform;

import java.util.zip.DataFormatException;
import java.util.zip.Inflater;

/**
 * When an image is sent in a ScreenVideo stream only pixel information for the
 * portions of the image that change are sent. FSImageBlock is used to
 * sub-divide an image into a set of blocks. The blocks from successive frames
 * can then be compared to determine which blocks are encoded in the stream.
 * 
 * An image is divided by tiling the blocks across the image from top-left to
 * bottom right. If the image is not covered an integer number of blocks then
 * the size of the blocks along the right and bottom edges of the image are
 * reduced in size.
 * 
 * The FSImageBlock is a contains class for the image data and is used in
 * conjunction with the FsStreamVideoPacket class.
 */
public class FSImageBlock implements Cloneable
{
	private int width;
	private int height;
	private byte[] block;

	/**
	 * Create a new image block with the specified width and height and image
	 * data. The image is compressed using the zip format.
	 * 
	 * @param width
	 *            the width of the block in pixels.
	 * @param height
	 *            the height of the block in pixels
	 * @param data
	 *            the pixels covered by the block, compressed using the zip
	 *            format.
	 */
	public FSImageBlock(int width, int height, byte[] data)
	{
		this.width = width;
		this.height = height;
		block = data;
	}

	/**
	 * Return the width of the block. although the block size is specified in
	 * parent FSScreenVideoPacket object the actual block size used may vary if
	 * the tiled array of blocks overlaps the edge of the image.
	 * 
	 * @return the width of the block in pixels.
	 */
	public int getWidth()
	{
		return width;
	}

	/**
	 * Return the height of the block. although the block size is specified in
	 * parent FSScreenVideoPacket object the actual block size used may vary if
	 * the tiled array of blocks overlaps the edge of the image.
	 * 
	 * @return the height of the block in pixels.
	 */
	public int getHeight()
	{
		return height;
	}

	/**
	 * Returns the zipped image data for the block.
	 * 
	 * @return an array of bytes containing the portion of the image in the
	 *         block.
	 */
	public byte[] getData()
	{
		return block;
	}

	/**
	 * When a ScreenVideo stream is created only the image blocks that change
	 * are included. The blocks that do not change are encoded as empty blocks
	 * which have width and height of zero and do not contain any image data.
	 * This convenience method is used to determine when an image block contains
	 * any valid image data.
	 * 
	 * @return true if the block covers an area of the image that changed or
	 *         false if no image data is included.
	 */
	public boolean isEmpty()
	{
		return width == 0 || height == 0 || block == null || block.length == 0;
	}

	public Object clone()
	{
		return new FSImageBlock(width, height, block);
	}

	public boolean equals(Object anObject)
	{
		if (anObject == null)
		{
			return false;
		}

		if (this == anObject)
		{
			return true;
		}

		boolean result = false;

		if (anObject instanceof FSImageBlock)
		{
			FSImageBlock typedObject = (FSImageBlock) anObject;

			try
			{
				result = width == typedObject.width;
				result = result && height == typedObject.height;

				byte[] a = unzip(block);
				byte[] b = unzip(typedObject.block);

				result = result && Transform.equals(a, b);
			} catch (DataFormatException e)
			{
				result = false;
			}
		}
		return result;
	}

	private byte[] unzip(byte[] bytes) throws DataFormatException
	{
		byte[] data = new byte[width * height * 3];
		int count = 0;

		Inflater inflater = new Inflater();
		inflater.setInput(bytes);
		count = inflater.inflate(data);

		byte[] uncompressedData = new byte[count];

		System.arraycopy(data, 0, uncompressedData, 0, count);

		return uncompressedData;
	}
}
