/*******************************************************************************
 * Copyright (c) 2011 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.views.contentoutline;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;

import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.jface.window.Window;
import org.eclipse.wst.sse.ui.internal.filter.OutlineCustomFiltersDialog;
import org.eclipse.wst.sse.ui.internal.filter.OutlineFilterDescriptor;
import org.eclipse.wst.sse.ui.internal.filter.OutlineNamePatternFilter;

/**
 * Action group to add the filter action to a view part's tool bar menu.
 * <p>
 * This class may be instantiated; it is not intended to be subclassed.
 * </p>
 * 
 * @noextend This class is not intended to be subclassed by clients.
 */
public class ContentOutlineFilterProcessor {

	private static final String SEPARATOR = ","; //$NON-NLS-1$
	private final String TAG_USER_DEFINED_PATTERNS_ENABLED = "userDefinedPatternsEnabled"; //$NON-NLS-1$
	private final String TAG_USER_DEFINED_PATTERNS = "userDefinedPatterns"; //$NON-NLS-1$

	private static class FilterItem {
		boolean enabled;
		boolean previouslyEnabled;
		OutlineFilterDescriptor descriptor;
		String id;

		private ViewerFilter filterInstance = null;

		public FilterItem(OutlineFilterDescriptor descriptor) {
			this.descriptor = descriptor;
			this.id = descriptor.getId();
			this.previouslyEnabled = false;
			this.enabled = descriptor.isEnabled();
		}

		public ViewerFilter getFilterInstance() {
			if (filterInstance == null) {
				filterInstance = descriptor.createViewerFilter();
			}
			return filterInstance;

		}
	}

	private final StructuredViewer fViewer;
	private final OutlineNamePatternFilter fPatternFilter;

	private boolean fUserDefinedPatternsEnabled;
	private String[] fUserDefinedPatterns;

	private String[] fPreviousPatterns;

	private final Map fFilterItems;

	private final String fTargetId;
	private IPreferenceStore fStore;

	/**
	 * Creates a new <code>CustomFilterAction</code>.
	 * 
	 * @param store
	 *            the preference Store
	 * @param ownerId
	 *            the id of this action group's owner
	 * @param viewer
	 *            the viewer to be filtered
	 */
	public ContentOutlineFilterProcessor(IPreferenceStore store, String ownerId, StructuredViewer viewer) {
		Assert.isNotNull(ownerId);
		Assert.isNotNull(viewer);
		fStore = store;
		fTargetId = ownerId;
		fViewer = viewer;
		fPatternFilter = new OutlineNamePatternFilter();

		fUserDefinedPatterns = new String[0];
		fUserDefinedPatternsEnabled = false;
		fPreviousPatterns = new String[0];

		fFilterItems = new HashMap();
		OutlineFilterDescriptor[] filterDescriptors = OutlineFilterDescriptor.getFilterDescriptors(fTargetId);
		for (int i = 0; i < filterDescriptors.length; i++) {
			FilterItem item = new FilterItem(filterDescriptors[i]);
			fFilterItems.put(item.id, item);

		}

		initializeWithViewDefaults();

		updateViewerFilters();

	}

	/*
	 * @see org.eclipse.jface.action.IContributionItem#isDynamic()
	 */
	public boolean isDynamic() {
		return true;
	}

	/**
	 * Returns a list of currently enabled filters. The filter is identified by
	 * its id.
	 * <p>
	 * This method is for internal use only and should not be called by clients
	 * outside of JDT/UI.
	 * </p>
	 * 
	 * @return a list of currently enabled filters
	 * 
	 * @noreference This method is not intended to be referenced by clients.
	 */
	public String[] internalGetEnabledFilterIds() {
		ArrayList enabledFilterIds = new ArrayList();
		for (Iterator iterator = fFilterItems.values().iterator(); iterator.hasNext();) {
			FilterItem item = (FilterItem) iterator.next();
			if (item.enabled) {
				enabledFilterIds.add(item.id);
			}
		}
		return (String[]) enabledFilterIds.toArray(new String[enabledFilterIds.size()]);
	}

	private void setEnabledFilterIds(String[] enabledIds) {
		// set all to false
		fUserDefinedPatternsEnabled = false;
		for (Iterator iterator = fFilterItems.values().iterator(); iterator.hasNext();) {
			FilterItem item = (FilterItem) iterator.next();
			item.enabled = false;
		}
		// set enabled to true
		for (int i = 0; i < enabledIds.length; i++) {
			FilterItem item = (FilterItem) fFilterItems.get(enabledIds[i]);
			if (item != null) {
				item.enabled = true;
			}
			if (fPatternFilter.getClass().getName().equals(enabledIds[i]))
				fUserDefinedPatternsEnabled = true;
		}
	}

	private void setUserDefinedPatterns(String[] patterns) {
		fUserDefinedPatterns = patterns;
	}

	private boolean areUserDefinedPatternsEnabled() {
		return fUserDefinedPatternsEnabled;
	}

	private void setUserDefinedPatternsEnabled(boolean state) {
		fUserDefinedPatternsEnabled = state;
	}

	// ---------- viewer filter handling ----------

	private boolean updateViewerFilters() {
		ViewerFilter[] installedFilters = fViewer.getFilters();
		ArrayList viewerFilters = new ArrayList(installedFilters.length);

		HashSet patterns = new HashSet();

		boolean hasChange = false;
		boolean patternChange = false;

		for (Iterator iterator = fFilterItems.values().iterator(); iterator.hasNext();) {
			FilterItem item = (FilterItem) iterator.next();
			if (item.descriptor.isCustomFilter()) {
				if (item.enabled != item.previouslyEnabled) {
					hasChange = true;
				}
				if (item.enabled) {
					ViewerFilter filter = item.getFilterInstance(); // only
																	// create
																	// when
																	// changed
					if (filter != null) {
						viewerFilters.add(filter);
					}
				}
			} else if (item.descriptor.isPatternFilter()) {
				if (item.enabled) {
					patterns.add(item.descriptor.getPattern());
				}
				patternChange |= (item.enabled != item.previouslyEnabled);
			}
			item.previouslyEnabled = item.enabled;
		}

		if (areUserDefinedPatternsEnabled()) {
			for (int i = 0; i < fUserDefinedPatterns.length; i++) {
				patterns.add(fUserDefinedPatterns[i]);
			}
		}
		if (!patternChange) { // no pattern change so far, test if the user
								// patterns made a difference
			patternChange = hasChanges(patterns, fPreviousPatterns);
		}

		fPreviousPatterns = (String[]) patterns.toArray(new String[patterns.size()]);
		if (patternChange || hasChange) {
			fPatternFilter.setPatterns(fPreviousPatterns);
			if (patterns.isEmpty()) {
				viewerFilters.remove(fPatternFilter);
			} else if (!viewerFilters.contains(fPatternFilter)) {
				boolean contains = false;
				for (int i = 0; i < viewerFilters.size(); i++) {
					if (viewerFilters.get(i) instanceof OutlineNamePatternFilter) {
						OutlineNamePatternFilter filter = (OutlineNamePatternFilter) viewerFilters.get(i);
						String[] a1 = filter.getPatterns();
						String[] a2 = fPatternFilter.getPatterns();
						if (a1[0].equals(a2[0]))
							contains = true;
						else {
							viewerFilters.remove(i);
						}
						break;
					}
				}
				if (!contains)
					viewerFilters.add(fPatternFilter);
			}
			hasChange = true;
		}
		if (hasChange) {
			fViewer.setFilters((ViewerFilter[]) viewerFilters.toArray(new ViewerFilter[viewerFilters.size()])); // will
																												// refresh
		}
		return hasChange;
	}

	private boolean hasChanges(HashSet patterns, String[] oldPatterns) {
		HashSet copy = (HashSet) patterns.clone();
		for (int i = 0; i < oldPatterns.length; i++) {
			boolean found = copy.remove(oldPatterns[i]);
			if (!found)
				return true;
		}
		return !copy.isEmpty();
	}

	// ---------- view kind/defaults persistency ----------

	private void initializeWithViewDefaults() {
		// get default values for view

		fUserDefinedPatternsEnabled = fStore.getBoolean(getPreferenceKey(TAG_USER_DEFINED_PATTERNS_ENABLED));
		setUserDefinedPatterns(OutlineCustomFiltersDialog.convertFromString(fStore.getString(getPreferenceKey(TAG_USER_DEFINED_PATTERNS)), SEPARATOR));

		for (Iterator iterator = fFilterItems.values().iterator(); iterator.hasNext();) {
			FilterItem item = (FilterItem) iterator.next();
			String id = item.id;
			// set default to value from plugin contributions (fixes
			// https://bugs.eclipse.org/bugs/show_bug.cgi?id=73991 ):
			fStore.setDefault(id, item.descriptor.isEnabled());
			item.enabled = fStore.getBoolean(id);
		}

	}

	private void storeViewDefaults() {
		// get default values for view

		fStore.setValue(getPreferenceKey(TAG_USER_DEFINED_PATTERNS_ENABLED), fUserDefinedPatternsEnabled);
		fStore.setValue(getPreferenceKey(TAG_USER_DEFINED_PATTERNS), OutlineCustomFiltersDialog.convertToString(fUserDefinedPatterns, SEPARATOR));

		boolean fFilterSelected = false;
		for (Iterator iterator = fFilterItems.values().iterator(); iterator.hasNext();) {
			FilterItem item = (FilterItem) iterator.next();
			fStore.setValue(item.id, item.enabled);
			if (item.enabled)
				fFilterSelected = true;
		}

		fStore.setValue(fTargetId, fUserDefinedPatternsEnabled || fFilterSelected);

	}

	private String getPreferenceKey(String tag) {
		return "CustomFiltersActionGroup." + fTargetId + '.' + tag; //$NON-NLS-1$
	}

	public void openDialog() {
		OutlineCustomFiltersDialog dialog = new OutlineCustomFiltersDialog(fViewer.getControl().getShell(), fTargetId, areUserDefinedPatternsEnabled(), fUserDefinedPatterns, internalGetEnabledFilterIds());

		if (dialog.open() == Window.OK) {

			setEnabledFilterIds(dialog.getEnabledFilterIds());
			setUserDefinedPatternsEnabled(dialog.areUserDefinedPatternsEnabled());
			setUserDefinedPatterns(dialog.getUserDefinedPatterns());
			storeViewDefaults();

			updateViewerFilters();
		} else {
			storeViewDefaults();
		}
	}
}
