package org.greenstone.gsdl3.action;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.io.FileUtils;
import org.greenstone.gsdl3.util.DerbyWrapper;
import org.greenstone.gsdl3.util.GSConstants;
import org.greenstone.gsdl3.util.GSParams;
import org.greenstone.gsdl3.util.GSXML;
import org.greenstone.gsdl3.util.GSXSLT;
import org.greenstone.gsdl3.util.UserContext;
import org.greenstone.gsdl3.util.XMLConverter;
import org.greenstone.util.GlobalProperties;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

public class VMManagerAction extends Action
{
	//Sub actions
	private final String VMM_RETRIEVE_WIZARD = "getwizard";
	private final String VMM_CREATE_VM = "createvm";
	private final String VMM_CLEAR_CACHE = "clearcache";
	private final String VMM_CLEAR_DATABASE = "cleardatabase";

  // cgi args
  private final String VMM_PAGE_ARG = "vmmPage";
  private final String CURRENT_PAGE_ARG = "currentPage";
  private final String FILE_TO_ADD_ARG = "fileToAdd";

    protected void retrieveWizard(HashMap<String, Serializable> params,  String collection, String currentUsername,
				  DerbyWrapper database,
				  boolean prevPageNumFail, int prevPageNum, int pageNum, int highestVisitedPage,
				  Document doc, Element response, Element responseMessage)
    {
	//Save given metadata
	StringBuilder saveString = new StringBuilder("[");
	Iterator<String> paramIter = params.keySet().iterator();
	while (paramIter.hasNext())
	    {
		String paramName = paramIter.next();
		if (paramName.startsWith(GSParams.MD_PREFIX))
		    {
			Object paramValue = params.get(paramName);
			
			if (paramValue instanceof String)
			    {
				saveString.append("{name:\"" + paramName + "\", value:\"" + (String) paramValue + "\"},");
			    }
			else if (paramValue instanceof HashMap)
			    {
				HashMap<String, String> subMap = (HashMap<String, String>) paramValue;
				Iterator<String> subKeyIter = subMap.keySet().iterator();
				while (subKeyIter.hasNext())
				    {
							String subName = subKeyIter.next();
							saveString.append("{name:\"" + paramName + "." + subName + "\", value:\"" + subMap.get(subName) + "\"},");
				    }
			    }
		    }
	    }
	if (saveString.length() > 2)
	    {
		saveString.deleteCharAt(saveString.length() - 1);
		saveString.append("]");
		
		if (!prevPageNumFail)
		    {
			database.addUserData(currentUsername, "VMM___" + collection + "___" + prevPageNum + "___CACHED_VALUES", saveString.toString());
		    }
	    }
	
	//Construct the xsl
	Document compiledVMManagerFile = null;
	try
	    {
		compiledVMManagerFile = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
	    }
	catch (Exception ex)
	    {
		ex.printStackTrace();
	    }
	Document vmmanagerBaseFile = GSXSLT.mergedXSLTDocumentCascade("vm-manager/vm-manager.xsl", (String) this.config_params.get(GSConstants.SITE_NAME), collection, (String) this.config_params.get(GSConstants.INTERFACE_NAME), (ArrayList<String>) this.config_params.get(GSConstants.BASE_INTERFACES), false);
	
	Element numOfPagesElement = GSXML.getNamedElement(vmmanagerBaseFile.getDocumentElement(), "xsl:variable", "name", "numOfPages");
	int numberOfPages = Integer.parseInt(numOfPagesElement.getTextContent());
	
	compiledVMManagerFile.appendChild(compiledVMManagerFile.importNode(vmmanagerBaseFile.getDocumentElement(), true));
	
	ArrayList<Document> pageDocs = new ArrayList<Document>();
	ArrayList<String> pageNames = new ArrayList<String>();
	for (int i = 1; i <= numberOfPages; i++)
	    {
		Document page = GSXSLT.mergedXSLTDocumentCascade("vm-manager/vmm_page" + i + ".xsl", (String) this.config_params.get(GSConstants.SITE_NAME), collection, (String) this.config_params.get(GSConstants.INTERFACE_NAME), (ArrayList<String>) this.config_params.get(GSConstants.BASE_INTERFACES), false);
		pageDocs.add(page);
		
		Element pageTitleElem = (Element) GSXML.getNamedElement(page.getDocumentElement(), "xsl:variable", "name", "title");
		pageNames.add(pageTitleElem.getTextContent());
		
		Element wizardPageElem = (Element) GSXML.getNamedElement(page.getDocumentElement(), "xsl:template", "name", "wizardPage");
		wizardPageElem.setAttribute("name", "wizardPage" + i);
		compiledVMManagerFile.getDocumentElement().appendChild(compiledVMManagerFile.importNode(wizardPageElem, true));
	    }
	
	//Create the wizard bar
	Element wizardBarTemplate = GSXML.getNamedElement(compiledVMManagerFile.getDocumentElement(), "xsl:template", "name", "wizardBar");
	Element wizardBar = compiledVMManagerFile.createElement("ul");
	wizardBar.setAttribute("id", "wizardBar");
	wizardBarTemplate.appendChild(wizardBar);
	
	for (int i = 0; i < pageNames.size(); i++)
	    {
		String pageName = pageNames.get(i);
		Element pageLi = compiledVMManagerFile.createElement("li");
		if (pageNum == i + 1)
		    {
			pageLi.setAttribute("class", "wizardStepLink ui-state-active ui-corner-all");
		    }
		else if (i + 1 > highestVisitedPage + 1 && i + 1 > pageNum + 1)
		    {
			pageLi.setAttribute("class", "wizardStepLink ui-state-disabled ui-corner-all");
		    }
		else
		    {
			pageLi.setAttribute("class", "wizardStepLink ui-state-default ui-corner-all");
		    }
		Element link = compiledVMManagerFile.createElement("a");
		pageLi.appendChild(link);
		
		link.setAttribute(GSXML.HREF_ATT, "javascript:;");
		link.setAttribute("page", "" + (i + 1));
		link.appendChild(compiledVMManagerFile.createTextNode(pageName));
		wizardBar.appendChild(pageLi);
	    }
	
	//Add a call-template call to the appropriate page in the xsl
	Element mainDePageElem = GSXML.getNamedElement(compiledVMManagerFile.getDocumentElement(), "xsl:template", "match", "/page");
	Element wizardContainer = GSXML.getNamedElement(mainDePageElem, "div", "id", "wizardContainer");
	Element formContainer = GSXML.getNamedElement(wizardContainer, "form", "name", "vmmanager-form");
	Element callToPage = compiledVMManagerFile.createElement("xsl:call-template");
	callToPage.setAttribute("name", "wizardPage" + pageNum);
	formContainer.appendChild(callToPage);
	
	Element cachedValueElement = doc.createElement("cachedValues");
	response.appendChild(cachedValueElement);
	try
	    {
		for (int i = pageNum; i > 0; i--)
		    {
			Element page = doc.createElement("pageCache");
			page.setAttribute("pageNum", "" + i);
			String cachedValues = database.getUserData(currentUsername, "VMM___" + collection + "___" + i + "___CACHED_VALUES");
			if (cachedValues != null)
			    {
				page.appendChild(doc.createTextNode(cachedValues));
				cachedValueElement.appendChild(page);
			    }
		    }
	    }
	catch (Exception ex)
	    {
		ex.printStackTrace();
	    }
	
	try
	    {
		Transformer transformer = TransformerFactory.newInstance().newTransformer();
		
		File newFileDir = new File(GlobalProperties.getGSDL3Home() + File.separator + "sites" + File.separator + this.config_params.get(GSConstants.SITE_NAME) + File.separator + "collect" + File.separator + collection + File.separator + "transform" + File.separator + "vm-manager");
		newFileDir.mkdirs();
		
		File newFile = new File(newFileDir, File.separator + "compiledVMManager.xsl");
		
		//initialize StreamResult with File object to save to file
		StreamResult sresult = new StreamResult(new FileWriter(newFile));
		DOMSource source = new DOMSource(compiledVMManagerFile);
		transformer.transform(source, sresult);
	    }
	catch (Exception ex)
	    {
		ex.printStackTrace();
	    }
	database.closeDatabase();
    }

    protected void createVM(HashMap<String, Serializable> params,  String collection, String currentUsername,
			    String fileToAdd, File tempFile,
			    DerbyWrapper database,
			    boolean prevPageNumFail, int prevPageNum, int pageNum, int highestVisitedPage,
			    Document doc, UserContext uc, Element response, Element responseMessage)

    {
		
		HashMap<String, String> metadataMap = new HashMap<String, String>();
		for (int i = pageNum; i > 0; i--)
		    {
			String cachedValues = database.getUserData(currentUsername, "VMM___" + collection + "___" + i + "___CACHED_VALUES");
			if (cachedValues != null)
			    {
				Type type = new TypeToken<List<Map<String, String>>>()
				    {
				    }.getType();
				
				Gson gson = new Gson();
				List<Map<String, String>> metadataList = gson.fromJson(cachedValues, type);
				for (Map<String, String> metadata : metadataList)
				    {
					metadataMap.put(metadata.get("name"), metadata.get("value"));
				    }
			    }
		    }
		
		String xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><!DOCTYPE DirectoryMetadata SYSTEM \"https://greenstone.org/dtd/DirectoryMetadata/1.0/DirectoryMetadata.dtd\"><DirectoryMetadata><FileSet>";
		xmlString += "<FileName>.*</FileName><Description>";
		for (String key : metadataMap.keySet())
		    {
			xmlString += "<Metadata name=\"" + key.substring("MD___".length()) + "\" mode=\"accumulate\">" + metadataMap.get(key) + "</Metadata>";
		    }
		xmlString += "</Description></FileSet></DirectoryMetadata>";
		
		File metadataFile = new File(GlobalProperties.getGSDL3Home() + File.separator + "sites" + File.separator + this.config_params.get(GSConstants.SITE_NAME) + File.separator + "collect" + File.separator + collection + File.separator + "import" + File.separator + fileToAdd + File.separator + "metadata.xml");
		
		try
		    {
			BufferedWriter bw = new BufferedWriter(new FileWriter(metadataFile));
			bw.write(xmlString);
			bw.close();
		    }
		catch (Exception ex)
		    {
			ex.printStackTrace();
		    }
		
		Element buildMessage = doc.createElement(GSXML.MESSAGE_ELEM);
		Element buildRequest = GSXML.createBasicRequest(doc, GSXML.REQUEST_TYPE_PROCESS, "ImportCollection", uc);
		buildMessage.appendChild(buildRequest);
		
		Element paramListElem = doc.createElement(GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
		buildRequest.appendChild(paramListElem);
		
		Element collectionParam = doc.createElement(GSXML.PARAM_ELEM);
		paramListElem.appendChild(collectionParam);
		collectionParam.setAttribute(GSXML.NAME_ATT, GSXML.COLLECTION_ATT);
		collectionParam.setAttribute(GSXML.VALUE_ATT, collection);
		
		Element documentsParam = doc.createElement(GSXML.PARAM_ELEM);
		paramListElem.appendChild(documentsParam);
		documentsParam.setAttribute(GSXML.NAME_ATT, "documents");
		documentsParam.setAttribute(GSXML.VALUE_ATT, fileToAdd);
		
		Element buildResponseMessage = (Element) this.mr.process(buildMessage);
		
		response.appendChild(doc.importNode(buildResponseMessage, true));
	   
    }
    
	public Node process(Node message)
	{
		Element request = (Element) GSXML.getChildByTagName(message, GSXML.REQUEST_ELEM);
		Document doc = request.getOwnerDocument();

		UserContext uc = new UserContext((Element) request);
		
		Element responseMessage = doc.createElement(GSXML.MESSAGE_ELEM);
		Element response = GSXML.createBasicResponse(doc, this.getClass().getSimpleName());
		responseMessage.appendChild(response);

		addSiteMetadata(response, uc);
		addInterfaceOptions(response);

		String currentUsername = uc.getUsername();
		
		// logger.debug("username="+username+", groups = "+groups);
		if (currentUsername == null || currentUsername.equals("")) 
		{
		
		  // TODO if user is not logged in, push to login page
		  request.setAttribute("subaction", "");
		  GSXML.addError(response, "You need to be logged in to use the VM Manager");
		  return responseMessage;
		}
		
		Element param_list = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
		HashMap<String, Serializable> params = GSXML.extractParams(param_list, false);

		String collection = (String) params.get(GSParams.COLLECTION);

		if (collection !=null && !collection.equals("")) {
		  if (!userHasCollectionEditPermission(collection, uc)) {
		    // we need to reset back to empty subaction here		    
		    request.setAttribute("subaction", "");
		    GSXML.addError(response, "You are not in the right group to access this collection. Please log in as a different user.");
		    return responseMessage;
		  
		  }
		}
		int pageNum = -1;
		boolean pageNumParseFail = false;
		try
		{
			pageNum = Integer.parseInt(((String) params.get(VMM_PAGE_ARG)));
		}
		catch (Exception ex)
		{
			pageNumParseFail = true;
		}

		int prevPageNum = -1;
		boolean prevPageNumFail = false;
		try
		{
			prevPageNum = Integer.parseInt((String) params.get(CURRENT_PAGE_ARG));
		}
		catch (Exception ex)
		{
			prevPageNumFail = true;
		}

		DerbyWrapper database = new DerbyWrapper(GlobalProperties.getGSDL3Home() + File.separatorChar + "etc" + File.separatorChar + "usersDB");
		if (pageNumParseFail)
		{
			try
			{
				pageNum = Integer.parseInt(database.getUserData(currentUsername, "VMM___" + collection + "___CACHED_PAGE"));
			}
			catch (Exception ex)
			{
				pageNum = 1;
			}
		}

		int highestVisitedPage = -1;
		String result = "";
		int counter = 1;
		while (result != null)
		{
			result = database.getUserData(currentUsername, "VMM___" + collection + "___" + counter + "___VISITED_PAGE");
			if (result != null)
			{
				counter++;
			}
		}
		highestVisitedPage = counter - 1;
		if (highestVisitedPage == 0)
		{
			highestVisitedPage = 1;
		}

		if (pageNum > highestVisitedPage + 1)
		{
			pageNum = highestVisitedPage + 1;
		}

		database.addUserData(currentUsername, "VMM___" + collection + "___" + pageNum + "___VISITED_PAGE", "VISITED");

		String subaction = ((Element) request).getAttribute(GSXML.SUBACTION_ATT);
		if (subaction.toLowerCase().equals(VMM_RETRIEVE_WIZARD))
		{
		    // An improvement would be to put all the pageNum related variables into a public class/struct and
		    // pass that instead
		    retrieveWizard(params,collection,currentUsername,database,prevPageNumFail,prevPageNum,pageNum,highestVisitedPage,
				   doc,response,responseMessage);
			
		}
		else if (subaction.toLowerCase().equals(VMM_CREATE_VM))
		{
		    String fileToAdd = (String) params.get(FILE_TO_ADD_ARG);
		    File tempFile = new File(GlobalProperties.getGSDL3Home() + File.separator + "tmp" + File.separator + fileToAdd);
		    if (tempFile.exists())
			{
		    
			    File newFileLocationDir = new File(GlobalProperties.getGSDL3Home() + File.separator + "sites" + File.separator + this.config_params.get(GSConstants.SITE_NAME) + File.separator + "collect" + File.separator + collection + File.separator + "import" + File.separator + fileToAdd);
			    if (!newFileLocationDir.exists())
				{
				    newFileLocationDir.mkdir();
				}
			    File newFileLocation = new File(newFileLocationDir, fileToAdd);
			    
			    try
				{
				    FileUtils.copyFile(tempFile, newFileLocation);
				}
			    catch (Exception ex)
				{
				    ex.printStackTrace();
				    GSXML.addError(responseMessage, "Failed to copy the deposited file into the collection.");
				    return responseMessage;
				}
		    
			    createVM(params,collection,currentUsername,
				     fileToAdd, tempFile,
				     database,prevPageNumFail,prevPageNum,pageNum,highestVisitedPage,
				     doc,uc,response,responseMessage);
			}
		}
		else if (subaction.toLowerCase().equals(VMM_CLEAR_CACHE))
		{
			database.clearUserDataWithPrefix(currentUsername, "VMM___");
		}
		else if (subaction.toLowerCase().equals(VMM_CLEAR_DATABASE))
		{
			database.clearUserData();
			database.clearTrackerData();
		}
		else
		{
			Element vmmanagerPage = doc.createElement("vmmanagerPage");
			response.appendChild(vmmanagerPage);

			Element collList = getCollectionsInSiteForUser(uc);
			vmmanagerPage.appendChild(doc.importNode(collList, true));
		}

		return responseMessage;
	}

    
    public Element getCollectionsInSiteForUser(UserContext uc) 
    {
	Document doc = XMLConverter.newDOM();
	Element message = doc.createElement(GSXML.MESSAGE_ELEM);
	Element request = GSXML.createBasicRequest(doc, GSXML.REQUEST_TYPE_DESCRIBE, "", new UserContext());
	message.appendChild(request);
	Element responseMessage = (Element) this.mr.process(message);
	
	Element response = (Element) GSXML.getChildByTagName(responseMessage, GSXML.RESPONSE_ELEM);
	Element collectionList = (Element) GSXML.getChildByTagName(response, GSXML.COLLECTION_ELEM + GSXML.LIST_MODIFIER);
	
	Element validCollectionList = doc.createElement(GSXML.COLLECTION_ELEM + GSXML.LIST_MODIFIER);

	NodeList collections = GSXML.getChildrenByTagName(collectionList, GSXML.COLLECTION_ELEM);
	for (int i = 0; i < collections.getLength(); i++) {
	    String coll_name = ((Element)collections.item(i)).getAttribute(GSXML.NAME_ATT);
	    if (userHasCollectionEditPermission(coll_name, uc)) {
		// only add to the new list if the user has edit permission
		validCollectionList.appendChild(doc.importNode(collections.item(i), true));
		
	    }
	}
	
	return validCollectionList;
    }

  // collection must be non-null and non-empty
  protected boolean userHasCollectionEditPermission(String collection, UserContext user_context) {

    for (String group : user_context.getGroups()) {
      // administrator always has permission
      if (group.equals("administrator")) {
	return true;
      }
      // all-collections-editor can edit any collection
      
      if (group.equals("all-collections-editor")) {
	return true;
      }
      if (group.equals(collection+"-collection-editor")) {
	  return true;
      }
    }
    
    // haven't found a group with edit permissions
    return false;
    
  }
}
