package net.oni2.aeinstaller.backend.packages;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashSet;

import net.oni2.aeinstaller.backend.CaseInsensitiveFile;
import net.oni2.aeinstaller.backend.Paths;
import net.oni2.aeinstaller.backend.oni.management.tools.ToolInstallationList;
import net.oni2.moddepot.DepotConfig;
import net.oni2.moddepot.ECompatiblePlatform;
import net.oni2.moddepot.model.NodeMod;
import net.oni2.platformtools.PlatformInformation;
import net.oni2.platformtools.PlatformInformation.Platform;
import net.oni2.platformtools.applicationinvoker.EExeType;

import org.apache.commons.io.FileUtils;

/**
 * @author Christian Illy
 */
public class Package implements Comparable<Package> {
	private String name = "";
	private int packageNumber = 0;

	private HashSet<Type> types = new HashSet<Type>();
	private boolean tool = false;
	private ECompatiblePlatform platform = null;
	private String version = "";
	private String submitter = "";
	private String creator = "";
	private transient EBSLInstallType bslInstallType = EBSLInstallType.NORMAL;
	private String description = "";
	private transient double aeVersion = 0;
	private int zipSize = 0;

	private URI nodeUrl = null;
	private String fileUrl = null;

	private long createdMillis = -1;
	private long updatedMillis = -1;

	private transient File exeFile = null;
	private transient EExeType exeType = EExeType.OSBINARY;
	private transient File iconFile = null;
	private transient String workingDir = "Base";

	private transient HashSet<Integer> incompatibilities = new HashSet<Integer>();
	private transient HashSet<Integer> dependencies = new HashSet<Integer>();
	private transient HashSet<Integer> unlockLevel = new HashSet<Integer>();

	private transient long localTimestamp = 0;

	/**
	 * Create a new Package entry from a given Mod-Node
	 * 
	 * @param nm
	 *            Mod-Node
	 */
	public Package(NodeMod nm) {
		name = nm.getTitle();
		packageNumber = nm.getPackageNumber();
		platform = nm.getPlatform();
		tool = nm.isTool();
		for (String nType : nm.getTypes()) {
			Type t = PackageManager.getInstance().getTypeByName(nType);
			types.add(t);
			if (!tool && !isCorePackage() && isValidOnPlatform())
				t.addEntry(this);
		}

		createdMillis = nm.getCreated();
		updatedMillis = nm.getUploads().firstElement().getTimestamp();

		try {
			nodeUrl = new URI(nm.getPath());
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}

		fileUrl = nm.getUploads().firstElement().getUri_full();
		zipSize = nm.getUploads().firstElement().getFilesize();

		version = nm.getVersion();
		submitter = nm.getName();
		creator = nm.getCreator();
		if (nm.getBody() != null) {
			description = nm.getBody().getSafe_value();
			if (!description.toLowerCase().startsWith("<p>"))
				description = "<p>" + description + "</p>";
		}

		if (isLocalAvailable())
			updateLocalData();
	}

	private void clearLocalOnlyInfo() {
		aeVersion = 0;
		bslInstallType = EBSLInstallType.NORMAL;

		dependencies = new HashSet<Integer>();
		incompatibilities = new HashSet<Integer>();
		unlockLevel = new HashSet<Integer>();

		exeFile = null;
		workingDir = null;
		iconFile = null;
	}

	/**
	 * Update information for local package existence
	 */
	public void updateLocalData() {
		File config = CaseInsensitiveFile.getCaseInsensitiveFile(
				getLocalPath(), "Mod_Info.cfg");
		File aeicfg = new File(getLocalPath(), "aei.cfg");
		File plain = CaseInsensitiveFile.getCaseInsensitiveFile(getLocalPath(),
				"plain");
		if (config.exists()) {
			Mod_Info mi = new Mod_Info(config, packageNumber);

			aeVersion = mi.getAeVersion();
			bslInstallType = mi.getBslInstallType();
			if (isLocalOnly()) {
				name = mi.getName();
				creator = mi.getCreator();
				version = mi.getVersion();
				description = mi.getDescription();
			}

			dependencies = mi.getDependencies();
			incompatibilities = mi.getIncompatibilities();
			unlockLevel = mi.getUnlockLevel();

			exeFile = mi.getExeFile();
			exeType = mi.getExeType();
			workingDir = mi.getWorkingDir();
			iconFile = mi.getIconFile();
		} else {
			clearLocalOnlyInfo();
//			System.err.println("No config found for mod folder: "
//					+ getLocalPath().getPath());
		}
		if (aeicfg.exists()) {
			try {
				FileInputStream fstream = new FileInputStream(aeicfg);
				InputStreamReader isr = new InputStreamReader(fstream);
				BufferedReader br = new BufferedReader(isr);
				String strLine;
				while ((strLine = br.readLine()) != null) {
					if (strLine.indexOf("->") < 1)
						continue;
					if (strLine.indexOf("//") >= 0)
						strLine = strLine.substring(0, strLine.indexOf("//"));
					String[] split = strLine.split("->", 2);
					String sName = split[0].trim();
					String sVal = split[1].trim();
					if (sName.equalsIgnoreCase("Timestamp")) {
						localTimestamp = Long.parseLong(sVal);
					}
				}
				isr.close();
			} catch (FileNotFoundException e) {
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		if (isLocalOnly())
			tool = plain.exists();
	}

	/**
	 * Create a new Mod entry from the given local mod folder
	 * 
	 * @param folder
	 *            Mod folder with Mod_Info.cfg
	 */
	public Package(File folder) {
		packageNumber = Integer.parseInt(folder.getName().substring(0, 5));
		updateLocalData();

		platform = ECompatiblePlatform.BOTH;
	}

	/**
	 * @return has separate paths for win/mac/common or not
	 */
	public boolean hasSeparatePlatformDirs() {
		return aeVersion >= 2;
	}

	private String getSanitizedPathName() {
		return name.replaceAll("[^a-zA-Z0-9_.-]", "_");
	}

	/**
	 * @return Path to local mod folder
	 */
	public File getLocalPath() {
		final String folderStart = String.format("%05d", packageNumber);

		if (Paths.getModsPath().exists()) {
			for (File f : Paths.getModsPath().listFiles(new FilenameFilter() {
				@Override
				public boolean accept(File d, String fn) {
					return fn.startsWith(folderStart);
				}
			})) {
				return f;
			}
		}

		return new File(Paths.getModsPath(), folderStart
				+ getSanitizedPathName());
	}

	/**
	 * @return Is there a newer version on the depot?
	 */
	public boolean isNewerAvailable() {
		if (!isLocalOnly())
			return updatedMillis > localTimestamp;
		else
			return false;
	}

	/**
	 * @return Mod exists within mods folder
	 */
	public boolean isLocalAvailable() {
		return getLocalPath().exists();
	}

	/**
	 * @return If this mod is only locally available (not on Depot)
	 */
	public boolean isLocalOnly() {
		return nodeUrl == null;
	}

	/**
	 * @return Is mod installed?
	 */
	public boolean isInstalled() {
		if (tool)
			return ToolInstallationList.getInstance()
					.isInstalled(packageNumber);
		else
			return PackageManager.getInstance().isModInstalled(this);
	}

	/**
	 * @return Name of mod
	 */
	public String getName() {
		return name;
	}

	/**
	 * @return the package number
	 */
	public int getPackageNumber() {
		return packageNumber;
	}

	/**
	 * @return the package number as 5 digit string
	 */
	public String getPackageNumberString() {
		return String.format("%05d", packageNumber);
	}

	/**
	 * @return Types of mod
	 */
	public HashSet<Type> getTypes() {
		return types;
	}

	/**
	 * @return Is this mod actually a tool?
	 */
	public boolean isTool() {
		return tool;
	}

	/**
	 * @return Compatible platforms
	 */
	public ECompatiblePlatform getPlatform() {
		return platform;
	}

	/**
	 * @return Version of mod
	 */
	public String getVersion() {
		return version;
	}

	/**
	 * @return Submitter of mod
	 */
	public String getSubmitter() {
		return submitter;
	}

	/**
	 * @return Creator of mod
	 */
	public String getCreator() {
		return creator;
	}

	/**
	 * @return Installation type of BSL files
	 */
	public EBSLInstallType getBSLInstallType() {
		return bslInstallType;
	}

	/**
	 * @return Description of mod
	 */
	public String getDescription() {
		return description;
	}

	/**
	 * @return Size of Zip file on Depot
	 */
	public int getZipSize() {
		return zipSize;
	}

	/**
	 * @return the createdMillis
	 */
	public long getCreatedMillis() {
		return createdMillis;
	}

	/**
	 * @return the updatedMillis
	 */
	public long getUpdatedMillis() {
		return updatedMillis;
	}

	/**
	 * @return the fileUrl
	 */
	public String getFileUrl() {
		return fileUrl;
	}

	/**
	 * @return Is a package that is always installed?
	 */
	public boolean isCorePackage() {
		return packageNumber < DepotConfig.corePackageNumberLimit;
	}

	/**
	 * @return Depot page URI
	 */
	public URI getUrl() {
		return nodeUrl;
	}

	@Override
	public String toString() {
		return name;
	}

	/**
	 * @return the incompabitilities
	 */
	public HashSet<Integer> getIncompabitilities() {
		return incompatibilities;
	}

	/**
	 * @return the dependencies
	 */
	public HashSet<Integer> getDependencies() {
		return dependencies;
	}

	/**
	 * @return the levels this mod will unlock
	 */
	public HashSet<Integer> getUnlockLevels() {
		return unlockLevel;
	}

	/**
	 * @return Executable name of this tool
	 */
	public File getExeFile() {
		return exeFile;
	}

	/**
	 * @return Executable type of this tool
	 */
	public EExeType getExeType() {
		return exeType;
	}

	/**
	 * @return Icon file of this tool
	 */
	public File getIconFile() {
		return iconFile;
	}

	/**
	 * @return Working directory of this tool
	 */
	public File getWorkingDir() {
		if (workingDir.equalsIgnoreCase("Exe")) {
			if (exeFile != null)
				return exeFile.getParentFile();
			else
				return Paths.getEditionGDF();
		} else if (workingDir.equalsIgnoreCase("GDF"))
			return Paths.getEditionGDF();
		else
			return Paths.getEditionBasePath();
	}

	/**
	 * @return Is this mod valid on the running platform?
	 */
	public boolean isValidOnPlatform() {
		switch (platform) {
			case BOTH:
				return true;
			case MACOS:
				return (PlatformInformation.getPlatform() == Platform.MACOS);
			case WIN:
				return (PlatformInformation.getPlatform() == Platform.WIN)
						|| (PlatformInformation.getPlatform() == Platform.LINUX);
		}
		return false;
	}

	/**
	 * Delete the local package folder
	 */
	public void deleteLocalPackage() {
		if (getLocalPath().exists()) {
			try {
				FileUtils.deleteDirectory(getLocalPath());
				updateLocalData();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	@Override
	public int compareTo(Package o) {
		return getPackageNumber() - o.getPackageNumber();
	}

}
