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.Collections;
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.lang3.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 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(final String path, final String rel, final 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>
		 * <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(final String path) {
			this.path = path;
		}

		public String getName() {
			return name;
		}

		public void setName(final 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 eu.dnetlib.enabling.inspector.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")
	String 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(final 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 "inspector/index";
	}

	/**
	 * 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.escapeHtml4(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) final 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 request  servlet request
	 * @param response servlet response
	 * @throws XMLDBException could happen
	 * @throws IOException
	 */
	@RequestMapping("/inspector/index.do/**/raw")
	public 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")
	public 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.escapeHtml4(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")
	public 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")
	public 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")
	public 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 for new subcollection
	 *
	 * @param model   model
	 * @param request request
	 * @return view name
	 * @throws XMLDBException happens
	 */
	@RequestMapping("/inspector/index.do/**/createsubcoll")
	public String createSubCollection(final Model model, final HttpServletRequest request) throws XMLDBException {
		return "inspector/createsubcoll";
	}

	/**
	 * @param request request
	 * @return view name
	 * @throws XMLDBException happens
	 */
	@RequestMapping("/inspector/index.do/**/savesubcoll")
	public String saveSubCollection(final HttpServletRequest request, @RequestParam("collectionPath") final String collectionPath)
			throws XMLDBException {
		final String path = request.getPathInfo().replace(INDEX_DO, "").replace("/savesubcoll", "");
		String fullPath = path + "/" + collectionPath;
		log.info("Creating subcollection: " + fullPath);
		if (!xmlDatabase.collectionExists(fullPath)) {
			xmlDatabase.createCollection(fullPath);
		} else {
			log.info("Subcollection: " + fullPath + " already exists");
		}
		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")
	public String create(final Model model, final HttpServletRequest request) throws XMLDBException {
		return "inspector/create";
	}

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

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

}
