package eu.dnetlib.enabling.inspector;

import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.DocumentException;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.xmldb.api.base.XMLDBException;

import com.google.common.collect.Lists;

import edu.emory.mathcs.backport.java.util.Collections;
import eu.dnetlib.miscutils.collections.MappedCollection;
import eu.dnetlib.miscutils.functional.UnaryFunction;
import eu.dnetlib.xml.database.XMLDatabase;

/**
 * test controller.
 * 
 * @author marko
 * 
 */
@Controller
public class ResourceTreeController extends AbstractInspectorController { // NOPMD

	/**
	 * The list view uses this model to represent a collection
	 * 
	 * @author marko
	 * 
	 */
	public class CollectionModel {
		/**
		 * absolute path used to query the xmldb.
		 */
		private String path;
		/**
		 * relative path, used to construct the uri used by the view.
		 */
		private String rel;

		/**
		 * the collection name.
		 */
		private String name;

		public CollectionModel(String path, String rel, String name) {
			super();
			this.path = path;
			this.rel = rel;
			this.name = name;
		}

		/**
		 * We want to be able to skip useless collections which have only one child collection etc etc.
		 * 
		 * <p>
		 * This method returns us the deepest path containing only one collection at each level
		 * </p>
		 * 
		 * @return list of collection names to be displayed in one "row"
		 */
		public Collection<CollectionModel> getCollectionPath() {
			final ArrayList<CollectionModel> res = Lists.newArrayList(this);

			try {
				List<String> children = xmlDatabase.listChildCollections(path + '/' + name);
				if (children.size() == 1)
					res.addAll(new CollectionModel(path + '/' + name, rel + '/' + name, children.get(0)).getCollectionPath());

				return res;
			} catch (XMLDBException e) {
				return res;
			}
		}

		/**
		 * Uri is computed from relative base path.
		 * 
		 * @return
		 */
		public String getUrl() {
			return (rel + '/' + getName());
		}

		public String getPath() {
			return path;
		}

		public void setPath(String path) {
			this.path = path;
		}

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

	}

	/**
	 * base index.do path.
	 */
	private static final String INDEX_DO = "/inspector/index.do";

	/**
	 * logger.
	 */
	private static final Log log = LogFactory.getLog(ResourceTreeController.class); // NOPMD by marko on 11/24/08 5:02 PM

	/**
	 * xml database.
	 */
	@Resource(name = "existDatabase")
	private transient XMLDatabase xmlDatabase;
	
	/**
	 * utility to parse resource ids and allows to navigate them.
	 */
	@Resource(name = "resourcelinkTool")
	private ResourceLinkTool linkTool;

	/**
	 * debug.
	 */
	public ResourceTreeController() {
		super();
		log.info("ResourceTreeController created");
	}

	/**
	 * handles relative paths.
	 * 
	 * @return redirect
	 */
	@RequestMapping("/inspector/")
	String indexSlash() {
		return "redirect:index.do/db/list";
	}

	/**
	 * handles relative paths.
	 * 
	 * @return redirect
	 */
	@RequestMapping("/inspector/index.do")
	String indexDo() {
		return "redirect:index.do/db/list";
	}

	/**
	 * index.
	 * 
	 * @param model
	 *            model
	 * @param request
	 *            http request
	 * @throws XMLDBException
	 *             happens
	 */
	@RequestMapping("/inspector/index.do/**/list")
	void list(final Model model, final HttpServletRequest request) throws XMLDBException {
		final String path = request.getPathInfo().replace(INDEX_DO, "").replace("/list", "");

		log.debug("xml db: " + xmlDatabase);

		final Collection<String> children = xmlDatabase.listChildCollections(path);
		final Collection<String> files = xmlDatabase.list(path);

		Collections.sort((List<String>) children);
		Collections.sort((List<String>) files);

		UnaryFunction<CollectionModel, String> mapper = new UnaryFunction<CollectionModel, String>() {
			@Override
			public CollectionModel evaluate(String name) {
				return new CollectionModel(path, ".", name);
			}
		};
		final Collection<CollectionModel> richChildren = Lists.newArrayList(new MappedCollection<CollectionModel, String>(children, mapper));

		model.addAttribute("path", path);
		model.addAttribute("pathComponents", extractPathComponents(path, ""));
		model.addAttribute("collections", richChildren);
		model.addAttribute("files", files);
		model.addAttribute("title", "Title");
	}

	/**
	 * return a list of pairs (name, relative url bases) for each path component.
	 * 
	 * @param path
	 *            slash separated path
	 * @param base
	 *            prepend this to all paths
	 * @return list of path components
	 */
	private List<Map<String, String>> extractPathComponents(final String path, final String base) {
		final String[] rawPathComponents = path.split("/");
		final List<Map<String, String>> pathComponents = new ArrayList<Map<String, String>>();
		for (String rawPathComponent : rawPathComponents) {
			final Map<String, String> pathElement = new HashMap<String, String>(); // NOPMD
			pathElement.put("name", rawPathComponent);

			pathComponents.add(pathElement);
		}
		Collections.reverse(pathComponents);
		final StringBuffer current = new StringBuffer(base); // NOPMD
		for (Map<String, String> pathComponent : pathComponents) {
			pathComponent.put("url", current.toString());
			current.append("../");
		}
		Collections.reverse(pathComponents);
		return pathComponents;
	}

	/**
	 * show a file.
	 * 
	 * @param model
	 *            model
	 * @param request
	 *            request
	 * @return view name
	 * @throws XMLDBException
	 *             happens
	 */
	@RequestMapping("/inspector/index.do/**/show")
	String show(final Model model, final HttpServletRequest request) throws XMLDBException {

		final String path = request.getPathInfo().replace(INDEX_DO, "").replace("/show", "");
		log.info("index: " + path);

		final File fileHelper = new File(path);
		final String collection = fileHelper.getParent();
		final String fileName = fileHelper.getName();
		final TouchUtils touch = new TouchUtils();
		
		String file = xmlDatabase.read(fileName, collection);
		if (file == null)
			file = "no such file, click on edit to create";

		file = touch.spanize(file);
		file = StringEscapeUtils.escapeHtml(file);
		file = touch.escape(file);
		//log.info("XML ESCAPED:" + file);

		model.addAttribute("file", linkTool.linkfyToHtml(file));
		model.addAttribute("pathComponents", extractPathComponents(collection, "../"));

		return "inspector/show";
	}

	@RequestMapping("/inspector/index.do/**/touch")
	public void updateDate(
			@RequestParam(value = "xpath", required = true) String xpath,
			final HttpServletRequest request,
			final HttpServletResponse response) throws XMLDBException {

		final String path = request.getPathInfo().replace(INDEX_DO, "").replace("/touch", "");
		final File fileHelper = new File(path);
		final String collection = fileHelper.getParent();
		final String fileName = fileHelper.getName();
		final TouchUtils touch = new TouchUtils();
		
		String file = xmlDatabase.read(fileName, collection);
		if (file != null) {
			String updatedProfile;
			try {
				updatedProfile = touch.updateProfile(file, xpath);
				xmlDatabase.update(fileName, collection, updatedProfile);
			} catch (DocumentException e) {
				log.warn(e);
			}

		}
	}

	/**
	 * Show raw profile.
	 * 
	 * @param model
	 *            mvc model
	 * @param request
	 *            servlet request
	 * @return mvc view
	 * @throws XMLDBException
	 *             could happen
	 * @throws IOException
	 */
	@RequestMapping("/inspector/index.do/**/raw")
	void raw(final HttpServletRequest request, final HttpServletResponse response) throws XMLDBException, IOException {

		final String path = request.getPathInfo().replace(INDEX_DO, "").replace("/raw", "");
		//log.info("index: " + path);

		final File fileHelper = new File(path);
		final String collection = fileHelper.getParent();
		final String fileName = fileHelper.getName();

		String file = xmlDatabase.read(fileName, collection);
		if (file == null)
			file = "no such file to show";

		response.setContentType("text/xml");
		ServletOutputStream out = response.getOutputStream();
		IOUtils.copy(new StringReader(file), out);
		out.flush();
		out.close();
	}

	/**
	 * show a file editor.
	 * 
	 * @param model
	 *            model
	 * @param request
	 *            request
	 * @return view name
	 * @throws XMLDBException
	 *             happens
	 */
	@RequestMapping("/inspector/index.do/**/edit")
	String edit(final Model model, final HttpServletRequest request) throws XMLDBException {

		final String path = request.getPathInfo().replace(INDEX_DO, "").replace("/edit", "");

		final File fileHelper = new File(path);
		final String collection = fileHelper.getParent();
		final String fileName = fileHelper.getName();

		final String file = xmlDatabase.read(fileName, collection);
		if (file == null)
			model.addAttribute("creating", "true");

		model.addAttribute("file", StringEscapeUtils.escapeHtml(file));
		model.addAttribute("pathComponents", extractPathComponents(collection, "../"));

		return "inspector/edit";
	}

	/**
	 * update or create a file.
	 * 
	 * @param model
	 *            model
	 * @param request
	 *            request
	 * @return view name
	 * @throws XMLDBException
	 *             happens
	 */
	@RequestMapping("/inspector/index.do/**/save")
	String save(final Model model, final HttpServletRequest request) throws XMLDBException {

		final String path = request.getPathInfo().replace(INDEX_DO, "").replace("/save", "");

		final File fileHelper = new File(path);
		final String collection = fileHelper.getParent();
		final String fileName = fileHelper.getName();

		log.info("saving: " + path);
		final String source = request.getParameter("source");

		if ("true".equals(request.getParameter("creating")))
			xmlDatabase.create(fileName, collection, source);
		else
			xmlDatabase.update(fileName, collection, source);

		return "redirect:show";
	}

	/**
	 * delete a file.
	 * 
	 * @param model
	 *            model
	 * @param request
	 *            request
	 * @return view name
	 * @throws XMLDBException
	 *             happens
	 */
	@RequestMapping("/inspector/index.do/**/delete")
	String delete(final Model model, final HttpServletRequest request) throws XMLDBException {

		final String path = request.getPathInfo().replace(INDEX_DO, "").replace("/delete", "");

		final File fileHelper = new File(path);
		final String collection = fileHelper.getParent();
		final String fileName = fileHelper.getName();

		log.info("deleting: " + path);
		xmlDatabase.remove(fileName, collection);

		return "redirect:../list";
	}

	/**
	 * delete a collection.
	 * 
	 * @param model
	 *            model
	 * @param request
	 *            request
	 * @return view name
	 * @throws XMLDBException
	 *             happens
	 */
	@RequestMapping("/inspector/index.do/**/deleteCollection")
	String deleteCollection(final Model model, final HttpServletRequest request) throws XMLDBException {

		final String path = request.getPathInfo().replace(INDEX_DO, "").replace("/deleteCollection", "");

		xmlDatabase.removeCollection(path);

		return "redirect:../list";
	}

	/**
	 * present a create form which will redirect to the edit form.
	 * 
	 * @param model
	 *            model
	 * @param request
	 *            request
	 * @return view name
	 * @throws XMLDBException
	 *             happens
	 */
	@RequestMapping("/inspector/index.do/**/create")
	String create(final Model model, final HttpServletRequest request) throws XMLDBException {
		return "inspector/create";
	}

	/**
	 * sample controller.
	 * 
	 * @param model
	 *            model
	 */
	@RequestMapping("/inspector/gadget.do")
	void gadget(final Model model) {
		log.info("GADGED CALLED");

		model.addAttribute("items", new String[] { "one", "two", "three" });
	}

}
