/*
 * Created by IntelliJ IDEA.
 * User: Mike
 * Date: Feb 19, 2004
 * Time: 1:48:19 PM
 */
package com.atlassian.confluence.rpc.soap.services;

import com.atlassian.confluence.core.ContentEntityManager;
import com.atlassian.confluence.core.ContentEntityObject;
import com.atlassian.confluence.labels.*;
import com.atlassian.confluence.labels.persistence.dao.LabelSearchResult;
import com.atlassian.confluence.renderer.WikiStyleRenderer;
import com.atlassian.confluence.rpc.NotPermittedException;
import com.atlassian.confluence.rpc.RemoteException;
import com.atlassian.confluence.rpc.soap.beans.RemoteLabel;
import com.atlassian.confluence.rpc.soap.beans.RemoteSearchResult;
import com.atlassian.confluence.rpc.soap.beans.RemoteSpace;
import com.atlassian.confluence.security.Permission;
import com.atlassian.confluence.security.PermissionManager;
import com.atlassian.confluence.security.SpacePermissionManager;
import com.atlassian.confluence.spaces.Space;
import com.atlassian.confluence.spaces.SpaceDescription;
import com.atlassian.confluence.spaces.SpaceManager;
import com.atlassian.confluence.user.AuthenticatedUserThreadLocal;
import com.atlassian.confluence.util.LabelUtil;
import com.opensymphony.util.TextUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

public class LabelsSoapService
{
    public void setLabelManager(LabelManager labelManager)
    {
        this.labelManager = labelManager;
    }

    public void setContentEntityManager(ContentEntityManager contentEntityManager)
    {
        this.contentEntityManager = contentEntityManager;
    }

    public void setSpacePermissionManager(SpacePermissionManager spacePermissionManager)
    {
        this.spacePermissionManager = spacePermissionManager;
    }

    public void setSpaceManager(SpaceManager spaceManager)
    {
        this.spaceManager = spaceManager;
    }

    public void setPermissionManager(PermissionManager permissionManager)
    {
        this.permissionManager = permissionManager;
    }

    public void setWikiStyleRenderer(WikiStyleRenderer wikiStyleRenderer)
    {
        this.wikiStyleRenderer = wikiStyleRenderer;
    }

    public void setSoapServiceHelper(SoapServiceHelper soapServiceHelper)
    {
        this.soapServiceHelper = soapServiceHelper;
    }

    LabelManager labelManager;
    ContentEntityManager contentEntityManager;
    SpacePermissionManager spacePermissionManager;
    SpaceManager spaceManager;
    PermissionManager permissionManager;
    WikiStyleRenderer wikiStyleRenderer;
    SoapServiceHelper soapServiceHelper;

    private RemoteLabel[] generateRemoteLabelArray(List list)
    {
        if (list == null)
            return null;

        RemoteLabel[] remoteLabelArray = new RemoteLabel[list.size()];

        for (int i = 0; i < list.size(); i++)
        {
            Label label = getLabelFromUnknownLabelLikeObject(list.get(i));

            RemoteLabel remoteLabel;

            // Add the correct type of label
            remoteLabel = new RemoteLabel(label);

            remoteLabelArray[i] = remoteLabel;
        }

        return remoteLabelArray;
    }

    private RemoteLabel[] generateRemoteLabelCountArray(List list)
    {
        if (list == null)
            return null;

        RemoteLabel[] remoteLabelArray = new RemoteLabel[list.size()];

        for (int i = 0; i < list.size(); i++)
        {
            Label label = getLabelFromUnknownLabelLikeObject(list.get(i));
            RemoteLabel remoteLabel;

            // Add the correct type of label
            remoteLabel = new RemoteLabel(label);

            remoteLabelArray[i] = remoteLabel;
        }

        return remoteLabelArray;
    }

    private Label getLabelFromUnknownLabelLikeObject(Object o)
    {
        if (o instanceof LabelSearchResult)
            o = ((LabelSearchResult) o).getLabel();

        return (Label) o;
    }

    public RemoteLabel[] getLabelsById(long objectId) throws RemoteException
    {
        ContentEntityObject object = contentEntityManager.getById(objectId);

        assertObjectExists(objectId, object);
        assertUserCanViewObject(object);

        List labelsArray = object.getLabels();

        // Filter out the labels that the CURRENT user cannot see
        List filteredList = LabelPermissionSupport.filterVisibleLabels(labelsArray, AuthenticatedUserThreadLocal.getUser(), true);

        return generateRemoteLabelArray(filteredList);
    }

    public RemoteLabel[] getMostPopularLabels(int maxCount) throws RemoteException
    {
        List labels = labelManager.getMostPopularLabels(maxCount);

        // Filter out the labels that the CURRENT user cannot see
        List filteredLabelsList = LabelPermissionSupport.filterVisibleLabels(labels, AuthenticatedUserThreadLocal.getUser(), true);

        return generateRemoteLabelArray(filteredLabelsList);
    }

    public RemoteLabel[] getMostPopularLabelsInSpace(String spaceKey, int maxCount) throws RemoteException
    {
        soapServiceHelper.retrieveSpace(spaceKey);

        List labelsList = labelManager.getMostPopularLabelsInSpace(spaceKey, maxCount);

        // Filter out the labels that the CURRENT user cannot see
        List filteredLabelsList = LabelPermissionSupport.filterVisibleLabels(labelsList, AuthenticatedUserThreadLocal.getUser(), true);

        return generateRemoteLabelCountArray(filteredLabelsList);
    }

    private RemoteSearchResult[] getFilteredContentForLabelObject(Label label) throws RemoteException
    {
        if (label == null)
            throw new RemoteException("The given label ID or name was not valid");

        List content = labelManager.getCurrentContentForLabel(label);
        ArrayList remoteContent = new ArrayList();

        // Filter out anything the user cannot see
        for (int i = 0; i < content.size(); i++)
        {
            ContentEntityObject object = (ContentEntityObject) content.get(i);

            // Check if the current user has permission to view the object
            if (!LabelPermissionSupport.userCanViewObject(object, permissionManager))
                continue;

            // Create a RemoteSearchResult object to represent the new object (which implemenets Addressable)
            RemoteSearchResult remoteObject = new RemoteSearchResult(object);

            remoteContent.add(remoteObject);
        }

        return (RemoteSearchResult[]) remoteContent.toArray(new RemoteSearchResult[remoteContent.size()]);
    }

    public RemoteSearchResult[] getLabelContentById(long labelId) throws RemoteException
    {
        Label label = labelManager.getLabel(labelId);

        return getFilteredContentForLabelObject(label);
    }

    public RemoteSearchResult[] getLabelContentByName(String labelName) throws RemoteException
    {
        Label label = validateAndGetLabel(labelName);

        return getFilteredContentForLabelObject(label);
    }

    public RemoteSearchResult[] getLabelContentByObject(RemoteLabel labelObject) throws RemoteException
    {
        if (labelObject == null)
            throw new RemoteException("The RemoteLabel object must be non-null");

        return getLabelContentById(labelObject.getId());
    }

    public RemoteLabel[] getRecentlyUsedLabels(int maxCount)
    {
        List labelsList = labelManager.getRecentlyUsedLabels(maxCount);

        // Filter out the labels that the CURRENT user cannot see
        List filteredLabelsList = LabelPermissionSupport.filterVisibleLabels(labelsList, AuthenticatedUserThreadLocal.getUser(), true);

        return generateRemoteLabelArray(filteredLabelsList);
    }

    public RemoteLabel[] getRecentlyUsedLabelsInSpace(String spaceKey, int maxCount) throws RemoteException
    {
        Space space = soapServiceHelper.retrieveSpace(spaceKey);

        List labelsList = labelManager.getRecentlyUsedLabelsInSpace(spaceKey, maxCount);

        // Filter out the labels that the CURRENT user cannot see
        List filteredLabelsList = LabelPermissionSupport.filterVisibleLabels(labelsList, AuthenticatedUserThreadLocal.getUser(), true);

        return generateRemoteLabelArray(filteredLabelsList);
    }

    public RemoteSpace[] getSpacesWithLabel(String labelName) throws RemoteException
    {
        Label label = validateAndGetLabel(labelName);

        List spacesList = labelManager.getSpacesWithLabel(label);

        // Filter out any spaces the current user is unable to see
        List permittedSpacesList = permissionManager.getPermittedEntities(AuthenticatedUserThreadLocal.getUser(), Permission.VIEW, spacesList);

        // Convert the List to an array with RemoteSpace objects
        RemoteSpace[] permittedSpacesArray = new RemoteSpace[permittedSpacesList.size()];

        for (int i = 0; i < permittedSpacesList.size(); i++)
        {
            Object o = permittedSpacesList.get(i);

            if (!(o instanceof Space))
                throw new RemoteException("Expected Space object in list, but was " + o.getClass());

            permittedSpacesArray[i] = new RemoteSpace((Space) o, wikiStyleRenderer);
        }

        return permittedSpacesArray;
    }

    public RemoteLabel[] getRelatedLabels(String labelName, int maxCount) throws RemoteException
    {
        Label label = validateAndGetLabel(labelName);

        List relatedLabelsList = labelManager.getRelatedLabels(label, maxCount);

        // Filter out the labels that the CURRENT user cannot see
        List filteredLabelsList = LabelPermissionSupport.filterVisibleLabels(relatedLabelsList, AuthenticatedUserThreadLocal.getUser(), true);

        return generateRemoteLabelArray(filteredLabelsList);
    }

    public RemoteLabel[] getRelatedLabelsInSpace(String labelName, String spaceKey, int maxCount) throws RemoteException
    {
        Label label = validateAndGetLabel(labelName);

        // Check that the spaceKey is valid and throw if it doesn't exist, or the user lacks permissions
        Space space = soapServiceHelper.retrieveSpace(spaceKey);

        List relatedLabelsList = labelManager.getRelatedLabelsInSpace(label, spaceKey, maxCount);

        // Filter out the labels that the CURRENT user cannot see
        List filteredLabelsList = LabelPermissionSupport.filterVisibleLabels(relatedLabelsList, AuthenticatedUserThreadLocal.getUser(), true);

        return generateRemoteLabelArray(filteredLabelsList);
    }

    public RemoteSpace[] getSpacesContainingContentWithLabel(String labelName) throws RemoteException
    {
        Label label = validateAndGetLabel(labelName);

        // Grab the spaces
        List spacesList = labelManager.getSpacesContainingContentWithLabel(label);

        // Filter out any spaces the current user is unable to see
        List permittedSpacesList = permissionManager.getPermittedEntities(AuthenticatedUserThreadLocal.getUser(), Permission.VIEW, spacesList);

        // Convert the List to an array with RemoteSpace objects
        RemoteSpace[] permittedSpacesArray = new RemoteSpace[permittedSpacesList.size()];

        for (int i = 0; i < permittedSpacesList.size(); i++)
        {
            Object o = permittedSpacesList.get(i);

            if (!(o instanceof Space))
                throw new RemoteException("Expected Space object in list, but was " + o.getClass());

            permittedSpacesArray[i] = new RemoteSpace((Space) o, wikiStyleRenderer);
        }

        return permittedSpacesArray;
    }

    public RemoteLabel[] getLabelsByDetail(String labelName, String namespace, String spaceKey, String owner) throws RemoteException
    {
        // If the labelName has been defined, validate it... we don't call validateAndGetLabel here because we don't want to parse the label name
        if (TextUtils.stringSet(labelName))
        {
            // Throw if the label isn't valid
            if (!TextUtils.stringSet(labelName))
                throw new RemoteException("Label name must be non-null");

            // Parse the label for its namespaces, etc and throw if not valid
            ParsedLabelName parsedLabel = LabelParser.parse(labelName);

            if (parsedLabel == null)
                throw new RemoteException("The label name '" + labelName + "' is not valid.");
        }

        // Don't check the namespace, as the user can define their own

        // If a space has been passed in, make sure it's valid
        if (TextUtils.stringSet(spaceKey))
            soapServiceHelper.retrieveSpace(spaceKey);

        // If a user has been passed in, make sure they're valid
        if (TextUtils.stringSet(owner))
            soapServiceHelper.retrieveUser(owner);

        List labelsList = labelManager.getLabelsByDetail(labelName, namespace, spaceKey, owner);

        // Filter out the labels that the CURRENT user cannot see
        List filteredLabelsList = LabelPermissionSupport.filterVisibleLabels(labelsList, AuthenticatedUserThreadLocal.getUser(), true);

        return generateRemoteLabelArray(filteredLabelsList);
    }

    // Helper method to check permissions and add labels to objects
    private boolean addLabelByLabelObject(Label label, long objectId) throws NotPermittedException, RemoteException
    {
        if (label == null)
            throw new RemoteException("The given label ID or name was not valid");

        ContentEntityObject object = contentEntityManager.getById(objectId);

        assertObjectExists(objectId, object);
        assertUserCanEditLabels(label, object);

        labelManager.addLabel(object, label);

        return true;
    }

    public boolean addLabelByName(String labelName, long objectId) throws NotPermittedException, RemoteException
    {
        if (!TextUtils.stringSet(labelName))
            throw new RemoteException("Label name must be non-null");

        // Try and retrieve the object, and make sure the user has view permissions
        ContentEntityObject object = contentEntityManager.getById(objectId);
        assertObjectExists(objectId, object);

        // Split the label reference
        Collection labelNameArray = LabelUtil.split(labelName);

        ArrayList labelsList = new ArrayList();

        // Iterate through the collection and create the labels as necessary
        for (Iterator iterator = labelNameArray.iterator(); iterator.hasNext();)
        {
            String tempLabelName = (String) iterator.next();

            // Check it's valid
            if (!LabelUtil.isValidLabelName(tempLabelName))
                throw new RemoteException("Label name is invalid: " + tempLabelName);

            ParsedLabelName pln = LabelParser.parse(tempLabelName);

            if (pln == null)
                continue;

            assertUserCanEditLabels(pln.toLabel(), object);

            labelsList.add(pln);
        }

        for (Iterator i = labelsList.iterator(); i.hasNext();)
        {
            ParsedLabelName pln = (ParsedLabelName) i.next();
            pln.addLabel(object, labelManager);
        }

        return true;
    }

    public boolean addLabelById(long labelId, long objectId) throws NotPermittedException, RemoteException
    {
        Label label = labelManager.getLabel(labelId);

        return addLabelByLabelObject(label, objectId);
    }

    public boolean addLabelByObject(RemoteLabel labelObject, long objectId) throws NotPermittedException, RemoteException
    {
        if (labelObject == null)
            throw new RemoteException("RemoteLabel object must be non-null");

        return addLabelById(labelObject.getId(), objectId);
    }

    public boolean addLabelByNameToSpace(String labelName, String spaceKey) throws RemoteException
    {
        // Throw if the label isn't valid
        if (!TextUtils.stringSet(labelName))
            throw new RemoteException("Label name must be non-null");

        // Make sure the spaceKey is valid
        Space space = soapServiceHelper.retrieveSpace(spaceKey);

        SpaceDescription object = space.getDescription();

        // Split the label reference
        Collection labelNameArray = LabelUtil.split(labelName);

        ArrayList labelsList = new ArrayList();

        // Iterate through the collection and create the labels as necessary
        for (Iterator iterator = labelNameArray.iterator(); iterator.hasNext();)
        {
            String tempLabelName = (String) iterator.next();

            ParsedLabelName pln = LabelParser.parse(tempLabelName);

            if (pln == null)
                throw new RemoteException("Label name is invalid: " + tempLabelName);

            if (!LabelPermissionSupport.userCanEditLabel(pln, object, permissionManager))
                throw new NotPermittedException("You do not have the permission to add the label");

            labelsList.add(pln);
        }

        // Once we've done the checks, go and add the labels
        for (Iterator i = labelsList.iterator(); i.hasNext();)
        {
            ParsedLabelName pln = (ParsedLabelName) i.next();

            pln.addLabel(object, labelManager);
        }

        return true;
    }

    public boolean removeLabelByName(String labelReferences, long objectId) throws NotPermittedException, RemoteException
    {
        if (!TextUtils.stringSet(labelReferences))
            throw new RemoteException("Label name must be non-null");

        // Try and retrieve the object, and make sure the user has view permissions
        ContentEntityObject object = contentEntityManager.getById(objectId);
        assertObjectExists(objectId, object);

        // Split the given label String into separate labels
        Collection splitLabels = LabelUtil.split(labelReferences);

        ArrayList labelsList = new ArrayList();

        // Iterate through the split labels and if they're valid, add to a list to be removed
        for (Iterator iterator = splitLabels.iterator(); iterator.hasNext();)
        {
            String labelReference = (String) iterator.next();

            Label label = labelManager.getLabel(LabelParser.parse(labelReference));

            if (label == null)
                throw new RemoteException("The given label does not exist: " + labelReference);

            assertUserCanRemoveLabel(label, object);

            labelsList.add(label);
        }

        labelManager.removeLabels(object, labelsList);

        return true;
    }

    public boolean removeLabelById(long labelId, long objectId) throws NotPermittedException, RemoteException
    {
        Label label = labelManager.getLabel(labelId);

        if (label == null)
            throw new RemoteException("The given label ID or name was not valid");

        ContentEntityObject object = contentEntityManager.getById(objectId);

        assertObjectExists(objectId, object);
        assertUserCanRemoveLabel(label, object);

        labelManager.removeLabel(object, label);

        return true;
    }

    public boolean removeLabelByObject(RemoteLabel labelObject, long objectId) throws NotPermittedException, RemoteException
    {
        if (labelObject == null)
            throw new RemoteException("RemoteLabel object must be non-null");

        return removeLabelById(labelObject.getId(), objectId);
    }

    public boolean removeLabelByNameFromSpace(String labelName, String spaceKey) throws RemoteException
    {
        if (!TextUtils.stringSet(labelName))
            throw new RemoteException("Label name must be non-null");

        // Grab the space and do necessary checks
        Space space = soapServiceHelper.retrieveSpace(spaceKey);

        soapServiceHelper.assertCanModifyObject(space, "spaces");

        SpaceDescription object = space.getDescription();

        // Split the given label String into separate labels
        Collection splitLabels = LabelUtil.split(labelName);

        ArrayList labelsList = new ArrayList();

        // Iterate through the split labels and if they're valid, add to a list to be removed
        for (Iterator iterator = splitLabels.iterator(); iterator.hasNext();)
        {
            String labelReference = (String) iterator.next();

            Label label = labelManager.getLabel(LabelParser.parse(labelReference));

            if (label == null)
                throw new RemoteException("The given label does not exist: " + labelReference);

            assertUserCanRemoveLabel(label, object);

            labelsList.add(label);
        }

        labelManager.removeLabels(object, labelsList);

        return true;
    }

    // Helper methods
    private void assertUserCanViewObject(Labelable object) throws RemoteException
    {
        if (!LabelPermissionSupport.userCanViewObject(object, permissionManager))
        {
            throw new RemoteException("You're not allowed to view that ContentEntityObject, or it does not exist.");
        }
    }

    private void assertUserCanEditLabels(Label label, Labelable object) throws NotPermittedException, RemoteException
    {
        if (!LabelPermissionSupport.userCanEditLabel(label, object, permissionManager))
        {
            throw new NotPermittedException("You do not have permissions to add labels to this object.");
        }
    }

    /**
     * Determines if the current user can remove the label from the given object
     *
     * Uses LabelPermissionSuppors.
     *
     * @param label the Label instance to be tested
     * @param object the Labelable object the Label is to be removed from
     * @throws NotPermittedException if the user lacks permission to remove the label
     */
    private void assertUserCanRemoveLabel(Label label, Labelable object) throws NotPermittedException
    {
        if (!LabelPermissionSupport.userCanEditLabel(label, object, permissionManager))
            throw new NotPermittedException("You do not have permission to remove the label '" + label.getName() + "'");
    }

    private void assertObjectExists(long id, ContentEntityObject object) throws RemoteException
    {
        if (object == null)
        {
            throw new RemoteException("The object with content id '" + id + "' does not exist.");
        }
    }

    /**
     * Tests whether a given label name is valid and returns the corresponding Label instance
     *
     * @param labelName the name of a label (namespace prefixes permitted)
     * @return The Label instance corresponding to the labelName
     * @throws RemoteException if the labelName is null or an empty String
     * @throws RemoteException if the labelName has an invalid prefix
     * @throws RemoteException if there is no Label for the labelName
     */
    private Label validateAndGetLabel(String labelName) throws RemoteException
    {
        // Throw if the label isn't valid
        if (!TextUtils.stringSet(labelName))
            throw new RemoteException("Label name must be non-null");

        // Parse the label for its namespaces, etc and throw if not valid
        ParsedLabelName parsedLabel = LabelParser.parse(labelName);

        if (parsedLabel == null)
            throw new RemoteException("The label name '" + labelName + "' is not valid.");

        // Make sure the label actually exists in the system
        Label label = labelManager.getLabel(parsedLabel);

        if (label == null)
            throw new RemoteException("The label '" + labelName + "' does not exist.");

        return label;
    }
}