package worldGen;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashSet;
import java.util.LinkedList;

import interfaces.Biomes;
import utils.Stack;
import utils.ThreadPool;
import utils.Utils;
 
/*  ------------- Nihilne ------------
    * Created by Mitch Weaver - 2016 *
      ~~~~ All Rights Reserved ~~~~ */

// MAP GENERATOR:
// http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation/demo.html

final class MapCreator implements Serializable, Runnable, Biomes {
	private static final long serialVersionUID = 1337;
	
	private static volatile LinkedList<byte[]> bytes_list;
	private static volatile Stack<String> filenames;
	private static volatile int count, currentThreadsRunning;
	private static float stackSize;
	
		public static final void main(String[] args) {
			bytes_list = new LinkedList<byte[]>(); filenames = new Stack<String>();
			final int numProcessors = ThreadPool.getNumProcessors();
			final ThreadPool pool = new ThreadPool(numProcessors);
			final MapCreator task = new MapCreator();
			final Integer NUMBER_OF_MAPS_PLUS_ONE = 21;
			
			// STEP 1: FILL STACK WITH MAP FILE PATHS
			for(int i = 1; i < NUMBER_OF_MAPS_PLUS_ONE; i++)
				filenames.add("/map" + i + ".png"); 
			
			stackSize = filenames.size();
				
			// STEP 2: RUN THREADS TO COMPUTE MAPS
			while(count < stackSize){
				
				while (currentThreadsRunning < numProcessors && count < stackSize){
					pool.runTask(task);
					currentThreadsRunning++;
				}
				
				try { Thread.sleep(1000); } catch (InterruptedException e) { }
			}
			
			// STEP 3: WHEN DONE, SAVE THE LIST OF BYTE[]'s
			save(bytes_list);
			System.err.println("ALL MAPS GENERATED!");
			bytes_list.clear(); bytes_list = null; filenames = null; System.exit(0);
		}
	
		public void run() { 
			if(filenames.isEmpty()) return;
			bytes_list.add(readMap(Utils.loadImage(filenames.pop()))); 
		}
		
		private static final byte[] readMap(BufferedImage image) {
			try {	byte[] bytes = new byte[image.getWidth() * image.getHeight()];
					final HashSet<Integer> needsReview = new HashSet<Integer>();
					final int nextRow = image.getWidth();
					
					READ_IN_BYTES(needsReview, image, bytes);
					
					bytes = REVIEW_FUZZY_PIXELS(needsReview, bytes, nextRow);
					bytes = FIX_RANDOM_ISLANDS_OF_LAND(nextRow, bytes);
					bytes = REMOVE_RIVERS_FROM_COASTLINE(bytes, nextRow);
					bytes = REMOVE_RIVERS_FROM_LAKE(bytes, nextRow);
					bytes = REMOVE_OCEAN_FROM_RIVERS(bytes, nextRow);
					
					needsReview.clear();
					printProgress();
					return bytes;
			} catch (Exception e) { e.printStackTrace(); return null; }
		}
		
		private synchronized static final void printProgress(){ 
			System.err.println("---  PROGRESS: " + (int)((++count / stackSize)*100) + "%"); 
			currentThreadsRunning--;
		}
		
		private static final byte[] REMOVE_OCEAN_FROM_RIVERS(final byte[] bytes, final int nextRow) {
			for(int ITERATIONS = 0; ITERATIONS < 10; ITERATIONS++) {
				boolean all_clear = true;
				for(int i = 0; i < bytes.length; i++) {
					if(bytes[i] == RIVER){
						try{   if(bytes[i + 1] == OCEAN) 		   			  { bytes[i + 1] = RIVER; all_clear = false; }
							   if(bytes[i - 1] == OCEAN) 		   			  { bytes[i - 1] = RIVER; all_clear = false; }
							   if(bytes[i + nextRow] == OCEAN)          { bytes[i + nextRow] = RIVER; all_clear = false; }
						 	   if(bytes[i - nextRow] == OCEAN)          { bytes[i - nextRow] = RIVER; all_clear = false; }
						 	   if(bytes[i + nextRow + 1] == OCEAN) 	{ bytes[i + nextRow + 1] = RIVER; all_clear = false; }
						 	   if(bytes[i + nextRow - 1] == OCEAN) 	{ bytes[i + nextRow - 1] = RIVER; all_clear = false; }
						 	   if(bytes[i - nextRow + 1] == OCEAN) 	{ bytes[i - nextRow + 1] = RIVER; all_clear = false; }
						 	   if(bytes[i - nextRow - 1] == OCEAN)	{ bytes[i - nextRow - 1] = RIVER; all_clear = false; }
							} catch (ArrayIndexOutOfBoundsException e) { continue; }
					}
				}
				if(all_clear) break;
			}
			return bytes;
		}
		
		private static final byte[] REMOVE_RIVERS_FROM_LAKE(final byte[] bytes, final int nextRow){
			final byte TEMP_BIOME = -120;
			for(int ITERATIONS = 0; ITERATIONS < 10; ITERATIONS++) {
				for(int i = 0; i < bytes.length; i++){ /* maps creates river around coastline, change them to ocean if by coastline */
					if(bytes[i] == RIVER){
						try{
								if(bytes[i + 1] == LAKE || bytes[i + 1] == TEMP_BIOME) 		               { bytes[i] = TEMP_BIOME; }
							    if(bytes[i - 1] == LAKE || bytes[i - 1] == TEMP_BIOME) 		               { bytes[i] = TEMP_BIOME; }
							    if(bytes[i + nextRow] == LAKE || bytes[i + nextRow] == TEMP_BIOME)         { bytes[i] = TEMP_BIOME; }
						 	    if(bytes[i - nextRow] == LAKE || bytes[i - nextRow] == TEMP_BIOME)         { bytes[i] = TEMP_BIOME; }
						 	    if(bytes[i + nextRow + 1] == LAKE || bytes[i + nextRow + 1] == TEMP_BIOME) { bytes[i] = TEMP_BIOME; }
						 	    if(bytes[i + nextRow - 1] == LAKE || bytes[i + nextRow - 1] == TEMP_BIOME) { bytes[i] = TEMP_BIOME; }
						 	    if(bytes[i - nextRow + 1] == LAKE || bytes[i - nextRow + 1] == TEMP_BIOME) { bytes[i] = TEMP_BIOME; }
						 	    if(bytes[i - nextRow - 1] == LAKE || bytes[i - nextRow - 1] == TEMP_BIOME) { bytes[i] = TEMP_BIOME; }
								
						} catch (ArrayIndexOutOfBoundsException e) {}
					}
				}
			}
			
			// replace the temp biome with lake
			for(int i = 0; i < bytes.length; i++) if(bytes[i] == TEMP_BIOME) bytes[i] = LAKE;
			
			return bytes;
		}
		
		private static final byte[] REMOVE_RIVERS_FROM_COASTLINE(final byte[] bytes, final int nextRow){
			for(int i = 0; i < bytes.length; i++){ /* maps creates river around coastline, change them to ocean if by coastline */
				if(bytes[i] == LAKE || bytes[i] == RIVER){
					try{
							if(bytes[i + 1] == COASTLINE) bytes[i] = OCEAN;
					   else if(bytes[i - 1] == COASTLINE) bytes[i] = OCEAN;
					   else if(bytes[i + nextRow] == COASTLINE) bytes[i] = OCEAN;
				 	   else if(bytes[i - nextRow] == COASTLINE) bytes[i] = OCEAN;
				 	   else if(bytes[i + nextRow + 1] == COASTLINE) bytes[i] = OCEAN;
				 	   else if(bytes[i + nextRow - 1] == COASTLINE) bytes[i] = OCEAN;
				 	   else if(bytes[i - nextRow + 1] == COASTLINE) bytes[i] = OCEAN;
				 	   else if(bytes[i - nextRow - 1] == COASTLINE) bytes[i] = OCEAN;
							
					} catch (ArrayIndexOutOfBoundsException e) {}
				}
			}
				return bytes;
		}
		
		private static final byte[] REVIEW_FUZZY_PIXELS(final HashSet<Integer> needsReview, final byte[] bytes, final int nextRow){
			// IF ANY TILES AROUND THE REVIEWING TILE ARE NOT WATER, MAKE IT THAT TILES BIOME
			// NOTE THESE ARE INDEXES, NOT BIOMES
			for(int i : needsReview) {
				
					int offset = 1;
					while(bytes[i] == OCEAN){
						 try{       if(bytes[i + offset] != OCEAN && bytes[i + offset] != RIVER && bytes[i + offset] != LAKE) bytes[i] = bytes[i + offset];
							   else if(bytes[i - offset] != OCEAN && bytes[i - offset] != RIVER && bytes[i - offset] != LAKE) bytes[i] = bytes[i - offset];
							   else if(bytes[i + nextRow] != OCEAN && bytes[i + nextRow] != RIVER && bytes[i + nextRow] != LAKE) bytes[i] = bytes[i + nextRow];
						 	   else if(bytes[i - nextRow] != OCEAN && bytes[i - nextRow] != RIVER && bytes[i - nextRow] != LAKE) bytes[i] = bytes[i - nextRow];
						 	   else if(bytes[i + nextRow + offset] != OCEAN && bytes[i + nextRow + offset] != RIVER && bytes[i + nextRow + offset] != LAKE) bytes[i] = bytes[i + nextRow + offset];
						 	   else if(bytes[i + nextRow - offset] != OCEAN && bytes[i - nextRow - offset] != RIVER && bytes[i + nextRow - offset] != LAKE) bytes[i] = bytes[i + nextRow - offset];
						 	   else if(bytes[i - nextRow + offset] != OCEAN && bytes[i + nextRow + offset] != RIVER && bytes[i - nextRow + offset] != LAKE) bytes[i] = bytes[i - nextRow + offset];
						 	   else if(bytes[i - nextRow - offset] != OCEAN && bytes[i - nextRow - offset] != RIVER && bytes[i - nextRow - offset] != LAKE) bytes[i] = bytes[i - nextRow - offset];
							   offset++;
						 } catch(ArrayIndexOutOfBoundsException e){ bytes[i] = OCEAN; System.err.println("PROBLEM!"); System.exit(0); }
					 }
			}
			return bytes;
		}
		
		private final static byte[] FIX_RANDOM_ISLANDS_OF_LAND(final int nextRow, final byte[] bytes){
			// TO FIX RANDOM STRAY ISLAND TILES OUT IN WATER, DO THIS LOOP
			// CONTINUE DOING THIS LOOP UNTIL THE CONDITION IS FALSE
			boolean ALL_CLEAR = false;
			while(!ALL_CLEAR) {
				
				boolean NO_TILES_RESET = true;
				int TILES_LEFT = 0;
			
				for(int i = 0; i < bytes.length; i++){
					try {
					// DONT NEED TO RUN THE TILE IF IT IS WATER OR COASTLINE, OBV
					if(bytes[i] == LAKE || bytes[i] == RIVER || bytes[i] == OCEAN || bytes[i] == COASTLINE) continue;
					
					// GO THROUGH ALL THE SURROUNDING TILES
					// IF ANY OF THE SURROUNDING TILES ARE WATER, INCREMENT COUNT
					int count = 0;
					
					final Stack<Byte> temp = new Stack<Byte>();
					
					if(bytes[i + 1] == OCEAN || bytes[i + 1] == RIVER || bytes[i + 1] == LAKE) { count++; temp.add(bytes[i + 1]); }
					if(bytes[i - 1] == OCEAN || bytes[i - 1] == RIVER || bytes[i - 1] == LAKE) { count++; temp.add(bytes[i - 1]); } 
					if(bytes[i + nextRow] == OCEAN || bytes[i + nextRow] == RIVER || bytes[i + nextRow] == LAKE) { count++; temp.add(bytes[i + nextRow]); } 
					if(bytes[i - nextRow] == OCEAN || bytes[i - nextRow] == RIVER || bytes[i - nextRow] == LAKE) { count++; temp.add(bytes[i - nextRow]); } 
					if(bytes[i + nextRow + 1] == OCEAN || bytes[i + nextRow + 1] == RIVER || bytes[i + nextRow + 1] == LAKE) { count++; temp.add(bytes[i + nextRow + 1]); } 
					if(bytes[i + nextRow - 1] == OCEAN || bytes[i + nextRow - 1] == RIVER || bytes[i + nextRow - 1] == LAKE) { count++; temp.add(bytes[i + nextRow - 1]); }  
			    	if(bytes[i - nextRow + 1] == OCEAN || bytes[i - nextRow + 1] == RIVER || bytes[i - nextRow + 1] == LAKE) { count++; temp.add(bytes[i - nextRow + 1]); } 					    	
			    	if(bytes[i - nextRow - 1] == OCEAN || bytes[i - nextRow - 1] == RIVER || bytes[i - nextRow - 1] == LAKE) { count++; temp.add(bytes[i - nextRow - 1]); } 
							
					// IF COUNT == 8, THEN ALL SURROUNDING TILES MUST BE WATER!
					// IF COUNT >= ~5, IT MUST BE A SHITTY PENINSULA, SCRAP IT --- (note change 5 to desire)
					// SET THIS ISLAND TILE TO ONE OF ITS WATER NEIGHBOURS
					
			    	if(count >= 6){
						NO_TILES_RESET = false;
						bytes[i] = temp.get(Utils.rand.nextInt(temp.size()));
						TILES_LEFT++;
//						bytes[i] = SNOW_TUNDA;
					}
					
					temp.clear();
					} catch (Exception e) { System.out.println("PROBLEM!"); System.exit(0); }
				}
				System.out.println("tiles left " + TILES_LEFT);
				if(NO_TILES_RESET) ALL_CLEAR = true;
			}
			
			return bytes;
		}
		
		private static final byte[] READ_IN_BYTES(HashSet<Integer> needsReview, BufferedImage image, byte[] bytes){
			int pixel = 0, red = 0, green = 0, blue = 0, index = 0;
			for (int i = 0; i < image.getWidth(); i++)
				for (int j = 0; j < image.getHeight(); j++, index++) {
					pixel = image.getRGB(i, j); red = Utils.convertRed(pixel); green = Utils.convertGreen(pixel); blue = Utils.convertBlue(pixel);
					
						 if(red == 68  && green == 68  && blue == 122) bytes[index] = OCEAN;
					else if(red == 160 && green == 144 && blue == 119) bytes[index] = COASTLINE;
					else if(red == 210 && green == 185 && blue == 139) bytes[index] = BEACH;
					else if(red == 136 && green == 170 && blue == 85)  bytes[index] = GRASS1;
					else if(red == 85  && green == 153 && blue == 68)  bytes[index] = GRASS2;
					else if(red == 103 && green == 148 && blue == 89)  bytes[index] = GRASS3;
					else if(red == 63  && green == 136 && blue == 85)  bytes[index] = GRASS4;
					else if(red == 51  && green == 119 && blue == 85)  bytes[index] = FOREST;
					else if(red == 201 && green == 210 && blue == 155) bytes[index] = DESERT;
					else if(red == 153 && green == 170 && blue == 119) bytes[index] = TAIGA;
					else if(red == 136 && green == 153 && blue == 119) bytes[index] = BARREN_ROCK;
					else if(red == 255 && green == 255 && blue == 255) bytes[index] = SNOW_TUNDA;
					else if(red == 136 && green == 136 && blue == 136) bytes[index] = ASH;
					else if(red == 85  && green == 85  && blue == 85)  bytes[index] = VOLCANIC;
					else if(red == 34  && green == 85  && blue == 136) bytes[index] = RIVER;
					else if(red == 51  && green == 102 && blue == 153) bytes[index] = LAKE;
					else if(red == 187 && green == 187 && blue == 170) bytes[index] = BACK_OF_VOLCANO;
					else needsReview.add(index); // for the fuzzy pixels
						
					/* if not one of the above RGB's, set it to one of its neighbors (avoids the big holes caused by the image's compression */
				}
			return bytes;
		}
		
		private static final synchronized void save(final Object obj){
			try { final File file = new File("res/data/mapsdata.nihil");
				  final ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
				  oos.writeObject(obj); oos.close();
			} catch (Exception e) { e.printStackTrace(); System.exit(1); }
		}
	}
