package engine;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.HashSet;

import hud.Map;
import hud.constructionmenu.ConstructionItem;
import interfaces.Mob;
import interfaces.Structure;
import player.Player;
import player.PlayerRender;
import player.PlayerTick;
import utils.Runner;
import utils.Stack;
import utils.Utils;
import world.Boulder;
import world.Debris;
import world.DroppedItem;
import world.Flower;
import world.Sapling;
import world.TallGrass;
import world.Tile;
import world.Tree;
import worldGen.WorldGenerator;

/*  ------------- Nihilne ------------
    * Created by Mitch Weaver - 2016 *
      ~~~~ All Rights Reserved ~~~~ */

public final class Handler {
	
	public static final int NUM_TYPES_OF_HASHSETS = 1; /* NOTE: only useful for static things that won't be interacted with (tiles, debris etc)  */
	
	public static int numSectionsRowsCols; /* number of rectangular sections of map */
	public static Rectangle[][] sections;
	public static int SECTION_SIZE_WIDTH_HEIGHT;
	public static final int numRowsCols = 2048; /* num of rows and cols of tiles */
	public static boolean creatingMap;
	
	public static HashSet<Tree> trees;
	public static HashSet<Boulder> boulders;
	public static HashSet<Flower> flowers;
	public static HashSet<TallGrass> grass;
	public static HashSet<Sapling> saplings;
	public static HashSet<DroppedItem> droppedItems;
	public static HashSet<Structure> structures;
	public static HashSet<Mob> mobs;
	public static HashSet<Debris> debris;
	
	public static ArrayList<ArrayList<ArrayList<Stack<Tile>>>> world;
	public static volatile Rectangle AREA_RECT;

	public static void init() {
		AREA_RECT = new Rectangle(0,0,0,0);
		trees = new HashSet<Tree>();
		boulders = new HashSet<Boulder>();
		mobs = new HashSet<Mob>();
		flowers = new HashSet<Flower>();
		grass = new HashSet<TallGrass>();
		saplings = new HashSet<Sapling>();
		droppedItems = new HashSet<DroppedItem>();
		structures = new HashSet<Structure>();
		debris = new HashSet<Debris>();
		
		WorldGenerator.generateWorld();
	}

	public static final void tick() { if(!WorldGenerator.WORLD_GENERATED) return;
		tickTiles();
		try{ for(final Debris i : debris)															   i.tick(); } catch (Exception e) {}
		try{ for(final Sapling i : saplings)		    if(AREA_RECT.contains(i.center.x, i.center.y)) i.tick(); } catch (Exception e) {};
		try{ for(final Flower i : flowers)  		    if(AREA_RECT.contains(i.center.x, i.center.y)) i.tick(); } catch (Exception e) {};
		try{ for(final TallGrass i : grass)  			if(AREA_RECT.contains(i.center.x, i.center.y)) i.tick(); } catch (Exception e) {};
		try{ for(final Tree i : trees)      		    if(AREA_RECT.contains(i.center.x, i.center.y)) i.tick(); } catch (Exception e) {};
		try{ for(final Boulder i : boulders)  			if(AREA_RECT.contains(i.center.x, i.center.y)) i.tick(); } catch (Exception e) {};
		try{ for(final DroppedItem i : droppedItems)    if(AREA_RECT.contains(i.center.x, i.center.y)) i.tick(); } catch (Exception e) {};
		try{ for(final Structure i : structures) 		if(AREA_RECT.contains(i.center.x, i.center.y)) i.tick(); } catch (Exception e) {};
		
		try{ for(final Mob i : mobs)        		    if(AREA_RECT.contains(i.center.x, i.center.y)) i.tick(); } catch (ConcurrentModificationException asdf) {} catch (Exception e) { e.printStackTrace(); };

		PlayerTick.tick();
	}

	private static boolean dontRenderPlayer;
	public static final void render(Graphics2D g2d) {  if(!WorldGenerator.WORLD_GENERATED) return;
		dontRenderPlayer = false;
	    renderTiles(g2d);
	  try{  for(final Debris i : debris)       		if(i.discovered && AREA_RECT.contains(i.x, i.y)) i.render(g2d); } 				 catch (Exception e) {}
	  try{  for(final Sapling i : saplings)           if(i.discovered && AREA_RECT.contains(i.center.x, i.center.y)) i.render(g2d); } catch (Exception e) {}
	  try{  for(final Flower i : flowers)             if(i.discovered && AREA_RECT.contains(i.center.x, i.center.y)) i.render(g2d); } catch (Exception e) {}
	  try{  for(final TallGrass i : grass)            if(i.discovered && AREA_RECT.contains(i.center.x, i.center.y)) i.render(g2d); } catch (Exception e) {}
	  try{  for(final Boulder i : boulders) 		    if(i.discovered && AREA_RECT.contains(i.center.x, i.center.y)) i.render(g2d); } catch (Exception e) {}
	  try{  for(final DroppedItem i : droppedItems)   if(i.discovered && AREA_RECT.contains(i.center.x, i.center.y)) i.render(g2d); } catch (Exception e) {}
	  try{  for(final Mob i : mobs)                   if(i.discovered && AREA_RECT.contains(i.center.x, i.center.y)) i.render(g2d); } catch (Exception e) {}
	  try{  for(final Structure i : structures)       if(AREA_RECT.contains(i.center.x, i.center.y)) i.render(g2d); } catch (Exception e) {}
	  
		
	  try{	if(!dontRenderPlayer) PlayerRender.render(g2d);                        							   } catch (Exception e) {}
	  try{	ConstructionItem.render(g2d);                                       					     	   } catch (Exception e) {}
	  try{  for(final Tree i : trees) if(i.discovered && AREA_RECT.contains(i.center.x, i.center.y)){
			if(i.bounds.contains(Player.center.x, Player.center.y)){
				if(i.bounds.y + i.bounds.height/1.25 < Player.center.y){
					i.render(g2d); PlayerRender.render(g2d); dontRenderPlayer = true;
				} else if(i.bounds.y + i.bounds.height/1.25 > Player.center.y){
					PlayerRender.render(g2d); dontRenderPlayer = true; i.render(g2d);
				}
			} else i.render(g2d); }} catch (Exception e) {e.printStackTrace();}
	}
	
	public static final void getMapImage(){
		creatingMap = true;
//		final long then = System.currentTimeMillis();
		final double scalefactor = 1d/96d;
		final int WH = numRowsCols/3;
		final BufferedImage map = new BufferedImage(WH, WH, BufferedImage.TYPE_INT_RGB);
		final Graphics2D g2d = (Graphics2D) map.createGraphics();
		g2d.scale(scalefactor, scalefactor);
		g2d.setColor(Color.red);
		
		Game.pool.runTask(new Runner(() -> {
			try{
			outerLoop:
				for(int i = 0; i < sections.length; i++)
					for(int j = 0; j < sections[i].length; j++)
						if(sections[i][j].contains(Player.center.x, Player.center.y)){
								try { for(final Tile tile : world.get(i-1).get(j-1).get(0)) tile.render(g2d); } catch (IndexOutOfBoundsException e) {}
								try { for(final Tile tile : world.get(i).get(j-1).get(0))   tile.render(g2d); } catch (IndexOutOfBoundsException e) {}
								try { for(final Tile tile : world.get(i+1).get(j-1).get(0)) tile.render(g2d); } catch (IndexOutOfBoundsException e) {}
								try { for(final Tile tile : world.get(i-1).get(j).get(0))   tile.render(g2d); } catch (IndexOutOfBoundsException e) {}
								try { for(final Tile tile : world.get(i).get(j).get(0))     tile.render(g2d); } catch (IndexOutOfBoundsException e) {}
								try { for(final Tile tile : world.get(i+1).get(j).get(0))   tile.render(g2d); } catch (IndexOutOfBoundsException e) {}
								try { for(final Tile tile : world.get(i-1).get(j+1).get(0)) tile.render(g2d); } catch (IndexOutOfBoundsException e) {}
								try { for(final Tile tile : world.get(i).get(j+1).get(0))   tile.render(g2d); } catch (IndexOutOfBoundsException e) {}
								try { for(final Tile tile : world.get(i+1).get(j+1).get(0)) tile.render(g2d); } catch (IndexOutOfBoundsException e) {}
							break outerLoop;
					}} catch (Exception e) { e.printStackTrace(); }
			
			
			for(int i = 0; i < sections.length/2; i++)
				for(int j = 0; j < sections[i].length; j++)
					for(final Tile tile : world.get(i).get(j).get(TILE_LIST))
						try { tile.render(g2d); } catch (Exception e) {}
		}));
		
		Game.pool.runTask(new Runner(() -> {
			try{
			for(int i = sections.length/2-1; i < sections.length; i++)
				for(int j = 0; j < sections[i].length; j++)
					for(final Tile tile : world.get(i).get(j).get(TILE_LIST))
						try { tile.render(g2d); } catch (Exception e) {}
			
					creatingMap = false; }catch(Exception e) {e.printStackTrace();}
			Map.map = map;
//			System.err.println("Map Painted --- Total Time Taken: " + Float.toString((System.currentTimeMillis() - then)/1000f)+ " seconds ~~~");
			g2d.dispose();
		}));
		
		
			BufferedImage overlay = new BufferedImage(WH, WH, BufferedImage.TYPE_INT_ARGB);
			Graphics2D copy = (Graphics2D) overlay.getGraphics();
			copy.scale(scalefactor, scalefactor);
			for(final Boulder i : boulders) i.render(copy);
			for(final Tree i : trees) i.render(copy);
			for(final Structure i : Handler.structures) i.render(copy);
			copy.dispose(); copy = null;
			Map.overlay = overlay;
	}
	
	public static int numSectionsRowsColsHalved;
	public static final void addTile(Tile t){
		int i = numSectionsRowsColsHalved, j = numSectionsRowsColsHalved;
		while(true){
				if(sections[i][j].contains(t.x, t.y)){
					world.get(i).get(j).get(0).push(t);
					return;
				} 
				else try{ if(t.x < sections[i][j].x) i--;
				  	 else if(t.x > sections[i][j].x && !(t.x < sections[i][j].x + SECTION_SIZE_WIDTH_HEIGHT)) i++;
				  	 if(t.y < sections[i][j].y) j--;
				  	 else if(t.y > sections[i][j].y && !(t.y < sections[i][j].y + SECTION_SIZE_WIDTH_HEIGHT)) j++;
				} catch (IndexOutOfBoundsException e){ i = j = Utils.rand.nextInt(numSectionsRowsCols); }
		}
	}
	
	/* Gets the Current section that the coord is in, (only the single section) */
	public static final Stack<Tile> currentTileSection(float x, float y){
			for(int i = 0; i < sections.length; i++)
				for(int j = 0; j < sections[i].length; j++)
					if(sections[i][j].contains(x, y))
							return world.get(i).get(j).get(TILE_LIST);
			
			return world.get(0).get(0).get(TILE_LIST);
	}
	
	private static final int TILE_LIST = 0; /* add more if wanting to add different ones later, but for now just tile is good */

	private static void tickTiles(){
		Tile.runAnimations();
		try{
		outerLoop:
			for(int i = 0; i < sections.length; i++)
				for(int j = 0; j < sections[i].length; j++)
					if(sections[i][j].contains(Player.center.x, Player.center.y)){
						try{ AREA_RECT.x = sections[i-1][j-1].x; AREA_RECT.y = sections[i-1][j-1].y; /* try left and above */
						} catch (IndexOutOfBoundsException e1){
								try { AREA_RECT.x = sections[i-1][j].x; AREA_RECT.y = sections[i-1][j].y; /* catch, try just to the left */
							} catch (IndexOutOfBoundsException e2){
								try { AREA_RECT.x = sections[i][j-1].x; AREA_RECT.y = sections[i][j-1].y; /* catch, try just above */
								} catch (IndexOutOfBoundsException e3){ /* catch, must be in the top left corner, same coords */
									try{ AREA_RECT.x = sections[i][j].x; AREA_RECT.y = sections[i][j].y;
									} catch (IndexOutOfBoundsException e4){ e4.printStackTrace(); System.exit(0); } /* catch, all hope is gone */
									
								}
							}
						}
							try { for(Tile tile : world.get(i-1).get(j-1).get(0)) tile.tick(); } catch (IndexOutOfBoundsException e) {}
							try { for(Tile tile : world.get(i).get(j-1).get(0))   tile.tick(); } catch (IndexOutOfBoundsException e) {}
							try { for(Tile tile : world.get(i+1).get(j-1).get(0)) tile.tick(); } catch (IndexOutOfBoundsException e) {}
							try { for(Tile tile : world.get(i-1).get(j).get(0))   tile.tick(); } catch (IndexOutOfBoundsException e) {}
							try { for(Tile tile : world.get(i).get(j).get(0))     tile.tick(); } catch (IndexOutOfBoundsException e) {}
							try { for(Tile tile : world.get(i+1).get(j).get(0))   tile.tick(); } catch (IndexOutOfBoundsException e) {}
							try { for(Tile tile : world.get(i-1).get(j+1).get(0)) tile.tick(); } catch (IndexOutOfBoundsException e) {}
							try { for(Tile tile : world.get(i).get(j+1).get(0))   tile.tick(); } catch (IndexOutOfBoundsException e) {}
							try { for(Tile tile : world.get(i+1).get(j+1).get(0)) tile.tick(); } catch (IndexOutOfBoundsException e) {}
						break outerLoop;
				}} catch (Exception e) { e.printStackTrace(); }
	}
	
	private static void renderTiles(Graphics2D g2d){
		try{
		outerLoop:
			for(int i = 0; i < sections.length; i++)
				for(int j = 0; j < sections[i].length; j++)
					if(sections[i][j].contains(Player.center.x, Player.center.y)){
						try{ AREA_RECT.x = sections[i-1][j-1].x; AREA_RECT.y = sections[i-1][j-1].y; /* try left and above */
						} catch (IndexOutOfBoundsException e1){
								try { AREA_RECT.x = sections[i-1][j].x; AREA_RECT.y = sections[i-1][j].y; /* catch, try just to the left */
							} catch (IndexOutOfBoundsException e2){
								try { AREA_RECT.x = sections[i][j-1].x; AREA_RECT.y = sections[i][j-1].y; /* catch, try just above */
								} catch (IndexOutOfBoundsException e3){ /* catch, must be in the top left corner, same coords */
									try{ AREA_RECT.x = sections[i][j].x; AREA_RECT.y = sections[i][j].y;
									} catch (IndexOutOfBoundsException e4){ e4.printStackTrace(); System.exit(0); } /* catch, all hope is gone */
									
								}
							}
						}
						
						for(int count = 0; count < NUM_TYPES_OF_HASHSETS; count++){ /* render the 3x3 grid of tile hashsets */
							try { for(Tile tile : world.get(i-1).get(j-1).get(count)) tile.render(g2d); } catch (IndexOutOfBoundsException e) {}
							try { for(Tile tile : world.get(i).get(j-1).get(count))   tile.render(g2d); } catch (IndexOutOfBoundsException e) {}
							try { for(Tile tile : world.get(i+1).get(j-1).get(count)) tile.render(g2d); } catch (IndexOutOfBoundsException e) {}
							try { for(Tile tile : world.get(i-1).get(j).get(count))   tile.render(g2d); } catch (IndexOutOfBoundsException e) {}
							try { for(Tile tile : world.get(i).get(j).get(count))     tile.render(g2d); } catch (IndexOutOfBoundsException e) {}
							try { for(Tile tile : world.get(i+1).get(j).get(count))   tile.render(g2d); } catch (IndexOutOfBoundsException e) {}
							try { for(Tile tile : world.get(i-1).get(j+1).get(count)) tile.render(g2d); } catch (IndexOutOfBoundsException e) {}
							try { for(Tile tile : world.get(i).get(j+1).get(count))   tile.render(g2d); } catch (IndexOutOfBoundsException e) {}
							try { for(Tile tile : world.get(i+1).get(j+1).get(count)) tile.render(g2d); } catch (IndexOutOfBoundsException e) {}
						}
						
						break outerLoop;
					}} catch (Exception e) { e.printStackTrace(); }
	}
	
	public static final synchronized void cleanUp() {
		try { mobs.clear(); structures.clear(); world.clear(); } catch (NullPointerException npe) { }
		world = null; sections = null; mobs = null; structures = null;
	}

}
