/*
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package com.l2jserver.gameserver;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPInputStream;

import javax.imageio.ImageIO;

import com.l2jserver.Config;
import com.l2jserver.gameserver.model.L2CharPosition;


public class Universe implements java.io.Serializable
{
	
	/**
	 * Comment for <code>serialVersionUID</code>
	 */
	private static final long serialVersionUID = -2040223695811104704L;
	public static final int MIN_X = -127900;
	public static final int MAX_X = 194327;
	public static final int MIN_Y = -30000;
	public static final int MAX_Y = 259536;
	public static final int MIN_Z = -17000;
	public static final int MAX_Z = 17000;
	public static final int MIN_X_GRID = 60;
	public static final int MIN_Y_GRID = 60;
	public static final int MIN_Z_GRID = 60;
	public static final int MIN_GRID = 360;
	protected static final Logger _log = Logger.getLogger(Universe.class.getName());
	
	protected List<Coord> _coordList;
	
	private HashSet<Integer> _logPlayers;
	private boolean _logAll = true;
	
	public static void main(String[] args)
	{
		Universe u = new Universe();
		u.load();
		//u.removeDoubles();
		u.implode(false);
	}
	
	@SuppressWarnings("unused")
	private class Position implements Comparable<Position>, java.io.Serializable
	{
		/**
		 * Comment for <code>serialVersionUID</code>
		 */
		private static final long serialVersionUID = -8798746764450022287L;
		protected int _x;
		protected int _flag;
		protected int _y;
		protected int _z;
		
		public Position(int x, int y, int z, int flag)
		{
			_x = x;
			_y = y;
			_z = z;
			_flag = flag;
		}
		
		public Position(L2CharPosition pos)
		{
			_x = pos.x;
			_y = pos.y;
			_z = pos.z;
			_flag = 0;
		}
		
		@Deprecated
		public L2CharPosition l2CP()
		{
			return new L2CharPosition(_x, _y, _z, 0);
		}
		
		public int compareTo(Position obj)
		{
			int res = Integer.valueOf(_x).compareTo(obj._x);
			if (res != 0)
				return res;
			res = Integer.valueOf(_y).compareTo(obj._y);
			if (res != 0)
				return res;
			res = Integer.valueOf(_z).compareTo(obj._z);
			return res;
		}
		
		@Override
		public String toString()
		{
			return String.valueOf(_x) + " " + _y + " " + _z + " " + _flag;
		}
	}
	
	@SuppressWarnings("unused")
	private class Coord implements Comparable<Position>, java.io.Serializable
	{
		/**
		 * Comment for <code>serialVersionUID</code>
		 */
		private static final long serialVersionUID = -558060332886829552L;
		protected int _x;
		protected int _y;
		protected int _z;
		
		public Coord(int x, int y, int z)
		{
			_x = x;
			_y = y;
			_z = z;
		}
		
		public Coord(L2CharPosition pos)
		{
			_x = pos.x;
			_y = pos.y;
			_z = pos.z;
		}
		
		public int compareTo(Position obj)
		{
			int res = Integer.valueOf(_x).compareTo(obj._x);
			if (res != 0)
				return res;
			res = Integer.valueOf(_y).compareTo(obj._y);
			if (res != 0)
				return res;
			res = Integer.valueOf(_z).compareTo(obj._z);
			return res;
		}
		
		@Override
		public String toString()
		{
			return String.valueOf(_x) + " " + _y + " " + _z;
		}
	}
	
	public static Universe getInstance()
	{
		return SingletonHolder._instance;
	}
	
	private Universe()
	{
		_coordList = new LinkedList<Coord>();
		_logPlayers = new HashSet<Integer>();
		
		ThreadPoolManager.getInstance().scheduleGeneralAtFixedRate(new UniverseDump(), 30000, 30000);
	}
	
	public void registerHeight(int x, int y, int z)
	{
		// don't overwrite obstacle entries
		//Position p  = new Position(x, y, z, 0);
		//_map.add(p);
		_coordList.add(new Coord(x, y, z));
	}
	
	public void registerObstacle(int x, int y, int z)
	{
		//Position p = new Position(x, y, z, -1);
		//_map.add(p);
		_coordList.add(new Coord(x, y, z));
	}
	
	public boolean shouldLog(Integer id)
	{
		return (_logPlayers.contains(id) || _logAll);
	}
	
	public void setLogAll(boolean flag)
	{
		_logAll = flag;
	}
	
	public void addLogPlayer(Integer id)
	{
		_logPlayers.add(id);
		_logAll = false;
	}
	
	public void removeLogPlayer(Integer id)
	{
		_logPlayers.remove(id);
	}
	
	public void loadAscii()
	{
		int initialSize = _coordList.size();
		BufferedReader r = null;
		try
		{
			r = new BufferedReader(new FileReader("data/universe.txt"));
			String line;
			while ((line = r.readLine()) != null)
			{
				StringTokenizer st = new StringTokenizer(line);
				String x1 = st.nextToken();
				String y1 = st.nextToken();
				String z1 = st.nextToken();
				//				String f1 = st.nextToken();
				int x = Integer.parseInt(x1);
				int y = Integer.parseInt(y1);
				int z = Integer.parseInt(z1);
				//				int f = Integer.parseInt(f1);
				_coordList.add(new Coord(x, y, z));
			}
			_log.info((_coordList.size() - initialSize) + " additional nodes loaded from text file.");
		}
		catch (Exception e)
		{
			_log.info("could not read text file universe.txt");
		}
		finally
		{
			try
			{
				r.close();
			}
			catch (Exception e)
			{
			}
		}
	}
	
	public void createMap()
	{
		int zoom = 100;
		int w = (MAX_X - MIN_X) / zoom;
		int h = (MAX_Y - MIN_Y) / zoom;
		BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_USHORT_GRAY);
		Graphics2D gr = bi.createGraphics();
		int min_z = 0, max_z = 0;
		for (Coord pos : _coordList)
		{
			if (pos == null)
				continue;
			
			if (pos._z < min_z)
				min_z = pos._z;
			if (pos._z > max_z)
				max_z = pos._z;
		}
		for (Coord pos : _coordList)
		{
			if (pos == null)
				continue;
			
			int x = (pos._x - MIN_X) / zoom;
			int y = (pos._y - MIN_Y) / zoom;
			int color = (int) (((long) pos._z - MIN_Z) * 0xFFFFFF / (MAX_Z - MIN_Z));
			gr.setColor(new Color(color));
			gr.drawLine(x, y, x, y);
		}
		try
		{
			ImageIO.write(bi, "png", new File("universe.png"));
		}
		catch (Exception e)
		{
			_log.log(Level.WARNING, "Cannot create universe.png: " + e.getMessage(), e);
		}
	}
	
	public class UniverseFilter implements FilenameFilter
	{
		String _ext = "";
		
		public UniverseFilter(String pExt)
		{
			_ext = pExt;
		}
		
		/* (non-Javadoc)
		 * @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
		 */
		public boolean accept(File arg0, String name)
		{
			return name.startsWith("universe") && name.endsWith("." + _ext);
		}
		
	}
	
	public void load()
	{
		int total = 0;
		if (_coordList == null)
		{
			_coordList = new LinkedList<Coord>();
		}
		try
		{
			loadBinFiles();
			
			loadHexFiles();
			
			loadFinFiles();
			
			_log.info(_coordList.size() + " map vertices loaded in total.");
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		_log.info("Total: " + total);
	}
	
	/**
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	private void loadFinFiles() throws FileNotFoundException, IOException
	{
		FilenameFilter filter = new UniverseFilter("fin");
		File directory = new File("data");
		File[] files = directory.listFiles(filter);
		for (File file : files)
		{
			FileInputStream fos = new FileInputStream(file); // Save to file
			DataInputStream data = new DataInputStream(fos);
			int count = data.readInt();
			List<Coord> newMap = new LinkedList<Coord>();
			for (int i = 0; i < count; i++)
			{
				newMap.add(new Coord(data.readInt(), data.readInt(), data.readInt()));
			}
			data.close(); // Close the stream.
			
			_log.info(newMap.size() + " map vertices loaded from file " + file.getName());
			
			_coordList.addAll(newMap);
		}
	}
	
	/**
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	private void loadHexFiles() throws FileNotFoundException, IOException
	{
		FilenameFilter filter = new UniverseFilter("hex");
		File directory = new File("data");
		File[] files = directory.listFiles(filter);
		for (File file : files)
		{
			FileInputStream fos = new FileInputStream(file); // Save to file
			GZIPInputStream gzos = new GZIPInputStream(fos);
			DataInputStream data = new DataInputStream(gzos);
			int count = data.readInt();
			List<Coord> newMap = new LinkedList<Coord>();
			for (int i = 0; i < count; i++)
			{
				newMap.add(new Coord(data.readInt(), data.readInt(), data.readInt()));
				data.readInt();
			}
			data.close(); // Close the stream.
			
			_log.info(newMap.size() + " map vertices loaded from file " + file.getName());
			
			_coordList.addAll(newMap);
		}
	}
	
	/**
	 * @throws FileNotFoundException
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	@SuppressWarnings(value = { "unchecked" })
	private void loadBinFiles() throws FileNotFoundException, IOException, ClassNotFoundException
	{
		FilenameFilter filter = new UniverseFilter("bin");
		File directory = new File("data");
		File[] files = directory.listFiles(filter);
		for (File file : files)
		{
			//Create necessary input streams
			FileInputStream fis = new FileInputStream(file); // Read from file
			GZIPInputStream gzis = new GZIPInputStream(fis); // Uncompress
			ObjectInputStream in = new ObjectInputStream(gzis); // Read objects
			// Read in an object. It should be a vector of scribbles
			
			TreeSet<Position> temp = (TreeSet<Position>) in.readObject();
			_log.info(temp.size() + " map vertices loaded from file " + file.getName());
			in.close(); // Close the stream.
			for (Position p : temp)
			{
				_coordList.add(new Coord(p._x, p._y, p._z));
			}
		}
	}
	
	public class UniverseDump implements Runnable
	{
		/* (non-Javadoc)
		 * @see java.lang.Runnable#run()
		 */
		public void run()
		{
			int size = _coordList.size();
			//_log.info("Univere Map has " + _map.size() + " nodes.");
			if (size > 100000)
			{
				flush();
			}
		}
	}
	
	public void flush()
	{
		//_log.info("Size of dump: "+coordList.size());
		List<Coord> oldMap = _coordList;
		_coordList = new LinkedList<Coord>();
		int size = oldMap.size();
		dump(oldMap, true);
		_log.info("Universe Map : Dumped " + size + " vertices.");
	}
	
	public int size()
	{
		int size = 0;
		if (_coordList != null)
			size = _coordList.size();
		return size;
	}
	
	public void dump(List<Coord> _map, boolean b)
	{
		FileOutputStream fos = null;
		DataOutputStream data = null;
		try
		{
			String pad = "";
			if (b)
				pad = "" + System.currentTimeMillis();
			fos = new FileOutputStream("data/universe" + pad + ".fin"); // Save to file
			data = new DataOutputStream(fos);
			int count = _map.size();
			//_log.info("Size of dump: "+count);
			data.writeInt(count);
			
			for (Coord p : _map)
			{
				if (p != null)
				{
					data.writeInt(p._x);
					data.writeInt(p._y);
					data.writeInt(p._z);
				}
			}
			_log.info("Universe Map saved to: " + "data/universe" + pad + ".fin");
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		finally
		{
			try
			{
				data.close();
			}
			catch (Exception e)
			{
			}
			
			try
			{
				fos.close();
			}
			catch (Exception e)
			{
			}
		}
	}
	
	// prepare for shutdown
	public void implode(boolean b)
	{
		createMap();
		dump(_coordList, b);
	}
	
	@SuppressWarnings("synthetic-access")
	private static class SingletonHolder
	{
		protected static final Universe _instance = Config.ACTIVATE_POSITION_RECORDER ? new Universe() : null;
	}
}
