/**
 * Copyright 2014-2015 SHAF-WORK
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.shaf.server.controller;

import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;

import org.shaf.core.util.Log;
import org.shaf.core.util.TextMatrix;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;

/**
 * The controller for the "Process" dashboard.
 * 
 * @author Mykola Galushka
 */
@Controller
@Secured("ROLE_USER")
@RequestMapping("/proc/action")
public class ActionProcessController extends GenericActionController {

	/**
	 * Defines a logger.
	 */
	private static final Log LOG = Log.forClass(ActionProcessController.class);

	/**
	 * Shows a list of all available process for the specified application.
	 * 
	 * @param app
	 *            the application name.
	 * @return the view model.
	 * @throws Exception
	 *             is the view constructing has failed.
	 */
	@RequestMapping(value = "/list/{app}", method = RequestMethod.GET)
	public ModelAndView onList(@PathVariable String app) throws Exception {
		LOG.debug("CALL: /proc/action/list/{" + app + "}");

		TextMatrix procs = OPER.getProcesses(app);

		return ViewProcess
				.getListView()
				.header("app",
						"The list of processes for "
								+ ("all".equals(app) ? "all applications."
										: "the '" + app + "' application."))
				.addProcessList(procs)
				.addProcessFilter(OPER.getProcessFilter())
				.info(super.getListDescription(procs, "process"));
	}

	/**
	 * Shows information about process.
	 * 
	 * @param cmd
	 *            the command which invokes a process.
	 * @return the view model.
	 * @throws Exception
	 *             is the view constructing has failed.
	 */
	@RequestMapping(value = "/info/{cmd}", method = RequestMethod.GET)
	public ModelAndView onProc(@PathVariable String cmd) throws Exception {
		LOG.debug("CALL: /proc/action/info/{" + cmd + "}");

		return ViewProcess.getInfoView().header("proc", "Process: " + cmd)
				.addProcessRef(cmd).addProcessDescr(OPER.getProcessDescr(cmd))
				.info("For configuring this process press button 'Config'.");
	}

	/**
	 * Configures process by the wizard.
	 * 
	 * @param cmd
	 *            the command which invokes a process.
	 * @return the view model.
	 * @throws Exception
	 *             is the view constructing has failed.
	 */
	@RequestMapping(value = "/wiz/{cmd}", method = RequestMethod.GET)
	public ModelAndView onConfigWizard(@PathVariable String cmd)
			throws Exception {
		LOG.debug("CALL: /proc/action/wiz/{" + cmd + "}");

		return ViewProcess.getWizView().header("proc", "Process: " + cmd)
				.addProcessRef(cmd).addProcessDescr(OPER.getProcessDescr(cmd))
				.info("For launching this process press the button 'Launch'.");
	}

	/**
	 * Configures process by the command line.
	 * 
	 * @param cmd
	 *            the command which invokes a process.
	 * @return the view model.
	 * @throws Exception
	 *             is the view constructing has failed.
	 */
	@RequestMapping(value = "/cmd/{cmd}", method = RequestMethod.GET)
	public ModelAndView onConfigCommandLine(@PathVariable String cmd)
			throws Exception {
		LOG.debug("CALL: /proc/action/cmd/{" + cmd + "}");

		return ViewProcess.getCmdView().header("proc", "Process: " + cmd)
				.addProcessRef(cmd).addProcessUsage(OPER.getProcessUsage(cmd))
				.info("For launching this process press the button 'Launch'.");
	}

	/**
	 * Launches the process execution,
	 * 
	 * @param cmd
	 *            the command which launches a process.
	 * @return the view model.
	 * @throws Exception
	 *             is the view constructing has failed.
	 */
	@RequestMapping(value = "/launch/{cmd}", method = RequestMethod.POST)
	public ModelAndView onLaunch(@PathVariable String cmd) throws Exception {
		LOG.debug("CALL: /proc/action/launch/{" + cmd + "}");

		// Launching parameters extraction.
		BiMap<String, String> params = HashBiMap.create();
		Enumeration<String> names = REQUEST.getParameterNames();
		while (names.hasMoreElements()) {
			String name = names.nextElement();
			String value = REQUEST.getParameter(name);
			LOG.debug("PARAMS: " + name + "=" + value);

			if (name.startsWith("radio")) {
				String group = name.substring(0, name.indexOf('-'));
				String quantifier = name.substring(name.indexOf('-') + 1);

				if (quantifier.equals("opt")) {
					// This is a selected radio button option.
					if (params.containsKey(name)) {
						params.inverse().put(params.get(name), value);
					} else {
						params.put(value, group + "-arg");
					}
				} else { 
					// This is a selected radio button argument.
					if (params.inverse().containsKey(name)) {
						params.put(params.inverse().get(name), value);
					} else {
						params.inverse().put(value, group + "-opt");
					}
				}
			} else {
				params.put(name, value);
			}
		}

		// Launching parameters cleanup.
		Iterator<String> keys = params.keySet().iterator();
		while (keys.hasNext()) {
			String key = keys.next();
			if (key.startsWith("radio")) {
				keys.remove();
			} else {

				String value = params.get(key);
				if (value == null) {
					params.put(key, "");
				} else {
					if (value.startsWith("radio")) {
						params.put(key, "");
					}
				}
			}

		}

		StringBuilder args = new StringBuilder();
		if (params.containsKey("command-line")) {
			args.append(params.get("command-line"));
		} else {
			for (Map.Entry<String, String> param : params.entrySet()) {
				if (args.length() > 0) {
					args.append(' ');
				}
				args.append('-');
				args.append(param.getKey());
				if (!param.getValue().isEmpty()) {
					args.append(' ');
					if (param.getValue().contains(" ")) {
						args.append('\"' + param.getValue() + '\"');
					} else {
						args.append(param.getValue());
					}
				}
			}
		}

		String id = OPER.launchProcess(cmd, args.toString());

		return ViewJob.getInfoView().header("job", "Job: " + id).addJobId(id)
				.addJobActiveStatus(true).addJobFailedStatus(false)
				.addJobOutcome(OPER.getJobOutcome(id))
				.info("The job execution in progress.");
	}
}
