package com.flagstone.transform;

/*
 * FSHeader.java
 * 
 * Copyright (c) 2001-2008 Flagstone Software Ltd. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 *  * Neither the name of Flagstone Software Ltd. nor the names of its contributors 
 *    may be used to endorse or promote products derived from this software 
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.util.zip.*;
import java.io.*;

/**
 * The FSHeader class can be used to quickly decode the header information in a
 * flash file to get information about the number of frames, screen size etc.
 * Only the first few bytes are decoded so this avoid the cost of using the
 * FSMovie class which decodes all the data structures. However if a file is
 * compressed then the entire contents will have to be unzipped first in order
 * to decode the header information.
 * 
 * Note: The coordinate system used for the screen extends from the TOP LEFT
 * corner with the positive x-axis extending to the left and the positive y-axis
 * extending DOWN, unlike "traditional" Cartesian systems. The (minX, minY) and
 * (maxX, maxY) coordinates specify the range of values used for the x and y
 * axes. (minX, minY) is often (0,0) but may contain any positive or negative
 * values.
 */
public final class FSHeader
{
	private String signature = null;

	private int version = 0;

	private int minX = 0;

	private int minY = 0;

	private int maxX = 0;

	private int maxY = 0;

	private int length = 0;

	private float frameRate = 0.0f;

	private int frameCount = 0;

	/**
	 * Create a new FSHeader object and initialize it with the information
	 * decoded from the Flash file.
	 * 
	 * @param filename
	 *            the path to a file.
	 * 
	 * @throws FileNotFoundException
	 *             if the file referenced by the path does not exist or refers
	 *             to a directory.
	 * 
	 * @throws IOException
	 *             if there is an error reading the file.
	 * 
	 * @throws DataFormatException
	 *             if the file is not a flash file or there is an error
	 *             unzipping a compressed flash file.
	 */
	public FSHeader(String filename) throws IOException, DataFormatException
	{
		decodeFromFile(filename);
	}

	/**
	 * Create a new FSHeader object and initialize it with the information
	 * decoded from the Flash file.
	 * 
	 * @param file
	 *            a File containing the abstract path to a file.
	 * 
	 * @throws FileNotFoundException
	 *             if the file referenced by the path does not exist or refers
	 *             to a directory.
	 * 
	 * @throws IOException
	 *             if there is an error reading the file.
	 * 
	 * @throws DataFormatException
	 *             if the file is not a flash file or there is an error
	 *             unzipping a compressed flash file.
	 */
	public FSHeader(File file) throws IOException, DataFormatException
	{
		decodeFromFile(file);
	}

	/**
	 * Create a new FSHeader object and initialize it with the information
	 * decoded from the binary data.
	 * 
	 * @param bytes
	 *            the encoded binary data from a Flash file.
	 * 
	 * @throws DataFormatException
	 *             if the data is not in the flash format or there is an error
	 *             unzipping the data if it is compressed.
	 */
	public FSHeader(byte[] bytes) throws IOException, DataFormatException
	{
		decodeFromData(bytes);
	}

	/**
	 * Return the signature used to identify the file type: "FWS" ("SWF" in
	 * reverse) for regular Flash files or "CWS" for compressed (zipped) Flash
	 * files.
	 * 
	 * @return the signature used to identify the file as a Flash file.
	 */
	public String getSignature()
	{
		return signature;
	}

	/**
	 * Return the version number of Flash format used n the file.
	 * 
	 * @return the Flash version number
	 */
	public int getVersion()
	{
		return version;
	}

	/**
	 * Return the length in bytes of the file when uncompressed.
	 * 
	 * @return the length of the (uncompressed) file in bytes.
	 */
	public int getLength()
	{
		return length;
	}

	/**
	 * Return the x-coordinate of the top left corner of the screen.
	 * 
	 * @return the x-coordinate of the top left corner
	 */
	public int getMinX()
	{
		return minX;
	}

	/**
	 * Return the x-coordinate of the bottom right corner of the screen.
	 * 
	 * @return the x-coordinate of the bottom right corner
	 */
	public int getMaxX()
	{
		return maxX;
	}

	/**
	 * The y-coordinate of the top left corner of the screen.
	 * 
	 * @return the y-coordinate of the top left corner
	 */
	public int getMinY()
	{
		return minY;
	}

	/**
	 * The y-coordinate of the bottom right corner of the screen.
	 * 
	 * @return the y-coordinate of the bottom right corner
	 */
	public int getMaxY()
	{
		return maxY;
	}

	/**
	 * Return the number of frames per second at which the Flash Player will
	 * attempt to display the file.
	 * 
	 * @return the frame rate in frames per second.
	 */
	public float getFrameRate()
	{
		return frameRate;
	}

	/**
	 * Return the number of frames in the movie.
	 * 
	 * @return the number of frames in the movie
	 */
	public int getNumberOfFrames()
	{
		return frameCount;
	}

	/**
	 * Initialize the FSHeader object with the specified flash file.
	 * 
	 * @param fileName
	 *            the path to a file.
	 * 
	 * @throws FileNotFoundException
	 *             if the file referenced by the path does not exist or refers
	 *             to a directory.
	 * 
	 * @throws IOException
	 *             if there is an error reading the file.
	 * 
	 * @throws DataFormatException
	 *             if the file is not a flash file or there is an error
	 *             unzipping a compressed flash file.
	 */
	public void decodeFromFile(String fileName) throws IOException,
					DataFormatException
	{
		decodeFromFile(new File(fileName));
	}

	/**
	 * Initialize the FSHeader object with the specified flash file.
	 * 
	 * @param file
	 *            a File containing the abstract path to a file.
	 * 
	 * @throws FileNotFoundException
	 *             if the file referenced by the path does not exist or refers
	 *             to a directory.
	 * 
	 * @throws IOException
	 *             if there is an error reading the file.
	 * 
	 * @throws DataFormatException
	 *             if the file is not a flash file or there is an error
	 *             unzipping a compressed flash file.
	 */
	public void decodeFromFile(File file) throws IOException,
					DataFormatException
	{
		FileInputStream fileContents = new FileInputStream(file);

		int fileLength = (int) file.length();
		byte[] contents = new byte[fileLength];

		fileContents.read(contents);
		fileContents.close();

		decodeFromData(contents);
	}

	/**
	 * Initialize the FSHeader object with binary flash data.
	 * 
	 * @param bytes
	 *            the encoded binary data from a Flash file.
	 * 
	 * @throws DataFormatException
	 *             if the data is not in the flash format or there is an error
	 *             unzipping the data if it is compressed.
	 */
	public void decodeFromData(byte[] bytes) throws DataFormatException
	{
		FSCoder coder = null;

		isFlash(bytes);

		if (bytes[0] == 0x43)
			coder = new FSCoder(FSCoder.LITTLE_ENDIAN, unzip(bytes));
		else
			coder = new FSCoder(FSCoder.LITTLE_ENDIAN, bytes);

		signature = coder.readString(3, "UTF8");
		version = coder.readWord(1, false);
		length = coder.readWord(4, false);

		coder.alignToByte();

		int fieldSize = coder.readBits(5, false);

		minX = coder.readBits(fieldSize, true);
		maxX = coder.readBits(fieldSize, true);
		minY = coder.readBits(fieldSize, true);
		maxY = coder.readBits(fieldSize, true);

		coder.alignToByte();

		frameRate = coder.readFixedWord(1, 1);
		frameCount = coder.readWord(2, false);
	}

	private void isFlash(byte[] bytes) throws DataFormatException
	{
		if (bytes == null || bytes.length < 8)
			throw new DataFormatException("Flash data is null or empty.");

		boolean isFlash = (bytes[0] == 0x43 || bytes[0] == 0x46)
						&& bytes[1] == 0x57 && bytes[2] == 0x53;

		if (isFlash == false)
			throw new DataFormatException(
							"Data does not start with a valid Flash signature.");
	}

	private byte[] unzip(byte[] bytes) throws DataFormatException
	{
		int movieLength = 0;

		for (int i = 0; i < 4; i++)
			movieLength += (bytes[i + 4] & 0x000000FF) << (i * 8);

		byte[] data = new byte[movieLength];

		// copy the uncompressed signature, version and length

		for (int i = 0; i < 8; i++)
			data[i] = bytes[i];

		Inflater inflater = new Inflater();
		inflater.setInput(bytes, 8, bytes.length - 8);
		inflater.inflate(data, 8, movieLength - 8);

		return data;
	}
}
