/*
 * Copyright (c) 2002-2004
 * All rights reserved.
 */

package com.atlassian.jira.web.action.issue;

import com.atlassian.jira.ComponentManager;
import com.atlassian.jira.ManagerFactory;
import com.atlassian.jira.exception.DataAccessException;
import com.atlassian.jira.exception.IssueNotFoundException;
import com.atlassian.jira.exception.IssuePermissionException;
import com.atlassian.jira.issue.Issue;
import com.atlassian.jira.issue.MutableIssue;
import com.atlassian.jira.issue.security.IssueSecurityLevelManager;
import com.atlassian.jira.security.Permissions;
import com.atlassian.jira.user.UserAccessor;
import com.atlassian.jira.util.EasyList;
import com.atlassian.jira.web.action.IssueActionSupport;
import com.atlassian.jira.web.action.util.JiraLicenseUtils;
import com.atlassian.license.License;
import com.atlassian.license.LicenseManager;

import com.opensymphony.user.User;
import com.opensymphony.util.TextUtils;
import org.ofbiz.core.entity.GenericEntityException;
import org.ofbiz.core.entity.GenericValue;

import webwork.action.ResultException;
import webwork.action.ServletActionContext;

import java.util.Map;

/**
 * An abstract action that should be extended by any action which wants to 'select' an issue <p/> TODO: Test this class
 * thoroughly - especially setKey()
 */
public abstract class AbstractIssueSelectAction extends IssueActionSupport
{
	protected Long id;
	private GenericValue project;
	private String key;
	private String viewIssueKey;
	private MutableIssue issueObject;

	/** have we tried to load the issue */
	private boolean loaded = false;

	// ------------------------------------------------------------------------------------------- webwork override hack

	/**
	 * Override the default {@link webwork.action.ActionSupport#validate()} method so we can return our own custom
	 * result if the user does not have permission to view an issue. This is always called first in an Action instances
	 * lifecycle so is guaranteed to invoke the IssuePermissionException, although subsequent calls to getIssue() do
	 * not.
	 * 
	 * @throws ResultException
	 *             this contains the result that webwork will return
	 * @see #getIssueObject()
	 */
	protected final void validate() throws ResultException
	{
		loadIssueObject();
		try
		{
			checkIssuePermissions();
		}
		catch (IssuePermissionException goToPermissionFailed)
		{
			ServletActionContext.getRequest().setAttribute("issue", getIssue());
			throw new ResultException(PERMISSION_VIOLATION_RESULT);
		}
		super.validate();
	}

	public final String execute() throws Exception
	{
		loadIssueObject();
		try
		{
			checkIssuePermissions();
		}
		catch (IssuePermissionException goToPermissionFailed)
		{
			return PERMISSION_VIOLATION_RESULT;
		}
		return super.execute();
	}

	/**
	 * all children should check this or replicate its behaviour, consider templating this method.
	 */
	public String doDefault() throws Exception
	{
		loadIssueObject();
		try
		{
			checkIssuePermissions();
		}
		catch (IssuePermissionException goToPermissionFailed)
		{
			return PERMISSION_VIOLATION_RESULT;
		}
		return SUCCESS;
	}

	// ----------------------------------------------------------------------------- support for common issue operations

	public boolean isIssueExists()
	{
		loadIssueObject();
		return issueObject != null;
	}

	/**
	 * Gets the current issue's GenericValue.
	 * 
	 * @throws IssuePermissionException
	 *             if the user does not have permission to view the issue
	 * @throws IllegalStateException
	 *             if the neither key or id are set
	 * @see #getIssueObject()
	 * @see #validate()
	 */
	public GenericValue getIssue() throws IssuePermissionException, IllegalStateException
	{
		loadIssueObject();
		checkIssuePermissions();
		return (issueObject == null) ? null : issueObject.getGenericValue();
	}

	protected void checkIssuePermissions() throws IssuePermissionException
	{
		if (issueObject == null)
		{
			return;
		}
		final User user = getRemoteUser();
		// if the user doesnt have access to this issue then dont return it
		if (!ManagerFactory.getPermissionManager().hasPermission(Permissions.BROWSE, issueObject.getGenericValue(), user))
		{
			if (errorMessages == null || !errorMessages.contains(getText("admin.errors.issues.no.permission.to.see")))
			{
				addErrorMessage(getText("admin.errors.issues.no.permission.to.see"));
			}
			String issueStr = issueObject.getKey() != null ? issueObject.getKey() : issueObject.toString();
			throw new IssuePermissionException("User '" + user + "' does not have permission to see issue '" + issueStr + "'");
		}
	}

	/**
	 * load the issueObject if not null
	 * 
	 * @throws IllegalStateException
	 *             if the neither key or id are set
	 * @throws IssueNotFoundException
	 *             if it cannot find the id or key
	 * @throws DataAccessException
	 *             if something goes really wrong at the backend
	 */
	private void loadIssueObject() throws IssueNotFoundException, IllegalStateException, DataAccessException
	{
		if (issueObject == null)
		{
			if (loaded)
			{
				return;
			}
			if (id != null)
			{
				loaded = true;
				issueObject = getIssueManager().getIssueObject(id);
			}
			else if (key != null)
			{
				loaded = true;
				issueObject = getIssueManager().getIssueObject(key);
			}
			else
			{
				throw new IllegalStateException("Cannot load an issue without an id or a key!");
			}
			if (issueObject == null)
			{
				if (errorMessages == null || !errorMessages.contains(getText("issue.wasdeleted")))
				{
					addErrorMessage(getText("issue.wasdeleted"));
				}
				throw new IssueNotFoundException("Issue with id '" + id + "' or key '" + key + "' could not be found in the system");
			}
		}
	}

	/**
	 * Set the current issue. WARNING, this method should be called very carefully, if at all.
	 * <p>
	 * This is public so tests can set the issue, see TestViewIssue
	 * 
	 * @throws IssuePermissionException
	 *             if the user does not have permission to view the issue
	 */
	public void setIssue(GenericValue issue) throws IssuePermissionException
	{
		if (issueObject != null)
		{
			throw new IllegalStateException("Cannot set the issue on this action, issue is already set!");
		}
		this.issueObject = ComponentManager.getInstance().getIssueFactory().getIssue(issue);
		checkIssuePermissions();
	}

	/**
	 * @deprecated this guy doesn't appear to have any clients, check the webwork to make sure then delete. DO NOT CALL.
	 *             No alternative implementation.
	 */
	protected void resetIssue()
	{
		issueObject = null;
	}

	/**
	 * Get Id of current issue.
	 * 
	 * @return Integer Id, or null if issue not set
	 */
	public Long getId() throws GenericEntityException
	{
		loadIssueObject();
		if (issueObject != null)
		{
			return issueObject.getId();
		}

		return id;
	}

	/**
	 * Set the the current issue by its id.
	 * 
	 * @param id
	 *            Eg. from {@link com.atlassian.jira.issue.Issue#getId()}
	 */
	public void setId(Long id)
	{
		this.id = id;
	}

	/**
	 * Get key of current issue.
	 * 
	 * @return Issue key, or null if not set
	 */
	protected String getKey() throws GenericEntityException
	{
		loadIssueObject();
		if (issueObject != null)
		{
			return issueObject.getKey();
		}

		return key;
	}

	/**
	 * Set current issue by its key.
	 * 
	 * @param key
	 *            Issue key.
	 */
	public void setKey(String key)
	{
		// Ensure key is uppercase for cache or database search
		this.key = key.toUpperCase();
	}

	public GenericValue getProject()
	{
		if (project == null && getIssueObject() != null)
		{
			project = getProjectManager().getProject(getIssue());
		}

		return project;
	}

	public String getSecurityLevelName() throws Exception
	{
		License license = LicenseManager.getInstance().getLicense(JiraLicenseUtils.JIRA_LICENSE_KEY);
		if (license != null && license.isLicenseLevel(EasyList.build(JiraLicenseUtils.JIRA_ENTERPRISE_LEVEL)))
		{
			if (getIssueObject() != null)
			{
				IssueSecurityLevelManager secur = ManagerFactory.getIssueSecurityLevelManager();
				return secur.getIssueSecurityName(getIssueObject().getSecurityLevelId());
			}
		}
		return null;
	}

	public GenericValue getSecurityLevel() throws Exception
	{
		//TODO make this and getSecurityLevel(id) use the Issue object
		License license = LicenseManager.getInstance().getLicense(JiraLicenseUtils.JIRA_LICENSE_KEY);
		if (license != null && license.isLicenseLevel(EasyList.build(JiraLicenseUtils.JIRA_ENTERPRISE_LEVEL)))
		{
			if (getIssueObject() != null)
			{
				return getIssueObject().getSecurityLevel();
			}
		}
		return null;
	}

	public GenericValue getSecurityLevel(Long id) throws Exception
	{
		//TODO make this and getSecurityLevel() use the Issue object
		License license = LicenseManager.getInstance().getLicense(JiraLicenseUtils.JIRA_LICENSE_KEY);
		if (license != null && license.isLicenseLevel(EasyList.build(JiraLicenseUtils.JIRA_ENTERPRISE_LEVEL)))
		{
			if (getIssue() != null)
			{
				IssueSecurityLevelManager secur = ManagerFactory.getIssueSecurityLevelManager();
				return secur.getIssueSecurity(id);
			}
		}
		return null;
	}

	/**
	 * Get the different levels of security that can be set for this issue
	 * 
	 * @return Map containing the security levels
	 * @throws Exception
	 */
	public Map getSecurityLevels() throws Exception
	{
		return ((UserAccessor) ComponentManager.getInstance().getContainer().getComponentInstance(UserAccessor.class)).getSecurityLevelsForProject(
		    getRemoteUser(), getProject());
	}

	/**
	 * Get the different levels of security that can be set for this project
	 * 
	 * @return Map containing the security levels
	 * @throws Exception
	 */
	public Map getSecurityLevels(GenericValue project) throws Exception
	{
		return ((UserAccessor) ComponentManager.getInstance().getContainer().getComponentInstance(UserAccessor.class)).getSecurityLevelsForProject(
		    getRemoteUser(), project);
	}

	/**
	 * Get the default security level for issues within the current project
	 * 
	 * @return The id of the security level
	 * @throws Exception
	 */
	public Long getDefaultSecurityLevel() throws Exception
	{
		License license = LicenseManager.getInstance().getLicense(JiraLicenseUtils.JIRA_LICENSE_KEY);
		if (license != null && license.isLicenseLevel(EasyList.build(JiraLicenseUtils.JIRA_ENTERPRISE_LEVEL)))
		{
			IssueSecurityLevelManager secur = ManagerFactory.getIssueSecurityLevelManager();

			return secur.getSchemeDefaultSecurityLevel(getProject());
		}

		return null;
	}

	public String getViewIssueKey()
	{
		return viewIssueKey;
	}

	public void setViewIssueKey(String viewIssueKey)
	{
		this.viewIssueKey = viewIssueKey;
	}

	protected String redirectToView() throws Exception
	{
		return getRedirect(getViewUrl());
	}

	protected String getViewUrl()
	{
		return "/browse/" + getRedirectKey();
	}

	/**
	 * Return a context-relative URL to this issue.
	 */
	public String getIssuePath()
	{
		return request.getContextPath() + "/browse/" + getRedirectKey();
	}

	private String getRedirectKey()
	{
		String key;
		if (TextUtils.stringSet(getViewIssueKey()))
		{
			key = getViewIssueKey();
		}
		else
		{
			key = getIssue().getString("key");
		}
		return key;
	}

	/**
	 * Determine whether the current user can edit or resolve this issue
	 */
	public boolean isEditable()
	{
		return isEditable(getIssueObject());
	}

	/**
	 * Determine whether the current user can edit or resolve the passed issue.
	 */
	public boolean isEditable(Issue issue)
	{
		// preconditions
		if (issue == null)
		{
			throw new IssueNotFoundException("Issue unexpectedly null");
		}

		boolean hasPermission = isHasIssuePermission(Permissions.EDIT_ISSUE, issue.getGenericValue());
		if (hasPermission)
		{
			return isWorkflowAllowsEdit(issue);
		}
		return false;
	}

	protected boolean isWorkflowAllowsEdit(Issue issue)
	{
		return getIssueManager().isEditable(issue);
	}

	/**
	 * Returns the current {@link Issue}.
	 * 
	 * @see #getIssue()
	 */
	public MutableIssue getIssueObject()
	{
		loadIssueObject();
		checkIssuePermissions();
		return issueObject;
	}
}