package com.gamepatriot.framework2d.implementation;

import java.util.ArrayList;
import java.util.Iterator;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.gamepatriot.androidframework.framework.AndroidImage;
import com.gamepatriot.androidframework.framework.AndroidInputter;
import com.gamepatriot.androidframework.framework.AndroidInputter.AndroidTouchEvent;
import com.gamepatriot.androidframework.framework.AndroidRenderer;
import com.gamepatriot.androidframework.framework.AndroidShape;
import com.gamepatriot.framework2d.classes.FPS;
import com.gamepatriot.framework2d.implementation.Inputter.TouchEvent;

/**
 * The Renderer class broadcasts input, broadcasts time-step logic, and renders the application's display. The renderer handles all frame-based actions - Input (by collecting
 * events from an {@link Inputter}), display (by behaving as a SurfaceView and drawing elements from {@link Screen}s that are associated with a referenced {@link Main} class), and 
 * time-steps (by triggering {@link Screen#update()} in-sync with updating the display).
 * 
 * @see AndroidRenderer
 * @author Pete Schmitz, May 9, 2013
 *
 */
public class Renderer extends SurfaceView implements AndroidRenderer, Runnable, SurfaceHolder.Callback {
	
	//Config
	
	/** The color used to flood fill the canvas before drawing elements. **/
	private static int FLOOD_BACKGROUND = 0;
	
	private static boolean IGNORE_FLOOD = false;
	
	
	//References
	
	/** Reference to the application's {@link Main} object. **/
	private final Main main;
	
	/** Reference to the holder used to lock and post the buffer canvas. **/
	private final SurfaceHolder holder;
	
	/** Reference to an {@link Inputter} that will have its {@link TouchEvent}s broadcasted to {@link #main}'s {@link Screen}s. **/
	private Inputter inputter;
	
	
	//Flags
	
	/** Flag to determine the continuation of updating for this single-thread application. **/
	private volatile boolean running = false;
	
	/** Flag to determine the {@link #thread}'s dynamic sleep value (to result in a more consistent framerate). **/
	private long lastUpdate;
	
	private boolean surfaceAvailable = false;
	
	
	//Containers
	
	/** Primary thread for this application, controls all display and time-step calls. **/
	private Thread thread;
	
	/** Canvas for the supplied frame buffer that was created from {@link #main}. **/
	private Canvas draw;
	
	/** Primary paint used for drawing bitmaps to the canvas/buffer. **/
	private final Paint paint;
	
	private final FPS fps;
	
	
	public Renderer(Context $main){
		super($main);
		
		//References
		main = (Main) $main;
		holder = getHolder();
		holder.addCallback(this);
		
		//Containers
		//draw = new Canvas(frameBuffer);
		//dstRect = new Rect();
		paint = new Paint();
		fps = new FPS();
		
		//Set the bitmap-paint flags
		paint.setAntiAlias(Main.ALLOW_ANTI_ALIASING);
		paint.setDither(Main.ALLOW_DITHER);
		paint.setFilterBitmap(Main.ALLOW_ANTI_ALIASING || Main.ALLOW_DITHER);
		
	}
	
	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
		Log.d("surface", "Surface changed");
		draw = null;
	}
	
	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		Log.d("surface", "Surface created");
		surfaceAvailable = true;
		draw = null;
	}
	
	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		Log.d("surface", "Surface destroyed");
		surfaceAvailable = false;
		draw = null;
	}
	
	@Override
	public void run() {
		
		while (running){
				
			//Calculate delay to apply and adjust update flag accordingly
			long $delay = (Main.MS_DELAY - (System.currentTimeMillis() - lastUpdate));
			lastUpdate = System.currentTimeMillis() + $delay;
				
			//Delay thread to match maximum-FPS rate
			if ($delay > 0){
				try {
					Thread.sleep($delay);
				} catch (InterruptedException e){
						
				}
			}
			
			//Trigger input
			inputStep();
			
			//Step update pass
			updatePass();
			
			// Screen queues
			main.queueStep();
				
			//Step render-routine (FPS based)
			renderStep();
				
			
		}
	}
	
	@Override
	public void pause() {
		running = false;
		while (thread.isAlive()){
			try {
				thread.join();
			} catch (InterruptedException e){
				//Retry
			}
		}
	}
	
	@Override
	public void resume() {
		running = true;
		lastUpdate = System.currentTimeMillis();
		thread = new Thread(this);
		thread.start();
	}
	
	@Override
	public void setInputter(AndroidInputter $inputter) {
		inputter = (Inputter) $inputter;
	}
	
	/** Display step to clear, draw, and post elements to the canvas. **/
	private void renderStep() {
		if (!surfaceAvailable) return;
		
		draw = null;
		
		try {
			
			// Get buffer
			draw = holder.lockCanvas();
			
			//Clear buffer
			if (!IGNORE_FLOOD) clearDraw();
			
			// Draw to buffer
			drawScreens();
			
			// Render FPS
			fps.tick(draw);
			
			
			
		} catch (Exception e){
			
			e.printStackTrace();
			
		} finally {
			
			// Post buffer
			if (draw != null) holder.unlockCanvasAndPost(draw);
		}
	}
	
	
	
	/** Flood the buffer (Called at the start of a redraw). **/
	private void clearDraw(){
		draw.drawRGB((FLOOD_BACKGROUND & 0xFF0000) >> 16, (FLOOD_BACKGROUND & 0xFF00) >> 8, (FLOOD_BACKGROUND & 0xFF));
	}
	
	/** Render elements from {@link Screen}s contained in the {@link #main} reference. **/
	private void drawScreens(){
		ArrayList<Screen> $screens = main.getScreens();
		
		ArrayList<AndroidImage> $images;
		ArrayList<AndroidShape> $shapes;
		
		for (Screen $screen : $screens){
			if ($screen == null || !$screen.activeRender) continue;
			
			$images = $screen.getImages();
			for (AndroidImage $image : $images){
				render($image);
			}
			
			$shapes = $screen.getShapes();
			for (AndroidShape $shape : $shapes){
				render($shape);
			}
		}
	}
	
	/** Individual image rendering call; Draw the image to the canvas reference. **/
	private void render(AndroidImage $androidImage){
		Image $image = (Image) $androidImage;
		
		paint.setAlpha($image.alpha);
		
		if ($image.source == null) return;
		
		if ($image.allowMatrix){
			draw.drawBitmap($image.matrixFrame, $image.matrix, paint);
		} else {
			draw.drawBitmap($image.source, $image.srcRect, $image.dstRect, paint);
		}
		
		if ($image.animate) $image.nextFrame();
	}
	
	/** Individual shape rendering call; Draw the shape to the canvas reference. **/
	private void render(AndroidShape $shape){
		$shape.draw(draw);
	}
	
	/** Time-step for {@link Screen}s contained in the {@link #main} reference. **/
	private void updatePass(){
		
		ArrayList<Screen> $screens = main.getScreens();
		
		for (Screen $screen : $screens){
			$screen.update();
		}
	}
	
	/** Input step for {@link Screen}s contained in the {@link #main} reference. **/
	private void inputStep(){
		if (inputter == null) return;
		
		ArrayList<AndroidTouchEvent> $events = inputter.getEvents();
		
		synchronized ($events){
			
			TouchEvent $touchEvent;
			Iterator<AndroidTouchEvent> $ir = $events.iterator();
			
			Screen $screen;
			ArrayList<Screen> $screens = main.getScreens();
			Iterator<Screen> $screenir;
			
			while($ir.hasNext()){
				
				$touchEvent = (TouchEvent) $ir.next();
				
				$screenir = $screens.iterator();
				
				while ($screenir.hasNext()){
					$screen = $screenir.next();
					
					switch ($touchEvent.type){
					case TouchEvent.TOUCH_PRESS:
						if ($screen.activeInputter) $screen.touchPress($touchEvent.x, $touchEvent.y);
						break;
						
					case TouchEvent.TOUCH_MOVE:
						if ($screen.activeInputter) $screen.touchMove($touchEvent.x, $touchEvent.y);
						break;
						
					case TouchEvent.TOUCH_RELEASE:
						if ($screen.activeInputter) $screen.touchRelease($touchEvent.x, $touchEvent.y);
						break;
						
					case TouchEvent.TOUCH_BACK:
						if ($screen.activeInputter) $screen.onBackPressed();
						break;	
					}
					
					
				}
				
				inputter.recycle($touchEvent);
			}
			inputter.clear();
		}
		
	}
	
	
	
}
