public class

TextureManager

extends Object
package org.andengine.opengl.texture;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

import org.andengine.opengl.texture.bitmap.BitmapTexture;
import org.andengine.opengl.texture.bitmap.BitmapTexture.BitmapTextureFormat;
import org.andengine.opengl.util.GLState;
import org.andengine.util.adt.io.in.AssetInputStreamOpener;
import org.andengine.util.adt.io.in.IInputStreamOpener;
import org.andengine.util.debug.Debug;

import android.content.res.AssetManager;

/**
 * (c) 2010 Nicolas Gramlich
 * (c) 2011 Zynga Inc.
 * 
 * @author Nicolas Gramlich
 * @since 17:48:46 - 08.03.2010
 */
public class TextureManager {
	// ===========================================================
	// Constants
	// ===========================================================

	// ===========================================================
	// Fields
	// ===========================================================

	private final HashSet<ITexture> mTexturesManaged = new HashSet<ITexture>();
	private final HashMap<String, ITexture> mTexturesMapped = new HashMap<String, ITexture>();

	private final ArrayList<ITexture> mTexturesLoaded = new ArrayList<ITexture>();

	private final ArrayList<ITexture> mTexturesToBeLoaded = new ArrayList<ITexture>();
	private final ArrayList<ITexture> mTexturesToBeUnloaded = new ArrayList<ITexture>();

	// ===========================================================
	// Constructors
	// ===========================================================

	// ===========================================================
	// Getter & Setter
	// ===========================================================

	// ===========================================================
	// Methods for/from SuperClass/Interfaces
	// ===========================================================

	// ===========================================================
	// Methods
	// ===========================================================

	public synchronized void onCreate() {

	}

	public synchronized void onReload() {
		final HashSet<ITexture> managedTextures = this.mTexturesManaged;
		if(!managedTextures.isEmpty()) {
			for(final ITexture texture : managedTextures) { // TODO Can the use of the iterator be avoided somehow?
				texture.setNotLoadedToHardware();
			}
		}

		if(!this.mTexturesLoaded.isEmpty()) {
			this.mTexturesToBeLoaded.addAll(this.mTexturesLoaded); // TODO Check if addAll uses iterator internally!
			this.mTexturesLoaded.clear();
		}

		if(!this.mTexturesToBeUnloaded.isEmpty()) {
			this.mTexturesManaged.removeAll(this.mTexturesToBeUnloaded); // TODO Check if removeAll uses iterator internally!
			this.mTexturesToBeUnloaded.clear();
		}
	}

	public synchronized void onDestroy() {
		final HashSet<ITexture> managedTextures = this.mTexturesManaged;
		for(final ITexture texture : managedTextures) { // TODO Can the use of the iterator be avoided somehow?
			texture.setNotLoadedToHardware();
		}

		this.mTexturesToBeLoaded.clear();
		this.mTexturesLoaded.clear();
		this.mTexturesManaged.clear();
		this.mTexturesMapped.clear();
	}

	public synchronized boolean hasMappedTexture(final String pID) {
		if(pID == null) {
			throw new IllegalArgumentException("pID must not be null!");
		}
		return this.mTexturesMapped.containsKey(pID);
	}

	public synchronized ITexture getMappedTexture(final String pID) {
		if(pID == null) {
			throw new IllegalArgumentException("pID must not be null!");
		}
		return this.mTexturesMapped.get(pID);
	}

	public synchronized void addMappedTexture(final String pID, final ITexture pTexture) throws IllegalArgumentException {
		if(pID == null) {
			throw new IllegalArgumentException("pID must not be null!");
		} else if(pTexture == null) {
			throw new IllegalArgumentException("pTexture must not be null!");
		} else if(this.mTexturesMapped.containsKey(pID)) {
			throw new IllegalArgumentException("Collision for pID: '" + pID + "'.");
		}
		this.mTexturesMapped.put(pID, pTexture);
	}

	public synchronized ITexture removedMappedTexture(final String pID) {
		if(pID == null) {
			throw new IllegalArgumentException("pID must not be null!");
		}
		return this.mTexturesMapped.remove(pID);
	}

	/**
	 * @param pTexture the {@link ITexture} to be loaded before the very next frame is drawn (Or prevent it from being unloaded then).
	 * @return <code>true</code> when the {@link ITexture} was previously not managed by this {@link TextureManager}, <code>false</code> if it was already managed.
	 */
	public synchronized boolean loadTexture(final ITexture pTexture) {
		if(pTexture == null) {
			throw new IllegalArgumentException("pTexture must not be null!");
		}

		if(this.mTexturesManaged.contains(pTexture)) {
			/* Just make sure it doesn't get deleted. */
			this.mTexturesToBeUnloaded.remove(pTexture);
			return false;
		} else {
			this.mTexturesManaged.add(pTexture);
			this.mTexturesToBeLoaded.add(pTexture);
			return true;
		}
	}

	/**
	 * Must be called from the GL-{@link Thread}.
	 *
	 * @param pGLState
	 * @param pTexture the {@link ITexture} to be loaded right now, if it is not loaded.
	 * @return <code>true</code> when the {@link ITexture} was previously not managed by this {@link TextureManager}, <code>false</code> if it was already managed.
	 */
	public synchronized boolean loadTexture(final GLState pGLState, final ITexture pTexture) throws IOException {
		if(pTexture == null) {
			throw new IllegalArgumentException("pTexture must not be null!");
		}

		if(!pTexture.isLoadedToHardware()) {
			pTexture.loadToHardware(pGLState);
		} else if(pTexture.isUpdateOnHardwareNeeded()) {
			pTexture.reloadToHardware(pGLState);
		}

		if(this.mTexturesManaged.contains(pTexture)) {
			/* Just make sure it doesn't get deleted. */
			this.mTexturesToBeUnloaded.remove(pTexture);
			return false;
		} else {
			this.mTexturesManaged.add(pTexture);
			this.mTexturesLoaded.add(pTexture);
			return true;
		}
	}

	/**
	 * @param pTexture the {@link ITexture} to be unloaded before the very next frame is drawn (Or prevent it from being loaded then).
	 * @return <code>true</code> when the {@link ITexture} was already managed by this {@link TextureManager}, <code>false</code> if it was not managed.
	 */
	public synchronized boolean unloadTexture(final ITexture pTexture) {
		if(pTexture == null) {
			throw new IllegalArgumentException("pTexture must not be null!");
		}

		if(this.mTexturesManaged.contains(pTexture)) {
			/* If the Texture is loaded, unload it.
			 * If the Texture is about to be loaded, stop it from being loaded. */
			if(this.mTexturesLoaded.contains(pTexture)) {
				this.mTexturesToBeUnloaded.add(pTexture);
			} else if(this.mTexturesToBeLoaded.remove(pTexture)) {
				this.mTexturesManaged.remove(pTexture);
			}
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Must be called from the GL-{@link Thread}.
	 *
	 * @param pGLState
	 * @param pTexture the {@link ITexture} to be unloaded right now, if it is loaded.
	 * @return <code>true</code> when the {@link ITexture} was already managed by this {@link TextureManager}, <code>false</code> if it was not managed.
	 */
	public synchronized boolean unloadTexture(final GLState pGLState, final ITexture pTexture) {
		if(pTexture == null) {
			throw new IllegalArgumentException("pTexture must not be null!");
		} else if(pTexture.isLoadedToHardware()) {
			pTexture.unloadFromHardware(pGLState);
		}

		if(this.mTexturesManaged.contains(pTexture)) {
			/* Just make sure it doesn't get loaded. */
			this.mTexturesLoaded.remove(pTexture);
			this.mTexturesToBeLoaded.remove(pTexture);

			return true;
		} else {
			return false;
		}
	}

	public synchronized void updateTextures(final GLState pGLState) {
		final HashSet<ITexture> texturesManaged = this.mTexturesManaged;
		final ArrayList<ITexture> texturesLoaded = this.mTexturesLoaded;
		final ArrayList<ITexture> texturesToBeLoaded = this.mTexturesToBeLoaded;
		final ArrayList<ITexture> texturesToBeUnloaded = this.mTexturesToBeUnloaded;

		/* First reload Textures that need to be updated. */
		for(int i = texturesLoaded.size() - 1; i >= 0; i--) {
			final ITexture textureToBeReloaded = texturesLoaded.get(i);
			if(textureToBeReloaded.isUpdateOnHardwareNeeded()) {
				try {
					textureToBeReloaded.reloadToHardware(pGLState);
				} catch (final IOException e) {
					Debug.e(e);
				}
			}
		}

		/* Then load pending Textures. */
		final int texturesToBeLoadedCount = texturesToBeLoaded.size();

		if(texturesToBeLoadedCount > 0) {
			for(int i = texturesToBeLoadedCount - 1; i >= 0; i--) {
				final ITexture textureToBeLoaded = texturesToBeLoaded.remove(i);
				if(!textureToBeLoaded.isLoadedToHardware()) {
					try {
						textureToBeLoaded.loadToHardware(pGLState);
					} catch (final IOException e) {
						Debug.e(e);
					}
				}
				texturesLoaded.add(textureToBeLoaded);
			}
		}

		/* Then unload pending Textures. */
		final int texturesToBeUnloadedCount = texturesToBeUnloaded.size();

		if(texturesToBeUnloadedCount > 0) {
			for(int i = texturesToBeUnloadedCount - 1; i >= 0; i--) {
				final ITexture textureToBeUnloaded = texturesToBeUnloaded.remove(i);
				if(textureToBeUnloaded.isLoadedToHardware()) {
					textureToBeUnloaded.unloadFromHardware(pGLState);
				}
				texturesLoaded.remove(textureToBeUnloaded);
				texturesManaged.remove(textureToBeUnloaded);
			}
		}

		/* Finally invoke the GC if anything has changed. */
		if((texturesToBeLoadedCount > 0) || (texturesToBeUnloadedCount > 0)) {
			System.gc();
		}
	}

	public synchronized ITexture getTexture(final String pID, final AssetManager pAssetManager, final String pAssetPath) throws IOException {
		return this.getTexture(pID, pAssetManager, pAssetPath, TextureOptions.DEFAULT);
	}

	public synchronized ITexture getTexture(final String pID, final AssetManager pAssetManager, final String pAssetPath, final TextureOptions pTextureOptions) throws IOException {
		if(this.hasMappedTexture(pID)) {
			return this.getMappedTexture(pID);
		} else {
			final ITexture texture = new BitmapTexture(this, new AssetInputStreamOpener(pAssetManager, pAssetPath), pTextureOptions);
			this.loadTexture(texture);
			this.addMappedTexture(pID, texture);

			return texture;
		}
	}

	public synchronized ITexture getTexture(final String pID, final IInputStreamOpener pInputStreamOpener) throws IOException {
		return this.getTexture(pID, pInputStreamOpener, TextureOptions.DEFAULT);
	}

	public synchronized ITexture getTexture(final String pID, final IInputStreamOpener pInputStreamOpener, final TextureOptions pTextureOptions) throws IOException {
		return this.getTexture(pID, pInputStreamOpener, BitmapTextureFormat.RGBA_8888, pTextureOptions);
	}

	public synchronized ITexture getTexture(final String pID, final IInputStreamOpener pInputStreamOpener, final BitmapTextureFormat pBitmapTextureFormat, final TextureOptions pTextureOptions) throws IOException {
		return this.getTexture(pID, pInputStreamOpener, pBitmapTextureFormat, pTextureOptions, true);
	}

	public synchronized ITexture getTexture(final String pID, final IInputStreamOpener pInputStreamOpener, final BitmapTextureFormat pBitmapTextureFormat, final TextureOptions pTextureOptions, final boolean pLoadToHardware) throws IOException {
		if(this.hasMappedTexture(pID)) {
			return this.getMappedTexture(pID);
		} else {
			final ITexture texture = new BitmapTexture(this, pInputStreamOpener, pBitmapTextureFormat, pTextureOptions);
			if(pLoadToHardware) {
				this.loadTexture(texture);
			}
			this.addMappedTexture(pID, texture);

			return texture;
		}
	}

	// ===========================================================
	// Inner and Anonymous Classes
	// ===========================================================
}