package com.gamepatriot.framework2d.implementation;

import java.util.ArrayList;

import android.app.Activity;
import android.graphics.Bitmap.Config;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.Window;
import android.view.WindowManager;

import com.gamepatriot.androidframework.framework.AndroidGameData;
import com.gamepatriot.androidframework.framework.AndroidInputter;
import com.gamepatriot.androidframework.framework.AndroidMain;
import com.gamepatriot.androidframework.framework.AndroidMusicHandler;
import com.gamepatriot.androidframework.framework.AndroidRenderer;
import com.gamepatriot.androidframework.framework.AndroidScreen;
import com.gamepatriot.androidframework.framework.AndroidSoundHandler;
import com.gamepatriot.framework2d.implementation.Screen.ScreenID;

/**
 * The Main class acts as the backbone of the application by providing references to all key components of this application and by behaving as the application's primary activity. 
 * In addition, this class provides the canvas used for rendering the application's display, global configurations, and the application's startup directive.
 * 
 * @see AndroidMain
 * @author Pete Schmitz, May 9, 2013
 *
 */
public class Main extends Activity implements AndroidMain{
	
	//Global Configuration
	
	/** The maximum frames per second this application is allowed to render at. **/
	public static int FPS_MAX = 30;
	
	/** The bitmap color configuration this application should use. **/
	public static Config CONFIG = Config.ARGB_8888;
	
	/** Whether or not anti-aliasing should be applied to the display canvas. **/
	public static boolean ALLOW_ANTI_ALIASING = true;
	
	/** Whether or not dithering should be applied to the displaycanvas. **/
	public static boolean ALLOW_DITHER = true;
	
	/** The maximum amount of sound effects that can play at any given time. **/
	public static int AUDIO_STREAM_MAX = 2;
	
	/** The default canvas width, in pixels, that this application should use if {@link #RUN_NATIVE_RESOLUTION} isn't enforced. **/
	public static int DEFAULT_CANVAS_WIDTH = 800;
	
	/** The default canvas height, in pixels, that this application should use if {@link #RUN_NATIVE_RESOLUTION} isn't enforced. **/
	public static int DEFAULT_CANVAS_HEIGHT = 480;
	
	/** Whether or not the canvas should be built at the native resolution of the device running this application. **/
	public static boolean RUN_NATIVE_RESOLUTION = true;
	
		
	//Auto-detected constants
	
	/** The maximum millisecond delay that should be applied on this application's single thread. **/
	public static int MS_DELAY = 1000/FPS_MAX;
	
	/** The native pixel width of the device running this application. **/
	public static int DEVICE_WIDTH;
	
	/** The native pixel height of the device running this application. **/
	public static int DEVICE_HEIGHT;
	
	/** The Android software version of the device running this application. **/
	public static int DEVICE_VERSION;
	
	/** The amount of scaling along the X-axis that is currently being applied on the display canvas. **/
	public static float CANVAS_SCALE_X;
	
	/** The amount of scaling along the Y-axis that is currently being applied on the display canvas. **/
	public static float CANVAS_SCALE_Y;
	
	/** The pixel width of the display canvas being used. **/
	public static int CANVAS_WIDTH;
	
	/** The pixel height of the display canvas being used. **/
	public static int CANVAS_HEIGHT;
	
	/** Primary custom typeface to use in this application. **/
	public static Typeface DEFAULT_TYPEFACE;
	
	/** Font paint for referenced usage of {@link #DEFAULT_TYPEFACE}. **/
	public static Paint PAINT_TEXT_LARGE;
	
	/** Font paint for referenced usage of {@link #DEFAULT_TYPEFACE}. **/
	public static Paint PAINT_TEXT_MEDIUM;
	
	/** Font paint for referenced usage of {@link #DEFAULT_TYPEFACE}. **/
	public static Paint PAINT_TEXT_SMALL;
	
	
		
	//Containers
	
	/** Collection of {@link Screen}s to render on the display canvas. **/
	private ArrayList<Screen> screens;
	
	/** Primary {@link Renderer} that this application uses. **/
	private Renderer frameRenderer;
	
	/** Primary {@link Inputter} that this application uses. **/
	private Inputter inputter;
	
	/** Primary {@link SoundHandler} that this application uses. **/
	private SoundHandler sound;
	
	/** Primary {@link MusicHandler} that this application uses. **/
	private MusicHandler music;
	
	/** Primary {@link GameData} that this application uses. **/
	private GameData gameData;
	
	/** Container for screens that are awaiting removal via a call from {@link Screen#remove()}. **/
	private ArrayList<Screen> removalQueue;
	
	/** Container for screens that are awaiting addition via a call from {@link Screen#add()}. **/
	private ArrayList<Screen> addQueue;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		// Remove title
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		
		// Set window flags
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
		
		// Find device version
		DEVICE_VERSION = Build.VERSION.SDK_INT;
		
		// Set default typeface
		DEFAULT_TYPEFACE = Typeface.createFromAsset(getAssets(), "fonts/GermaniaOne.ttf");
		getTextPaints();
		
		// Find device height/width
		DisplayMetrics $dm = new DisplayMetrics();
		getWindowManager().getDefaultDisplay().getMetrics($dm);
		DEVICE_WIDTH = $dm.widthPixels;
		DEVICE_HEIGHT = $dm.heightPixels;
		
		// Find canvas height/width
		CANVAS_WIDTH = RUN_NATIVE_RESOLUTION ? DEVICE_WIDTH : DEFAULT_CANVAS_WIDTH;
		CANVAS_HEIGHT = RUN_NATIVE_RESOLUTION ? DEVICE_HEIGHT : DEFAULT_CANVAS_HEIGHT;
		
		// Find scaling required
		CANVAS_SCALE_X = (float) DEVICE_WIDTH / (float) CANVAS_WIDTH;
		CANVAS_SCALE_Y = (float) DEVICE_HEIGHT / (float) CANVAS_HEIGHT;
		
		// Create sound and music handlers
		sound = new SoundHandler(this, true);
		music = new MusicHandler(this, true);
		
		// Create and set render view
		frameRenderer = new Renderer(this);
		setContentView(frameRenderer);
		
		// Create and set inputter
		inputter = new Inputter(frameRenderer);
		frameRenderer.setInputter(inputter);
		
		// Initialize containers
		screens = new ArrayList<Screen>();
		removalQueue = new ArrayList<Screen>();
		addQueue = new ArrayList<Screen>();
		
		// Initialize game-specific constructor
		constructGame();
	}
	
	@Override
	public void constructGame() {

		//Create game data
		gameData = new GameData(this);
		
		//Initialize atlas (images)
		Atlas.load(this);
		
		//Instantiate and display starting screen
		addScreen(ScreenID.EXAMPLE);
	}
	
	@Override
	protected void onPause() {
		super.onPause();
		
		frameRenderer.pause();
		inputter.pause();
		music.disableMusic();
	}
	
	@Override
	protected void onResume() {
		super.onResume();
		
		frameRenderer.resume();
		inputter.resume();
		music.enableMusic();
	}
	
	@Override
	protected void onDestroy() {
		super.onDestroy();
		
		gameData.save();
	}
	
	@Override
	public void stop() {
		finish();
	}
	
	@Override
	public void onBackPressed() {
		
		/* Comment super call to prevent app from closing on back-press. */
		super.onBackPressed();
		
		inputter.insertBackPressed();
	}
	
	@Override
	public void addScreen(AndroidScreen $screen) {
		if (screens.indexOf($screen) > -1) return;
		
		addQueue.add((Screen)$screen);
	}
	
	@Override
	public void addScreenAt(int $index, AndroidScreen $screen) {
		
		// TODO: May cause CME; Use queue to modify screens array if so
		
		if (screens.indexOf($screen) > -1) return;
		
		if ($index < 0){
			screens.add(0, (Screen)$screen);
			return;
		}
		
		if ($index >= screens.size()){
			screens.add((Screen)$screen);
			return ;
		}
		
		screens.add($index, (Screen)$screen);
	}
	
	/** 
	 * Conveniently instantiate a new screen and add it to the display list at the specified index.
	 * @param $index		The position in Main's display list to add and render this new screen.
	 * @param $sid			The {@link ScreenID} of the new {@link Screen} to add.
	 */
	public void addScreenAt(int $index, ScreenID $sid) {
		
		AndroidScreen $screen = createScreen($sid);
		
		if ($screen == null) return;
		
		addScreenAt($index, $screen);
	}
	
	/**
	 * Conveniently instantiate a new screen add it to the display list.
	 * @param $sid			The {@link ScreenID} of the new {@link Screen} to add.
	 * @return				Whether or not the screen was successfully instantiated and added.
	 */
	public boolean addScreen(ScreenID $sid){
		
		AndroidScreen $screen = createScreen($sid);
		
		if ($screen == null) return false;
		
		addScreen($screen);
		
		return true;
	}
	
	@Override
	public void removeScreens() {
		screens.clear();
	}
	
	/**
	 * Instantiate and return a new specificied class of type {@link Screen}.
	 * @param $sid		The {@link ScreenID} of the requested Screen to instantiate.
	 * @return			The instantiated Screen; null if invalid ScreenID.
	 */
	public AndroidScreen createScreen(ScreenID $sid) {
		AndroidScreen $screen = $sid.createScreen(this);
		return $screen;
	}

	@Override
	public int getScreenIndex(AndroidScreen $screen) {
		return screens.indexOf($screen);
	}
	
	@Override
	public boolean removeScreen(AndroidScreen $screen) {
		return screens.remove($screen);
	}
	
	@Override
	public void queueScreenRemoval(AndroidScreen $screen) {
		if (screens.indexOf($screen) == -1) return;
		
		removalQueue.add((Screen) $screen);
	}
	
	@Override
	public void queueScreenAddition(AndroidScreen $screen) {
		addQueue.add((Screen) $screen);
	}
	
	public void queueStep(){
		clearQueueRemoval();
		clearQueueAdd();
	}
	
	@Override
	public void clearQueueRemoval() {
		
		if (removalQueue.size() <= 0) return;
		
		for (Screen $screen : removalQueue){
			screens.remove($screen);
		}
		
		removalQueue.clear();
	}
	
	public void clearQueueAdd() {
		
		if (addQueue.size() <= 0) return;
		
		for (Screen $screen : addQueue){
			if (screens.indexOf($screen) == -1) screens.add($screen);
		}
		
		addQueue.clear();
	}
	
	@Override
	public AndroidScreen removeScreenAt(int $index) {
		return screens.remove($index);
	}
	
	/** Return the display list. **/
	public ArrayList<Screen> getScreens() {
		return screens;
	}
	
	@Override
	public AndroidInputter getInputter() {
		return inputter;
	}
	
	@Override
	public AndroidMusicHandler getMusic() {
		return music;
	}
	
	@Override
	public AndroidSoundHandler getSound() {
		return sound;
	}
	
	@Override
	public AndroidGameData getGameData(){
		return gameData;
	}
	
	@Override
	public AndroidRenderer getRenderer(){
		return frameRenderer;
	}
	
	public void switchScreen(ScreenID $screen){
		switchScreen($screen.createScreen(this));
	}
	
	public void switchScreen(Screen $screen){
		for (Screen $removeScreen : screens) $removeScreen.remove();
		$screen.add();
	}
	
	private void getTextPaints(){
		
		Paint $small = new Paint();
		$small.setAntiAlias(ALLOW_ANTI_ALIASING);
		$small.setDither(ALLOW_DITHER || ALLOW_ANTI_ALIASING);
		$small.setTextAlign(Paint.Align.CENTER);
		$small.setTypeface(DEFAULT_TYPEFACE);
		$small.setAlpha(225);
		$small.setColor(Color.WHITE);
		$small.setTextSize(28);
		
		Paint $medium = new Paint($small);
		$medium.setTextSize(34);
		
		Paint $large = new Paint($small);
		$large.setTextSize(40);
		
		PAINT_TEXT_SMALL = $small;
		PAINT_TEXT_MEDIUM = $medium;
		PAINT_TEXT_LARGE = $large;
	}
	
}
