/*
 * RHQ Management Platform
 * Copyright (C) 2005-2011 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.inventory.resource.discovery;

import java.util.ArrayList;
import java.util.Date;
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.DSResponse;
import com.smartgwt.client.data.DataSource;
import com.smartgwt.client.data.fields.DataSourceTextField;
import com.smartgwt.client.types.DSDataFormat;
import com.smartgwt.client.types.DSProtocol;
import com.smartgwt.client.widgets.tree.TreeGrid;
import com.smartgwt.client.widgets.tree.TreeNode;

import org.rhq.core.domain.authz.Permission;
import org.rhq.core.domain.criteria.ResourceCriteria;
import org.rhq.core.domain.resource.InventoryStatus;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.resource.ResourceCategory;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.core.domain.util.PageControl;
import org.rhq.core.domain.util.PageList;
import org.rhq.core.domain.util.PageOrdering;
import org.rhq.coregui.client.CoreGUI;
import org.rhq.coregui.client.Messages;
import org.rhq.coregui.client.PermissionsLoadedListener;
import org.rhq.coregui.client.PermissionsLoader;
import org.rhq.coregui.client.gwt.GWTServiceLookup;
import org.rhq.coregui.client.gwt.ResourceGWTServiceAsync;
import org.rhq.coregui.client.util.Log;

/**
 * @author Greg Hinkle
 */
public class AutodiscoveryQueueDataSource extends DataSource {

    private static Messages MSG = CoreGUI.getMessages();

    public static final String NEW = MSG.common_button_new();
    public static final String IGNORED = MSG.view_autoDiscoveryQ_ignored();
    public static final String NEW_AND_IGNORED = MSG.view_autoDiscoveryQ_newAndIgnored();

    private static final String NO_MANAGE_INVENTORY_PERMS_EMPTY_MESSAGE = MSG.view_autoDiscoveryQ_noperm();
    private static final String EMPTY_MESSAGE = MSG.common_msg_noItemsToShow();

    private static final Permission MANAGE_INVENTORY = Permission.MANAGE_INVENTORY;

    private int unlimited = -1;
    private int maximumPlatformsToDisplay = -1;

    // Specify 60s timeout to compensate for slow loading of this view due to lack of paging of results.
    // TODO (ips, 08/31/11): Remove this once paging has been implemented.
    private ResourceGWTServiceAsync resourceService = GWTServiceLookup.getResourceService(60 * 1000);

    private PermissionsLoader permissionsLoader = new PermissionsLoader();
    private TreeGrid dataContainerReference = null;

    private List<AsyncCallback> failedFetchListeners = new ArrayList<AsyncCallback>();

    public AutodiscoveryQueueDataSource() {
        setClientOnly(false);
        setDataProtocol(DSProtocol.CLIENTCUSTOM);
        setDataFormat(DSDataFormat.CUSTOM);

        DataSourceTextField idField = new DataSourceTextField("id", MSG.common_title_id());
        idField.setPrimaryKey(true);

        DataSourceTextField parentIdField = new DataSourceTextField("parentId",
            MSG.view_autoDiscoveryQ_field_parentId());
        parentIdField.setForeignKey("id");

        DataSourceTextField resourceNameField = new DataSourceTextField("name", MSG.common_title_resource_name());

        DataSourceTextField resourceKeyField = new DataSourceTextField("resourceKey", MSG.common_title_resource_key());

        DataSourceTextField resourceTypeField = new DataSourceTextField("typeName", MSG.common_title_resource_type());

        DataSourceTextField descriptionField = new DataSourceTextField("description", MSG.common_title_description());

        DataSourceTextField timestampField = new DataSourceTextField("ctime",
            MSG.view_autoDiscoveryQ_field_discoveryTime());

        DataSourceTextField statusField = new DataSourceTextField("statusLabel",
            MSG.view_autoDiscoveryQ_field_inventoryStatus());

        setFields(idField, parentIdField, resourceNameField, resourceKeyField, resourceTypeField, descriptionField,
            statusField, timestampField);
    }

    public AutodiscoveryQueueDataSource(TreeGrid treeGrid) {
        this();
        this.dataContainerReference = treeGrid;
    }

    protected Object transformRequest(DSRequest request) {
        DSResponse response = new DSResponse();
        response.setAttribute("clientContext", request.getAttributeAsObject("clientContext"));
        // assume success
        response.setStatus(0);
        switch (request.getOperationType()) {
        case FETCH:
            executeFetch(request, response);
            break;
        default:
            break;
        }

        return request.getData();
    }

    protected void executeFetch(final DSRequest request, final DSResponse response) {
        final String platformId = request.getCriteria().getAttribute("parentId");
        final ArrayList<InventoryStatus> statuses = new ArrayList<InventoryStatus>();

        String statusesString = request.getCriteria().getAttributeAsString("status");
        if (statusesString != null) {
            if (NEW.equals(statusesString)) {
                statuses.add(InventoryStatus.NEW);
            } else if (IGNORED.equals(statusesString)) {
                statuses.add(InventoryStatus.IGNORED);
            } else {
                statuses.add(InventoryStatus.NEW);
                statuses.add(InventoryStatus.IGNORED);
            }
        } else {
            statuses.add(InventoryStatus.NEW);
        }

        //determine if has manage inventory perms, if so then chain and proceed with getting Q resources
        permissionsLoader.loadExplicitGlobalPermissions(new PermissionsLoadedListener() {
            @Override
            public void onPermissionsLoaded(Set<Permission> permissions) {
                if (permissions != null) {
                    if (!permissions.contains(MANAGE_INVENTORY)) {
                        Log.debug("(User does not have required managed inventory permissions. " + EMPTY_MESSAGE);
                        response.setTotalRows(0);
                        if (dataContainerReference != null) {
                            Log.trace("Setting better empty container message."
                                + NO_MANAGE_INVENTORY_PERMS_EMPTY_MESSAGE);
                            dataContainerReference.setEmptyMessage(NO_MANAGE_INVENTORY_PERMS_EMPTY_MESSAGE);
                        }
                        processResponse(request.getRequestId(), response);
                        return;
                    }

                    if (dataContainerReference != null) {
                        dataContainerReference.setEmptyMessage(EMPTY_MESSAGE);
                    }

                    if (null == platformId) {
                        // query for platforms
                        final PageControl pc = getPageControl(request);
                        resourceService.getQueuedPlatforms(statuses, pc, new AsyncCallback<PageList<Resource>>() {
                            public void onFailure(Throwable caught) {
                                CoreGUI.getErrorHandler().handleError(MSG.view_autoDiscoveryQ_loadFailure(), caught);
                                for (AsyncCallback failedFetchListener : failedFetchListeners) {
                                    failedFetchListener.onFailure(caught);
                                }
                            }

                            public void onSuccess(PageList<Resource> result) {
                                response.setData(buildPlatformNodes(result));
                                processResponse(request.getRequestId(), response);
                            }
                        });
                    } else {
                        final int platformResourceId = Integer.valueOf(platformId);
                        final Resource parentResourceStub = new Resource(platformResourceId);

                        ResourceCriteria fetchCriteria = new ResourceCriteria();
                        fetchCriteria.addFilterParentResourceId(platformResourceId);
                        fetchCriteria.addFilterResourceCategories(ResourceCategory.SERVER);
                        fetchCriteria.addFilterInventoryStatuses(statuses);
                        fetchCriteria.clearPaging();
                        fetchCriteria.addSortName(PageOrdering.ASC);
                        resourceService.findResourcesByCriteria(fetchCriteria, new AsyncCallback<PageList<Resource>>() {
                            public void onFailure(Throwable caught) {
                                CoreGUI.getErrorHandler().handleError(MSG.view_autoDiscoveryQ_loadFailure(), caught);
                                for (AsyncCallback failedFetchListener : failedFetchListeners) {
                                    failedFetchListener.onFailure(caught);
                                }
                            }

                            public void onSuccess(PageList<Resource> result) {
                                response.setData(buildServerNodes(parentResourceStub, result));
                                processResponse(request.getRequestId(), response);
                            }
                        });
                    }
                }
            }
        });
    }

    /**
     * @param callback The onFailure() method will be invoked if the DS fails a fetch operation.
     */
    public void addFailedFetchListener(AsyncCallback callback) {
        failedFetchListeners.add(callback);
    }

    private TreeNode[] buildPlatformNodes(PageList<Resource> platforms) {

        ArrayList<ResourceTreeNode> nodes = new ArrayList<ResourceTreeNode>();
        for (Resource platform : platforms) {
            nodes.add(new ResourceTreeNode(platform));
        }
        TreeNode[] treeNodes = nodes.toArray(new TreeNode[nodes.size()]);
        return treeNodes;
    }

    private TreeNode[] buildServerNodes(Resource parentResource, PageList<Resource> servers) {

        ArrayList<ResourceTreeNode> nodes = new ArrayList<ResourceTreeNode>();
        for (Resource server : servers) {
            server.setParentResource(parentResource); // set the parent so the tree relationship gets set
            nodes.add(new ResourceTreeNode(server));
        }
        TreeNode[] treeNodes = nodes.toArray(new TreeNode[nodes.size()]);
        return treeNodes;
    }

    /**
     * Returns a pre-populated PageControl based on the provided DSRequest. This will set sort fields,
     * pagination, but *not* filter fields.
     *
     * @param request the request to turn into a page control
     * @return the page control for passing to criteria and other queries
     */
    protected PageControl getPageControl(DSRequest request) {
        // Initialize paging.
        PageControl pageControl;
        if (getMaximumPlatformsToDisplay() > -1) {
            pageControl = new PageControl(0, getMaximumPlatformsToDisplay());
        } else {
            pageControl = new PageControl(0, unlimited);
        }

        // Initialize sorting.
        String sortBy = request.getAttribute("sortBy");
        if (sortBy != null) {
            String[] sorts = sortBy.split(",");
            for (String sort : sorts) {
                PageOrdering ordering = (sort.startsWith("-")) ? PageOrdering.DESC : PageOrdering.ASC;
                String columnName = (ordering == PageOrdering.DESC) ? sort.substring(1) : sort;
                pageControl.addDefaultOrderingField(columnName, ordering);
            }
        }

        return pageControl;
    }

    public static class ResourceTreeNode extends TreeNode {

        private Resource resource;

        private ResourceTreeNode(Resource resource) {
            this.resource = resource;

            String id = String.valueOf(resource.getId());
            String parentId = resource.getParentResource() == null ? null : String.valueOf((resource
                .getParentResource().getId()));

            setID(id);
            setParentID(parentId);
            setIsFolder(null == parentId);

            setAttribute("id", id);
            setAttribute("parentId", parentId);
            setAttribute("name", resource.getName());
            setAttribute("typeName", resource.getResourceType().getName());
            setAttribute("resourceKey", resource.getResourceKey());
            setAttribute("description", resource.getDescription());
            setAttribute("ctime", new Date(resource.getCtime()));
            setAttribute("status", resource.getInventoryStatus().name());
            switch (resource.getInventoryStatus()) {
            case NEW:
                setAttribute("statusLabel", MSG.common_button_new());
                break;
            case COMMITTED:
                setAttribute("statusLabel", MSG.view_autoDiscoveryQ_committed());
                break;
            case IGNORED:
                setAttribute("statusLabel", MSG.view_autoDiscoveryQ_ignored());
                break;
            case DELETED:
                setAttribute("statusLabel", MSG.view_autoDiscoveryQ_deleted());
                break;
            case UNINVENTORIED:
                setAttribute("statusLabel", MSG.view_autoDiscoveryQ_uninventoried());
                break;
            }
        }

        public Resource getResource() {
            return resource;
        }

        public void setResource(Resource resource) {
            this.resource = resource;
        }

        public ResourceType getResourceType() {
            return resource.getResourceType();
        }

        public String getParentId() {
            return getAttribute("parentId");
        }
    }

    public int getMaximumPlatformsToDisplay() {
        return maximumPlatformsToDisplay;
    }

    public void setMaximumPlatformsToDisplay(int maximumPlatformsToDisplay) {
        this.maximumPlatformsToDisplay = maximumPlatformsToDisplay;
    }

}
