/*******************************************************************************
 * Copyright (c) 2006, 2010 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.examples.dsf.timers;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.RejectedExecutionException;

import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.Query;
import org.eclipse.cdt.dsf.concurrent.ThreadSafe;
import org.eclipse.cdt.dsf.concurrent.ThreadSafeAndProhibitedFromDsfExecutor;
import org.eclipse.cdt.dsf.service.DsfServices;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.examples.dsf.DsfExamplesPlugin;
import org.eclipse.cdt.examples.dsf.timers.AlarmService.TriggerDMContext;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.swt.widgets.Shell;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.util.tracker.ServiceTracker;

/**
 * Cell modifier used to edit the trigger value.
 */
@ThreadSafeAndProhibitedFromDsfExecutor("fSession.getExecutor()")
public class TriggerCellModifier implements ICellModifier {

    private final DsfSession fSession;
    
    // Need to use the OSGi service tracker (instead of DsfServiceTracker), 
    // because it's being accessed on multiple threads.
    @ThreadSafe
    private ServiceTracker fServiceTracker;

    /**
     * Constructor for the modifier requires a valid session in order to 
     * initialize the service tracker.  
     * @param session DSF session this modifier will use.
     */
    public TriggerCellModifier(DsfSession session) {
        fSession = session;
    }

    public boolean canModify(Object element, String property) {
        return TimersViewColumnPresentation.COL_VALUE.equals(property) && 
            getAlarmDMC(element) != null; 
    }

    public Object getValue(Object element, String property) {
        if (!TimersViewColumnPresentation.COL_VALUE.equals(property)) return ""; 
        
        // Get the context and the session.  If element is not an trigger 
        // context or if the session is stale then bail out. 
        TriggerDMContext triggerCtx = getAlarmDMC(element);
        if (triggerCtx == null) return ""; 
        DsfSession session = DsfSession.getSession(triggerCtx.getSessionId());
        if (session == null) return ""; 
        
        // Create the query to request the value from service.  
        GetValueQuery query = new GetValueQuery(triggerCtx);
        try {
            session.getExecutor().execute(query);
        } catch (RejectedExecutionException e) {
            return "";
        }
        try {
            return query.get().toString();
        } catch (InterruptedException e) {
            assert false;
            return ""; 
        } catch (ExecutionException e) {
            return ""; 
        }
    }

    
    public void modify(Object element, String property, Object value) {
        if (!TimersViewColumnPresentation.COL_VALUE.equals(property)) return;

        TriggerDMContext dmc = getAlarmDMC(element);
        if (dmc == null) return;
        DsfSession session = DsfSession.getSession(dmc.getSessionId());
        if (session == null) return;

        // Shell is used in displaying error dialogs.
        Shell shell = getShell();
        if (shell == null) return;
        
        Integer intValue = null;
        if (value instanceof String) {
            try {
                intValue = new Integer(((String)value).trim());
            } catch (NumberFormatException e) {
                MessageDialog.openError(shell, "Invalid Value", 
                    "Please enter a positive integer");  
                return;
            }
            if (intValue.intValue() <= 0) {
                MessageDialog.openError(shell, "Invalid Value", 
                    "Please enter a positive integer");  
                return;
            }
        }

        // Create the query to write the value to the service.
        SetValueQuery query = new SetValueQuery(dmc, intValue);

        try {
            session.getExecutor().execute(query);
        } catch (RejectedExecutionException e) {
            // View must be shutting down, no need to show error dialog.
        }
        try {
            // Return value is irrelevant, any error would come through with an exception.
            query.get().toString();
        } catch (InterruptedException e) {
            assert false;
        } catch (ExecutionException e) {
            // View must be shutting down, no need to show error dialog.
        }
    }

    /**
     * Need to dispose the cell modifier property because of the service 
     * tracker.
     */
    @ThreadSafe
    public synchronized void dispose() {
        if (fServiceTracker != null) {
            fServiceTracker.close();
        }
    }
    
    private Shell getShell() {
        if (DsfExamplesPlugin.getDefault().getWorkbench().getActiveWorkbenchWindow() != null) {
            return DsfExamplesPlugin.getDefault().getWorkbench().getActiveWorkbenchWindow().getShell();
        }
        return null;
    }

    private TriggerDMContext getAlarmDMC(Object element) {
        if (element instanceof IAdaptable) {
            return (TriggerDMContext)((IAdaptable)element).getAdapter(TriggerDMContext.class);
        }
        return null;
    }

    @ThreadSafe
    private synchronized AlarmService getService(TriggerDMContext dmc) {
        // Create and initialize the service tracker if needed.
    	String serviceId = DsfServices.createServiceFilter( AlarmService.class, fSession.getId() ); 
        if (fServiceTracker == null) {
            try {
                fServiceTracker = new ServiceTracker(
                    DsfExamplesPlugin.getBundleContext(), 
                    DsfExamplesPlugin.getBundleContext().createFilter(serviceId), 
                    null);
                fServiceTracker.open();
            } catch (InvalidSyntaxException e) {
                return null;
            }
        }
        // Get the service.
        return (AlarmService)fServiceTracker.getService();
    }

    
    private class GetValueQuery extends Query<Integer> {
        final TriggerDMContext fDmc;
        
        private GetValueQuery(TriggerDMContext dmc) {
            super();
            fDmc = dmc; 
        }
        
        @Override
        protected void execute(final DataRequestMonitor<Integer> rm) {
            // Guard against the session being disposed.  If session is disposed
            // it could mean that the executor is shut-down, which in turn 
            // could mean that we can't execute the "done" argument.
            // In that case, cancel to notify waiting thread.
            final DsfSession session = DsfSession.getSession(fDmc.getSessionId());
            if (session == null) {
                rm.setStatus(new Status(IStatus.ERROR, DsfExamplesPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_STATE, "Debug session already shut down.", null)); //$NON-NLS-1$
                rm.done();
                return;
            }
                
            AlarmService service = getService(fDmc);
            if (service == null) {
                rm.setStatus(new Status(IStatus.ERROR, DsfExamplesPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_STATE, 
                                          "Service not available", null)); 
                rm.done();
                return;
            }
            
            int value = service.getTriggerValue(fDmc);
            if (value == -1) {
                rm.setStatus(new Status(IStatus.ERROR, DsfExamplesPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_HANDLE, 
                    "Invalid context", null)); 
                rm.done();                
                return;
            }
            
            rm.setData(value);
            rm.done();
        }
    }

    private class SetValueQuery extends Query<Object> {
        
        TriggerDMContext fDmc;
        int fValue;
        
        SetValueQuery(TriggerDMContext dmc, int value) {
            super();
            fDmc = dmc;
            fValue = value;
        }
        
        @Override
        protected void execute(final DataRequestMonitor<Object> rm) {
            // Guard against terminated session
            final DsfSession session = DsfSession.getSession(fDmc.getSessionId());
            if (session == null) {
                rm.setStatus(new Status(IStatus.ERROR, DsfExamplesPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_STATE, "Debug session already shut down.", null)); //$NON-NLS-1$
                rm.done();
                return;
            }

            // Guard against a disposed service
            AlarmService service = getService(fDmc);
            if (service == null) {
                rm.setStatus(new Status(IStatus.ERROR, DsfExamplesPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_STATE, 
                                          "Service not available", null)); 
                rm.done();
                return;
            }

            // Finally set the value and return.
            service.setTriggerValue(fDmc, fValue);
            
            // Return value is irrelevant.
            rm.setData(new Object());
            rm.done();
        }
    }
}
