package org.newdawn.slick.opengl.pbuffer;

import java.util.HashMap;

import org.lwjgl.opengl.GLContext;
import org.lwjgl.opengl.Pbuffer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.util.Log;

/**
 * A factory to produce an appropriate render to texture graphics context based on current
 * hardware
 *
 * @author kevin
 */
public class GraphicsFactory {
	/** The graphics list of graphics contexts created */
	private static HashMap graphics = new HashMap();
	/** True if pbuffers are supported */
	private static boolean pbuffer = true;
	/** True if pbuffer render to texture are supported */
	private static boolean pbufferRT = true;
	/** True if fbo are supported */
	private static boolean fbo = true;
	/** True if we've initialised */
	private static boolean init = false;
	
	/**
	 * Initialise offscreen rendering by checking what buffers are supported
	 * by the card
	 * 
	 * @throws SlickException Indicates no buffers are supported
	 */
	private static void init() throws SlickException {
		init = true;
		
		if (fbo) {
			fbo = GLContext.getCapabilities().GL_EXT_framebuffer_object;
		}
		pbuffer = (Pbuffer.getCapabilities() & Pbuffer.PBUFFER_SUPPORTED) != 0;
		pbufferRT = (Pbuffer.getCapabilities() & Pbuffer.RENDER_TEXTURE_SUPPORTED) != 0;
		
		if (!fbo && !pbuffer && !pbufferRT) {
			throw new SlickException("Your OpenGL card does not support offscreen buffers and hence can't handle the dynamic images required for this application.");
		}
		
		Log.info("Offscreen Buffers FBO="+fbo+" PBUFFER="+pbuffer+" PBUFFERRT="+pbufferRT);
	}
	
	/**
	 * Force FBO use on or off
	 * 
	 * @param useFBO True if we should try and use FBO for offscreen images
	 */
	public static void setUseFBO(boolean useFBO) {
		fbo = useFBO;
	}
	
	/**
	 * Check if we're using FBO for dynamic textures
	 * 
	 * @return True if we're using FBOs
	 */
	public static boolean usingFBO() {
		return fbo;
	}

	/**
	 * Check if we're using PBuffer for dynamic textures
	 * 
	 * @return True if we're using PBuffer
	 */
	public static boolean usingPBuffer() {
		return !fbo && pbuffer;
	}
	
	/**
	 * Get a graphics context for a particular image
	 * 
	 * @param image The image for which to retrieve the graphics context
	 * @return The graphics context
	 * @throws SlickException Indicates it wasn't possible to create a graphics context
	 * given available hardware.
	 */
	public static Graphics getGraphicsForImage(Image image) throws SlickException {
		Graphics g = (Graphics) graphics.get(image.getTexture());
		
		if (g == null) {
			g = createGraphics(image);
			graphics.put(image.getTexture(), g);
		}
		
		return g;
	}
	
	/**
	 * Release any graphics context that is assocaited with the given image
	 * 
	 * @param image The image to release
	 * @throws SlickException Indicates a failure to release the context
	 */
	public static void releaseGraphicsForImage(Image image) throws SlickException {
		Graphics g = (Graphics) graphics.remove(image.getTexture());
		
		if (g != null) {
			g.destroy();
		}
	}
	
	/** 
	 * Create an underlying graphics context for the given image
	 * 
	 * @param image The image we want to render to
	 * @return The graphics context created
	 * @throws SlickException
	 */
	private static Graphics createGraphics(Image image) throws SlickException {
		init();
		
		if (fbo) {
			try {
				return new FBOGraphics(image);
			} catch (Exception e) {
				fbo = false;
				Log.warn("FBO failed in use, falling back to PBuffer");
			}
		}
		
		if (pbuffer) {
			if (pbufferRT) {
				return new PBufferGraphics(image);
			} else {
				return new PBufferUniqueGraphics(image);
			}
		}
		
		throw new SlickException("Failed to create offscreen buffer even though the card reports it's possible");
	}
}