/*
 * RHQ Management Platform
 * Copyright (C) 2005-2012 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.rhq.coregui.client.admin.roles;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import com.google.gwt.user.client.rpc.AsyncCallback;
import com.smartgwt.client.data.DSRequest;
import com.smartgwt.client.data.Record;
import com.smartgwt.client.types.Overflow;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.Label;
import com.smartgwt.client.widgets.form.fields.FormItem;
import com.smartgwt.client.widgets.form.fields.TextItem;
import com.smartgwt.client.widgets.grid.ListGridRecord;
import com.smartgwt.client.widgets.tab.Tab;
import com.smartgwt.client.widgets.tab.TabSet;

import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.authz.Permission;
import org.rhq.core.domain.authz.Role;
import org.rhq.core.domain.resource.group.GroupCategory;
import org.rhq.coregui.client.BookmarkableView;
import org.rhq.coregui.client.CoreGUI;
import org.rhq.coregui.client.ImageManager;
import org.rhq.coregui.client.PermissionsLoadedListener;
import org.rhq.coregui.client.PermissionsLoader;
import org.rhq.coregui.client.UserSessionManager;
import org.rhq.coregui.client.ViewPath;
import org.rhq.coregui.client.admin.users.UsersDataSource;
import org.rhq.coregui.client.bundle.group.BundleGroupSelector;
import org.rhq.coregui.client.components.form.AbstractRecordEditor;
import org.rhq.coregui.client.components.form.EnhancedDynamicForm;
import org.rhq.coregui.client.components.selector.AssignedItemsChangedEvent;
import org.rhq.coregui.client.components.selector.AssignedItemsChangedHandler;
import org.rhq.coregui.client.gwt.GWTServiceLookup;
import org.rhq.coregui.client.inventory.resource.selection.ResourceGroupSelector;
import org.rhq.coregui.client.util.enhanced.EnhancedVLayout;

/**
 * A form for viewing and/or editing an RHQ {@link Role role}.
 *
 * @author Ian Springer
 */
public class RoleEditView extends AbstractRecordEditor<RolesDataSource> implements BookmarkableView {

    private static final String HEADER_ICON = "global/Role_24.png";

    private Tab permissionsTab;
    private PermissionsEditor permissionsEditor;

    private Tab resourceGroupsTab;
    private ResourceGroupSelector resourceGroupSelector;

    private Tab subjectsTab;
    private RoleSubjectSelector subjectSelector;

    private Tab ldapGroupsTab;
    private RoleLdapGroupSelector ldapGroupSelector;

    private Tab bundleGroupsTab;
    private BundleGroupSelector bundleGroupSelector;

    private boolean hasManageSecurityPermission;
    private boolean isLdapConfigured;
    private boolean isSystemRole;

    public RoleEditView(int roleId) {
        super(new RolesDataSource(), roleId, MSG.common_label_role(), HEADER_ICON);
    }

    @Override
    public void renderView(ViewPath viewPath) {
        super.renderView(viewPath);

        this.isSystemRole = RolesDataSource.isSystemRoleId(getRecordId());

        // Step 1 of async init: load current user's global permissions.
        new PermissionsLoader().loadExplicitGlobalPermissions(new PermissionsLoadedListener() {
            public void onPermissionsLoaded(Set<Permission> perms) {
                if (perms == null) {
                    // TODO: i18n
                    CoreGUI.getErrorHandler().handleError(
                        "Failed to load global permissions for current user. Perhaps the Server is down.");
                    return;
                }
                RoleEditView.this.hasManageSecurityPermission = perms.contains(Permission.MANAGE_SECURITY);

                // Step 2 of async init: check if LDAP is configured.
                checkIfLdapConfigured();
            }
        });
    }

    @Override
    protected boolean isFormReadOnly() {
        return (isReadOnly() || this.isSystemRole);
    }

    private void checkIfLdapConfigured() {
        GWTServiceLookup.getLdapService().checkLdapConfiguredStatus(new AsyncCallback<Boolean>() {
            public void onSuccess(Boolean isLdapConfigured) {
                RoleEditView.this.isLdapConfigured = isLdapConfigured;

                // Step 3 of async init: call super.init().
                init();
            }

            public void onFailure(Throwable caught) {
                CoreGUI.getErrorHandler().handleError(MSG.view_adminRoles_failLdap(), caught);
                return;
            }
        });
    }

    private void init() {
        final boolean isReadOnly = (!this.hasManageSecurityPermission);
        init(isReadOnly);
    }

    @Override
    protected EnhancedVLayout buildContentPane() {
        EnhancedVLayout contentPane = new EnhancedVLayout();
        contentPane.setWidth100();
        contentPane.setHeight100();
        contentPane.setOverflow(Overflow.AUTO);

        EnhancedDynamicForm form = buildForm();
        setForm(form);

        EnhancedVLayout topPane = new EnhancedVLayout();
        topPane.setWidth100();
        topPane.setHeight(80);
        topPane.addMember(form);

        contentPane.addMember(topPane);

        TabSet tabSet = new TabSet();
        tabSet.setWidth100();
        tabSet.setHeight100();

        // TODO: Also add these tabs if the session subject is a member of the Role being viewed.
        if (this.hasManageSecurityPermission) {
            this.permissionsTab = buildPermissionsTab(tabSet);
            tabSet.addTab(permissionsTab);

            if (!this.isSystemRole) {
                this.resourceGroupsTab = buildResourceGroupsTab(tabSet);
                tabSet.addTab(resourceGroupsTab);

                this.bundleGroupsTab = buildBundleGroupsTab(tabSet);
                tabSet.addTab(bundleGroupsTab);
            }

            this.subjectsTab = buildSubjectsTab(tabSet);
            tabSet.addTab(subjectsTab);

            this.ldapGroupsTab = buildLdapGroupsTab(tabSet);
            tabSet.addTab(ldapGroupsTab);
        }

        contentPane.addMember(tabSet);

        return contentPane;
    }

    private Tab buildPermissionsTab(TabSet tabSet) {
        Tab tab = new Tab(MSG.common_title_permissions(), "global/Locked_16.png");
        // NOTE: We will set the tab content to the permissions editor later once the Role has been fetched.

        return tab;
    }

    private Tab buildResourceGroupsTab(TabSet tabSet) {
        Tab tab = new Tab(MSG.common_title_resourceGroups(), ImageManager.getGroupIcon(GroupCategory.MIXED));
        // NOTE: We will set the tab content to the resource group selector later once the Role has been fetched.

        return tab;
    }

    private Tab buildBundleGroupsTab(TabSet tabSet) {
        Tab tab = new Tab(MSG.common_title_bundleGroups(), ImageManager.getBundleGroupIcon());
        // NOTE: We will set the tab content to the bundle group selector later once the Role has been fetched.

        return tab;
    }

    private Tab buildSubjectsTab(TabSet tabSet) {
        Tab tab = new Tab(MSG.common_title_users(), "global/User_16.png");
        // NOTE: We will set the tab content to the subject selector later once the Role has been fetched.

        return tab;
    }

    private Tab buildLdapGroupsTab(TabSet tabSet) {
        Tab tab = new Tab(MSG.common_title_ldapGroups(), "global/Role_16.png");
        // NOTE: We will set the tab content to the LDAP group selector later once the Role has been fetched.

        return tab;
    }

    @Override
    protected Record createNewRecord() {
        Role role = new Role();
        role.addPermission(Permission.VIEW_USERS);
        Record roleRecord = RolesDataSource.getInstance().copyValues(role);
        return roleRecord;
    }

    protected void editRecord(Record record) {
        super.editRecord(record);

        // A user can always view their own assigned roles, but only users with MANAGE_SECURITY can view or update
        // other users' assigned roles.
        Subject sessionSubject = UserSessionManager.getSessionSubject();
        int sessionSubjectId = sessionSubject.getId();
        Record[] subjectRecords = record.getAttributeAsRecordArray(RolesDataSource.Field.SUBJECTS);
        boolean isMemberOfRole = false;
        for (Record subjectRecord : subjectRecords) {
            int subjectId = subjectRecord.getAttributeAsInt(RolesDataSource.Field.ID);
            if (subjectId == sessionSubjectId) {
                isMemberOfRole = true;
            }
        }

        if (this.hasManageSecurityPermission || isMemberOfRole) {
            // Create the permission editor and selectors and add them to the corresponding tabs.

            this.permissionsEditor = new PermissionsEditor(this, !hasManageSecurityPermission || this.isSystemRole);
            updateTab(this.permissionsTab, this.permissionsEditor);

            if (!this.isSystemRole) {
                Record[] groupRecords = record.getAttributeAsRecordArray(RolesDataSource.Field.RESOURCE_GROUPS);
                ListGridRecord[] groupListGridRecords = toListGridRecordArray(groupRecords);
                this.resourceGroupSelector = new RoleResourceGroupSelector(groupListGridRecords,
                    !this.hasManageSecurityPermission);
                this.resourceGroupSelector.addAssignedItemsChangedHandler(new AssignedItemsChangedHandler() {
                    public void onSelectionChanged(AssignedItemsChangedEvent event) {
                        onItemChanged();
                    }
                });
                updateTab(this.resourceGroupsTab, this.resourceGroupSelector);

                Record[] bundleGroupRecords = record.getAttributeAsRecordArray(RolesDataSource.Field.BUNDLE_GROUPS);
                ListGridRecord[] bundleGroupListGridRecords = toListGridRecordArray(bundleGroupRecords);
                this.bundleGroupSelector = new RoleBundleGroupSelector(bundleGroupListGridRecords,
                    !this.hasManageSecurityPermission);
                this.bundleGroupSelector.addAssignedItemsChangedHandler(new AssignedItemsChangedHandler() {
                    public void onSelectionChanged(AssignedItemsChangedEvent event) {
                        onItemChanged();
                    }
                });
                updateTab(this.bundleGroupsTab, this.bundleGroupSelector);

            }

            ListGridRecord[] subjectListGridRecords = toListGridRecordArray(subjectRecords);
            if (getRecordId() == RolesDataSource.ID_SUPERUSER) {
                // If this is the superuser role, make sure the rhqadmin record is disabled, so it cannot be removed
                // from the role, and filter the overlord record out, so users don't even know it exists.
                List<ListGridRecord> filteredSubjectRecords = new ArrayList<ListGridRecord>();
                for (ListGridRecord subjectListGridRecord : subjectListGridRecords) {
                    int subjectId = subjectListGridRecord.getAttributeAsInt(UsersDataSource.Field.ID);
                    if (subjectId == UsersDataSource.ID_RHQADMIN) {
                        subjectListGridRecord.setEnabled(false);
                    }
                    if (subjectId != UsersDataSource.ID_OVERLORD) {
                        filteredSubjectRecords.add(subjectListGridRecord);
                    }
                }
                subjectListGridRecords = filteredSubjectRecords.toArray(new ListGridRecord[filteredSubjectRecords
                    .size()]);
            }
            this.subjectSelector = new RoleSubjectSelector(subjectListGridRecords, !this.hasManageSecurityPermission);
            this.subjectSelector.addAssignedItemsChangedHandler(new AssignedItemsChangedHandler() {
                public void onSelectionChanged(AssignedItemsChangedEvent event) {
                    onItemChanged();
                }
            });
            updateTab(this.subjectsTab, this.subjectSelector);

            if (this.isLdapConfigured) {
                Record[] ldapGroupRecords = record.getAttributeAsRecordArray(RolesDataSource.Field.LDAP_GROUPS);
                ListGridRecord[] ldapGroupListGridRecords = toListGridRecordArray(ldapGroupRecords);
                this.ldapGroupSelector = new RoleLdapGroupSelector(ldapGroupListGridRecords,
                    !this.hasManageSecurityPermission);
                this.ldapGroupSelector.addAssignedItemsChangedHandler(new AssignedItemsChangedHandler() {
                    public void onSelectionChanged(AssignedItemsChangedEvent event) {
                        onItemChanged();
                    }
                });
                updateTab(this.ldapGroupsTab, this.ldapGroupSelector);
            } else {
                // LDAP is not configured for this RHQ Server - display a message on the LDAP Groups tab informing the
                // user of this along with a link to the System Settings view.
                Label label = new Label("<b>"
                    + MSG.common_msg_emphasizedNotePrefix()
                    + "</b> "
                    + MSG.view_adminRoles_noLdap("href='#Administration/Configuration/SystemSettings'",
                        MSG.view_adminConfig_systemSettings()));
                label.setWidth100();
                label.setHeight(20);
                label.setPadding(10);
                updateTab(this.ldapGroupsTab, label);
            }
        }

        this.permissionsEditor.redraw();
    }

    @Override
    protected List<FormItem> createFormItems(EnhancedDynamicForm form) {
        List<FormItem> items = new ArrayList<FormItem>();

        TextItem nameItem = new TextItem(RolesDataSource.Field.NAME);
        nameItem.setShowTitle(true);
        nameItem.setSelectOnFocus(true);
        nameItem.setTabIndex(1);
        nameItem.setAttribute(EnhancedDynamicForm.OUTPUT_AS_HTML_ATTRIBUTE, true);
        items.add(nameItem);

        TextItem descriptionItem = new TextItem(RolesDataSource.Field.DESCRIPTION);
        descriptionItem.setShowTitle(true);
        descriptionItem.setTabIndex(5);
        descriptionItem.setColSpan(form.getNumCols());
        descriptionItem.setAttribute(EnhancedDynamicForm.OUTPUT_AS_HTML_ATTRIBUTE, true);
        items.add(descriptionItem);

        return items;
    }

    @Override
    protected void save(DSRequest requestProperties) {
        // Grab the currently assigned sets from each of the selectors and stick them into the corresponding canvas
        // items on the form, so when the form is saved, they'll get submitted along with the rest of the simple fields
        // to the datasource's add or update methods.
        if (this.resourceGroupSelector != null) {
            ListGridRecord[] resourceGroupRecords = this.resourceGroupSelector.getSelectedRecords();
            getForm().setValue(RolesDataSource.Field.RESOURCE_GROUPS, resourceGroupRecords);
        }

        if (this.subjectSelector != null) {
            ListGridRecord[] subjectRecords = this.subjectSelector.getSelectedRecords();
            getForm().setValue(RolesDataSource.Field.SUBJECTS, subjectRecords);
        }

        if (this.ldapGroupSelector != null) {
            ListGridRecord[] ldapGroupRecords = this.ldapGroupSelector.getSelectedRecords();
            getForm().setValue(RolesDataSource.Field.LDAP_GROUPS, ldapGroupRecords);
        }

        if (this.bundleGroupSelector != null) {
            ListGridRecord[] bundleGroupRecords = this.bundleGroupSelector.getSelectedRecords();
            getForm().setValue(RolesDataSource.Field.BUNDLE_GROUPS, bundleGroupRecords);
        }

        // Submit the form values to the datasource.
        super.save(requestProperties);
    }

    @Override
    protected void reset() {
        super.reset();

        if (this.permissionsEditor != null) {
            this.permissionsEditor.reset();
        }

        if (this.resourceGroupSelector != null) {
            this.resourceGroupSelector.reset();
        }
        if (this.subjectSelector != null) {
            this.subjectSelector.reset();
        }
        if (this.ldapGroupSelector != null) {
            this.ldapGroupSelector.reset();
        }
        if (this.bundleGroupSelector != null) {
            this.bundleGroupSelector.reset();
        }
    }

    private static void updateTab(Tab tab, Canvas content) {
        if (tab == null) {
            throw new IllegalStateException("A null tab was specified.");
        }
        tab.getTabSet().updateTab(tab, content);
    }

}
