/*******************************************************************************
 * Copyright (c) 2010, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.wst.sse.ui.internal.contentassist;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jface.action.LegacyActionTools;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.eclipse.wst.sse.core.internal.util.Assert;
import org.eclipse.wst.sse.ui.contentassist.CompletionProposalInvocationContext;
import org.eclipse.wst.sse.ui.preferences.ICompletionProposalCategoriesConfigurationReader;
import org.osgi.framework.Bundle;

/**
 * Describes a category extension to the
 * <code>org.eclipse.wst.sse.ui.completionProposal</code> extension point.
 */
public final class CompletionProposalCategory {
	/** The extension schema name of the icon attribute. */
	private static final String ICON= "icon"; //$NON-NLS-1$
	
	/** The extension schema name of the ID attribute. */
	private static final String ID= "id"; //$NON-NLS-1$
	
	/** The extension schema name of the name attribute. */
	private static final String NAME= "name"; //$NON-NLS-1$

	/** ID of this completion category */
	private final String fId;
	
	/** Name of this completion category */
	private final String fName;
	
	/** The image descriptor for this category, or <code>null</code> if none specified. */
	private final ImageDescriptor fImage;
	
	/** The last error reported by this category */
	private String fLastError = null;

	/**
	 * <p>Construct the category by parsing the given element.</p>
	 * 
	 * @param element {@link IConfigurationElement} containing the configuration of this category
	 * @throws CoreException if the given {@link IConfigurationElement} does not contain the correct
	 * elements and attributes.
	 */
	CompletionProposalCategory(IConfigurationElement element) throws CoreException {
		Assert.isLegal(element != null);
		
		//get & verify ID
		fId = element.getAttribute(ID);
		ContentAssistUtils.checkExtensionAttributeNotNull(fId, ID, element);
		
		//get & verify optional name
		String name= element.getAttribute(NAME);
		if (name == null) {
			fName = fId;
		} else {
			fName= name;
		}

		//get & verify optional icon
		String icon= element.getAttribute(ICON);
		ImageDescriptor img= null;
		if (icon != null) {
			Bundle bundle= ContentAssistUtils.getBundle(element);
			if (bundle != null) {
				img = AbstractUIPlugin.imageDescriptorFromPlugin(bundle.getSymbolicName(), icon);
			}
		}
		fImage= img;
	}

	/**
	 * <p>Creates a category with the given name and ID</p>
	 * 
	 * @param id the unique ID of the new category
	 * @param name the name of the new category
	 */
	CompletionProposalCategory(String id, String name) {
		fId= id;
		fName= name;
		fImage= null;
	}

	/**
	 * <p>Returns the unique identifier of the category</p>
	 *
	 * @return Returns the id
	 */
	public String getId() {
		return fId;
	}

	/**
	 * <p>Returns the human readable name of the category.
	 * It may contain mnemonics.</p>
	 *
	 * @return Returns the name
	 */
	public String getName() {
		return fName;
	}

	/**
	 * <p>Returns the human readable name of the category
	 * without mnemonic hint in order to be displayed
	 * in a message.</p>
	 *
	 * @return Returns the name
	 */
	public String getDisplayName() {
		return LegacyActionTools.removeMnemonics(fName);
	}

	/**
	 * <p>Returns the image descriptor of the category.</p>
	 *
	 * @return the image descriptor of the category
	 */
	public ImageDescriptor getImageDescriptor() {
		return fImage;
	}

	/**
	 * @return <code>true</code> if this category should be displayed on
	 * its own content assist page, <code>false</code> otherwise
	 */
	public boolean isDisplayedOnOwnPage(String contentTypeID) {
		boolean displayOnOwnPage = ICompletionProposalCategoriesConfigurationReader.DEFAULT_DISPLAY_ON_OWN_PAGE;
		
		ICompletionProposalCategoriesConfigurationReader properties =
			CompletionProposoalCatigoriesConfigurationRegistry.getDefault().getReadableConfiguration(contentTypeID);
		if(properties != null) {
			displayOnOwnPage = properties.shouldDisplayOnOwnPage(this.fId);
		}
		
		return displayOnOwnPage;
	}

	/**
	 * @return <code>true</code> if this category should be displayed in
	 * the default content assist page, <code>false</code> otherwise
	 */
	public boolean isIncludedOnDefaultPage(String contentTypeID) {
		boolean includeOnDefaultPage = ICompletionProposalCategoriesConfigurationReader.DEFAULT_INCLUDE_ON_DEFAULTS_PAGE;
		
		ICompletionProposalCategoriesConfigurationReader properties =
			CompletionProposoalCatigoriesConfigurationRegistry.getDefault().getReadableConfiguration(contentTypeID);
		if(properties != null) {
			includeOnDefaultPage = properties.shouldDisplayOnDefaultPage(this.fId);
		}
		
		return includeOnDefaultPage;
	}
	
	/**
	 * <p>Given a content type ID determines the rank of this
	 * category for sorting the content assist pages</p>
	 * 
	 * @return the sort rank of this category
	 */
	public int getPageSortRank(String contentTypeID) {
		int sortOrder = ICompletionProposalCategoriesConfigurationReader.DEFAULT_SORT_ORDER;
		
		ICompletionProposalCategoriesConfigurationReader properties =
			CompletionProposoalCatigoriesConfigurationRegistry.getDefault().getReadableConfiguration(contentTypeID);
		if(properties != null) {
			sortOrder = properties.getPageSortOrder(this.fId);
		}
		
		return sortOrder;
	}
	
	/**
	 * <p>Given a content type ID determines the rank of this
	 * category for sorting on the default content assist page
	 * with other categories</p>
	 * 
	 * @return the sort rank of this category
	 */
	public int getDefaultPageSortRank(String contentTypeID) {
		int sortOrder = ICompletionProposalCategoriesConfigurationReader.DEFAULT_SORT_ORDER;
		
		ICompletionProposalCategoriesConfigurationReader properties =
			CompletionProposoalCatigoriesConfigurationRegistry.getDefault().getReadableConfiguration(contentTypeID);
		if(properties != null) {
			sortOrder = properties.getDefaultPageSortOrder(this.fId);
		}
		
		return sortOrder;
	}
	
	/**
	 * <p><b>NOTE: </b> enablement is not the same as weather a category
	 * should be displayed on its own page or the default page, it describes
	 * if the category should be used at all.  Currently categories are always
	 * enabled.  There maybe cases in the future where a category should be
	 * disabled entirely though.</p>
	 * 
	 * @return <code>true</code> if this category is enabled,
	 * <code>false</code> otherwise
	 */
	public boolean isEnabled() {
		return true;
	}

	/**
	 * @return <code>true</code> if the category contains any computers, <code>false</code>
	 *         otherwise
	 */
	public boolean hasComputers() {
		List descriptors=  CompletionProposalComputerRegistry.getDefault().getProposalComputerDescriptors();
		for (Iterator it= descriptors.iterator(); it.hasNext();) {
			CompletionProposalComputerDescriptor desc= (CompletionProposalComputerDescriptor) it.next();
			if (desc.getCategory() == this) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * @param contentTypeID the content type ID
	 * @param partitionTypeID the partition
	 * @return <code>true</code> if the category contains any computers, in the given partition type
	 * in the given content type, <code>false</code> otherwise
	 */
	public boolean hasComputers(String contentTypeID, String partitionTypeID) {
		List descriptors = CompletionProposalComputerRegistry.getDefault().getProposalComputerDescriptors(contentTypeID, partitionTypeID);
		for (Iterator it = descriptors.iterator(); it.hasNext();) {
			CompletionProposalComputerDescriptor desc= (CompletionProposalComputerDescriptor) it.next();
			if (desc.getCategory() == this) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * @param contentTypeID the content type ID
	 * @return <code>true</code> if the category contains any computers, in the given partition type
	 * in the given content type, <code>false</code> otherwise
	 */
	public boolean hasComputers(String contentTypeID) {
		List descriptors = CompletionProposalComputerRegistry.getDefault().getProposalComputerDescriptors(contentTypeID);
		for (Iterator it = descriptors.iterator(); it.hasNext();) {
			CompletionProposalComputerDescriptor desc= (CompletionProposalComputerDescriptor) it.next();
			if (desc.getCategory() == this) {
				return true;
			}
		}
		return false;
	}

	/**
	 * <p>Safely computes completion proposals of all computers of this category through their
	 * extension.</p>
	 *
	 * @param context the invocation context passed on to the extension
	 * @param contentTypeID the content type ID where the invocation occurred
	 * @param partitionTypeID the partition type where the invocation occurred
	 * @param monitor the progress monitor passed on to the extension
	 * @return the list of computed completion proposals (element type:
	 *         {@link org.eclipse.jface.text.contentassist.ICompletionProposal})
	 */
	public List computeCompletionProposals(CompletionProposalInvocationContext context,
			String contentTypeID, String partitionTypeID, SubProgressMonitor monitor) {
		
		fLastError = null;
		List result = new ArrayList();
		List descriptors = new ArrayList(CompletionProposalComputerRegistry.getDefault().getProposalComputerDescriptors(contentTypeID, partitionTypeID));
		for (Iterator it = descriptors.iterator(); it.hasNext();) {
			CompletionProposalComputerDescriptor desc = (CompletionProposalComputerDescriptor) it.next();
			if (desc.getCategory() == this) {
				result.addAll(desc.computeCompletionProposals(context, monitor));
			}
			
			if (fLastError == null && desc.getErrorMessage() != null) {
				fLastError = desc.getErrorMessage();
			}
		}
		return result;
	}

	/**
	 * <p>Safely computes context information objects of all computers of this category through their
	 * extension.</p>
	 *
	 * @param context the invocation context passed on to the extension
	 * @param contentTypeID the content type ID where the invocation occurred
	 * @param partitionTypeID the partition type where the invocation occurred
	 * @param monitor the progress monitor passed on to the extension
	 * @return the list of computed context information objects (element type:
	 *         {@link org.eclipse.jface.text.contentassist.IContextInformation})
	 */
	public List computeContextInformation(CompletionProposalInvocationContext context,
			String contentTypeID, String partitionTypeID, SubProgressMonitor monitor) {
		
		fLastError= null;
		List result= new ArrayList();
		List descriptors= new ArrayList(CompletionProposalComputerRegistry.getDefault().getProposalComputerDescriptors(contentTypeID, partitionTypeID));
		for (Iterator it= descriptors.iterator(); it.hasNext();) {
			CompletionProposalComputerDescriptor desc= (CompletionProposalComputerDescriptor) it.next();
			if (desc.getCategory() == this && (isIncludedOnDefaultPage(contentTypeID) || isDisplayedOnOwnPage(contentTypeID))) {
				result.addAll(desc.computeContextInformation(context, monitor));
			}
			if (fLastError == null) {
				fLastError= desc.getErrorMessage();
			}
		}
		return result;
	}

	/**
	 * @return the last error message reported by a computer in this category
	 */
	public String getErrorMessage() {
		return fLastError;
	}

	/**
	 * <p>Notifies the computers in this category of a proposal computation session start.</p>
	 */
	public void sessionStarted() {
		List descriptors= new ArrayList(CompletionProposalComputerRegistry.getDefault().getProposalComputerDescriptors());
		for (Iterator it= descriptors.iterator(); it.hasNext();) {
			CompletionProposalComputerDescriptor desc= (CompletionProposalComputerDescriptor) it.next();
			if (desc.getCategory() == this)
				desc.sessionStarted();
			if (fLastError == null)
				fLastError= desc.getErrorMessage();
		}
	}

	/**
	 * <p>Notifies the computers in this category of a proposal computation session end.</p>
	 */
	public void sessionEnded() {
		List descriptors= new ArrayList(CompletionProposalComputerRegistry.getDefault().getProposalComputerDescriptors());
		for (Iterator it= descriptors.iterator(); it.hasNext();) {
			CompletionProposalComputerDescriptor desc= (CompletionProposalComputerDescriptor) it.next();
			if (desc.getCategory() == this)
				desc.sessionEnded();
			if (fLastError == null)
				fLastError= desc.getErrorMessage();
		}
	}
	
	/**
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		return fId + ": " + fName; //$NON-NLS-1$
	}
}
