package dk.itu.mario.scene;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.event.MouseEvent;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;

import dk.itu.mario.engine.Art;
import dk.itu.mario.engine.BgRenderer;
import dk.itu.mario.engine.DataRecorder;
import dk.itu.mario.engine.LevelRenderer;
import dk.itu.mario.engine.MarioComponent;
import dk.itu.mario.engine.sonar.FixedSoundSource;
import dk.itu.mario.engine.sprites.BulletBill;
import dk.itu.mario.engine.sprites.CoinAnim;
import dk.itu.mario.engine.sprites.FireFlower;
import dk.itu.mario.engine.sprites.Fireball;
import dk.itu.mario.engine.sprites.Mario;
import dk.itu.mario.engine.sprites.Mushroom;
import dk.itu.mario.engine.sprites.Particle;
import dk.itu.mario.engine.sprites.Shell;
import dk.itu.mario.engine.sprites.Sparkle;
import dk.itu.mario.engine.sprites.Sprite;
import dk.itu.mario.engine.sprites.SpriteContext;
import dk.itu.mario.engine.sprites.SpriteTemplate;
import dk.itu.mario.level.Level;

public class LevelScene extends Scene implements SpriteContext {
	protected List<Sprite> sprites = new ArrayList<Sprite>();
	protected List<Sprite> spritesToAdd = new ArrayList<Sprite>();
	protected List<Sprite> spritesToRemove = new ArrayList<Sprite>();

	public Level level;
	public Mario mario;
	public float xCam, yCam, xCamO, yCamO;
	public static Image tmpImage;
	protected int tick;

	protected LevelRenderer layer;
	protected BgRenderer[] bgLayer = new BgRenderer[2];
	protected Level currentLevel;
	protected GraphicsConfiguration graphicsConfiguration;

	public boolean paused = false;
	public int startTime = 0;
	public int timeLeft;

	protected long levelSeed;
	protected MarioComponent marioComponent;
	//protected int levelType;
	//protected int levelDifficulty;

	public static DataRecorder recorder;

	public boolean gameStarted;

	public static boolean bothPlayed = false;

	private int[] xPositionsArrow;
	private int[] yPositionsArrow;
	private int widthArrow, heightArrow, tipWidthArrow;
	private int xArrow, yArrow;

	public LevelScene(GraphicsConfiguration graphicsConfiguration,
			MarioComponent renderer, long seed/*, int levelDifficulty, int type*/) {
		this.graphicsConfiguration = graphicsConfiguration;
		this.levelSeed = seed;
		this.marioComponent = renderer;
		//this.levelDifficulty = levelDifficulty;
		//this.levelType = type;

		widthArrow = 25;
		tipWidthArrow = 10;
		heightArrow = 20;

		xArrow = 160;
		yArrow = 40;

		xPositionsArrow = new int[] { xArrow + -widthArrow / 2,
				xArrow + widthArrow / 2 - tipWidthArrow,
				xArrow + widthArrow / 2 - tipWidthArrow,
				xArrow + widthArrow / 2,
				xArrow + widthArrow / 2 - tipWidthArrow,
				xArrow + widthArrow / 2 - tipWidthArrow,
				xArrow + -widthArrow / 2 };
		yPositionsArrow = new int[] { yArrow + -heightArrow / 4,
				yArrow + -heightArrow / 4, yArrow + -heightArrow / 2,
				yArrow + 0, yArrow + heightArrow / 2, yArrow + heightArrow / 4,
				yArrow + heightArrow / 4 };
	}

	public void init() {

	}

	public int fireballsOnScreen = 0;

	List<Shell> shellsToCheck = new ArrayList<Shell>();

	public void checkShellCollide(Shell shell) {
		shellsToCheck.add(shell);
	}

	List<Fireball> fireballsToCheck = new ArrayList<Fireball>();

	public void checkFireballCollide(Fireball fireball) {
		fireballsToCheck.add(fireball);
	}

	public void tick() {
		timeLeft--;

		if (widthArrow < 0) {
			widthArrow *= -1;
			tipWidthArrow *= -1;

			xPositionsArrow = new int[] { xArrow + -widthArrow / 2,
					xArrow + widthArrow / 2 - tipWidthArrow,
					xArrow + widthArrow / 2 - tipWidthArrow,
					xArrow + widthArrow / 2,
					xArrow + widthArrow / 2 - tipWidthArrow,
					xArrow + widthArrow / 2 - tipWidthArrow,
					xArrow + -widthArrow / 2 };
			yPositionsArrow = new int[] { yArrow + -heightArrow / 4,
					yArrow + -heightArrow / 4, yArrow + -heightArrow / 2,
					yArrow + 0, yArrow + heightArrow / 2,
					yArrow + heightArrow / 4, yArrow + heightArrow / 4 };

		}

		if (timeLeft == 0) {
			mario.dieTime();
		}

		xCamO = xCam;
		yCamO = yCam;

		if (startTime > 0) {
			startTime++;
		}

		float targetXCam = mario.x - 160;

		xCam = targetXCam;

		if (xCam < 0)
			xCam = 0;
		if (xCam > level.getWidth() * 16 - 320)
			xCam = level.getWidth() * 16 - 320;

		/*
		 * if (recorder != null) { recorder.addTick(mario.getKeyMask()); }
		 * 
		 * if (replayer!=null) { mario.setKeys(replayer.nextTick()); }
		 */

		fireballsOnScreen = 0;

		for (Sprite sprite : sprites) {
			if (sprite != mario) {
				float xd = sprite.x - xCam;
				float yd = sprite.y - yCam;
				if (xd < -64 || xd > 320 + 64 || yd < -64 || yd > 240 + 64) {
					removeSprite(sprite);
				} else {
					if (sprite instanceof Fireball) {
						fireballsOnScreen++;
					}
				}
			}
		}

		if (paused) {
			for (Sprite sprite : sprites) {
				if (sprite == mario) {
					sprite.tick();
				} else {
					sprite.tickNoMove();
				}
			}
		} else {

			tick++;
			level.tick();

			boolean hasShotCannon = false;
			int xCannon = 0;

			for (int x = (int) xCam / 16 - 1; x <= (int) (xCam + layer.width) / 16 + 1; x++)
				for (int y = (int) yCam / 16 - 1; y <= (int) (yCam + layer.height) / 16 + 1; y++) {
					int dir = 0;

					if (x * 16 + 8 > mario.x + 16)
						dir = -1;
					if (x * 16 + 8 < mario.x - 16)
						dir = 1;

					SpriteTemplate st = level.getSpriteTemplate(x, y);

					if (st != null) {
						if (st.lastVisibleTick != tick - 1) {
							if (st.sprite == null
									|| !sprites.contains(st.sprite)) {
								st.spawn(this, x, y, dir);

							}
						}

						st.lastVisibleTick = tick;
					}

					if (dir != 0) {
						byte b = level.getBlock(x, y);
						if (((Level.TILE_BEHAVIORS[b & 0xff]) & Level.BIT_ANIMATED) > 0) {
							if ((b % 16) / 4 == 3 && b / 16 == 0) {
								if ((tick - x * 2) % 100 == 0) {
									xCannon = x;
									for (int i = 0; i < 8; i++) {
										addSprite(new Sparkle(x * 16 + 8, y
												* 16
												+ (int) (Math.random() * 16),
												(float) Math.random() * dir, 0,
												0, 1, 5));
									}
									addSprite(new BulletBill(this, x * 16 + 8
											+ dir * 8, y * 16 + 15, dir));
									hasShotCannon = true;
								}
							}
						}
					}
				}

			if (hasShotCannon) {
				sound.play(Art.samples[Art.SAMPLE_CANNON_FIRE],
						new FixedSoundSource(xCannon * 16, yCam + 120), 1, 1, 1);
			}

			for (Sprite sprite : sprites) {
				sprite.tick();
			}

			for (Sprite sprite : sprites) {
				sprite.collideCheck();
			}

			for (Shell shell : shellsToCheck) {
				for (Sprite sprite : sprites) {
					if (sprite != shell && !shell.dead) {
						if (sprite.shellCollideCheck(shell)) {
							if (mario.carried == shell && !shell.dead) {
								mario.carried = null;
								shell.die();
							}
						}
					}
				}
			}
			shellsToCheck.clear();

			for (Fireball fireball : fireballsToCheck) {
				for (Sprite sprite : sprites) {
					if (sprite != fireball && !fireball.dead) {
						if (sprite.fireballCollideCheck(fireball)) {
							fireball.die();
						}
					}
				}
			}
			fireballsToCheck.clear();
		}

		sprites.addAll(0, spritesToAdd);
		sprites.removeAll(spritesToRemove);
		spritesToAdd.clear();
		spritesToRemove.clear();
	}

	private DecimalFormat df = new DecimalFormat("00");
	private DecimalFormat df2 = new DecimalFormat("000");
	
	public void render(Graphics g, float alpha) {
		int xCam = (int) (mario.xOld + (mario.x - mario.xOld) * alpha) - 160;
		int yCam = (int) (mario.yOld + (mario.y - mario.yOld) * alpha) - 120;

		if (xCam < 0)
			xCam = 0;
		if (yCam < 0)
			yCam = 0;
		if (xCam > level.getWidth() * 16 - 320)
			xCam = level.getWidth() * 16 - 320;
		if (yCam > level.getHeight() * 16 - 240)
			yCam = level.getHeight() * 16 - 240;

		// g.drawImage(Art.background, 0, 0, null);

		for (int i = 0; i < 2; i++) {
			bgLayer[i].setCam(xCam, yCam);
			bgLayer[i].render(g, tick, alpha);
		}

		g.translate(-xCam, -yCam);
		for (Sprite sprite : sprites) {
			if (sprite.layer == 0)
				sprite.render(g, alpha);
		}
		g.translate(xCam, yCam);

		// //////////THIS RENDERS THE LEVEL
		layer.setCam(xCam, yCam);
		layer.render(g, tick, paused ? 0 : alpha);
		layer.renderExit0(g, tick, paused ? 0 : alpha, mario.winTime == 0);
		// //////////END OF LEVEL RENDER

		// //////////RENDERS SPRITES
		g.translate(-xCam, -yCam);
		for (Sprite sprite : sprites) {
			if (sprite.layer == 1)
				sprite.render(g, alpha);
		}
		g.translate(xCam, yCam);
		g.setColor(Color.BLACK);
		layer.renderExit1(g, tick, paused ? 0 : alpha);
		// //////////END OF SPRITE RENDERING

		drawStringDropShadow(g, "MARIO " + df.format(Mario.lives), 0, 0, 7);
		// drawStringDropShadow(g, "00000000", 0, 1, 7);

		drawStringDropShadow(g, "COIN", 14, 0, 7);
		drawStringDropShadow(g, " " + df.format(Mario.coins), 14, 1, 7);

		drawStringDropShadow(g, "WORLD", 24, 0, 7);
		drawStringDropShadow(g, " " + Mario.levelString, 24, 1, 7);

		drawStringDropShadow(g, "TIME", 35, 0, 7);
		int time = (timeLeft + 15 - 1) / 15;
		if (time < 0)
			time = 0;
		drawStringDropShadow(g, " " + df2.format(time), 35, 1, 7);

		renderDirectionArrow(g);

		if (startTime > 0) {
			float t = startTime + alpha - 2;
			t = t * t * 0.6f;
			renderBlackout(g, 160, 120, (int) (t));
		}
		// mario.x>level.xExit*16
		if (mario.winTime > 0) {
			float t = mario.winTime + alpha;
			t = t * t * 0.2f;

			if (t > 0) {
				if (recorder != null) {
					recorder.stopRecord();
					recorder.levelWon();
					// recorder.printAll();
				}

			}

			if (t > 900) {

				winActions();
				return;

				// replayer = new Replayer(recorder.getBytes());
				// init();
			}

			renderBlackout(g, (int) (mario.xDeathPos - xCam),
					(int) (mario.yDeathPos - yCam), (int) (320 - t));
		}

		if (mario.deathTime > 0) {
			g.setColor(Color.BLACK);
			float t = mario.deathTime + alpha;
			t = t * t * 0.4f;

			if (t > 0 && Mario.lives <= 0) {
				if (recorder != null) {
					recorder.stopRecord();
				}
			}

			if (t > 1800) {
				Mario.lives--;
				deathActions();
			}

			renderBlackout(g, (int) (mario.xDeathPos - xCam),
					(int) (mario.yDeathPos - yCam), (int) (320 - t));
		}
	}

	public void winActions() {

	}

	public void deathActions() {

	}

	protected void reset() {
		paused = false;
		Sprite.spriteContext = this;
		sprites.clear();

		try {
			level = currentLevel.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		level.resetSpriteTemplate();

		layer = new LevelRenderer(level, graphicsConfiguration, 320, 240);

		mario = new Mario(this);
		sprites.add(mario);
		startTime = 1;

		timeLeft = 200 * 15;
		Art.startMusic(1);
		tick = 0;

		if (recorder != null) {
			recorder.detailedLog = "";
		}
		gameStarted = false;
	}

	private void renderDirectionArrow(Graphics g) {
		if (widthArrow < 0)
			g.setColor(new Color(0, 0, 255, 150));
		else
			g.setColor(new Color(255, 0, 0, 150));

		g.fillPolygon(xPositionsArrow, yPositionsArrow,
				Math.min(xPositionsArrow.length, yPositionsArrow.length));
		g.setColor(new Color(0, 0, 0, 255));
		g.drawPolygon(xPositionsArrow, yPositionsArrow,
				Math.min(xPositionsArrow.length, yPositionsArrow.length));
	}

	private void drawStringDropShadow(Graphics g, String text, int x, int y,
			int c) {
		drawString(g, text, x * 8 + 5, y * 8 + 5, 0);
		drawString(g, text, x * 8 + 4, y * 8 + 4, c);
	}

	private void drawString(Graphics g, String text, int x, int y, int c) {
		char[] ch = text.toCharArray();
		for (int i = 0; i < ch.length; i++) {
			g.drawImage(Art.font[ch[i] - 32][c], x + i * 8, y, null);
		}
	}

	float decrease = (float) 0.03;
	float factor = 0;
	boolean in = true;
	String flipText = "FLIP! MOVE THE OTHER WAY!";

	private void renderBlackout(Graphics g, int x, int y, int radius) {
		if (radius > 320)
			return;

		int[] xp = new int[20];
		int[] yp = new int[20];
		for (int i = 0; i < 16; i++) {
			xp[i] = x + (int) (Math.cos(i * Math.PI / 15) * radius);
			yp[i] = y + (int) (Math.sin(i * Math.PI / 15) * radius);
		}
		xp[16] = 320;
		yp[16] = y;
		xp[17] = 320;
		yp[17] = 240;
		xp[18] = 0;
		yp[18] = 240;
		xp[19] = 0;
		yp[19] = y;
		g.fillPolygon(xp, yp, xp.length);

		for (int i = 0; i < 16; i++) {
			xp[i] = x - (int) (Math.cos(i * Math.PI / 15) * radius);
			yp[i] = y - (int) (Math.sin(i * Math.PI / 15) * radius);
		}
		xp[16] = 320;
		yp[16] = y;
		xp[17] = 320;
		yp[17] = 0;
		xp[18] = 0;
		yp[18] = 0;
		xp[19] = 0;
		yp[19] = y;

		g.fillPolygon(xp, yp, xp.length);
	}

	public void addSprite(Sprite sprite) {
		spritesToAdd.add(sprite);
		sprite.tick();
	}

	public void removeSprite(Sprite sprite) {
		spritesToRemove.add(sprite);
	}

	public float getX(float alpha) {
		int xCam = (int) (mario.xOld + (mario.x - mario.xOld) * alpha) - 160;

		if (xCam < 0)
			xCam = 0;

		return xCam + 160;
	}

	public float getY(float alpha) {
		return 0;
	}

	public void bump(int x, int y, boolean canBreakBricks) {
		byte block = level.getBlock(x, y);

		if ((Level.TILE_BEHAVIORS[block & 0xff] & Level.BIT_BUMPABLE) > 0) {
			bumpInto(x, y - 1);
			level.setBlock(x, y, (byte) 4);

			if (((Level.TILE_BEHAVIORS[block & 0xff]) & Level.BIT_SPECIAL) > 0) {
				sound.play(Art.samples[Art.SAMPLE_ITEM_SPROUT],
						new FixedSoundSource(x * 16 + 8, y * 16 + 8), 1, 1, 1);
				if (!Mario.large) {
					addSprite(new Mushroom(this, x * 16 + 8, y * 16 + 8));
				} else {
					addSprite(new FireFlower(this, x * 16 + 8, y * 16 + 8));
				}

				if (recorder != null) {
					recorder.blockPowerDestroyRecord();
				}
			} else {
				if (recorder != null) {
					recorder.blockCoinDestroyRecord();
				}

				Mario.getCoin();
				sound.play(Art.samples[Art.SAMPLE_GET_COIN],
						new FixedSoundSource(x * 16 + 8, y * 16 + 8), 1, 1, 1);
				addSprite(new CoinAnim(x, y));
			}
		}

		if ((Level.TILE_BEHAVIORS[block & 0xff] & Level.BIT_BREAKABLE) > 0) {
			bumpInto(x, y - 1);
			if (canBreakBricks) {
				if (recorder != null) {
					recorder.blockEmptyDestroyRecord();
				}

				sound.play(Art.samples[Art.SAMPLE_BREAK_BLOCK],
						new FixedSoundSource(x * 16 + 8, y * 16 + 8), 1, 1, 1);
				level.setBlock(x, y, (byte) 0);
				for (int xx = 0; xx < 2; xx++)
					for (int yy = 0; yy < 2; yy++)
						addSprite(new Particle(x * 16 + xx * 8 + 4, y * 16 + yy
								* 8 + 4, (xx * 2 - 1) * 4, (yy * 2 - 1) * 4 - 8));
			}

		}
	}

	public void bumpInto(int x, int y) {
		byte block = level.getBlock(x, y);
		if (((Level.TILE_BEHAVIORS[block & 0xff]) & Level.BIT_PICKUPABLE) > 0) {
			Mario.getCoin();
			sound.play(Art.samples[Art.SAMPLE_GET_COIN], new FixedSoundSource(
					x * 16 + 8, y * 16 + 8), 1, 1, 1);
			level.setBlock(x, y, (byte) 0);
			addSprite(new CoinAnim(x, y + 1));

			// TODO no idea when this happens... maybe remove coin count
			if (recorder != null)
				recorder.recordCoin();
		}

		for (Sprite sprite : sprites) {
			sprite.bumpCheck(x, y);
		}
	}

	public void setLevel(Level level) {
		this.level = level;
	}

	@Override
	public void mouseClicked(MouseEvent me) {
	}
}
