package de.heatup.mapengine;



import java.awt.Point;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Scanner;

import javax.swing.JPanel;

import de.heatup.cfg.Config;
import de.heatup.highscore.HighscoreHandler;
import de.heatup.logging.Logger;
import de.heatup.objects.*;
/**
 * Diese Klasse soll als primäre Schnittstelle dieses Packages zu anderen Spielmodulen dienen.
 * Sie generiert die Map als StaticObjects um diese dann auf ein Spielfeld zu malen.
 * Außerdem erstellt sie eine MapGrid, welches zur Kollisionserkennung und Positionierung
 * von Entitäten dient.
 * @author Daniel
 *
 */
public class MapEngine {
	
	private static final int singleTileWidth = Config.singleTilePixels;
	
	private final int mapHeight = Config.mapMaxTilesHeight;
	
	private final int mapWidth = Config.mapMaxTilesWidth;
	
	private final int gamePanelWidth = Config.gamePanelWidth;
	
	private static String mapString = "";
	
	private HighscoreHandler highscoreHandler;
	
	HashMap<Character, ArrayList<Point>> allMapTiles = new HashMap<Character, ArrayList<Point>>();
	ArrayList<Character> availableHardwareTileTypes = new ArrayList<Character>();
	/**
	 * Lädt die Map mit angegebener mapNumber.
	 * @param mapNumber Die Nummer der zu ladenen Map
	 */
	public MapEngine(int mapNumber) {
		Scanner input = null;
		MapLoader ml = new MapLoader();
		input = new Scanner(ml.loadMap(mapNumber));
		this.createTiles(input);
		highscoreHandler = new HighscoreHandler(mapString);
	}
	/**
	 * Öffnet einen File Chooser um eine Custom Map auszuwählen, welche anschließend geladen wird.
	 */
	public MapEngine() {
		Scanner input = null;
		MapLoader ml = new MapLoader();
		try {
			input = new Scanner(ml.chooseMap());
		} catch (FileNotFoundException e) {
			Logger.write("Map file not found");
			e.printStackTrace();
		}
		this.createTiles(input);
		highscoreHandler = new HighscoreHandler(mapString);
	}
	public HighscoreHandler getHighscoreHandler() {
		return highscoreHandler;
	}
	/**
	 * Erstellt aus einem gegebenen Scanner, welche eine Datei einliest,
	 * eine Map, welche dann zu einem Spielfeld hinzugefügt werden kann.
	 * @param input Ein Scanner der auf eine Map-Datei zeigt.
	 */
	private void createTiles(Scanner input) {
		int lineCount = 0;
		int x = 0;
		int y = 0;
		while (input.hasNextLine() && lineCount <= mapHeight) {
			String currentLine = input.nextLine();
			mapString = mapString+currentLine;
			for (int i = 0; i<mapWidth; i++) {
				/* MapGrid implementation */
				if (checkIfPath(currentLine.charAt(i))) {
					if (MapGrid.getGridCell(i, lineCount) == null) {
						MapGrid.addGrid(i, lineCount, true);
					} else {
						MapGrid.getGridCell(i, lineCount).setPath(true);
					}
				} else {
					if (checkIfHardware(currentLine.charAt(i))) {
						if (MapGrid.getGridCell(i, lineCount) == null) {
							MapGrid.addGrid(i, lineCount, false);
						} else {
							MapGrid.getGridCell(i, lineCount).setPath(false);
						}
						/* trigger setzen */
						/* trigger 1 */
						if (MapGrid.getGridCell(i+1, lineCount) == null) {
							MapGrid.addGrid(i+1, lineCount, true, currentLine.charAt(i));
						} else {
							MapGrid.getGridCell(i+1, lineCount).setTrigger(true);
							MapGrid.getGridCell(i+1, lineCount).setCorrespondingHardwareToTrigger(currentLine.charAt(i));
						}
						/* trigger 2 */
						if (MapGrid.getGridCell(i-1, lineCount) == null) {
							MapGrid.addGrid(i-1, lineCount, true, currentLine.charAt(i));
						} else {
							MapGrid.getGridCell(i-1, lineCount).setTrigger(true);
							MapGrid.getGridCell(i-1, lineCount).setCorrespondingHardwareToTrigger(currentLine.charAt(i));
						}
						/* trigger 3 */
						if (MapGrid.getGridCell(i, lineCount+1) == null) {
							MapGrid.addGrid(i, lineCount+1, true, currentLine.charAt(i));
						} else {
							MapGrid.getGridCell(i, lineCount+1).setTrigger(true);
							MapGrid.getGridCell(i, lineCount+1).setCorrespondingHardwareToTrigger(currentLine.charAt(i));
						}
						/* trigger 4 */
						if (MapGrid.getGridCell(i, lineCount-1) == null) {
							MapGrid.addGrid(i, lineCount-1, true, currentLine.charAt(i));
						} else {
							MapGrid.getGridCell(i, lineCount-1).setTrigger(true);
							MapGrid.getGridCell(i, lineCount-1).setCorrespondingHardwareToTrigger(currentLine.charAt(i));
						}
					} else {
						if (MapGrid.getGridCell(i, lineCount) == null) {
							MapGrid.addGrid(i, lineCount, false);
						} else {
							MapGrid.getGridCell(i, lineCount).setPath(false);
						}
					}
				}
				/* End of MapGrid implementation */
				if (!allMapTiles.containsKey(currentLine.charAt(i))) {
					ArrayList<Point> newTileType = new ArrayList<Point>();
					newTileType.add(new Point(x,y));
					allMapTiles.put(currentLine.charAt(i), newTileType);
					for (char j = Config.firstHardwareChar; j <=Config.lastHardwareChar; j++) {
						if (j==currentLine.charAt(i)) {
							availableHardwareTileTypes.add(currentLine.charAt(i));
						}
					}
				} else {
					allMapTiles.get(currentLine.charAt(i)).add(new Point(x,y));
				}
				if (x+singleTileWidth >= gamePanelWidth) {
					x = 0;
					y += singleTileWidth;
				} else {
					x += singleTileWidth;
				}
			}
			lineCount++;
		}
	}
	
	private HashMap<Character, ArrayList<Point>> getAllMapTiles() {
		return allMapTiles;
	}
	
	protected ArrayList<Character> getAvailableHardwareTileTypes() {
		return availableHardwareTileTypes;
	}
	
	protected ArrayList<Point> getAllCoordinatesOfTileType(char c) {
		return getAllMapTiles().get(c);
	}
	/**
	 * @deprecated
	 * @param c
	 * @return
	 */
	protected Point getFirstTilesCoordinatesOfTileType(char c) {
		return getAllMapTiles().get(c).get(0);
	}
	/**
	 * @deprecated
	 * @param c
	 * @return
	 */
	protected Point getLastTilesCoordinatesOfTileType(char c) {
		return getAllMapTiles().get(c).get(getAllMapTiles().get(c).size()-1);
	}
	/**
	 * 
	 * @return Gibt alle MapObjekte als ArrayList<StaticObject> zurück.
	 */
	public ArrayList<StaticObject> getAllMapObjects() {
		return MapObjectHandler.getAllMapObjects(this);
	}
	/**
	 * 
	 * @return Gibt alle HardwareObjekte auf einer Map als ArrayList<StaticObject> zurück.
	 */
	public ArrayList<StaticObject> getAllHardwareObjects() {
		return MapObjectHandler.getAllHardwareObjects();
	}
	/**
	 * 
	 * @return Gibt alle WallObjekte auf einer Map als ArrayList<StaticObject> zurück.
	 */
	public ArrayList<StaticObject> getAllWallObjects() {
		return MapObjectHandler.getAllWallObjects();
	}
	/**
	 * 
	 * @return Gibt alle WayObjekte auf einer Map als ArrayList<StaticObject> zurück.
	 */
	public ArrayList<StaticObject> getAllWayObjects() {
		return MapObjectHandler.getAllWayObjects();
	}
	/**
	 * 
	 * @return Gibt alle HardwareTriggerObjekte auf einer Map als ArrayList<StaticObject> zurück.
	 */
	public ArrayList<StaticObject> getAllHardwareTriggerObjects() {
		return MapObjectHandler.getAllHardwareTriggerObjects();
	}
	
	private boolean checkIfPath(char c) {
		return (c==Config.wayChar);
	}
	
	private boolean checkIfHardware(char c) {
		for (char j = Config.firstHardwareChar; j <=Config.lastHardwareChar; j++) {
			if (j==c) {
				return true;
			}
		}
		return false;
	}
	/**
	 * @deprecated
	 *  Schreibt eine mit Trigger konvertierte Map-Datei in die Konsole.
	 *  Diese Methode existiert nur zu Testzwecken und ist daher deprecated markiert.
	 */
	public void printMapGridWithTriggers() {
		for (int i = 0; i < Config.mapMaxTilesHeight; i++) {
			for (int j = 0; j < Config.mapMaxTilesWidth; j++) {
				if (!MapGrid.getGridCell(j, i).isPath() && !MapGrid.getGridCell(j, i).isTrigger()) {
					System.out.print(Config.wallChar + "");
				} else {
					if (MapGrid.getGridCell(j, i).isTrigger()) {
						System.out.print(MapGrid.getGridCell(j, i).getCorrespondingHardwareToTrigger()+"");
					}
					if (MapGrid.getGridCell(j, i).isPath() && !MapGrid.getGridCell(j, i).isTrigger()) {
						System.out.print(Config.wayChar + "");
					}
				}
				
			}
			System.out.println();
		}
	}
	/**
	 * Zeichnet die Map auf angegebenen Panel.
	 * @param gamePanel das JPanel auf das gezeichnet werden soll.
	 */
	public void addAllToPanel(JPanel gamePanel) {
		for (StaticObject currentStaticObject : this.getAllMapObjects()) {
			gamePanel.add(currentStaticObject);
		}
	}
	public int getMapHash() {
		return highscoreHandler.getCurrentMapHash();
	}
}
