package com.atlassian.confluence.rpc.soap.services;

import bucket.core.InfrastructureException;
import bucket.core.actions.PaginationSupport;
import bucket.core.persistence.AnyTypeObjectDao;
import bucket.search.Searcher;
import com.atlassian.confluence.core.Addressable;
import com.atlassian.confluence.core.ContentPermission;
import com.atlassian.confluence.core.ContentEntityManager;
import com.atlassian.confluence.core.ContentEntityObject;
import com.atlassian.confluence.core.actions.StylesheetAction;
import com.atlassian.confluence.pages.*;
import com.atlassian.confluence.renderer.PageContext;
import com.atlassian.confluence.rpc.RemoteException;
import com.atlassian.confluence.rpc.VersionMismatchException;
import com.atlassian.confluence.rpc.NotPermittedException;
import com.atlassian.confluence.rpc.soap.SoapUtils;
import com.atlassian.confluence.rpc.soap.beans.*;
import com.atlassian.confluence.search.actions.SearchBean;
import com.atlassian.confluence.search.actions.SearchQueryBean;
import com.atlassian.confluence.search.actions.SearchResultWithExcerpt;
import com.atlassian.confluence.security.PermissionManager;
import com.atlassian.confluence.security.Permission;
import com.atlassian.confluence.setup.BootstrapManager;
import com.atlassian.confluence.spaces.Space;
import com.atlassian.confluence.spaces.SpaceManager;
import com.atlassian.confluence.user.AuthenticatedUserThreadLocal;
import com.atlassian.confluence.user.UserAccessor;
import com.atlassian.confluence.labels.LabelManager;
import com.atlassian.confluence.event.EventManager;
import com.atlassian.confluence.event.events.search.SearchPerformedEvent;
import com.atlassian.renderer.WikiStyleRenderer;
import com.opensymphony.util.TextUtils;

import java.io.IOException;
import java.util.*;

public class PagesSoapService
{
    EventManager eventManager;
    PageManager pageManager;
    SpaceManager spaceManager;
    ContentEntityManager contentEntityManager;
    PermissionManager permissionManager;
    CommentManager commentManager;
    WikiStyleRenderer wikiStyleRenderer;
    BootstrapManager bootstrapManager;
    AnyTypeObjectDao anyTypeObjectDao;
    Searcher searcher;
    SoapServiceHelper soapServiceHelper;
    private UserAccessor userAccessor;
    private LabelManager labelManager;

    public RemotePageSummary[] getPages(String spaceKey) throws RemoteException
    {
        Space space = soapServiceHelper.retrieveSpace(spaceKey);

        return SoapUtils.getPageSummaries(getPermittedEntities(spaceManager.getPages(space, true)));
    }

    public RemotePage getPage(long pageId) throws RemoteException
    {
        return new RemotePage((Page) soapServiceHelper.retrieveAbstractPage(pageId));
    }

    public RemotePage getPage(String spaceKey, String pageTitle) throws RemoteException
    {
        return new RemotePage(soapServiceHelper.retrievePage(spaceKey, pageTitle));
    }

    public RemoteComment[] getComments(long pageId) throws RemoteException
    {
        AbstractPage p = soapServiceHelper.retrieveAbstractPage(pageId);
        return SoapUtils.getComments(p.getComments());
    }

    public RemoteComment getComment(long commentId) throws RemoteException
    {
        ContentEntityObject object = contentEntityManager.getById(commentId);

        if (object == null || !permissionManager.hasPermission(AuthenticatedUserThreadLocal.getUser(), Permission.VIEW, object))
            throw new RemoteException("You do not have permission to view the comment, or it does not exist.");

        if (!(object instanceof Comment))
            throw new RemoteException("Object for given comment ID is not a comment.");

        Comment comment = (Comment) object;

        if (!permissionManager.hasPermission(AuthenticatedUserThreadLocal.getUser(), Permission.VIEW, comment.getPage()))
            throw new RemoteException("You do not have permission to view the comment, or it does not exist.");

        return new RemoteComment(comment);
    }

    public RemoteComment addComment(RemoteComment comment) throws NotPermittedException, RemoteException
    {
        long pageId = comment.getPageId();

        AbstractPage page = pageManager.getAbstractPage(pageId);

        if (page == null)
            throw new RemoteException("Can not add comment to non-existant page with id: " + pageId);

        soapServiceHelper.assertCanView(page);

        // See if the user can add comments
        if (!permissionManager.hasCreatePermission(AuthenticatedUserThreadLocal.getUser(), page, Comment.class))
            throw new NotPermittedException("You do not have the permissions to perform this action");

        // Make sure everything needed is in the object
        if (comment.getContent() == null || comment.getContent().length() == 0)
            throw new RemoteException("Comment text must be non-null");

        Comment parentComment = null;

        // Grab the parent, if it exists
        if (comment.getParentId() != 0)
        {
            parentComment = commentManager.getComment(comment.getParentId());

            // If the parent ID was not valid, throw an exception
            if (parentComment == null)
                throw new RemoteException("That parent comment does not exist.");
        }

        // Create a new comment and add it
        Comment newComment = commentManager.addCommentToPage(page, parentComment, comment.getContent());

        return new RemoteComment(newComment);
    }

    public boolean removeComment(long commentId) throws NotPermittedException, RemoteException
    {
        Comment comment = commentManager.getComment(commentId);

        if (comment == null)
            throw new RemoteException("That comment does not exist.");

        AbstractPage page = comment.getPage();

        soapServiceHelper.assertCanView(page);

        // See if the user can remove comments
        if (!permissionManager.hasPermission(AuthenticatedUserThreadLocal.getUser(), Permission.REMOVE, comment))
            throw new NotPermittedException("You do not have the permissions to perform this action");

        commentManager.removeCommentFromPage(commentId);

        return true;
    }

    public RemotePageSummary[] getDescendents(long pageId) throws RemoteException
    {
        AbstractPage p = soapServiceHelper.retrieveAbstractPage(pageId);

        if (p instanceof Page)
        {
            Page page = (Page) p;
            List permittedDescendants = getPermittedEntities(pageManager.getDescendents(page));
            Collections.sort(permittedDescendants);
            return SoapUtils.getPageSummaries(permittedDescendants);
        }

        throw new RemoteException(pageId + " is not a page?");
    }

    public RemotePageSummary[] getAncestors(long pageId) throws RemoteException
    {
        AbstractPage p = soapServiceHelper.retrieveAbstractPage(pageId);

        if (p instanceof Page)
        {
            Page page = (Page) p;
            return SoapUtils.getPageSummaries(getPermittedEntities(page.getAncestors()));
        }

        throw new RemoteException(pageId + " is not a page?");
    }

    public RemotePageSummary[] getChildren(long pageId) throws RemoteException
    {
        AbstractPage p = soapServiceHelper.retrieveAbstractPage(pageId);

        if (p instanceof Page)
        {
            Page page = (Page) p;
            return SoapUtils.getPageSummaries(getPermittedEntities(page.getChildren()));
        }

        throw new RemoteException(pageId + " is not a page?");
    }

    public RemoteAttachment[] getAttachments(long pageId) throws RemoteException
    {
        AbstractPage p = soapServiceHelper.retrieveAbstractPage(pageId);
        return SoapUtils.getAttachments(p.getLatestVersionsOfAttachments());
    }

    public RemotePageHistory[] getPageHistory(long pageId) throws RemoteException
    {
        AbstractPage p = soapServiceHelper.retrieveAbstractPage(pageId);

        if (p.getOriginalVersion() != null)
            throw new VersionMismatchException("This is not the most recent version of this page");

        return SoapUtils.getPageHistory(p);
    }

    public Boolean removePage(long pageId) throws RemoteException
    {
        AbstractPage page = soapServiceHelper.retrieveAbstractPage(pageId);

        // Check the user can firstly view the page
        soapServiceHelper.assertCanRemove(page);

        if (page.getOriginalVersion() != null)
            throw new RemoteException("You can't remove an old version of the page - remove the current version.");

        // Page is already in trash, nothing more to do.
        if (page.isDeleted())
            return Boolean.TRUE;

        pageManager.trashPage(page);
        return Boolean.TRUE;
    }

    public RemoteSearchResult[] search(String query, Map params, int maxResults) throws RemoteException
    {
        try
        {
            SearchQueryBean sqb = new SearchQueryBean(searcher, spaceManager, userAccessor, labelManager);
            sqb.setQueryString(query);

            if (params.containsKey("spaceKey"))
                sqb.setSpaceKey((String) params.get("spaceKey"));

            if (params.containsKey("type"))
                sqb.setType((String) params.get("type"));

            if (params.containsKey("modified"))
                sqb.setLastModified((String) params.get("modified"));

            SearchBean sb = new SearchBean();
            sb.setPaginationSupport(new PaginationSupport(maxResults));
            sb.setAnyTypeObjectDao(anyTypeObjectDao);
            sb.setSearcher(searcher);

            List searchResults = sb.search(sqb.buildQuery());
            int trueSize = Math.min(maxResults, searchResults.size());
            List results = new ArrayList(trueSize);

            for (int i = 0; i < trueSize; i++)
            {
                SearchResultWithExcerpt result = (SearchResultWithExcerpt) searchResults.get(i);
                Addressable addressable = (Addressable) result.getResultObject();
                if (addressable != null)
                    results.add(new RemoteSearchResult(addressable));
            }

            SearchQueryBean queryForEvent = new SearchQueryBean(sqb);
            queryForEvent.unwire();
            eventManager.publishEvent(new SearchPerformedEvent(this, queryForEvent, AuthenticatedUserThreadLocal.getUser(), results.size()));

            return (RemoteSearchResult[]) results.toArray(new RemoteSearchResult[results.size()]);
        }
        catch (IOException e)
        {
            throw new RemoteException(e);
        }
    }

    public RemoteSearchResult[] search(String query, int maxResults) throws RemoteException
    {
        return search(query, Collections.EMPTY_MAP, maxResults);
    }

    public String renderContent(String spaceKey, long pageId, String newContent) throws RemoteException
    {
        return renderContent(spaceKey, pageId, newContent, null);
    }

    public String renderContent(String spaceKey, long pageId, String newContent, Map parameters) throws RemoteException
    {
        AbstractPage page = null;
        PageContext ctx;

        if (pageId > 0) // we're using an existing page
        {
            page = soapServiceHelper.retrieveAbstractPage(pageId);
            ctx = page.toPageContext();
        }
        else if (!TextUtils.stringSet(spaceKey))
        {
            throw new RemoteException("You must specify a space key to render non existant content.");
        }
        else
        {
            ctx = new PageContext(spaceKey);
        }

        String contentToRender = newContent;

        if (!TextUtils.stringSet(contentToRender))
        {
            contentToRender = page.getContent();
        }

        final String renderedContent = wikiStyleRenderer.convertWikiToXHtml(ctx, contentToRender);

        if (parameters != null)
        {
            if (parameters.containsKey("style") && "clean".equalsIgnoreCase((String) parameters.get("style")))
            {
                return "<div id=\"ConfluenceContent\">" + renderedContent + "</div>";
            }
        }

        String basicRenderedPage = "<html><head>\n";
        basicRenderedPage += "<title>" + (page != null ? page.getTitle() : "Untitled") + "</title>\n";
        basicRenderedPage += "<style>\n" + StylesheetAction.renderSpaceStylesheet((page != null ? page.getSpace() : null)) + "</style>\n";
        basicRenderedPage += "<base href=\"" + bootstrapManager.getBaseUrl() + "\"/>\n";
        basicRenderedPage += "</head>\n<body>\n<div id=\"Content\" style=\"padding: 5px;\">\n";
        basicRenderedPage += renderedContent;
        basicRenderedPage += "\n</div>\n</body></html>";
        return basicRenderedPage;
    }


    public RemotePage storePage(RemotePage rpage) throws RemoteException
    {
        if (rpage.getId() <= 0)
            return createPage(rpage);
        else
            return updatePage(rpage);
    }

    private RemotePage createPage(RemotePage rpage) throws RemoteException
    {
        Space space = soapServiceHelper.retrieveSpace(rpage.getSpace());
        soapServiceHelper.assertCanView(space);

        Page page = pageManager.getPage(rpage.getSpace(), rpage.getTitle());
        soapServiceHelper.assertCanCreatePage(space);

        if (page != null)
        {
            throw new RemoteException("The page you are trying to create already exists.");
        }

        page = new Page();
        page.setSpace(space);
        page.setTitle(rpage.getTitle());
        page.setContent(rpage.getContent());

        if (rpage.getParentId() > 0)
        {
            Page potentialParent = pageManager.getPage(rpage.getParentId());
            if (potentialParent == null)
            {
                throw new RemoteException("The parent ID specified does not exist?");
            }

            potentialParent.addChild(page);
        }

        pageManager.saveContentEntity(page, null);
        return new RemotePage(page);
    }

    private RemotePage updatePage(RemotePage rpage) throws RemoteException
    {
        Page page = (Page) soapServiceHelper.retrieveAbstractPage(rpage.getId());

        soapServiceHelper.assertCanModify(page);

        if (!page.getSpace().getKey().equals(rpage.getSpace()))
        {
            throw new RemoteException("You can't change an existing page's space.");
        }

        // check the version of the page
        if (page.getVersion() != rpage.getVersion())
        {
            throw new VersionMismatchException("You're trying to edit an outdated version of that page.");
        }

        Page originalPage;
        try
        {
            originalPage = (Page) page.clone();
        }
        catch (CloneNotSupportedException e)
        {
            throw new InfrastructureException("Uh oh, couldn't clone a page?!");
        }
        boolean storeRequired = false;

        if (!page.getTitle().equals(rpage.getTitle()))
        {
            soapServiceHelper.renamePage(page, rpage.getTitle());
        }

        if (!rpage.getContent().equals(page.getContent()))
        {
            page.setContent(rpage.getContent());
            storeRequired = true;
        }

        // update the pages parent. if the requested parent id does not exist, then the parent will be set to null.
        Page potentialParent = pageManager.getPage(rpage.getParentId());
        Page existingParent = page.getParent();
        if (potentialParent == null)
        {
            if (existingParent == null)
            {
                // nothing to do. both are null and the same...
            }
            else
            {
                // existing parent exists, new parent is null, therefore clean up the link with the existing parent.
                existingParent.removeChild(page);
                page.setParentPage(null);
                storeRequired = true;
            }
        }
        else
        {
            if (existingParent == null)
            {
                // new parent not null, no existing parent.
                potentialParent.addChild(page);
                storeRequired = true;
            }
            else
            {
                // new parent and existing parent not null, only update if the ids are different.
                if (existingParent.getId() != potentialParent.getId())
                {
                    existingParent.removeChild(page);
                    potentialParent.addChild(page);
                    storeRequired = true;
                }
            }
        }

        if (storeRequired)
        {
            pageManager.saveContentEntity(page, originalPage, null);
        }

        return new RemotePage(page);
    }

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

    public RemotePermission[] getPermissions(long pageId) throws RemoteException
    {
        AbstractPage page = soapServiceHelper.retrieveAbstractPage(pageId);
        List permissions = new ArrayList(page.getPermissions().size());
        for (Iterator it = page.getPermissions().iterator(); it.hasNext();)
        {
            ContentPermission permission = (ContentPermission) it.next();
            permissions.add(new RemotePermission(permission));
        }

        return (RemotePermission[]) permissions.toArray(new RemotePermission[permissions.size()]);
    }

    private List getPermittedEntities(List entities)
    {
        return permissionManager.getPermittedEntities(AuthenticatedUserThreadLocal.getUser(), Permission.VIEW, entities);
    }

    public void setPageManager(PageManager pageManager)
    {
        this.pageManager = pageManager;
    }

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

    public void setBootstrapManager(BootstrapManager bootstrapManager)
    {
        this.bootstrapManager = bootstrapManager;
    }

    public void setAnyTypeObjectDao(AnyTypeObjectDao anyTypeObjectDao)
    {
        this.anyTypeObjectDao = anyTypeObjectDao;
    }

    public void setSearcher(Searcher searcher)
    {
        this.searcher = searcher;
    }

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

    public void setUserAccessor(UserAccessor userAccessor)
    {
        this.userAccessor = userAccessor;
    }

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

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

    public void setCommentManager(CommentManager commentManager)
    {
        this.commentManager = commentManager;
    }

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

    public void setEventManager(EventManager eventManager)
    {
        this.eventManager = eventManager;
    }
}
