/*
 * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
 *
 * The contents of this file are subject to the CCM Public
 * License (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.redhat.com/licenses/ccmpl.html.
 *
 * Software distributed under the License is distributed on an
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 * or implied. See the License for the specific language
 * governing rights and limitations under the License.
 *
 */
package com.arsdigita.cms.ui;


import com.arsdigita.bebop.ActionLink;
import com.arsdigita.bebop.Form;
import com.arsdigita.bebop.FormProcessException;
import com.arsdigita.bebop.Label;
import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.ParameterSingleSelectionModel;
import com.arsdigita.bebop.Resettable;
import com.arsdigita.bebop.SegmentedPanel;
import com.arsdigita.bebop.SingleSelectionModel;
import com.arsdigita.bebop.event.ActionEvent;
import com.arsdigita.bebop.event.ActionListener;
import com.arsdigita.bebop.event.ChangeEvent;
import com.arsdigita.bebop.event.ChangeListener;
import com.arsdigita.bebop.event.FormProcessListener;
import com.arsdigita.bebop.event.FormSectionEvent;
import com.arsdigita.bebop.event.FormSubmissionListener;
import com.arsdigita.bebop.event.PrintEvent;
import com.arsdigita.bebop.event.PrintListener;
import com.arsdigita.bebop.parameters.BigDecimalParameter;
import com.arsdigita.cms.CMS;
import com.arsdigita.cms.ContentSection;
import com.arsdigita.cms.Folder;
import com.arsdigita.cms.PageLocations;
import com.arsdigita.cms.SecurityManager;
import com.arsdigita.cms.dispatcher.Utilities;
import com.arsdigita.cms.ui.authoring.CreationSelector;
import com.arsdigita.cms.ui.authoring.NewItemForm;
import com.arsdigita.cms.ui.folder.FolderCreator;
import com.arsdigita.cms.ui.folder.FolderEditor;
import com.arsdigita.cms.ui.folder.FolderManipulator;
import com.arsdigita.cms.ui.folder.FolderRequestLocal;
import com.arsdigita.cms.ui.folder.FolderSelectionModel;
import com.arsdigita.cms.ui.folder.ItemPath;
import com.arsdigita.cms.ui.permissions.CMSPermissionsPane;
import com.arsdigita.cms.util.GlobalizationUtil;
import com.arsdigita.domain.DomainObjectFactory;
import com.arsdigita.globalization.GlobalizedMessage;
import com.arsdigita.kernel.ACSObject;
import com.arsdigita.kernel.KernelHelper;
import com.arsdigita.kernel.User;
import com.arsdigita.kernel.permissions.ObjectPermissionCollection;
import com.arsdigita.kernel.permissions.PermissionDescriptor;
import com.arsdigita.kernel.permissions.PermissionService;
import com.arsdigita.kernel.permissions.PrivilegeDescriptor;
import com.arsdigita.persistence.DataObject;
import com.arsdigita.persistence.DataQuery;
import com.arsdigita.persistence.SessionManager;
import com.arsdigita.toolbox.ui.ActionGroup;
import com.arsdigita.util.Assert;
import com.arsdigita.web.Web;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Encapsulates a {@link FolderManipulator} in order to create a flat
 * item listing. Also contains a new item form.
 *
 * @author <a href="mailto:sfreidin@arsdigita.com">Stanislav Freidin</a>
 * @version $Revision: #17 $ $DateTime: 2004/04/07 16:07:11 $
 */
public class FlatItemList extends SegmentedPanel
        implements FormProcessListener, ChangeListener, FormSubmissionListener,
                   Resettable, ActionListener {
    public static final String versionId =
        "$Id: //cms/dev/src/com/arsdigita/cms/ui/FlatItemList.java#17 $" +
        "$Author: dennis $" +
        "$DateTime: 2004/04/07 16:07:11 $";

    private static final String CONTENT_TYPE_ID = "ct";
    private static final String CMS_PRIVILEGES = "com.arsdigita.cms.getPrivileges";
    private static final String PRIVILEGE = "privilege";
    private static final String PRIVILEGE_NAME = "prettyName";

    //maximum number of items displayed in the table at a time
    public static int LIST_SIZE = 20;

    // The folder selectors
    private FolderSelectionModel m_folderSel;
    private FolderRequestLocal m_folder;

    private NewItemForm m_newItem;
    private SingleSelectionModel m_typeSel;
    private CreationSelector m_selector;
    private FolderManipulator m_folderManip;

    private FolderCreator m_folderCreator;
    private ActionLink m_setHomeFolderAction;
    private ActionLink m_createFolderAction;
    private ActionLink m_togglePrivateAction;
    private Label m_homeFolderLabel;
    
    private Segment m_browseSeg;
    private Segment m_newItemSeg;
    private Segment m_newFolderSeg;
    private Segment m_editFolderSeg;
    private Segment m_permissionsSeg;
    private CMSPermissionsPane m_permPane;

    // Folder edit/rename functionality.
    private ActionLink m_editFolderAction;
    private FolderEditor m_folderEditor;


    private Label m_contentLabel;
    private ItemPath m_itemPath;
    private Label m_chooseLabel;

    /**
     * Construct a new item listing pane. The provided folder selection model
     * is used to keep track of the currently displayed folder.
     *
     * @param folderSel maintains the currently displayed folder.
     */
    public FlatItemList(final FolderRequestLocal folder,
                        final FolderSelectionModel model) {
        m_folder = folder;
        m_folderSel = model;
        m_folderSel.addChangeListener(new ChangeListener() {
                public void stateChanged(ChangeEvent e) {
                    PageState s = e.getPageState();
                    reset(s);
                }
            });

        setIdAttr("flat-item-list");

        m_newItemSeg = addSegment();
        m_newItemSeg.setIdAttr("folder-new-item");

        m_newFolderSeg = addSegment();
        m_newFolderSeg.setIdAttr("folder-new-folder");

        m_editFolderSeg = addSegment();
        m_editFolderSeg.setIdAttr("folder-edit-folder");

        m_browseSeg = addSegment();
        m_browseSeg.setIdAttr("folder-browse");

        final ActionGroup browseActions = new ActionGroup();
        m_browseSeg.add(browseActions);

        // The top 'browse' segment
        m_contentLabel = new Label(globalize("cms.ui.contents_of"), false);
        m_browseSeg.addHeader(m_contentLabel);
        m_chooseLabel = new Label(globalize("cms.ui.choose_target_folder"), false);
        m_browseSeg.addHeader(m_chooseLabel);
        m_itemPath = new ItemPath(m_folderSel);
        //m_itemPath = new Label(new PrintListener() {
        //        public final void prepare(final PrintEvent e) {
        //            final Folder f = m_folder.getFolder(e.getPageState());
        //            ((Label) e.getTarget()).setLabel(f.getDisplayName());
        //        }
        //    });

        m_browseSeg.addHeader(m_itemPath);
        m_folderManip = new FolderManipulator(m_folderSel);
        m_folderManip.getItemView().addProcessListener(this);
        m_folderManip.getTargetSelector().addProcessListener(this);
        m_folderManip.getTargetSelector().addSubmissionListener(this);

        browseActions.setSubject(m_folderManip);

        // The actions

        m_createFolderAction = new ActionLink(new Label(globalize("cms.ui.new_folder")));
        m_createFolderAction.addActionListener(this);
        browseActions.addAction(m_createFolderAction);

        m_editFolderAction = new ActionLink(new Label(globalize("cms.ui.edit_folder")));
        m_editFolderAction.addActionListener(this);
        browseActions.addAction(m_editFolderAction);

        m_setHomeFolderAction = new ActionLink(new Label("Set as home folder"));
        m_setHomeFolderAction.addActionListener(this);
        browseActions.addAction(m_setHomeFolderAction);

	m_homeFolderLabel = new Label(new PrintListener() {
		public final void prepare(final PrintEvent e) {
		    Label label = (Label)e.getTarget();
		    User user = Web.getContext().getUser();

		    Folder folder = Folder.getUserHomeFolder(user,CMS.getContext().getContentSection());
		    if ( folder != null ) {
			String url = folder.getContentSection().getURL() + PageLocations.SECTION_PAGE + "?" + ContentSectionPage.SET_FOLDER + "=" + folder.getID();
			label.setLabel("Go to home folder: <a href=\"\">" + folder.getLabel() + "</a>");
		    } else {
			label.setLabel("<font color=\"red\">No home folder selected</font>");
		    }
		    label.setOutputEscaping(false);
                }
	    });
	browseActions.addAction(m_homeFolderLabel);

        m_newItem = new SectionNewItemForm("newItem");
        m_newItem.addProcessListener(this);
        browseActions.addAction(m_newItem);

        m_permissionsSeg = addSegment();
        m_permissionsSeg.setIdAttr("folder-permissions");

        final ActionGroup permActions = new ActionGroup();
        m_permissionsSeg.add(permActions);

        // The permissions segment
        m_permissionsSeg.addHeader(new Label(GlobalizationUtil.globalize("cms.ui.permissions")));

        List privs = new ArrayList();
        Map privNameMap = new HashMap();
        DataQuery query = SessionManager.getSession().retrieveQuery(CMS_PRIVILEGES);
        query.addFilter("scope != 'section'");
        query.addOrder("sortOrder");
        while (query.next()) {
            String privilege = (String) query.get(PRIVILEGE);
            String privilegeName = (String) query.get(PRIVILEGE_NAME);
            privs.add(PrivilegeDescriptor.get(privilege));
            privNameMap.put(privilege, privilegeName);
        }
        query.close();

        m_permPane = new CMSPermissionsPane
            ((PrivilegeDescriptor[]) privs.toArray
                 (new PrivilegeDescriptor[privs.size()]),
             privNameMap,
             m_folderSel);
        permActions.setSubject(m_permPane);

        // An action

        m_togglePrivateAction = new ActionLink(new Label(new PrintListener() {
                public void prepare(PrintEvent e) {
                    PageState state = e.getPageState();
                    Label target = (Label) e.getTarget();
                    Folder currentFolder = m_folder.getFolder(state);
                    // ACSObject parent = currentFolder.getParent();
                    DataObject context = PermissionService.getContext(currentFolder);
                    if (context == null) {
                        target.setLabel( (String) GlobalizationUtil.globalize("cms.ui.restore_default_permissions").localize());
                    } else {
                        target.setLabel( (String) GlobalizationUtil.globalize("cms.ui.use_custom_permissions").localize());
                    }
                }
            }));
        m_togglePrivateAction.addActionListener(this);
        permActions.addAction(m_togglePrivateAction);

        // The 'new item' segment
        m_newItemSeg.addHeader(new Label(globalize("cms.ui.new_item")));
        m_typeSel = new ParameterSingleSelectionModel(new BigDecimalParameter(CONTENT_TYPE_ID));
        m_typeSel.addChangeListener(this);

        m_selector = new CreationSelector(m_typeSel, m_folderSel);
        m_newItemSeg.add(m_selector);
        m_newItemSeg.add(new Label("<br/>", false));

        // The 'new folder' segment
        m_newFolderSeg.addHeader(new Label(globalize("cms.ui.new_folder")));
        Form folderCreate = new Form("fcreat");
        m_folderCreator = new FolderCreator("fcreat", m_folderSel);
        m_folderCreator.addSubmissionListener(this);
        m_folderCreator.addProcessListener(this);
        folderCreate.add(m_folderCreator);
        m_newFolderSeg.add(folderCreate);
        m_newFolderSeg.add(new Label("<br/>", false));

        m_editFolderSeg.addHeader(new Label(globalize("cms.ui.edit_folder")));
        m_folderEditor = new FolderEditor("fedit", m_folderSel);
        m_folderEditor.addSubmissionListener(this);
        m_folderEditor.addProcessListener(this);

        Form folderEditorForm = new Form( "fedit_form" );
        folderEditorForm.add( m_folderEditor );
        m_editFolderSeg.add(folderEditorForm);
        m_editFolderSeg.add(new Label("<br/>", false));
    }

    public void register(Page p) {
        super.register(p);

        p.setVisibleDefault(m_chooseLabel, false);
        p.setVisibleDefault(m_newItemSeg, false);
        p.setVisibleDefault(m_newFolderSeg, false);
        p.setVisibleDefault(m_editFolderSeg, false);

        p.addComponentStateParam(this, m_typeSel.getStateParameter());

        p.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    final PageState state = e.getPageState();

                    if (state.isVisibleOnPage(FlatItemList.this)) {
                        showHideSegments(state);
                    }
                }
            });
    }

    /**
     * Show/hide segments based on access checks.
     *
     * @param state The page state
     * @pre ( state != null )
     */
    private void showHideSegments(PageState state) {
        SecurityManager sm = Utilities.getSecurityManager(state);
        Folder folder = m_folder.getFolder(state);
        Assert.assertNotNull(folder);

        // MP: This should be checked on the current folder instead of just
        //     the content section.
        boolean newItem =
            sm.canAccess(state.getRequest(),
                         SecurityManager.NEW_ITEM,
                         folder);

        if (!newItem) {
            browseMode(state);
        }
        m_createFolderAction.setVisible(state, newItem);
        m_newItem.setVisible(state, newItem);

        final boolean editItem = sm.canAccess(state.getRequest(),
                                              SecurityManager.EDIT_ITEM,
                                              folder);

        m_editFolderAction.setVisible(state, editItem);

        User user = KernelHelper.getCurrentUser(state.getRequest());
        PermissionDescriptor perm =
            new PermissionDescriptor(PrivilegeDescriptor.ADMIN,
                                     folder,
                                     user);

        if (PermissionService.checkPermission(perm)) {
            m_permissionsSeg.setVisible(state, true);
        } else {
            m_permissionsSeg.setVisible(state, false);
        }
    }

    private void browseMode(PageState s) {
        m_browseSeg.setVisible(s, true);
        m_permissionsSeg.setVisible(s, true);
        m_chooseLabel.setVisible(s, false);
        m_contentLabel.setVisible(s, true);
        m_itemPath.setVisible(s, true);
        m_newItemSeg.setVisible(s, false);
        m_newFolderSeg.setVisible(s, false);
        m_editFolderSeg.setVisible(s, false);

        m_typeSel.clearSelection(s);
    }

    private void newItemMode(PageState s) {
        m_permissionsSeg.setVisible(s, false);
        m_newItemSeg.setVisible(s, true);
    }

    private void newFolderMode(PageState s) {
        m_permissionsSeg.setVisible(s, false);
        m_newFolderSeg.setVisible(s, true);
    }

    public void submitted(FormSectionEvent e)
        throws FormProcessException {
        PageState s = e.getPageState();
        if ( e.getSource() == m_folderCreator
             && m_folderCreator.isCancelled(s) ) {
            browseMode(s);
            throw new FormProcessException( (String) GlobalizationUtil.globalize("cms.ui.cancelled").localize());
        } else if (e.getSource() == m_folderEditor && m_folderEditor.isCancelled(s)) {
            browseMode(s);
            throw new FormProcessException( (String) GlobalizationUtil.globalize("cms.ui.cancelled").localize());
        } else if ( e.getSource() == m_folderManip.getTargetSelector() ) {
            // This only works if this submission listener is run
            // after the target selector's one
            if ( ! m_folderManip.getTargetSelector().isVisible(s) ) {
                browseMode(s);
            }
        }
    }

    public void process(FormSectionEvent e) {
        PageState s = e.getPageState();
        final Object source = e.getSource();
        if (source == m_newItem) {
            BigDecimal typeID = m_newItem.getTypeID(s);
            m_typeSel.setSelectedKey(s, typeID);
            newItemMode(s);
        } else if (source == m_folderCreator || source == m_folderEditor) {
            browseMode(s);
        } else if (source == m_folderManip.getItemView()) {
            // Hide everything except for the browseSeg
            m_permissionsSeg.setVisible(s, false);
            m_chooseLabel.setVisible(s, true);
            m_contentLabel.setVisible(s, false);
            m_itemPath.setVisible(s, false);
        } else if (source == m_folderManip.getTargetSelector()) {
            browseMode(s);
        }
    }

    public void stateChanged(ChangeEvent e) {
        PageState s = e.getPageState();
        if ( e.getSource().equals(m_typeSel) ) {
            if ( ! m_typeSel.isSelected(s) ) {
                browseMode(s);
            }
        }
    }

    public void actionPerformed(ActionEvent e) {
        PageState s = e.getPageState();
        Object source = e.getSource();
        if ( source == m_createFolderAction ) {
            newFolderMode(s);
        } else if (source == m_editFolderAction) {
            m_permissionsSeg.setVisible(s, false);
            m_editFolderSeg.setVisible(s, true);
        }
        else if (source == m_togglePrivateAction) {
            togglePermissions(s);
        } else if ( source == m_setHomeFolderAction ) {
	    User user = Web.getContext().getUser();
	    Folder folder = m_folder.getFolder(s);
	    user = (User)DomainObjectFactory.newInstance(user.getOID());
	    Folder.setUserHomeFolder(user,folder);
	}
    }

    private void togglePermissions(PageState state) {
        Folder currentFolder = m_folder.getFolder(state);
        if (!Utilities.getSecurityManager(state).canAccess(state.getRequest(), SecurityManager.STAFF_ADMIN)) {
            throw new com.arsdigita.cms.dispatcher.AccessDeniedException();
        }
        DataObject context = PermissionService.getContext(currentFolder);
        if (context != null) {
            PermissionService.clonePermissions(currentFolder);
            Folder liveFolder = (Folder) currentFolder.getLiveVersion();
            if (liveFolder != null) {
                PermissionService.clonePermissions(liveFolder);
            }
        } else {
            ACSObject parent = currentFolder.getParent();
            if (parent != null) {
                PermissionService.setContext(currentFolder, parent);
            } else {
                // if the folder has no parent, it must be a root folder or template folder
                // in this case, set it's context to the ContentSection
                ContentSection section = currentFolder.getContentSection();
                if (section != null) {
                    PermissionService.setContext(currentFolder, section);
                } else {
                    throw new IllegalStateException("Cannot set the context for a folder with " +
                                                    "no parent and no Content Section");
                }
            }

            Folder liveVersion = (Folder) currentFolder.getLiveVersion();
            if (liveVersion != null) {
                ACSObject liveParent = liveVersion.getParent();
                if (liveParent != null) {
                    PermissionService.setContext(liveVersion, liveParent);
                } else {
                    ContentSection liveSection = liveVersion.getContentSection();
                    if (liveSection != null) {
                        PermissionService.setContext(liveVersion, liveSection);
                    } else {
                        throw new IllegalStateException("Cannot set the context for a folder with " +
                                                        "no parent and no Content Section");
                    }
                }
            }

            // revoke all direct permissions so folder will only have inherited permissions
            ObjectPermissionCollection perms = PermissionService.getGrantedPermissions(currentFolder.getOID());
            while (perms.next()) {
                if (!perms.isInherited() && !perms.getPrivilege().equals(PrivilegeDescriptor.ADMIN)) {
                    PermissionDescriptor desc = new PermissionDescriptor(perms.getPrivilege(), currentFolder.getOID(),
                                                                         perms.getGranteeOID());
                    PermissionService.revokePermission(desc);
                }
            }

            if (liveVersion != null) {
                ObjectPermissionCollection livePerms = PermissionService.getGrantedPermissions(liveVersion.getOID());
                while (livePerms.next()) {
                    if (!livePerms.isInherited()) {
                        PermissionDescriptor desc2 = new PermissionDescriptor(livePerms.getPrivilege(), liveVersion.getOID(),
                                                                              livePerms.getGranteeOID());
                        PermissionService.revokePermission(desc2);
                    }
                }
            }

        }
        m_permPane.reset(state);
    }

    public void reset(PageState s) {
        browseMode(s);
        m_folderManip.reset(s);
    }

    public final FolderManipulator getManipulator() {
        return m_folderManip;
    }

    public final CMSPermissionsPane getPermissionsPane() {
        return m_permPane;
    }

    public void setPermissionLinkVis(PageState state) {
        if (!Utilities.getSecurityManager(state).
            canAccess(state.getRequest(), SecurityManager.STAFF_ADMIN)) {
            m_togglePrivateAction.setVisible(state, false);
        }
    }

    private static class SectionNewItemForm extends NewItemForm {

        public SectionNewItemForm(String name) {
            super(name);
        }

        public ContentSection getContentSection(PageState s) {
            return CMS.getContext().getContentSection();
        }
    }

    /**
     * Getting the GlobalizedMessage using a CMS Class targetBundle.
     *
     * @param key The resource key
     * @pre ( key != null )
     */
    private static GlobalizedMessage globalize(String key) {
        return ContentSectionPage.globalize(key);
    }

}
