/*
 * RHQ Management Platform
 * Copyright (C) 2005-2008 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.enterprise.gui.discovery;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.faces.application.FacesMessage;
import javax.faces.model.DataModel;
import javax.faces.model.SelectItem;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.resource.InventoryStatus;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.resource.composite.DisambiguationReport;
import org.rhq.core.domain.util.PageControl;
import org.rhq.core.domain.util.PageList;
import org.rhq.core.gui.util.FacesContextUtility;
import org.rhq.core.util.IntExtractor;
import org.rhq.enterprise.gui.common.framework.PagedDataTableUIBean;
import org.rhq.enterprise.gui.common.paging.PageControlView;
import org.rhq.enterprise.gui.common.paging.PagedListDataModel;
import org.rhq.enterprise.gui.common.paging.ResourceNameDisambiguatingPagedListDataModel;
import org.rhq.enterprise.gui.util.EnterpriseFacesContextUtility;
import org.rhq.enterprise.server.discovery.DiscoveryBossLocal;
import org.rhq.enterprise.server.resource.ResourceManagerLocal;
import org.rhq.enterprise.server.resource.disambiguation.DefaultDisambiguationUpdateStrategies;
import org.rhq.enterprise.server.util.LookupUtil;

public class AutoDiscoveryQueueUIBean extends PagedDataTableUIBean {
    private final Log log = LogFactory.getLog(AutoDiscoveryQueueUIBean.class);

    public static final String MANAGED_BEAN_NAME = "AutoDiscoveryQueueUIBean";

    private DiscoveryBossLocal discoveryBoss = LookupUtil.getDiscoveryBoss();
    private ResourceManagerLocal resourceManager = LookupUtil.getResourceManager();
    private Map<DisambiguationReport<Resource>, List<DisambiguationReport<Resource>>> platformsAndServers = new HashMap<DisambiguationReport<Resource>, List<DisambiguationReport<Resource>>>();

    private static final IntExtractor<Resource> RESOURCE_ID_EXTRACTOR = new IntExtractor<Resource>() {
        public int extract(Resource object) {
            return object.getId();
        }
    };
        
    public AutoDiscoveryQueueUIBean() {
    }

    @Override
    public DataModel getDataModel() {
        if (dataModel == null) {
            dataModel = new AutoDiscoveryQueueDataModel(PageControlView.AutoDiscoveryPlatformList, MANAGED_BEAN_NAME);
        }

        return dataModel;
    }

    @Override
    public SelectItem[] getPageSizes() {
        return new SelectItem[] { new SelectItem("10", "10"), new SelectItem("25", "25"), new SelectItem("50", "50"),
            new SelectItem("75", "75"), new SelectItem("100", "100") };
    }

    @Override
    public int getMinimumPageSize() {
        return 10;
    }

    public Map<DisambiguationReport<Resource>, List<DisambiguationReport<Resource>>> getPlatformsAndServers() {
        return platformsAndServers;
    }

    public String rebuildTable() {
        // this method is called when the table needs to be rebuilt - clear out our session to start anew
        getExpandedPlatforms().clear();
        getSelectedResources().clear();
        return "sort";
    }

    public String importResources() {
        Subject subject = EnterpriseFacesContextUtility.getSubject();
        int platformCount = 0;
        int serverCount = 0;

        try {
            Map<Integer, Boolean> selectedResources = getSelectedResources();

            // do it one platform at a time so we give each its own transaction
            List<DisambiguationReport<Resource>> platform = new ArrayList<DisambiguationReport<Resource>>(1);

            for (Map.Entry<Integer, Boolean> selected : selectedResources.entrySet()) {
                // if current 'selected' is a platform, import the selected things for that platform
                DisambiguationReport<Resource> resource = findResource(selected.getKey());
                if ((resource != null) && selected.getValue().booleanValue()
                    && platformsAndServers.containsKey(resource)) {
                    platform.clear();
                    platform.add(resource);
                    if (resource.getOriginal().getInventoryStatus() == InventoryStatus.NEW) {
                        platformCount++;
                    }

                    List<DisambiguationReport<Resource>> allServers = platformsAndServers.get(resource);
                    List<DisambiguationReport<Resource>> servers = new ArrayList<DisambiguationReport<Resource>>();
                    for (DisambiguationReport<Resource> server : allServers) {
                        if (selectedResources.containsKey(server.getOriginal().getId())
                            && selectedResources.get(server.getOriginal().getId()).booleanValue()
                            && (server.getOriginal().getInventoryStatus() == InventoryStatus.NEW)) {
                            serverCount++;
                            servers.add(server);
                        }
                    }

                    log.debug("AIQueue import: platform=" + platform + "| servers=" + servers);
                    // TODO: Why do we update the platform's status even when it was already COMMITTED?? (ips, 07/10/08)
                    discoveryBoss.updateInventoryStatus(subject, getOriginals(platform), getOriginals(servers), InventoryStatus.COMMITTED);
                }
            }

            FacesContextUtility.addMessage(FacesMessage.SEVERITY_INFO, "Imported [" + platformCount
                + "] platforms and [" + serverCount + "] servers.");

            getSelectedResources().clear();
        } catch (Exception e) {
            FacesContextUtility.addMessage(FacesMessage.SEVERITY_ERROR, "Import failed.", e);
            log.error("Import failed", e);
        }

        return "success";
    }

    public String ignoreResources() {
        Subject subject = EnterpriseFacesContextUtility.getSubject();
        int serverCount = 0;

        try {
            // do it one platform at a time so we give each its own transaction
            List<DisambiguationReport<Resource>> platform = new ArrayList<DisambiguationReport<Resource>>(1);

            Map<Integer, Boolean> selectedResources = getSelectedResources();
            for (Map.Entry<Integer, Boolean> selected : selectedResources.entrySet()) {
                // if current 'selected' is a platform, ignore the selected things for that platform
                DisambiguationReport<Resource> resource = findResource(selected.getKey());
                if ((resource != null) && selected.getValue().booleanValue()
                    && platformsAndServers.containsKey(resource)) {
                    List<DisambiguationReport<Resource>> allServers = platformsAndServers.get(resource);
                    List<DisambiguationReport<Resource>> servers = new ArrayList<DisambiguationReport<Resource>>();
                    for (DisambiguationReport<Resource> server : allServers) {
                        if (selectedResources.containsKey(server.getOriginal().getId())
                            && selectedResources.get(server.getOriginal().getId()).booleanValue()
                            && (server.getOriginal().getInventoryStatus() == InventoryStatus.NEW)) {
                            serverCount++;
                            servers.add(server);
                        }
                    }

                    log.debug("AIQueue ignore: platform=" + platform + "| servers=" + servers);
                    discoveryBoss.updateInventoryStatus(subject, getOriginals(platform), getOriginals(servers), InventoryStatus.IGNORED);
                }
            }

            FacesContextUtility.addMessage(FacesMessage.SEVERITY_INFO, "Ignored [" + serverCount + "] servers.");

            getSelectedResources().clear();
        } catch (Exception e) {
            FacesContextUtility.addMessage(FacesMessage.SEVERITY_ERROR, "Ignore failed.", e);
            log.error("Ignore failed", e);
        }

        return "success";
    }

    public String unignoreResources() {
        Subject subject = EnterpriseFacesContextUtility.getSubject();
        int serverCount = 0;

        try {
            // do it one platform at a time so we give each its own transaction
            List<DisambiguationReport<Resource>> platform = new ArrayList<DisambiguationReport<Resource>>(1);

            Map<Integer, Boolean> selectedResources = getSelectedResources();
            for (Map.Entry<Integer, Boolean> selected : selectedResources.entrySet()) {
                // if current 'selected' is a platform, unignore its servers; otherwise, just go on to the next
                DisambiguationReport<Resource> resource = findResource(selected.getKey());
                if ((resource != null) && selected.getValue().booleanValue()
                    && platformsAndServers.containsKey(resource)) {
                    if (resource.getOriginal().getInventoryStatus() != InventoryStatus.COMMITTED) {
                        FacesContextUtility.addMessage(FacesMessage.SEVERITY_WARN,
                            "Cannot un-ignore servers from an uncommitted platform [" + resource.getOriginal().getName()
                                + "]. Aborting.");

                        break;
                    }

                    List<DisambiguationReport<Resource>> allServers = platformsAndServers.get(resource);
                    List<DisambiguationReport<Resource>> servers = new ArrayList<DisambiguationReport<Resource>>();
                    for (DisambiguationReport<Resource> server : allServers) {
                        if (selectedResources.containsKey(server.getOriginal().getId())
                            && selectedResources.get(server.getOriginal().getId()).booleanValue()
                            && (server.getOriginal().getInventoryStatus() == InventoryStatus.IGNORED)) {
                            serverCount++;
                            servers.add(server);
                        }
                    }

                    log.debug("AIQueue unignore: platform=" + platform + "| servers=" + servers);
                    discoveryBoss.updateInventoryStatus(subject, getOriginals(platform), getOriginals(servers), InventoryStatus.NEW);
                }
            }

            FacesContextUtility.addMessage(FacesMessage.SEVERITY_INFO, "Un-ignored [" + serverCount + "] servers.");

            getSelectedResources().clear();
        } catch (Exception e) {
            FacesContextUtility.addMessage(FacesMessage.SEVERITY_ERROR, "Un-ignore failed.", e);
            log.error("Unignore failed", e);
        }

        return "success";
    }

    private Map<Integer, Boolean> getSelectedResources() {
        return FacesContextUtility.getManagedBean(AutoDiscoverySessionUIBean.class).getSelectedResources();
    }

    private Map<Integer, Boolean> getExpandedPlatforms() {
        return FacesContextUtility.getManagedBean(AutoDiscoverySessionUIBean.class).getExpandedMap();
    }

    private DisambiguationReport<Resource> findResource(Integer id) {
        for (Map.Entry<DisambiguationReport<Resource>, List<DisambiguationReport<Resource>>> entry : platformsAndServers.entrySet()) {
            DisambiguationReport<Resource> platform = entry.getKey();
            List<DisambiguationReport<Resource>> servers = entry.getValue();

            if (platform.getOriginal().getId() == id.intValue()) {
                return platform;
            }

            for (DisambiguationReport<Resource> server : servers) {
                if (server.getOriginal().getId() == id.intValue()) {
                    return server;
                }
            }
        }

        return null;
    }

    private EnumSet<InventoryStatus> getShowNewIgnoreEnumSet() {
        AutoDiscoverySessionUIBean bean = FacesContextUtility.getManagedBean(AutoDiscoverySessionUIBean.class);
        String showNewIgnore = bean.getShowNewIgnore();

        if ("BOTH".equals(showNewIgnore)) {
            return EnumSet.of(InventoryStatus.NEW, InventoryStatus.IGNORED);
        } else if (InventoryStatus.NEW.name().equals(showNewIgnore)) {
            return EnumSet.of(InventoryStatus.NEW);
        } else if (InventoryStatus.IGNORED.name().equals(showNewIgnore)) {
            return EnumSet.of(InventoryStatus.IGNORED);
        }

        return EnumSet.of(InventoryStatus.NEW);
    }

    private class AutoDiscoveryQueueDataModel extends PagedListDataModel<DisambiguationReport<Resource>> {
        public AutoDiscoveryQueueDataModel(PageControlView view, String beanName) {
            super(view, beanName);
        }

        public PageList<DisambiguationReport<Resource>> fetchPage(PageControl pc) {
            Subject subject = EnterpriseFacesContextUtility.getSubject();
            EnumSet<InventoryStatus> newIgnoredSet = getShowNewIgnoreEnumSet();

            platformsAndServers.clear();

            PageList<Resource> queuedPlatforms = discoveryBoss.getQueuedPlatforms(subject, newIgnoredSet, pc);
            
            List<DisambiguationReport<Resource>> disambiguatedQueuedPlatforms = 
                resourceManager.disambiguate(queuedPlatforms,
                    RESOURCE_ID_EXTRACTOR, DefaultDisambiguationUpdateStrategies.getDefault()).getResolution();
            
            PageList<DisambiguationReport<Resource>> disambiguatedQueuedPlaformsPageList = 
                new PageList<DisambiguationReport<Resource>>(disambiguatedQueuedPlatforms, queuedPlatforms.getTotalSize(), queuedPlatforms.getPageControl());

            for (DisambiguationReport<Resource> platform : disambiguatedQueuedPlatforms) {
                List<DisambiguationReport<Resource>> queuedServers = new ArrayList<DisambiguationReport<Resource>>();
                for (InventoryStatus status : newIgnoredSet) {
                    List<Resource> childServers = discoveryBoss.getQueuedPlatformChildServers(subject, status, platform.getOriginal());
                    List<DisambiguationReport<Resource>> disambiguatedChildServers = 
                        resourceManager.disambiguate(childServers, RESOURCE_ID_EXTRACTOR, DefaultDisambiguationUpdateStrategies.getDefault()).getResolution();
                    
                    
                    queuedServers.addAll(disambiguatedChildServers);
                }

                platformsAndServers.put(platform, queuedServers);
            }

            // In order to be able to expandAll/collapseAll, the expandedPlatforms map must have all platforms in it.
            // Prepare the expandedPlatforms, maintaining prior state for platforms that were already in the map.
            Map<Integer, Boolean> expandedPlatforms = getExpandedPlatforms();
            Map<Integer, Boolean> newExpandedPlatforms = new HashMap<Integer, Boolean>();
            for (DisambiguationReport<Resource> platform : platformsAndServers.keySet()) {
                Boolean expanded = expandedPlatforms.get(platform.getOriginal().getId());
                newExpandedPlatforms.put(platform.getOriginal().getId(), (expanded != null) ? expanded : Boolean.FALSE);
            }
            expandedPlatforms.clear();
            expandedPlatforms.putAll(newExpandedPlatforms);

            return disambiguatedQueuedPlaformsPageList;
        }
    }
    
    private static <T> List<T> getOriginals(List<DisambiguationReport<T>> reports) {
        List<T> ret = new ArrayList<T>();
        for(DisambiguationReport<T> report : reports) {
            ret.add(report.getOriginal());
        }
        
        return ret;
    }
}