package net.oni2.moddepot;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Vector;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import net.oni2.ProxySettings;
import net.oni2.httpfiledownloader.FileDownloader;
import net.oni2.moddepot.model.File;
import net.oni2.moddepot.model.Node;
import net.oni2.moddepot.model.NodeField_Body;
import net.oni2.moddepot.model.NodeField_Upload;
import net.oni2.moddepot.model.NodeMod;
import net.oni2.moddepot.model.TaxonomyTerm;
import net.oni2.moddepot.model.TaxonomyVocabulary;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.XStreamException;
import com.thoughtworks.xstream.io.xml.StaxDriver;

/**
 * @author Christian Illy
 */
public class DepotManager {
	private static DepotManager instance = null;

	private HashMap<Integer, TaxonomyVocabulary> taxonomyVocabulary = new HashMap<Integer, TaxonomyVocabulary>();
	private HashMap<Integer, TaxonomyTerm> taxonomyTerms = new HashMap<Integer, TaxonomyTerm>();

	private HashMap<Integer, Node> nodes = new HashMap<Integer, Node>();
	private HashMap<String, HashMap<Integer, Node>> nodesByType = new HashMap<String, HashMap<Integer, Node>>();

	private HashMap<Integer, File> files = new HashMap<Integer, File>();

	/**
	 * Caching the vocab id for the package type vocabulary
	 */
	public int vocabId_type = -1;
	/**
	 * Caching the vocab id for the platform vocabulary
	 */
	public int vocabId_platform = -1;
	/**
	 * Caching the vocab id for the install method vocabulary
	 */
	public int vocabId_instmethod = -1;

	/**
	 * @return Singleton instance
	 */
	public static DepotManager getInstance() {
		if (instance == null)
			instance = new DepotManager();
		return instance;
	}

	/**
	 * Update local Depot information cache
	 * 
	 * @return Update successfully done?
	 */
	public boolean updateInformation() {
		try {
			java.io.File zipName = new java.io.File(
					System.getProperty("java.io.tmpdir"), "aei_jsoncache.zip");
			FileDownloader fd = new FileDownloader(DepotConfig.depotUrl
					+ "jsoncache/jsoncache.zip?ts="
					+ (new Date().getTime() / 1000), zipName);
			fd.start();
			while (fd.getState() != FileDownloader.EState.FINISHED) {
				Thread.sleep(50);
			}

			String jsonVocab = null;
			String jsonTerms = null;
			String jsonNodes = null;
			String jsonFiles = null;

			ZipFile zf = null;
			try {
				zf = new ZipFile(zipName);

				for (Enumeration<? extends ZipEntry> e = zf.entries(); e
						.hasMoreElements();) {
					ZipEntry ze = e.nextElement();
					if (!ze.isDirectory()) {
						BufferedReader input = new BufferedReader(
								new InputStreamReader(zf.getInputStream(ze)));
						StringBuffer json = new StringBuffer();

						char data[] = new char[1024];
						int dataRead;
						while ((dataRead = input.read(data, 0, 1024)) != -1) {
							json.append(data, 0, dataRead);
						}

						if (ze.getName().toLowerCase().contains("vocabulary"))
							jsonVocab = json.toString();
						if (ze.getName().toLowerCase().contains("terms"))
							jsonTerms = json.toString();
						if (ze.getName().toLowerCase().contains("nodes"))
							jsonNodes = json.toString();
						if (ze.getName().toLowerCase().contains("files"))
							jsonFiles = json.toString();
					}
				}
			} finally {
				if (zf != null)
					zf.close();
				zipName.delete();
			}

			initVocabulary(new JSONArray(jsonVocab));

			vocabId_type = getVocabulary(DepotConfig.vocabName_ModType)
					.getVid();
			vocabId_platform = getVocabulary(DepotConfig.vocabName_Platform)
					.getVid();
			vocabId_instmethod = getVocabulary(
					DepotConfig.vocabName_InstallType).getVid();

			initTerms(new JSONArray(jsonTerms));
			initFiles(new JSONArray(jsonFiles));
			initNodes(new JSONArray(jsonNodes));

			return true;
		} catch (JSONException e) {
			e.printStackTrace();
		} catch (IOException e1) {
			e1.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return false;
	}

	private void initFiles(JSONArray ja) throws JSONException {
		files = new HashMap<Integer, File>();
		JSONObject jo;
		for (int i = 0; i < ja.length(); i++) {
			jo = ja.getJSONObject(i);
			int fid = jo.getInt("fid");

			File f = new File(jo);
			files.put(fid, f);
		}
	}

	private void initNodes(JSONArray ja) throws JSONException {
		nodes = new HashMap<Integer, Node>();
		nodesByType = new HashMap<String, HashMap<Integer, Node>>();
		JSONObject jo;
		for (int i = 0; i < ja.length(); i++) {
			jo = ja.getJSONObject(i);

			int nid = jo.getInt("nid");
			String type = jo.getString("type");

			Node n = null;
			if (type.equalsIgnoreCase(DepotConfig.nodeType_Package))
				n = new NodeMod(jo);
			else
				n = new Node(jo);

			if (n.getStatus() > 0) {
				nodes.put(nid, n);
				if (!nodesByType.containsKey(type))
					nodesByType.put(type, new HashMap<Integer, Node>());
				nodesByType.get(type).put(nid, n);
			}
		}
	}

	private void initTerms(JSONArray ja) throws JSONException {
		taxonomyTerms.clear();
		JSONObject jo;
		for (int i = 0; i < ja.length(); i++) {
			jo = ja.getJSONObject(i);
			TaxonomyTerm tt = new TaxonomyTerm(jo);
			taxonomyTerms.put(tt.getTid(), tt);
		}
	}

	private void initVocabulary(JSONArray ja) throws JSONException {
		taxonomyVocabulary.clear();
		JSONObject jo;
		for (int i = 0; i < ja.length(); i++) {
			jo = ja.getJSONObject(i);
			TaxonomyVocabulary tv = new TaxonomyVocabulary(jo);
			taxonomyVocabulary.put(tv.getVid(), tv);
		}
	}

	/**
	 * @return Can we connect to the Depot?
	 */
	public boolean checkConnection() {
		try {
			HttpURLConnection con = (HttpURLConnection) new URL(
					DepotConfig.depotUrl).openConnection(ProxySettings
					.getInstance().getProxy());
			con.setRequestMethod("HEAD");
			int code = con.getResponseCode();
			return (code >= 200) && (code <= 299);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return false;
	}

	private TaxonomyVocabulary getVocabulary(String name) {
		for (TaxonomyVocabulary v : taxonomyVocabulary.values()) {
			if (v.getName().equalsIgnoreCase(name))
				return v;
		}
		return null;
	}

	/**
	 * @param vocabId
	 *            Get all taxonomy terms of a given vocabulary
	 * @return TaxTerms
	 */
	private Vector<TaxonomyTerm> getTaxonomyTermsByVocabulary(int vocabId) {
		Vector<TaxonomyTerm> res = new Vector<TaxonomyTerm>();
		for (TaxonomyTerm t : taxonomyTerms.values()) {
			if (t.getVid() == vocabId)
				res.add(t);
		}
		return res;
	}

	/**
	 * @return All defined types
	 */
	public Vector<TaxonomyTerm> getTypes() {
		return getTaxonomyTermsByVocabulary(vocabId_type);
	}

	/**
	 * @param id
	 *            Get taxonomy term by given ID
	 * @return TaxTerm
	 */
	public TaxonomyTerm getTaxonomyTerm(int id) {
		return taxonomyTerms.get(id);
	}

	/**
	 * Get all nodes of given node type
	 * 
	 * @param nodeType
	 *            Node type
	 * @return Nodes of type nodeType
	 */
	public Vector<Node> getNodesByType(String nodeType) {
		if (nodesByType.get(nodeType) == null)
			return new Vector<Node>();
		return new Vector<Node>(nodesByType.get(nodeType).values());
	}

	/**
	 * @return Mod-Nodes
	 */
	public Vector<NodeMod> getModPackageNodes() {
		Vector<NodeMod> result = new Vector<NodeMod>();
		String instMethName = DepotConfig.taxName_InstallType_Package;

		Vector<Node> files = getNodesByType(DepotConfig.nodeType_Package);
		for (Node n : files) {
			if (n instanceof NodeMod) {
				NodeMod nm = (NodeMod) n;
				if (nm.getInstallMethod().equalsIgnoreCase(instMethName)) {
					if (nm.getPackageNumber() >= 0)
						result.add(nm);
					else
						System.err.println("Node " + nm.getNid()
								+ " does not have a package number!!!");
				}
			}
		}
		return result;
	}

	/**
	 * @param id
	 *            ID of file to get
	 * @return the file
	 */
	public File getFile(int id) {
		return files.get(id);
	}

	private static XStream getXStream() {
		XStream xs = new XStream(new StaxDriver());
		xs.alias("Depot", DepotManager.class);
		xs.alias("File", net.oni2.moddepot.model.File.class);
		xs.alias("Node", Node.class);
		xs.alias("NodeField_Body", NodeField_Body.class);
		xs.alias("NodeField_Upload", NodeField_Upload.class);
		xs.alias("NodeMod", NodeMod.class);
		xs.alias("TaxonomyTerm", TaxonomyTerm.class);
		xs.alias("TaxonomyVocabulary", TaxonomyVocabulary.class);
		return xs;
	}

	/**
	 * Save Depot cache instance to file
	 * 
	 * @param cacheFile
	 *            File to save to
	 */
	public void saveToCacheFile(java.io.File cacheFile) {
		try {
			FileOutputStream fos = new FileOutputStream(cacheFile);
			XStream xs = getXStream();
			xs.toXML(this, fos);
			fos.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Load cache from file
	 * 
	 * @param cacheFile
	 *            File to load
	 */
	public static void loadFromCacheFile(java.io.File cacheFile) {
		try {
			FileInputStream fis = new FileInputStream(cacheFile);
			XStream xs = getXStream();
			Object obj = xs.fromXML(fis);
			fis.close();
			if (obj instanceof DepotManager)
				instance = (DepotManager) obj;
		} catch (XStreamException e) {
		} catch (FileNotFoundException e) {
		} catch (IOException e) {
		}
	}
}
