package org.apache.manifoldcf.authorities.authorities.confluence;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.manifoldcf.agents.interfaces.ServiceInterruption;
import org.apache.manifoldcf.authorities.authorities.BaseAuthorityConnector;
import org.apache.manifoldcf.authorities.interfaces.AuthorizationResponse;
import org.apache.manifoldcf.core.interfaces.ConfigParams;
import org.apache.manifoldcf.core.interfaces.IHTTPOutput;
import org.apache.manifoldcf.core.interfaces.IPasswordMapperActivity;
import org.apache.manifoldcf.core.interfaces.IPostParameters;
import org.apache.manifoldcf.core.interfaces.IThreadContext;
import org.apache.manifoldcf.core.interfaces.ManifoldCFException;
import org.apache.manifoldcf.crawler.connectors.confluence.ConfluenceConfiguration;
import org.apache.manifoldcf.crawler.connectors.confluence.client.ConfluenceClient;
import org.apache.manifoldcf.crawler.connectors.confluence.model.ConfluenceUser;
import org.apache.manifoldcf.crawler.system.Logging;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>
 * Confluence Authority Connector class
 * </p>
 * <p>
 * ManifoldCF Authority connector to deal with Confluence documents
 * </p>
 * 
 * @author Antonio David Perez Morales <adperezmorales@gmail.com>
 *
 */
public class ConfluenceAuthorityConnector extends BaseAuthorityConnector {

	/*
	 * Prefix for Confluence configuration and specification parameters
	 */
	private static final String PARAMETER_PREFIX = "confluence_";

	/* Configuration tabs */
	private static final String CONF_SERVER_TAB_PROPERTY = "ConfluenceAuthorityConnector.Server";

	// pages & js
	// Template names for Confluence configuration
	/**
	 * Forward to the javascript to check the configuration parameters
	 */
	private static final String EDIT_CONFIG_HEADER_FORWARD = "editConfiguration_conf.js";
	/**
	 * Server tab template
	 */
	private static final String EDIT_CONFIG_FORWARD_SERVER = "editConfiguration_conf_server.html";

	/**
	 * Forward to the HTML template to view the configuration parameters
	 */
	private static final String VIEW_CONFIG_FORWARD = "viewConfiguration_conf.html";


	private Logger logger = LoggerFactory
			.getLogger(ConfluenceAuthorityConnector.class);

	/* Confluence instance parameters */
	protected String protocol = null;
	protected String host = null;
	protected String port = null;
	protected String path = null;
	protected String username = null;
	protected String password = null;

	protected ConfluenceClient confluenceClient = null;

	/**
	 * <p>
	 * Default constructor
	 * </p>
	 */
	public ConfluenceAuthorityConnector() {
		super();
	}
	
	/**
	 * Used Mainly for testing
	 * 
	 * @param client Injected Confluence Client
	 */
	public void setConfluenceClient(ConfluenceClient client){
		this.confluenceClient = client;
	}

	

	/**
	 * Close the connection. Call this before discarding the connection.
	 */
	@Override
	public void disconnect() throws ManifoldCFException {
		if (confluenceClient != null) {
			confluenceClient = null;
		}

		protocol = null;
		host = null;
		port = null;
		path = null;
		username = null;
		password = null;

	}

	/**
	 * Makes connection to server
	 * 
	 * 
	 */
	@Override
	public void connect(ConfigParams configParams) {
		super.connect(configParams);

		protocol = params.getParameter(ConfluenceConfiguration.Server.PROTOCOL);
		host = params.getParameter(ConfluenceConfiguration.Server.HOST);
		port = params.getParameter(ConfluenceConfiguration.Server.PORT);
		path = params.getParameter(ConfluenceConfiguration.Server.PATH);
		username = params.getParameter(ConfluenceConfiguration.Server.USERNAME);
		password = params
				.getObfuscatedParameter(ConfluenceConfiguration.Server.PASSWORD);

		try {
			initConfluenceClient();
		} catch (ManifoldCFException e) {
			logger.debug(
					"Not possible to initialize Confluence client. Reason: {}",
					e.getMessage());
		}
	}

	/**
	 * Checks if connection is available
	 */
	@Override
	public String check() throws ManifoldCFException {
		try {
			if (!isConnected()) {
				initConfluenceClient();
			}
			Boolean result = confluenceClient.checkAuth();
			if (result)
				return super.check();
			else
				throw new ManifoldCFException(
						"Confluence instance could not be reached");
		} catch (ServiceInterruption e) {
			return "Connection temporarily failed: " + e.getMessage();
		} catch (ManifoldCFException e) {
			return "Connection failed: " + e.getMessage();
		} catch (Exception e) {
			return "Connection failed: " + e.getMessage();
		}
	}

	/**
	 * <p>
	 * Initialize Confluence client using the configured parameters
	 * 
	 * @throws ManifoldCFException
	 */
	protected void initConfluenceClient() throws ManifoldCFException {
		if (confluenceClient == null) {

			if (StringUtils.isEmpty(protocol)) {
				throw new ManifoldCFException("Parameter "
						+ ConfluenceConfiguration.Server.PROTOCOL
						+ " required but not set");
			}

			if (Logging.connectors.isDebugEnabled()) {
				Logging.connectors.debug("Confluence protocol = '" + protocol
						+ "'");
			}

			if (StringUtils.isEmpty(host)) {
				throw new ManifoldCFException("Parameter "
						+ ConfluenceConfiguration.Server.HOST
						+ " required but not set");
			}

			if (Logging.connectors.isDebugEnabled()) {
				Logging.connectors.debug("Confluence host = '" + host + "'");
			}

			if (Logging.connectors.isDebugEnabled()) {
				Logging.connectors.debug("Confluence port = '" + port + "'");
			}

			if (StringUtils.isEmpty(path)) {
				throw new ManifoldCFException("Parameter "
						+ ConfluenceConfiguration.Server.PATH
						+ " required but not set");
			}

			if (Logging.connectors.isDebugEnabled()) {
				Logging.connectors.debug("Confluence path = '" + path + "'");
			}

			if (Logging.connectors.isDebugEnabled()) {
				Logging.connectors.debug("Confluence username = '" + username
						+ "'");
			}

			if (Logging.connectors.isDebugEnabled()) {
				Logging.connectors
						.debug("Confluence password '" + password != null ? "set"
								: "not set" + "'");
			}

			int portInt;
			if (port != null && port.length() > 0) {
				try {
					portInt = Integer.parseInt(port);
				} catch (NumberFormatException e) {
					throw new ManifoldCFException("Bad number: "
							+ e.getMessage(), e);
				}
			} else {
				if (protocol.toLowerCase(Locale.ROOT).equals("http"))
					portInt = 80;
				else
					portInt = 443;
			}

			/* Generating a client to perform Confluence requests */
			confluenceClient = new ConfluenceClient(protocol, host, portInt,
					path, username, password);
		}

	}

	/**
	 * This method is called to assess whether to count this connector instance
	 * should actually be counted as being connected.
	 *
	 * @return true if the connector instance is actually connected.
	 */
	@Override
	public boolean isConnected() {
		return confluenceClient != null;
	}


	private void fillInServerConfigurationMap(Map<String, String> serverMap,
			IPasswordMapperActivity mapper, ConfigParams parameters) {
		String confluenceProtocol = parameters
				.getParameter(ConfluenceConfiguration.Server.PROTOCOL);
		String confluenceHost = parameters
				.getParameter(ConfluenceConfiguration.Server.HOST);
		String confluencePort = parameters
				.getParameter(ConfluenceConfiguration.Server.PORT);
		String confluencePath = parameters
				.getParameter(ConfluenceConfiguration.Server.PATH);
		String confluenceUsername = parameters
				.getParameter(ConfluenceConfiguration.Server.USERNAME);
		String confluencePassword = parameters
				.getObfuscatedParameter(ConfluenceConfiguration.Server.PASSWORD);

		if (confluenceProtocol == null)
			confluenceProtocol = ConfluenceConfiguration.Server.PROTOCOL_DEFAULT_VALUE;
		if (confluenceHost == null)
			confluenceHost = ConfluenceConfiguration.Server.HOST_DEFAULT_VALUE;
		if (confluencePort == null)
			confluencePort = ConfluenceConfiguration.Server.PORT_DEFAULT_VALUE;
		if (confluencePath == null)
			confluencePath = ConfluenceConfiguration.Server.PATH_DEFAULT_VALUE;

		if (confluenceUsername == null)
			confluenceUsername = ConfluenceConfiguration.Server.USERNAME_DEFAULT_VALUE;
		if (confluencePassword == null)
			confluencePassword = ConfluenceConfiguration.Server.PASSWORD_DEFAULT_VALUE;
		else
			confluencePassword = mapper.mapPasswordToKey(confluencePassword);

		serverMap.put(PARAMETER_PREFIX
				+ ConfluenceConfiguration.Server.PROTOCOL, confluenceProtocol);
		serverMap.put(PARAMETER_PREFIX + ConfluenceConfiguration.Server.HOST,
				confluenceHost);
		serverMap.put(PARAMETER_PREFIX + ConfluenceConfiguration.Server.PORT,
				confluencePort);
		serverMap.put(PARAMETER_PREFIX + ConfluenceConfiguration.Server.PATH,
				confluencePath);
		serverMap.put(PARAMETER_PREFIX
				+ ConfluenceConfiguration.Server.USERNAME, confluenceUsername);
		serverMap.put(PARAMETER_PREFIX
				+ ConfluenceConfiguration.Server.PASSWORD, confluencePassword);
	}

	@Override
	public void viewConfiguration(IThreadContext threadContext,
			IHTTPOutput out, Locale locale, ConfigParams parameters)
			throws ManifoldCFException, IOException {
		Map<String, String> paramMap = new HashMap<String, String>();

		/* Fill server configuration parameters */
		fillInServerConfigurationMap(paramMap, out, parameters);

		Messages.outputResourceWithVelocity(out, locale, VIEW_CONFIG_FORWARD,
				paramMap, true);
	}

	@Override
	public void outputConfigurationHeader(IThreadContext threadContext,
			IHTTPOutput out, Locale locale, ConfigParams parameters,
			List<String> tabsArray) throws ManifoldCFException, IOException {
		// Add the Server tab
		tabsArray.add(Messages.getString(locale, CONF_SERVER_TAB_PROPERTY));
		// Map the parameters
		Map<String, String> paramMap = new HashMap<String, String>();

		/* Fill server configuration parameters */
		fillInServerConfigurationMap(paramMap, out, parameters);

		// Output the Javascript - only one Velocity template for all tabs
		Messages.outputResourceWithVelocity(out, locale,
				EDIT_CONFIG_HEADER_FORWARD, paramMap, true);
	}

	@Override
	public void outputConfigurationBody(IThreadContext threadContext,
			IHTTPOutput out, Locale locale, ConfigParams parameters,
			String tabName) throws ManifoldCFException, IOException {

		// Call the Velocity templates for each tab
		Map<String, String> paramMap = new HashMap<String, String>();
		// Set the tab name
		paramMap.put("TabName", tabName);

		// Fill in the parameters
		fillInServerConfigurationMap(paramMap, out, parameters);

		// Server tab
		Messages.outputResourceWithVelocity(out, locale,
				EDIT_CONFIG_FORWARD_SERVER, paramMap, true);

	}

	/*
	 * Repository specification post handle, (server and proxy & client secret
	 * etc)
	 * 
	 * @see
	 * org.apache.manifoldcf.core.connector.BaseConnector#processConfigurationPost
	 * (org.apache.manifoldcf.core.interfaces.IThreadContext,
	 * org.apache.manifoldcf.core.interfaces.IPostParameters,
	 * org.apache.manifoldcf.core.interfaces.ConfigParams)
	 */
	@Override
	public String processConfigurationPost(IThreadContext threadContext,
			IPostParameters variableContext, ConfigParams parameters)
			throws ManifoldCFException {

		String confluenceProtocol = variableContext
				.getParameter(PARAMETER_PREFIX
						+ ConfluenceConfiguration.Server.PROTOCOL);
		if (confluenceProtocol != null)
			parameters.setParameter(ConfluenceConfiguration.Server.PROTOCOL,
					confluenceProtocol);

		String confluenceHost = variableContext.getParameter(PARAMETER_PREFIX
				+ ConfluenceConfiguration.Server.HOST);
		if (confluenceHost != null)
			parameters.setParameter(ConfluenceConfiguration.Server.HOST,
					confluenceHost);

		String confluencePort = variableContext.getParameter(PARAMETER_PREFIX
				+ ConfluenceConfiguration.Server.PORT);
		if (confluencePort != null)
			parameters.setParameter(ConfluenceConfiguration.Server.PORT,
					confluencePort);

		String confluencePath = variableContext.getParameter(PARAMETER_PREFIX
				+ ConfluenceConfiguration.Server.PATH);
		if (confluencePath != null)
			parameters.setParameter(ConfluenceConfiguration.Server.PATH,
					confluencePath);

		String confluenceUsername = variableContext
				.getParameter(PARAMETER_PREFIX
						+ ConfluenceConfiguration.Server.USERNAME);
		if (confluenceUsername != null)
			parameters.setParameter(ConfluenceConfiguration.Server.USERNAME,
					confluenceUsername);

		String confluencePassword = variableContext
				.getParameter(PARAMETER_PREFIX
						+ ConfluenceConfiguration.Server.PASSWORD);
		if (confluencePassword != null)
			parameters.setObfuscatedParameter(
					ConfluenceConfiguration.Server.PASSWORD,
					variableContext.mapKeyToPassword(confluencePassword));

		/* null means process configuration has been successful */
		return null;
	}
	
	  /*
	   * (non-Javadoc)
	   * @see org.apache.manifoldcf.authorities.authorities.BaseAuthorityConnector#getDefaultAuthorizationResponse(java.lang.String)
	   */
	  @Override
	  public AuthorizationResponse getDefaultAuthorizationResponse(String userName) {
	    return RESPONSE_UNREACHABLE;
	  }

	  /*
	   * (non-Javadoc)
	   * @see org.apache.manifoldcf.authorities.authorities.BaseAuthorityConnector#getAuthorizationResponse(java.lang.String)
	   */
	  @Override
	  public AuthorizationResponse getAuthorizationResponse(String userName)
	      throws ManifoldCFException {
	    try {
	      ConfluenceUser confluenceUser = confluenceClient.getUserAuthorities(userName);
	      if (confluenceUser.getUsername() == null
	          || confluenceUser.getUsername().isEmpty()
	          || confluenceUser.getAuthorities().isEmpty())
	        return RESPONSE_USERNOTFOUND;
	      else
	        return new AuthorizationResponse(
	            confluenceUser.getAuthorities().toArray(new String[confluenceUser.getAuthorities().size()]),
	            AuthorizationResponse.RESPONSE_OK);
	    } catch (Exception e) {
	      return RESPONSE_UNREACHABLE;
	    }
	  }

}
