/*******************************************************************************
 * Copyright (c) 2010, 2014 Wind River Systems and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Wind River Systems - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.dsf.debug.ui.viewmodel.numberformat;

import java.util.Dictionary;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.concurrent.RejectedExecutionException;

import org.eclipse.cdt.dsf.concurrent.CountingRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.ImmediateCountingRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ImmediateDataRequestMonitor;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.IExpressions.IExpressionDMContext;
import org.eclipse.cdt.dsf.debug.service.IExpressions.IExpressionGroupDMContext;
import org.eclipse.cdt.dsf.debug.service.IRegisters;
import org.eclipse.cdt.dsf.debug.service.IRegisters.IRegisterDMContext;
import org.eclipse.cdt.dsf.debug.service.IRegisters.IRegisterDMData;
import org.eclipse.cdt.dsf.debug.ui.viewmodel.SimpleMapPersistable;
import org.eclipse.cdt.dsf.debug.ui.viewmodel.update.ElementFormatEvent;
import org.eclipse.cdt.dsf.internal.ui.DsfUIPlugin;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.dsf.ui.viewmodel.AbstractVMProvider;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMContext;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMNode;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMProvider;
import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.IDMVMContext;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.ui.IWorkbenchPart;
import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;

/**
 * Default implementation of the {@link IElementFormatProvider}.  It can be 
 * used within any {@link IVMProvider} to store and persist number-formats 
 * selected by user for different elements.
 * 
 * @since 2.5
 */
public class ElementNumberFormatProvider implements IElementFormatProvider
{
	private static final String ELEMENT_FORMAT_PERSISTABLE_PROPERTY = "org.eclipse.cdt.dsf.ui.elementFormatPersistable"; //$NON-NLS-1$
	private static final String FILTER_PROVIDER_ID = ElementNumberFormatProvider.class.getName() + ".eventFilter"; //$NON-NLS-1$

    private final IVMProvider fVMProvider;
    private final DsfSession fSession;
    private final Dictionary<String, String> fFilterProperties = new Hashtable<>();
    
    public ElementNumberFormatProvider(IVMProvider vmProvider, DsfSession session) {
        fVMProvider = vmProvider;
        fSession = session;
        initialize();
	}

    protected void initialize() {
    	IPresentationContext presentationCtx = getVMProvider().getPresentationContext(); 

    	IWorkbenchPart part = presentationCtx.getPart();
    	String provider;
    	if (part != null) {
    		// Use an id that is unique to the instance of the view
    		// Note that although each view, including cloned ones, has its own presentation context,
    		// the presentation context id returned by getPresentationContext().getId() is the
    		// same for cloned views even though the presentation context itself is different.
    		// To get a unique id for each cloned view we can use the title of the view.
    		provider = part.getTitle();
    	} else {
    		// In some cases, we are not dealing with a part, e.g., the hover.
    		// In this case, use the presentation context id directly.
    		// Note that the hover will probably not provide per-element formating,
    		// but some extenders may choose to do so.
    		provider = getVMProvider().getPresentationContext().getId();
    	}
    	
    	// Create the filter properties targeted at our provider, to be used when sending events
    	fFilterProperties.put(FILTER_PROVIDER_ID, provider);

    	// Properly formatted OSGI filter string aimed at our provider
        String filterStr = "(&(" + FILTER_PROVIDER_ID + "=" + provider + "))"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

		try {
			final Filter filter = DsfUIPlugin.getBundleContext().createFilter(filterStr);
			fSession.getExecutor().execute(new DsfRunnable() {
				@Override
				public void run() {
					// Only listen to events that are aimed at our provider by using a filter.
					// That avoids updating ourselves for an event that was triggered by another view.
					fSession.addServiceEventListener(ElementNumberFormatProvider.this, filter);
				}
			});
		} catch (InvalidSyntaxException e) {
			assert false : e.getMessage();
		} catch (RejectedExecutionException e) {
		}
    }

    public void dispose() {
		try {
			fSession.getExecutor().execute(new DsfRunnable() {
				@Override
				public void run() {
					fSession.removeServiceEventListener(ElementNumberFormatProvider.this);
				}
			});
		} catch (RejectedExecutionException e) {
		}    	
    }
    
    @DsfServiceEventHandler
    public final void eventDispatched(ElementFormatEvent event) {
		if (getVMProvider() instanceof AbstractVMProvider) {
			((AbstractVMProvider)getVMProvider()).handleEvent(event);
		}
	}
	
    private IVMProvider getVMProvider() {
        return fVMProvider;
    }

    @Override
	public void getActiveFormat(IPresentationContext context, IVMNode node, Object viewerInput, final TreePath elementPath,
        final DataRequestMonitor<String> rm) 
    {
        getElementKey(
            viewerInput, elementPath, 
            new ImmediateDataRequestMonitor<String>(rm) {
                @Override
                protected void handleSuccess() {
                    SimpleMapPersistable<String> persistable = getPersistable();
                    rm.done(persistable.getValue(getData()));
                }
            });
    }

    @Override
	public void setActiveFormat(IPresentationContext context, IVMNode[] node, Object viewerInput,
        TreePath[] elementPaths, final String format) 
    {
        final HashSet<Object> elementsToRefresh = new HashSet<>();
        final CountingRequestMonitor crm = new ImmediateCountingRequestMonitor() {
            @Override
            protected void handleCompleted() {
            	if (elementsToRefresh.size() > 0) {
            		// Send the event to all DSF sessions as they share the same view and the 
            		// change of format will affect them as well.  This is because they key
            		// we use from this implementation of getElementKey() is not specific to
            		// a session (and should not be if we want to have proper persistence).
            		for (DsfSession session : DsfSession.getActiveSessions()) {
            			// Use the filterProperties to specify that this event only impacts the current view.
            			session.dispatchEvent(new ElementFormatEvent(elementsToRefresh, 1), fFilterProperties);
            		}
            	}
            }
        };
        for (final TreePath path : elementPaths) {
            getElementKey(
                viewerInput, path, 
                new ImmediateDataRequestMonitor<String>(crm) {
                    @Override
                    protected void handleSuccess() {
                        SimpleMapPersistable<String> persistable = getPersistable();
                        persistable.setValue(getData(), format);
                        elementsToRefresh.add(path.getLastSegment());
                        crm.done();
                    }
                });
        }
        crm.setDoneCount(elementPaths.length);
    }

    @Override
	public boolean supportFormat(IVMContext context) {
    	if (context instanceof IDMVMContext) {
    		// The expressions view supports expression groups, which have no value,
    		// so we should not support formatting for expression groups.
    		if (((IDMVMContext)context).getDMContext() instanceof IExpressionGroupDMContext) { 
    			return false;
    		}
    	}
        return context instanceof IFormattedValueVMContext;
    }

    // We do not make the element key session-specific or else when we start a new session for the same
    // program, the format we chose will not be persisted.  Instead, make the format change valid for
    // any session, even if other sessions run a different program.  The idea is that a user usually
    // names her variables similarly so the chosen format should apply properly anyway.
    protected void getElementKey(Object viewerInput, TreePath elementPath, final DataRequestMonitor<String> rm) {
    	Object element = elementPath.getLastSegment();
    	if (element instanceof IDMVMContext) {
			final IDMContext dmc = ((IDMVMContext)element).getDMContext();
			if (dmc instanceof IExpressionDMContext) {
				rm.done(((IExpressionDMContext)dmc).getExpression());
				return;
			} else if (dmc instanceof IRegisterDMContext) {
				fSession.getExecutor().execute(new DsfRunnable() {					
					@Override
					public void run() {
						DsfServicesTracker tracker = new DsfServicesTracker(DsfUIPlugin.getBundleContext(), fSession.getId());
						IRegisters regService = tracker.getService(IRegisters.class);
						tracker.dispose();
						
						regService.getRegisterData((IRegisterDMContext)dmc, new ImmediateDataRequestMonitor<IRegisterDMData>(rm) {
							@Override
							protected void handleSuccess() {
								rm.done(getData().getName());
							}
						});
					}
				});
				return;
			}
		}
        rm.done(new Status(IStatus.ERROR, DsfUIPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_HANDLE, "Cannot calculate peristable key for element: " + element, null)); //$NON-NLS-1$
    }

    @SuppressWarnings("unchecked")
	protected SimpleMapPersistable<String> getPersistable() {
        Object p = getVMProvider().getPresentationContext().getProperty(ELEMENT_FORMAT_PERSISTABLE_PROPERTY);
        if (p instanceof SimpleMapPersistable) {
            return (SimpleMapPersistable<String>)p;
        } else {
        	SimpleMapPersistable<String> persistable = new SimpleMapPersistable<>(String.class);
            getVMProvider().getPresentationContext().setProperty(ELEMENT_FORMAT_PERSISTABLE_PROPERTY, persistable);
            return persistable;
        }
    }
}
