package net.phatcode.rel;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.image.BufferStrategy;
import java.util.ArrayList;

import net.phatcode.rel.assets.ImageTextureDataDefault;
import net.phatcode.rel.math.Utils;
import net.phatcode.rel.utils.Explosion;
import net.phatcode.rel.utils.ImageAtlas;
import net.phatcode.rel.utils.Keyboard;
import net.phatcode.rel.utils.Sonics;
import net.phatcode.rel.utils.SpriteFont;


/**
 * @author Richard Eric M. Lope
 *
 */

public class Game
{

	enum GameState
	{
		START,
		PLAY,
		WIN,
		LOOSE,
		GAME_OVER,
		INTERMISSION,
		TITLE,
	}
	
	private final int MAX_WRONG_LETTERS = 10;
	
	private double accumulator = 0;

	private int fps = 0;
	private int framesPerSecond = 0;
	private double previousTime = 0;
	private double oldTime = 0;
	
	private int timeLeft = 0;
	private int maxTime = 60 * 60 * 2;  // 2 minutes
	private static final int maxStartCountdown = 60 * 6; 
	private int startCountdown = maxStartCountdown; 
	private boolean blinkMe = true;
	private int ticks = 0;
	
	private SpriteFont fontDigi = new SpriteFont( new ImageAtlas( new ImageTextureDataDefault("gfx/outlinefont24x32.png"), 24, 32 ) );
	private SpriteFont fontKrom = new SpriteFont( new ImageAtlas( new ImageTextureDataDefault("gfx/kromasky16x16.png"), 16, 16 ) );
	private SpriteFont fontBig = new SpriteFont( new ImageAtlas( new ImageTextureDataDefault("gfx/nuskool_krome_64x64.png"), 64, 64 ) );
	
	private ImageAtlas heroineImages = new ImageAtlas( new ImageTextureDataDefault("gfx/heroine.png"), 32, 32 );
	private ImageAtlas shotImages = new ImageAtlas( new ImageTextureDataDefault("gfx/shot.png"), 22, 35 );
	
	
	private LevelMap parallaxLevel = new LevelMap( 3, 256/2, 256, 
			 new ImageAtlas( new ImageTextureDataDefault("gfx/space_background.png"), 256, 256 ) );
	private LevelMap baseLevel = new LevelMap( 3, 256, 256, 
		 new ImageAtlas( new ImageTextureDataDefault("gfx/planet_tiles.png"), 256, 256 ) );

	private Fighter fighter = new Fighter();
	
	private GameState gameState = GameState.START;
	
	private ArrayList<Letter> letters = new ArrayList<Letter>();
	
	private StringBuilder hiddenAnswer = new StringBuilder(""); 
	private StringBuilder playerAnswer = new StringBuilder(""); 
	private Dictionary dictionary = new Dictionary(); 

	int numCorrectLetters = 0;
	int numWrongLetters = 0;
	
	private float alphaInterpolator = 0;
	private float elecPosition = Constants.SCREEN_HEIGHT / 2;
	private float elecTabs;
	private float elecLerp = 0;
	
	public Game()
	{
		dictionary.Load( "assets/words.dic" );
		Explosion.getInstance().init();
	
		parallaxLevel.fill( 1 );
		baseLevel.randomize( baseLevel.getNumImages() );
		
		

	}

	public void init( BufferStrategy strategy, Screen screen )
	{
		 
		Graphics2D g2D = (Graphics2D) strategy.getDrawGraphics();
		g2D.setColor(Color.BLACK);
		g2D.fillRect(0, 0, Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT);

		Toolkit.getDefaultToolkit().sync();

		g2D.dispose();
		strategy.show();
	
		
		Sonics.getInstance().addMusic("audio/title.mid");
		Sonics.getInstance().addMusic("audio/level.mid");
		
		Sonics.getInstance().addEffect("audio/shot.wav");
		Sonics.getInstance().addEffect("audio/correct.wav");
		Sonics.getInstance().addEffect("audio/wrong.wav");
		Sonics.getInstance().addEffect("audio/win.wav");
		Sonics.getInstance().addEffect("audio/loose.wav");
		
		Sonics.getInstance().playMusic("audio/title.mid", true);
		
		reset();
		
		gameState = GameState.TITLE;
		

	}
	
	private void reset()
	{
		
		gameState = GameState.START;
		timeLeft = maxTime;
		startCountdown = maxStartCountdown; 
		ticks = 0;
		elecPosition = Constants.SCREEN_HEIGHT / 8;
		elecTabs = (fighter.getY() -  elecPosition) / (MAX_WRONG_LETTERS);
		elecLerp = 0;
		
		fighter.reset();
		
		letters.clear();
		
		for( int i = 0; i < 26; i++ )
		{
			Letter letter = new Letter();
			letter.randomize();
			letter.setAlpha( "" + Letter.alphabet[i]);
			letters.add( letter );
		}
		
		hiddenAnswer.delete(0, 255);
		playerAnswer.delete(0, 255);
		
		hiddenAnswer.append(dictionary.getRandomWord());
		
		int size = hiddenAnswer.length();
		for( int i = 0; i < size; i++ )
		{
			char c = hiddenAnswer.charAt(i);
			hiddenAnswer.setCharAt(i, Character.toUpperCase(c));;
			playerAnswer.append("-");
		}
		
		numCorrectLetters = 0;
		numWrongLetters = 0;
	}
	
	boolean checkLetter( String alphaLetter )
	{
		
		char letter = alphaLetter.charAt( 0 );
		
		int oldNumCorrectLetters = numCorrectLetters;
		
		for( int i = 0; i < hiddenAnswer.length(); i++ )
		{
			char alpha = hiddenAnswer.charAt(i);
			if( letter == alpha )
			{
				numCorrectLetters++;
				playerAnswer.setCharAt( i, alpha );
				fighter.addToScore(123 + (int)(Math.random() * 500));
				Sonics.getInstance().playEffect("audio/correct.wav");
				
			}
			
		}
		
		if( oldNumCorrectLetters == numCorrectLetters )
		{
			//fighter.decLives();
			elecLerp = 0;
			numWrongLetters++;
			Sonics.getInstance().playEffect("audio/wrong.wav");
			if( numWrongLetters == MAX_WRONG_LETTERS )
			{
				Sonics.getInstance().playEffect("audio/loose.wav");
				playerAnswer.replace( 0, playerAnswer.length(), hiddenAnswer.toString() );
				gameState = GameState.LOOSE;
			}
		}
		
		if( numCorrectLetters == (hiddenAnswer.length() - 1 ) )
		{
			return true;
		}
		
		return false;
				
	}
	
	private void update() 
	{

		if( (ticks++ & 63) == 0 ) 
			blinkMe = !blinkMe;
		
		switch( gameState )
		{
			case START:
				stateStart();
				break;
			case PLAY:
				statePlay();
				break;
			case WIN:
				stateWin();
				break;
			case LOOSE:
				stateLoose();
				break;
			case GAME_OVER:
				stateGameOver();
				break;
			case INTERMISSION:
				stateIntermission();
				break;
			case TITLE:
				stateTitle();
				break;
			default:
				break;
		}
		
		Keyboard.getInstance().resetKeys();
		
		
		
	}
	
	private void handleCollisions()
	{
		Shot bullet = fighter.getShot();
		if( bullet.isActive() )
		{
			int size = letters.size();
			for( int i = 0; i < size; i++ )
			{
				Letter let = letters.get(i);
				if( let.isActive() )
				{
					if( bullet.collidesWith(let) )
					{
						checkLetter(let.getAlpha());
						bullet.kill();
						let.kill();
					}
				}
			}
		}
	}

	

	public void run( BufferStrategy strategy, Screen screen ) 
	{

		double dt = getDeltaTime(getSystemTime());
		
		init( strategy, screen  );
		
		while( true ) 
		{

			dt = getDeltaTime(getSystemTime());
			if( dt > Constants.FIXED_TIME_STEP ) dt = Constants.FIXED_TIME_STEP;
			
			accumulator += dt;
			
			while( accumulator >= Constants.FIXED_TIME_STEP ) 
			{

				accumulator -= Constants.FIXED_TIME_STEP;
				update();
								
			}

			
			Graphics2D g2D = (Graphics2D) strategy.getDrawGraphics();
			g2D.setColor(Color.BLACK);
			g2D.fillRect(0, 0, Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT);

			render( g2D, screen );
			g2D.dispose();
			strategy.show();
			Toolkit.getDefaultToolkit().sync();
			

			if( Keyboard.getInstance().isKeyPressed( KeyEvent.VK_ESCAPE ) )
			{
				System.exit(0);
			}
			
			try 
			{
				Thread.sleep(1);
			} 
			catch (InterruptedException e)
			{
				System.out.println("interrupted");
			}
			
		}

	}

	
	
	public Keyboard getKeyHandler()
	{
		return Keyboard.getInstance();
	}
	
	
	public double getSystemTime() 
	{
		return System.nanoTime() / 1000000000.0;
	}

	public double getDeltaTime( double timerInSeconds ) 
	{

		double currentTime = timerInSeconds;
		double elapsedTime = currentTime - oldTime;
		oldTime = currentTime;

		framesPerSecond++;

		if ((currentTime - previousTime) > 1.0) 
		{
			previousTime = currentTime;
			fps = framesPerSecond;
			framesPerSecond = 0;
		}

		return elapsedTime;
	}
	
	private void render( Graphics2D g2D, Screen screen ) 
	{
		parallaxLevel.renderMap( screen, g2D );
		baseLevel.renderMap( screen, g2D );
		
		switch( gameState )
		{
			case START:
				renderStart( g2D, screen );
				break;
			case PLAY:
				renderPlay( g2D, screen );
				break;
			case WIN:
				renderWin( g2D, screen );
				break;
			case LOOSE:
				renderLoose( g2D, screen );
				break;
			case GAME_OVER:
				renderGameOver( g2D, screen );
				break;
			case INTERMISSION:
				renderIntermission( g2D, screen );
				break;
			case TITLE:
				renderTitle( g2D, screen );
				break;
			default:
				break;
		}
	}
	
	private void statePlay()
	{
		
		int size = letters.size();
		for( int i = 0; i < size; i++ )
		{
			Letter letter = letters.get(i);
			if( letter.isActive() )
			{
				letter.update();
			}
		}
		
		fighter.update( true );
		Explosion.getInstance().updateAll();
		
		parallaxLevel.update( 1.0f );
		baseLevel.update( 2.0f );
		
		timeLeft--;
		handleCollisions();
		if( numCorrectLetters == hiddenAnswer.length() )
		{
			Sonics.getInstance().playEffect("audio/win.wav");
			gameState = GameState.WIN;
			startCountdown = maxStartCountdown;
			
		}
		else
		{
			if( timeLeft < 0 )
			{
				Sonics.getInstance().playEffect("audio/loose.wav");
				playerAnswer = hiddenAnswer;
				gameState = GameState.LOOSE;
				startCountdown = maxStartCountdown;
			}
		}
		
		elecLerp = Utils.clamp( elecLerp += 0.01f, 0.0f, 1.0f );
		elecPosition = 	Utils.lerpSmooth( elecPosition, (Constants.SCREEN_HEIGHT / 8) + numWrongLetters * elecTabs, elecLerp );
			
	}
	
	private void stateStart()
	{
		
		int size = letters.size();
		for( int i = 0; i < size; i++ )
		{
			Letter letter = letters.get(i);
			if( letter.isActive() )
			{
				letter.update();
			}
		}
		
		fighter.update( false );
		
		Explosion.getInstance().updateAll();
		
		parallaxLevel.update( 1.0f );
		baseLevel.update( 2.0f );
		
		
		alphaInterpolator = Utils.clamp(alphaInterpolator -= 0.015f, 0, 1.0f);
		if( --startCountdown < 0 ) 
		{	
			gameState = GameState.PLAY;
			startCountdown = maxStartCountdown;
			Sonics.getInstance().playMusic("audio/level.mid", true);
		}

	}
	
	private void stateWin()
	{
		
		int size = letters.size();
		for( int i = 0; i < size; i++ )
		{
			Letter letter = letters.get(i);
			if( letter.isActive() )
			{
				letter.update();
			}
		}
		
		fighter.update( false );
		
		Explosion.getInstance().updateAll();
		
		parallaxLevel.update( 1.0f );
		baseLevel.update( 2.0f );
		
		if( --startCountdown < 0 )
		{
			gameState = GameState.INTERMISSION;
			startCountdown = maxStartCountdown;
		}
		
	}
	
	private void stateLoose()
	{
		int size = letters.size();
		for( int i = 0; i < size; i++ )
		{
			Letter letter = letters.get(i);
			if( letter.isActive() )
			{
				letter.update();
			}
		}
		
		fighter.update( false );
		Explosion.getInstance().updateAll();
		
		parallaxLevel.update( 1.0f );
		baseLevel.update( 2.0f );
		
		elecLerp = Utils.clamp( elecLerp += 0.01f, 0.0f, 1.0f );
		elecPosition = 	Utils.lerpSmooth( elecPosition, (Constants.SCREEN_HEIGHT / 8) + numWrongLetters * elecTabs, elecLerp );
		
		if( (ticks & 7) == 0 )
		{
			Explosion.getInstance().spawn( fighter.getX(), 
					   fighter.getY(),
					   0,
					   0, 
					   42, 5 );	
			
			int max = 4 + (int)(Math.random() * 5);
			for( int i = 0; i < max; i++ )
			{
				Explosion.getInstance().spawn( fighter.getX(), 
									   fighter.getY(), 
									   ticks + (2 * Utils.PI * i) / (float)max,
									   5, 
									   80, 6 );	

			}

		}
		if( --startCountdown < 0 )
		{
			fighter.decLives();
			if( fighter.getLives() < 0 )
			{
				gameState = GameState.GAME_OVER;
				Sonics.getInstance().stopMusic("audio/level.mid");
			}
			else
			{
				gameState = GameState.INTERMISSION;
			}
			startCountdown = maxStartCountdown;
		}

	}
	
	private void stateGameOver()
	{
		int size = letters.size();
		for( int i = 0; i < size; i++ )
		{
			Letter letter = letters.get(i);
			if( letter.isActive() )
			{
				letter.update();
			}
		}
		
		fighter.update( false );
		Explosion.getInstance().updateAll();
		
		parallaxLevel.update( 1.0f );
		baseLevel.update( 2.0f );
		
		if( --startCountdown < 0 )
		{
			reset();
			gameState = GameState.TITLE;
			fighter.setLives(3);
			fighter.setScore(0);
			startCountdown = maxStartCountdown;
			Sonics.getInstance().playMusic("audio/title.mid", true);
		}
		
	}
	
	private void stateIntermission()
	{
		int size = letters.size();
		for( int i = 0; i < size; i++ )
		{
			Letter letter = letters.get(i);
			if( letter.isActive() )
			{
				letter.update();
			}
		}
		
		fighter.update( false );
		
		Explosion.getInstance().updateAll();
		
		parallaxLevel.update( 1.0f );
		baseLevel.update( 2.0f );
		
		alphaInterpolator = Utils.clamp(alphaInterpolator += 0.01f, 0, 1.0f);
		if( --startCountdown <  (maxStartCountdown/3) )
		{
			reset();
			parallaxLevel.reset();
			baseLevel.reset();
			gameState = GameState.START;
			startCountdown = maxStartCountdown;
		}
		
	}
	
	private void stateTitle()
	{
		
		parallaxLevel.update( 1.0f );
		baseLevel.update( 2.0f );
		
		if( Keyboard.getInstance().isKeyPressed(KeyEvent.VK_ENTER))
		{
			gameState = GameState.START;
			Sonics.getInstance().stopMusic("audio/title.mid");
		}
		
		
	}
	
	private void renderPlay( Graphics2D g2D, Screen screen ) 
	{
		
		int size = letters.size();
		for( int i = 0; i < size; i++ )
		{
			Letter letter = letters.get(i);
			if( letter.isActive() )
			{
				letter.render( screen, g2D, fontDigi );
			}
				
		}
		
		fighter.render( screen, g2D, heroineImages, shotImages );
		
		Explosion.getInstance().renderAll( screen, g2D );
		
		
		printHUD( g2D, screen );
		
	}

	private void renderStart( Graphics2D g2D, Screen screen ) 
	{
		
		
		int size = letters.size();
		for( int i = 0; i < size; i++ )
		{
			Letter letter = letters.get(i);
			if( letter.isActive() )
			{
				letter.render( screen, g2D, fontDigi );
			}
				
		}
		
		fighter.render( screen, g2D, heroineImages, shotImages );
		
		if( blinkMe )
		{
			fontBig.printCenter( 0,  Constants.SCREEN_HEIGHT/2 - 80, 
								  Constants.SCREEN_WIDTH,
								  "R E A D Y",
								  screen, g2D );
		}

		printHUD( g2D, screen );
		
		renderFade( g2D, screen );

		
	}

	private void renderWin( Graphics2D g2D, Screen screen ) 
	{
		
		int size = letters.size();
		for( int i = 0; i < size; i++ )
		{
			Letter letter = letters.get(i);
			if( letter.isActive() )
			{
				letter.render( screen, g2D, fontDigi );
			}
				
		}
		
		fighter.render( screen, g2D, heroineImages, shotImages );
		
		Explosion.getInstance().renderAll( screen, g2D );
		
		if( blinkMe )
		{
			fontBig.printCenter( 0, Constants.SCREEN_HEIGHT/2 - 80, 
								  Constants.SCREEN_WIDTH,
								  "YOU WIN",
								  screen, g2D );
		}

		printHUD( g2D, screen );
				
	}

	private void renderLoose( Graphics2D g2D, Screen screen ) 
	{
		
		
		int size = letters.size();
		for( int i = 0; i < size; i++ )
		{
			Letter letter = letters.get(i);
			if( letter.isActive() )
			{
				letter.render( screen, g2D, fontDigi );
			}
				
		}
		
		fighter.render( screen, g2D, heroineImages, shotImages );
		
		Explosion.getInstance().renderAll( screen, g2D );
		
		if( blinkMe )
		{
			fontBig.printCenter( 0,  Constants.SCREEN_HEIGHT/2 - 80, 
								  Constants.SCREEN_WIDTH,
								  "YOU LOOSE",
								  screen, g2D );
		}

		printHUD( g2D, screen );
				
	}

	private void renderGameOver( Graphics2D g2D, Screen screen ) 
	{
		
		
		int size = letters.size();
		for( int i = 0; i < size; i++ )
		{
			Letter letter = letters.get(i);
			if( letter.isActive() )
			{
				letter.render( screen, g2D, fontDigi );
			}
				
		}
		
		fighter.render( screen, g2D, heroineImages, shotImages );
		
		Explosion.getInstance().renderAll( screen, g2D );
		
		if( blinkMe )
		{
			fontBig.printCenter( 0,  Constants.SCREEN_HEIGHT/2 - 80, 
								  Constants.SCREEN_WIDTH,
								  "GAME OVER",
								  screen, g2D );
		}

		printHUD( g2D, screen );
				
	}

	private void renderIntermission( Graphics2D g2D, Screen screen ) 
	{

		int size = letters.size();
		for( int i = 0; i < size; i++ )
		{
			Letter letter = letters.get(i);
			if( letter.isActive() )
			{
				letter.render( screen, g2D, fontDigi );
			}
				
		}
		
		//fighter.render( screen, g2D, heroineImages, shotImages );
		
		Explosion.getInstance().renderAll( screen, g2D );
		
		printHUD( g2D, screen );
		
		renderFade( g2D, screen );

	}
	
	public void renderTitle( Graphics2D g2D, Screen screen )
	{
		fontBig.printCenter( 0,  40, 
	  			  			 Constants.SCREEN_WIDTH,
	  			  			 "HANGVADER",
	  			  			 screen, g2D );

		
		if( blinkMe )
		{
			fontDigi.printCenter( 0,  240, 
		  			  			  Constants.SCREEN_WIDTH,
		  			  			  "Press Enter to Start Game",
		  			  			  screen, g2D );
		}

		
		fontKrom.printCenter( 0,  360, 
				  			  Constants.SCREEN_WIDTH,
				  			  "DESIGN: ANYA THERESE B. LOPE",
				  			  screen, g2D );

		fontKrom.printCenter( 0,  380, 
				  			  Constants.SCREEN_WIDTH,
				  			  "CODE: RICHARD ERIC M. LOPE",
				  			  screen, g2D );

		fontKrom.printCenter( 0,  400, 
				  			  Constants.SCREEN_WIDTH,
				  			  "TINY SOUND: FINN KUUSISTO",
				  			  screen, g2D );

		fontKrom.printCenter( 0,  420, 
				  			  Constants.SCREEN_WIDTH,
				  			  "GFX: MARC RUSSELL / JENSEN",
				  			  screen, g2D );

		fontKrom.printCenter( 0,  440, 
				  			  Constants.SCREEN_WIDTH,
				  			  "SONICS: VGMUSIC",
				  			  screen, g2D );

		fontKrom.printCenter( 0,  460, 
		  			 		  Constants.SCREEN_WIDTH,
		  			 		  "10/17/2013 ILOILO PHILIPPINES",
		  			 		  screen, g2D );

		
	}
	
	private void renderFade( Graphics2D g2D, Screen screen ) 
	{
		
		
		AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alphaInterpolator );
    	g2D.setComposite(ac);
    	
    	g2D.setColor( Color.GRAY );
        g2D.fillRect(0, 0, Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT);
	
		//ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f );
    	//g2D.setComposite(ac);
		//fontDigi.printCenter( 0, Constants.SCREEN_HEIGHT-40, 
		//					  Constants.SCREEN_WIDTH,
		//					  "FADING", 
		//		              screen, g2D );

						
	}

	private void renderElectric( Graphics2D g2D )
	{
		
		final int minWidth = 5;
		
		int x = 0;
		int y =  (int)elecPosition;
		int oldX = x;
		int oldY = y;
		
		while( x < Constants.SCREEN_WIDTH )
		{
			int currentWidth =  minWidth * (int)(Math.random() * minWidth);
			int currentHeight =  -2 * (int)(Math.random() * 4);
			x += currentWidth;
			g2D.drawLine( oldX, oldY, x, y + currentHeight );
			oldX = x;
			oldY = y + currentHeight;
		}
		
	}
	private void printHUD( Graphics2D g2D, Screen screen )
	{
		int minutes = Utils.getMinutes((int)timeLeft / 60);
		int seconds = Utils.getSeconds((int)timeLeft / 60);
		int milliSeconds = Utils.getSeconds((int)timeLeft);
		if(milliSeconds < 0) milliSeconds = 0;
		
		renderElectric( g2D );

		fontKrom.print( 0, 0, 
					"TIMER " + minutes + ":" + seconds + ":" + milliSeconds, 
					screen, g2D );

		fontKrom.print( 415, 0, 
						"SCORE:" + Utils.int2Score(fighter.getScore(), "00000000"), 
						screen, g2D );

		int lives = fighter.getLives();
		if( lives <  0 ) lives = 0;
		fontKrom.print( 510, 20, 
						"LIVES:" + Utils.int2Score(lives, "00"), 
						screen, g2D );

		fontKrom.print( 0,  460, 
 		  		"FPS:" + fps,
 		  		screen, g2D );

		printAnswer( g2D, screen );
		
		
		/*
		fontKrom.print( 0, 60, 
						"CORRECT LETTERS: " + numCorrectLetters, 
						screen, g2D );

		fontKrom.print( 0, 100, 
				"NUMLETTERS: " + letters.size(), 
				screen, g2D );
		*/
		
	}
	
	void printAnswer(  Graphics2D g2D, Screen screen  )
	{
		
		fontDigi.printCenter( 0, Constants.SCREEN_HEIGHT-40, 
							  Constants.SCREEN_WIDTH,
							  "" + playerAnswer, 
				              screen, g2D );

	}
}
