package StevenDimDoors.mod_pocketDim.saving;

import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import net.minecraftforge.common.DimensionManager;
import StevenDimDoors.mod_pocketDim.Point3D;
import StevenDimDoors.mod_pocketDim.core.DimLink;
import StevenDimDoors.mod_pocketDim.core.LinkTypes;
import StevenDimDoors.mod_pocketDim.core.NewDimData;
import StevenDimDoors.mod_pocketDim.core.PocketManager;
import StevenDimDoors.mod_pocketDim.dungeon.DungeonData;
import StevenDimDoors.mod_pocketDim.dungeon.pack.DungeonPack;
import StevenDimDoors.mod_pocketDim.dungeon.pack.DungeonType;
import StevenDimDoors.mod_pocketDim.helpers.DungeonHelper;
import StevenDimDoors.mod_pocketDim.util.ConfigurationProcessingException;
import StevenDimDoors.mod_pocketDim.util.FileFilters;
import StevenDimDoors.mod_pocketDim.util.Point4D;

import com.google.common.io.Files;

public class DDSaveHandler
{
	public static boolean loadAll()
	{
		// SenseiKiwi: Loading up our save data is not as simple as just reading files.
		// To properly restore dimensions, we need to make sure we always load
		// a dimension's parent and root before trying to load it. We'll use
		// topological sorting to determine the order in which to recreate the
		// dimension objects such that we respect those dependencies.
		// Links must be loaded after instantiating all the dimensions and must
		// be checked against our dimension blacklist.
		
		// Don't surround this code with try-catch. Our mod should crash if an error
		// occurs at this level, since it could lead to some nasty problems.
		
		String basePath = DimensionManager.getCurrentSaveRootDirectory() + "/DimensionalDoors/data/";
		File dataDirectory = new File(basePath);
		
		// Check if the folder exists. If it doesn't, just return.
		if (!dataDirectory.exists())
		{
			return true;
		}
		
		// Load the dimension blacklist
		File blacklistFile = new File(basePath+"blacklist.txt");
		
		if(blacklistFile.exists())
		{
			BlacklistProcessor blacklistReader = new BlacklistProcessor();
			List<Integer> blacklist = readBlacklist(blacklistFile,blacklistReader);
			PocketManager.createAndRegisterBlacklist(blacklist);
		}
		
		// List any dimension data files and read each dimension
		DimDataProcessor reader = new DimDataProcessor();
		List<PackedDimData> packedDims = new ArrayList<PackedDimData>();
		FileFilter dataFileFilter = new FileFilters.RegexFileFilter("dim_-?\\d+\\.txt");
		
		File[] dataFiles = dataDirectory.listFiles(dataFileFilter);
		for (File dataFile : dataFiles)
		{
			PackedDimData packedDim = readDimension(dataFile, reader);
			packedDims.add(packedDim);
		}
		
		List<PackedLinkData> linksToUnpack = new ArrayList<PackedLinkData>();
		//get the grand list of all links to unpack
		for(PackedDimData packedDim : packedDims)
		{
			linksToUnpack.addAll(packedDim.Links);
		}

		
		return unpackDimData(packedDims)&&unpackLinkData(linksToUnpack);
	}
	
	/**
	 * Takes a list of packedDimData and rebuilds the DimData for it
	 * @param packedDims
	 * @return
	 */
	public static boolean unpackDimData(List<PackedDimData> packedDims)
	{
		List<PackedDimData> unpackedDims = new ArrayList<PackedDimData>();
		
		
		//Load roots
		for(PackedDimData packedDim : packedDims)
		{
			if(packedDim.ParentID==packedDim.ID)
			{
				PocketManager.registerPackedDimData(packedDim);
				unpackedDims.add(packedDim);
			}
		}
		packedDims.removeAll(unpackedDims);
		unpackedDims.clear();
		
		//Load the pockets
		while(!packedDims.isEmpty())
		{
			for(PackedDimData packedDim : packedDims)
			{
				if(PocketManager.isRegisteredInternally(packedDim.ParentID))
				{
					PocketManager.registerPackedDimData(packedDim);
					unpackedDims.add(packedDim);
				}
				else
				{
					//break here gracefully
				}
			}
			packedDims.removeAll(unpackedDims);
			unpackedDims.clear();
			
		}
		return true;
	}
	
	public static boolean unpackLinkData(List<PackedLinkData> linksToUnpack)
	{
		Point3D fakePoint = new Point3D(-1,-1,-1);
		List<PackedLinkData> unpackedLinks = new ArrayList<PackedLinkData>();
		/**
		 * sort through the list, unpacking links that do not have parents. 
		 */
		//TODO- what we have a loop of links?
		for(PackedLinkData packedLink : linksToUnpack)
		{
			if(packedLink.parent.equals(fakePoint))
			{
				NewDimData data = PocketManager.getDimensionData(packedLink.source.getDimension());
				int linkType = packedLink.tail.linkType;
				
				if((linkType < LinkTypes.ENUM_MIN || linkType > LinkTypes.ENUM_MAX) && linkType != LinkTypes.CLIENT_SIDE)
				{
					linkType = LinkTypes.NORMAL;
				}
				
				@SuppressWarnings("deprecation")
				DimLink link = data.createLink(packedLink.source, linkType, packedLink.orientation);
				Point4D destination = packedLink.tail.destination;
				if(destination!=null)
				{
					PocketManager.getDimensionData(destination.getDimension()).setDestination(link, destination.getX(),destination.getY(),destination.getZ());
				}
				unpackedLinks.add(packedLink);
			}
		}
		linksToUnpack.removeAll(unpackedLinks);
		
		//unpack remaining children
		while(!linksToUnpack.isEmpty())
		{
			for(PackedLinkData packedLink : linksToUnpack)
			{
				NewDimData data = PocketManager.getDimensionData(packedLink.source.getDimension());
				if(data.getLink(packedLink.parent)!=null)
				{
					data.createChildLink(packedLink.source.getX(), packedLink.source.getY(), packedLink.source.getZ(), data.getLink(packedLink.parent));
				}
				unpackedLinks.add(packedLink);
			}
			linksToUnpack.removeAll(unpackedLinks);
		}
		return true;
	}
	

	private static PackedDimData readDimension(File dataFile, DimDataProcessor reader)
	{
		try
		{
			return reader.readFromFile(dataFile);
		}
		catch (Exception e)
		{
			System.err.println("Could not read dimension data from: " + dataFile.getAbsolutePath());
			System.err.println("The following error occurred:");
			printException(e, false);
			return null;
		}
	}
	
	public static boolean saveAll(Iterable<? extends IPackable<PackedDimData>> dimensions, List<Integer> blacklist) throws IOException
	{
		// Create the data directory for our dimensions
		// Don't catch exceptions here. If we can't create this folder,
		// the mod should crash to let the user know early on.

		String basePath = DimensionManager.getCurrentSaveRootDirectory() + "/DimensionalDoors/data/";
		File basePathFile = new File(basePath);
		Files.createParentDirs(basePathFile);
		basePathFile.mkdir();
				
		BlacklistProcessor blacklistReader = new BlacklistProcessor();
		writeBlacklist(blacklist, blacklistReader,basePath);		
		
		FileFilter dataFileFilter = new FileFilters.RegexFileFilter("dim_-?\\d+\\.txt");
		
		//TODO Deal with temp files correctly
		File[] dataFiles = basePathFile.listFiles(dataFileFilter);
		for (File dataFile : dataFiles)
		{
			dataFile.delete();
		}
		
		
		basePathFile = null;
		basePath += "dim_";
		
		boolean succeeded = true;
		DimDataProcessor writer = new DimDataProcessor();
		for (IPackable<PackedDimData> dimension : dimensions)
		{
			succeeded &= writeDimension(dimension, writer, basePath);
		}
		return succeeded;
	}
	
	private static boolean writeBlacklist(List<Integer> blacklist, BlacklistProcessor writer, String basePath)
	{
		try
		{
			File tempFile = new File(basePath + "blacklist.tmp");
			File saveFile = new File(basePath + "blacklist.txt");
			writer.writeToFile(tempFile, blacklist);
			saveFile.delete();
			tempFile.renameTo(saveFile);
			return true;
		}
		catch (Exception e)
		{
			System.err.println("Could not save blacklist. The following error occurred:");
			printException(e, true);
			return false;
		}
		
	}
	private static boolean writeDimension(IPackable<PackedDimData> dimension, DimDataProcessor writer, String basePath)
	{
		try
		{
			File tempFile = new File(basePath + (dimension.name() + ".tmp"));
			File saveFile = new File(basePath + (dimension.name() + ".txt"));
			writer.writeToFile(tempFile, dimension.pack());
			saveFile.delete();
			tempFile.renameTo(saveFile);
			return true;
		}
		catch (Exception e)
		{
			System.err.println("Could not save data for dimension #" + dimension.name() + ". The following error occurred:");
			printException(e, true);
			return false;
		}
	}
	
	private static void printException(Exception e, boolean verbose)
	{
		if (e.getCause() == null)
		{
			if (verbose)
			{
				e.printStackTrace();
			}
			else
			{
				System.err.println(e.getMessage());
			}
		}
		else
		{
			System.out.println(e.getMessage());
			System.err.println("Caused by an underlying error:");
			if (verbose)
			{
				e.getCause().printStackTrace();
			}
			else
			{
				System.err.println(e.getCause().getMessage());
			}
		}
	}

	//TODO - make this more robust
	public static DungeonData unpackDungeonData(PackedDungeonData packedDungeon)
	{	
		for(DungeonData data  : DungeonHelper.instance().getRegisteredDungeons())
		{
			if(data.schematicName().equals(packedDungeon.SchematicName))
			{
				return data;
			}
		}
		return null;
	}

	public static List<Integer> readBlacklist(File blacklistFile, BlacklistProcessor reader)
	{
	
		try
		{
			return reader.readFromFile(blacklistFile);
		}
		catch (Exception e)
		{
			e.printStackTrace();
			return null;
		}
		
	}
}
