package com.mygdx.game;

import java.io.Serializable;
import java.util.*;
import com.mygdx.game.MyGdxGame;
import com.badlogic.gdx.graphics.g3d.ModelInstance;
import com.badlogic.gdx.graphics.g3d.Model;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.g2d.BitmapFont;

import com.badlogic.gdx.math.Vector3;

class InvalidSelectionException extends Exception {};

public class GameMechanics extends MyGdxGame implements Serializable {
	int numPlayers = 0;
	List<Player> players = new ArrayList<>();
	Player winner = null;

	List<Field> fields = new ArrayList<>();
	List<GameFigure> figures = new ArrayList<>();

	BitmapFont font;

	Field active = null;
	List<Field> mRange = null;
	List<Field> aRange = null;
	boolean resetTiles = false;

	//Internal class
	class Player implements Serializable {

		int playerID;

		public Player(){
			numPlayers++;
			playerID = numPlayers;
		}

		@Override
		public String toString(){
			return String.valueOf(playerID);
		}

		public void makeMove(){
			GameFigure chosenFigure = null;
			Field targetField;

			boolean validMove = false;
			do {
				try {
					chosenFigure = chooseFigure();
					targetField = chooseTargetField(chosenFigure);
					chosenFigure.act(targetField);
					validMove = true;
				}
				catch (InvalidMoveException e){}
				catch (InvalidSelectionException e){}
				finally {
					resetTiles = true;
				}
			} while (!validMove);
		}

		public GameFigure chooseFigure() throws InvalidSelectionException {
			System.out.println("Choose Figure, Player " + playerID);
			Field figureField = chooseField();
			System.out.println("Player " + playerID + " chose Field" + figureField.index);

			if (figureField.figure == null){
				System.out.println("Field is empty");
				throw new InvalidSelectionException();
			}

			GameFigure figure = figureField.figure;
			if (playerID == figure.getPlayerID()){

				active = figure.field;
				mRange = figure.getMoveRange();
				aRange = figure.getAttackRange();

				return figure;
			}
			else {
				System.out.println("This is the enemies figure");
				throw new InvalidSelectionException();
			}
		}

		public Field chooseField() throws InvalidSelectionException {
			System.out.println("Choose Field, Player " + playerID);
			while (pickedTile == -1){
				try {
					//System.out.println("In while loop");
					Thread.sleep(500); //sleep for 500ms
				} catch (InterruptedException e) {}
			}
			System.out.println("Player " + playerID + " chose Field " + pickedTile);
			int chosenField = pickedTile;
			pickedTile = -1;
			return fields.get(chosenField);
		}

		public Field chooseTargetField(GameFigure figure)
				throws InvalidSelectionException {
				System.out.println("Choose TargetField, Player " + playerID);
				Field targetField = chooseField();
				if ((figure.getMoveRange().contains(targetField) ||
							figure.getAttackRange().contains(targetField)))
				{
					return targetField;
				}
				else
				{
					throw new InvalidSelectionException();
				}
		}


	}

	//Internal class
	abstract class GameFigure extends ModelInstance implements Serializable {
		private int health;
		private Field field;
		protected List<Field> moveRange;
		protected List<Field> attackRange;
		private int playerID;

		public GameFigure(Model model, Field field, int playerID){
			super(model);
			this.field = field;
			this.health = 100;
			this.playerID = playerID;
			this.transform.idt();
			//this.transform.rotate(1,0,0,-90);
			this.transform.translate(field.position);

			Vector3 up = new Vector3(0,1,0);
			if (Math.abs(field.position.dot(up)) > 0.99999)
				up = new Vector3(1,0,0);
			this.transform.rotateTowardDirection(field.position, up);
			setMoveRange(field);
			setAttackRange(field);
		}

		public int getHealth(){
			return this.health;
		}
		public void reduceHealthBy(int damage) {
			this.health -= damage;
		}

		public int getPlayerID(){
			return this.playerID;
		}

		public void setField(Field field){
			this.field = field;
		}

		public Field getField(){
			return this.field;
		}

		public void setMoveRange(List<Field> range){
			this.moveRange = range;
		}

		public void setAttackRange(List<Field> range){
			this.attackRange = range;
		}

		public void setMoveRange(Field field){
			this.moveRange = field.neighbours;
		}

		public void setAttackRange(Field field){
			this.attackRange = field.neighbours;
		}

		public List<Field> getMoveRange(){
			return this.moveRange;
		}

		public List<Field> getAttackRange(){
			return this.attackRange;
		}

		public void act(Field targetField)
				throws InvalidMoveException{
				if (targetField.figure != null &&
						attackRange.contains(targetField)){
					//Targetfield is occupied by player's own figure
					if (targetField.figure.getPlayerID() == playerID){
						System.out.println("You can't attack your own figure");
						throw new InvalidMoveException();
					}
					//Targetfield is occupied by an enemy
					else{
						//Targetfield is a pentagon occupied by enemy
						if (targetField.neighbours.size() == 5) {
							attackPentagon(targetField);
						}
						//Targefield is a hexagon occupied by enemy
						else {
							attack(targetField);
						}
					}
						}
				//Targetfield is unoccupied
				else if (targetField.figure == null &&
						moveRange.contains(targetField)){
					if (targetField.neighbours.size() == 5) {
						occupyPentagon(targetField);
					}
					else {
						occupy(targetField);
					}
						}
				else
					throw new InvalidMoveException();

		}

		private void occupy(Field targetField) {
			Field origin = this.field;
			Vector3 originPosition = origin.position.cpy();
			origin.figure = null;
			this.field = targetField;
			targetField.figure = this;
			this.setMoveRange(targetField);
			this.setAttackRange(targetField);
			this.transform.idt();
			this.transform.translate(field.position);
			Vector3 up = new Vector3(0,1,0);
			if (Math.abs(field.position.dot(up)) > 0.99999)
				up = new Vector3(1,0,0);
			this.transform.rotateTowardDirection(field.position, up);

			this.transform.rotate(1,0,0,-90);
		}

		private void occupyPentagon(Field targetField) {
			occupy(targetField);
			//extend to have interesting behaviour at pentagons
		}

		protected void attackPentagon(Field targetField){
			targetField.figure.reduceHealthBy(25);
			updateField(targetField);
		}

		protected void attack(Field targetField){
			targetField.figure.reduceHealthBy(25);
			updateField(targetField);
		}

		protected void updateField(Field targetField) {
			//damage dealt by attack methods in subclasses
			if (targetField.figure.getHealth() <= 0){
				int playerID = targetField.figure.playerID;
				figures.remove(targetField.figure);
				targetField.figure = null;

				boolean playerDead = true;
				for (GameFigure figure: figures){
					if (figure.playerID == playerID){
						playerDead = false;
						break;
					}
				}
				if (playerDead){
					players.remove(playerID - 1);
				}
			}
		}

	}

	class Knight extends GameFigure {
		public Knight(Field field, int playerID){
			super(Figures.getCube(playerID), field, playerID);
		}

		@Override
		public void setMoveRange(Field field){
			this.moveRange = field.nextNextNearestNeighbour();
		}

		@Override
		public void setAttackRange(Field field){
			this.attackRange = field.nearestNeighbour();
		}
	}

	class Archer extends GameFigure {
		private int damage = 25;
		public Archer(Field field, int playerID){
			super(Figures.getCone(playerID), field, playerID);
			this.transform.rotate(1,0,0,-90);
		}

		@Override
		public void setMoveRange(Field field){
			this.moveRange = field.nextNearestNeighbour();
		}

		@Override
		public void setAttackRange(Field field){
			this.attackRange = field.nextNearestNeighbour();
		}
	}

	class Mage extends GameFigure {
		public Mage(Field field, int playerID){
			super(Figures.getSphere(playerID), field, playerID);
		}

		@Override
		public void setMoveRange(Field field){
			this.moveRange = field.nearestNeighbour();
		}

		@Override
		public void setAttackRange(Field field){
			this.attackRange = field.nextNextNearestNeighbour();
		}

		@Override
		protected void attackPentagon(Field targetField){
			targetField.figure.reduceHealthBy(50);
			updateField(targetField);
		}

		@Override
		protected void attack(Field targetField){
			targetField.figure.reduceHealthBy(25);
			updateField(targetField);
		}
	}

	class Field implements Serializable {
		int index;
		List<Field> neighbours;
		Vector3 position;
		GameFigure figure;

		public Field(int index, Vector3 position){
			this.index = index;
			this.position = position;
			figure = null;
		}

		public List<Field> nearestNeighbour(){
			return this.neighbours;
		}

		public List<Field> nextNearestNeighbour(){
			List<Field> newRange = new ArrayList<>();
			for (Field firstNeighbour: this.neighbours){
				if (!newRange.contains(firstNeighbour)) {
					newRange.add(firstNeighbour);
				}
				for (Field secondNeighbour: firstNeighbour.neighbours){
					if (!newRange.contains(secondNeighbour)) {
						newRange.add(secondNeighbour);
					}
				}
			}
			return newRange;
		}

		public List<Field> nextNextNearestNeighbour(){
			List<Field> newRange = new ArrayList<>();
			for (Field firstNeighbour: this.neighbours){
				if (!newRange.contains(firstNeighbour)) {
					newRange.add(firstNeighbour);
				}
				for (Field secondNeighbour: firstNeighbour.neighbours){
					if (!newRange.contains(secondNeighbour)) {
						newRange.add(secondNeighbour);
					}
					for (Field thirdNeighbour: secondNeighbour.neighbours){
						if (!newRange.contains(thirdNeighbour)){
							newRange.add(thirdNeighbour);
						}
					}
				}
			}
			System.out.println(newRange);
			return newRange;
		}

		@Override
		public String toString(){
			return new String().valueOf(this.index);
		}
	}

	public void createAllFields(){
		for (int i=0; i<primalVertices.size(); i++){
			fields.add(new Field(i, primalVertices.get(i)));
		}
	}

	public void createNeighbourHoods(){
		for (int i=0; i<primalVertices.size(); i++){
			List<Field> neighbours = new ArrayList<>();
			for (Integer j: adjList.get(i)){
				neighbours.add(fields.get(j));
			}
			fields.get(i).neighbours = neighbours;
		}
	}

	class GameLoop implements Runnable{
		public void run(){
			gameLoop();
		}
	}

	public void addMage(Field field, int playerID){
		GameFigure newFigure = new Mage(field, playerID);
		figures.add(newFigure);
		field.figure = newFigure;
	}

	public void addKnight(Field field, int playerID){
		GameFigure newFigure = new Knight(field, playerID);
		figures.add(newFigure);
		field.figure = newFigure;
	}

	public void addArcher(Field field, int playerID){
		GameFigure newFigure = new Archer(field, playerID);
		figures.add(newFigure);
		field.figure = newFigure;
	}

	@Override
	public void create(){
		super.create();
		instances = null;
		players.add(new Player());
		players.add(new Player());
		createAllFields();
		createNeighbourHoods();

		addMage(fields.get(3), 1);
		addKnight(fields.get(88), 1);
		addKnight(fields.get(86), 1);
		addKnight(fields.get(93), 1);
		addArcher(fields.get(101), 1);
		addArcher(fields.get(108), 1);

		addMage(fields.get(0), 2);
		addKnight(fields.get(44), 2);
		addKnight(fields.get(42), 2);
		addKnight(fields.get(133), 2);
		addArcher(fields.get(131), 2);
		addArcher(fields.get(52), 2);

		font = new BitmapFont();

		Thread gameLogic = new Thread(new GameLoop());
		gameLogic.start();
	}


	@Override
	public void render(){

		if (!resetTiles)
		{
			if (active != null)
				setTileAttribute(active.index, 3, 1.0f);

			if (mRange != null)
				for (Field f : mRange)
					setTileAttribute(f.index, 4, 1.0f);

			if (aRange != null)
				for (Field f : aRange)
					setTileAttribute(f.index, 5, 1.0f);
		}
		else
		{
			if (active != null)
				setTileAttribute(active.index, 3, 0.0f);

			if (mRange != null)
				for (Field f : mRange)
					setTileAttribute(f.index, 4, 0.0f);

			if (aRange != null)
				for (Field f : aRange)
					setTileAttribute(f.index, 5, 0.0f);

			active = null;
			mRange = null;
			aRange = null;
			resetTiles = false;
		}

		super.render();
		modelBatch.begin(cam);
		Gdx.gl.glEnable(GL20.GL_CULL_FACE);
		Gdx.gl.glFrontFace(GL20.GL_CCW);
		Gdx.gl.glCullFace(GL20.GL_BACK);
		Gdx.gl.glEnable(GL20.GL_DEPTH_TEST);
		modelBatch.render(figures, environment);
		modelBatch.end();

		batch.begin();
		for (GameFigure gf : figures)
		{
			Vector3 pos = gf.field.position.cpy();
			cam.project(pos);
			font.draw(batch, String.valueOf(gf.health), pos.x, pos.y);
		}
		batch.end();
	}

	public void gameLoop(){
		System.out.println("Enter game loop");
		while (winner == null){
			nextRound();
		}
		System.out.println("Player " + winner + " has won!");
	}

	public void nextRound(){
		for (Player player: players){
			System.out.println("Player " + player.playerID + "'s turn");
			player.makeMove();
			boolean gameEnded = gameEnds();
			if (gameEnded){
				break;
			}
		}
	}

	public boolean gameEnds(){
		if (players.size() == 1){
			winner = players.get(0);
			return true;
		}
		else {
			boolean gameEnds = false;
			return gameEnds;
		}
	}

}




class InvalidMoveException extends Exception {}

