package server.match;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.ListIterator;
import java.util.Timer;
import java.util.TimerTask;

import resources.image.Images;
import resources.image.Images.ImageType;
import server.LevelReader;
import server.NetworkUser;
import server.match.levels.Level;
import server.match.levels.LevelSorter;
import server.match.objects.Ball;
import server.match.objects.Player;
import server.match.objects.ShootingLine;

public class Match implements Runnable {
	private NetworkUser nwuser1, nwuser2;
	private Player player1;
	private Player player2;
	private ShootingLine line1;
	private ShootingLine line2;
	private ArrayList<Ball> balls;
	private int p1direction, p2direction;

	private ArrayList<Level> levels;
	private Timer timer;
	private int currentlevel;
	private int timeleft;
	private long lasttime;

	// Contains all the server game logic.
	// The server decides when collision occurs or a player is allowed to move.
	// Also manages health, time and chat
	public Match(NetworkUser nwuser1, NetworkUser nwuser2) {
		nwuser1.startMatch(nwuser1.getName(), nwuser2.getName(), 1, this);
		nwuser2.startMatch(nwuser2.getName(), nwuser1.getName(), 2, this);
		player1 = new Player(Images.getImage(ImageType.PLAYER1), 40, 550);
		player2 = new Player(Images.getImage(ImageType.PLAYER1), 240, 550);
		balls = new ArrayList<Ball>();
		this.nwuser1 = nwuser1;
		this.nwuser2 = nwuser2;
		System.out.println("New Match!");
		levels = LevelReader.readLevelsFromFiles();
		Collections.sort(levels, new LevelSorter());
		playLevel(getCurrentLevel());

		timer = new Timer();
		timer.schedule(new TimerTask() {
			@Override
			public void run() {
				update();
			}
		}, 0, 1000 / 30);
	}

	// Play a selected level
	public void playLevel(Level level) {
		balls.clear();
		if (level == null) {
			timer.cancel();
			setOverlayMessage("No more levels to play!");
		} else {
			setOverlayMessage("Level " + (currentlevel + 1));
			player1.reset();
			player2.reset();
			line1 = null;
			line2 = null;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			for (Ball b : level.getStartballs()) {
				balls.add(b.clone());
			}
			timeleft = level.getTime();
			lasttime = System.currentTimeMillis();
			setOverlayMessage("");
		}
	}

	public Level getCurrentLevel() {
		if (currentlevel >= 0 && currentlevel < levels.size()) {
			return levels.get(currentlevel);
		} else {
			stopMatch();
		}
		return null;
	}

	/* UPDATE */
	public void update() {
		if (player1.getHealth() == 0 && player2.getHealth() == 0) {
			return;
		}
		if (player1.getHealth() > 0) {
			if (p1direction == 1)
				player1.walkRight();
			else if (p1direction == -1)
				player1.walkLeft();
			player1.update();
		} else {
			player1.setY(-100);
		}
		if (player2.getHealth() > 0) {
			if (p2direction == 1)
				player2.walkRight();
			else if (p2direction == -1)
				player2.walkLeft();
			player2.update();
		} else {
			player2.setY(-100);
		}

		if (line1 != null) {
			line1.setLength(line1.getLength() + 1);
			if (line1.getMaxHeight() < 0) { // Line hit the ceiling, so "remove"
											// it
				line1 = null;
			}
		}
		if (line2 != null) {
			line2.setLength(line2.getLength() + 1);
			if (line2.getMaxHeight() < 0) {
				line2 = null;
			}
		}

		// Collision detection
		try {
			ListIterator<Ball> b = balls.listIterator();
			while (b.hasNext()) {
				Ball bal = b.next();
				if (line1 != null && bal.hitLine(line1)) { // Collision between
															// line and ball
					line1 = null;
					b.remove();
					b.add(new Ball(bal.getSize() - 1, (int) Math.max(bal.getBounceHeight() / 1.5, player1.getHeigth() + 5), bal.getColor(), bal.getX(), bal.getY(), -1, Math.abs(bal.getYSpeed()) * -1));
					b.add(new Ball(bal.getSize() - 1, (int) Math.max(bal.getBounceHeight() / 1.5, player1.getHeigth() + 5), bal.getColor(), bal.getX(), bal.getY(), 1, Math.abs(bal.getYSpeed()) * -1));
				} else if (line2 != null && bal.hitLine(line2)) { // Collision
																	// between
																	// line and
																	// ball
					line2 = null;
					b.remove();
					b.add(new Ball(bal.getSize() - 1, (int) Math.max(bal.getBounceHeight() / 1.5, player1.getHeigth() + 5), bal.getColor(), bal.getX(), bal.getY(), -1, Math.abs(bal.getYSpeed()) * -1));
					b.add(new Ball(bal.getSize() - 1, (int) Math.max(bal.getBounceHeight() / 1.5, player1.getHeigth() + 5), bal.getColor(), bal.getX(), bal.getY(), 1, Math.abs(bal.getYSpeed()) * -1));
				} else if (bal.hitPlayer(player1)) { // Collision between player
														// and ball
					player1.setHealth(player1.getHealth() - 1);
					playLevel(getCurrentLevel());
				} else if (bal.hitPlayer(player2)) { // Collision between player
														// and ball
					player2.setHealth(player2.getHealth() - 1);
					playLevel(getCurrentLevel());
				} else if (bal.getSize() <= 0) { // Remove ball from arraylist
													// when size is 0 (not
													// visible)
					b.remove();
				} else {
					bal.update();
				}
			}
		} catch (ConcurrentModificationException e) {
		}
		timeleft -= (System.currentTimeMillis() - lasttime);
		if (balls.size() == 0) {
			if (player1.getHealth() > 0)
				player1.setScore(player1.getScore() + (timeleft / 100));
			else
				player1.setScore(player1.getScore() + (timeleft / 100) / 2);

			if (player2.getHealth() > 0)
				player2.setScore(player2.getScore() + (timeleft / 100));
			else
				player2.setScore(player2.getScore() + (timeleft / 100) / 2);
			currentlevel++;
			playLevel(getCurrentLevel());
		} else if (timeleft < 0) {
			player1.setHealth(player1.getHealth() - 1);
			player2.setHealth(player2.getHealth() - 1);
			playLevel(getCurrentLevel());
		} else if (player1.getHealth() <= 0) {

		}
		lasttime = System.currentTimeMillis();

		// Sending the new information to the players
		String playerinfo = player1.getX() + "|" + player1.getY() + "|" + player1.getHealth() + "|" + player1.getScore() + "|" + player2.getX() + "|" + player2.getY() + "|" + player2.getHealth() + "|" + player2.getScore();

		String ballsinfo = ",";
		for (Ball b : balls) {
			String color = Integer.toHexString(b.getColor().getRGB());
			color = color.substring(2, color.length());
			ballsinfo += b.getX() + "|" + b.getY() + "|" + b.getSize() + "|0x" + color + "|";
		}

		String linesinfo = ",";
		if (line1 != null)
			linesinfo += line1.getX() + "|" + line1.getY() + "|" + line1.getLength() + "|";
		else
			linesinfo += "0|0|0|";
		if (line2 != null)
			linesinfo += line2.getX() + "|" + line2.getY() + "|" + line2.getLength();
		else
			linesinfo += "0|0|0|";

		String timeleft = ",0";
		if (getCurrentLevel() != null) {
			timeleft = "," + (int) (this.timeleft / (double) getCurrentLevel().getTime() * 100);
		}

		nwuser1.sendUpdateMessage(playerinfo + ballsinfo + linesinfo + timeleft);
		nwuser2.sendUpdateMessage(playerinfo + ballsinfo + linesinfo + timeleft);

	}

	/* GETTERS AND SETTERS */
	public void setOverlayMessage(String m) {
		nwuser1.sendOverlayText(m);
		nwuser2.sendOverlayText(m);
	}

	public void setPlayerDirection(int pid, int dir) {
		if (pid == 1)
			p1direction = dir;
		if (pid == 2)
			p2direction = dir;
	}

	public void playerShoot(int pid) {
		if (pid == 1 && player1.getHealth() > 0)
			if (line1 == null)
				line1 = new ShootingLine(player1.getX() + player1.getWidth() / 2, player1.getY() + player1.getHeigth());
		if (pid == 2 && player2.getHealth() > 0)
			if (line2 == null)
				line2 = new ShootingLine(player2.getX() + player2.getWidth() / 2, player2.getY() + player2.getHeigth());
	}

	public void stopMatch() {
		if (timer != null)
			timer.cancel();
		try {
			nwuser1.close();
		} catch (IOException e) {
		}
		try {
			nwuser2.close();
		} catch (IOException e) {
		}
	}

	@Override
	public void run() {

	}

	public void sendChatMessage(String string) {
		nwuser1.sendChatMessage(string);
		nwuser2.sendChatMessage(string);
	}
}
