/*******************************************************************************
 * Copyright (c) 2007, 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
 *     Ericsson Communication - upgrade IF to IMemoryBlockRetrievalExtension
 *     Ericsson Communication - added Expression evaluation
 *     Ericsson Communication - added support for 64 bit processors
 *     Ericsson Communication - added support for event handling
 *******************************************************************************/
package org.eclipse.cdt.dsf.debug.model;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;

import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DsfExecutor;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.Query;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.IExpressions;
import org.eclipse.cdt.dsf.debug.service.IExpressions.IExpressionDMContext;
import org.eclipse.cdt.dsf.debug.service.IFormattedValues;
import org.eclipse.cdt.dsf.debug.service.IFormattedValues.FormattedValueDMContext;
import org.eclipse.cdt.dsf.debug.service.IFormattedValues.FormattedValueDMData;
import org.eclipse.cdt.dsf.debug.service.IMemory;
import org.eclipse.cdt.dsf.debug.service.IMemory.IMemoryDMContext;
import org.eclipse.cdt.dsf.internal.DsfPlugin;
import org.eclipse.cdt.dsf.service.DsfServices;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.dsf.service.IDsfService;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.PlatformObject;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IMemoryBlock;
import org.eclipse.debug.core.model.IMemoryBlockExtension;
import org.eclipse.debug.core.model.IMemoryBlockRetrievalExtension;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.util.tracker.ServiceTracker;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Implementation of memory access API of the Eclipse standard debug model.
 * 
 * The DsfMemoryBlockRetrieval is not an actual memory block but rather a
 * reference to the memory address space for an execution context (e.g. a 
 * process) within a debug session. From this debug 'context', memory blocks 
 * can then be read/written.
 * 
 * Note: For the reference application, The IMemoryBlockRetrievalExtension
 * is implemented. This will result in getExtendedMemoryBlock() being called
 * when a memory block is selected from the platform memory pane.
 * 
 * However, if the 'simpler' IMemoryBlockRetrieval is to be implemented, the
 * code will still be functional after some trivial adjustments.
 * 
 * @since 1.0
 */
public class DsfMemoryBlockRetrieval extends PlatformObject implements IMemoryBlockRetrievalExtension
{
	private final String           fModelId;
	private final DsfSession       fSession;
    private final DsfExecutor      fExecutor;
    private final String           fContextString;
    private final ServiceTracker<IMemory, IMemory>   fMemoryServiceTracker;
    private final ServiceTracker<IExpressions, IExpressions>   fExpressionServiceTracker;

    private final ILaunchConfiguration fLaunchConfig;
	private final ILaunch          fLaunch;
	private final IDebugTarget     fDebugTarget;
    private final boolean          fSupportsValueModification;
    private final boolean          fSupportBaseAddressModification;
    private final int              fAddressSize;
    private final int              fWordSize;  // Number of bytes per address
	
	/**
	 * Constructor
	 * 
	 * @param modelId
	 * @param dmc
	 * @throws DebugException
	 */
	public DsfMemoryBlockRetrieval(String modelId, ILaunchConfiguration config, DsfSession session) throws DebugException {

	    // DSF stuff
        fModelId = modelId;

        // FIXME: (Bug228573) Currently memory contexts are differentiated by
        // sessionID so there is no way to guarantee the memory blocks will be
        // reinstated in the correct memory space.
        // Need a way to create deterministically the context ID from a unique
        // target, ideally from the launch configuration (or derived from it).
        // For the time being, just put some constant. This will work until we
        // support multiple targets in the same launch.
        // fContextString = fContext.toString();
        fContextString = "Context string";  //$NON-NLS-1$

        fSession = session;
		if (fSession == null) {
			throw new IllegalArgumentException(
					"Session " + session + " is not active"); //$NON-NLS-1$ //$NON-NLS-2$
		}
		fExecutor = fSession.getExecutor();
 		BundleContext bundle = DsfPlugin.getBundleContext();

 		// Here we chose to use 2 distinct service trackers instead of an
 		// amalgamated one because it is less error prone (and we are lazy).

 		// Create a tracker for the MemoryService
 		String memoryServiceFilter = DsfServices.createServiceFilter(IMemory.class, session.getId());

 		try {
			fMemoryServiceTracker = new ServiceTracker<>(
					bundle,	bundle.createFilter(memoryServiceFilter), null);
		} catch (InvalidSyntaxException e) {
			throw new DebugException(new Status(IStatus.ERROR,
					DsfPlugin.PLUGIN_ID, DebugException.INTERNAL_ERROR,
					"Error creating service filter.", e)); //$NON-NLS-1$
		}
		fMemoryServiceTracker.open();

		// Create a tracker for the ExpressionService
 		String expressionServiceFilter = "(&" + //$NON-NLS-1$
				"(OBJECTCLASS=" //$NON-NLS-1$
				+ IExpressions.class.getName()
				+ ")" + //$NON-NLS-1$
				"(" + IDsfService.PROP_SESSION_ID //$NON-NLS-1$
				+ "=" + session.getId() + ")" + //$NON-NLS-1$//$NON-NLS-2$
				")"; //$NON-NLS-1$

		try {
			fExpressionServiceTracker = new ServiceTracker<>(
					bundle, bundle.createFilter(expressionServiceFilter), null);
		} catch (InvalidSyntaxException e) {
			throw new DebugException(new Status(IStatus.ERROR,
					DsfPlugin.PLUGIN_ID, DebugException.INTERNAL_ERROR,
					"Error creating service filter.", e)); //$NON-NLS-1$
		}
		fExpressionServiceTracker.open();

        // Launch configuration information
        fLaunchConfig = config;
        fLaunch       = null;
        fDebugTarget  = null;
        fAddressSize  = 4;    // Get this from the launch configuration
        fWordSize     = 1;    // Get this from the launch configuration
        fSupportsValueModification = true;          // Get this from the launch configuration
        fSupportBaseAddressModification = false;    // Get this from the launch configuration
	}

	///////////////////////////////////////////////////////////////////////////
	// Memory monitors persistence
	///////////////////////////////////////////////////////////////////////////

	/*
	 * In the launch configuration file, the memory block entry is structured
	 *  as follows (note: this differs from CDI):
	 * 
	 *  <stringAttribute
	 *     key="org.eclipse.dsf.launch.MEMORY_BLOCKS" 
	 *     value="<?xml version="1.0" encoding="UTF-8" standalone="no"?>
	 *         <memoryBlockExpressionList context=[memory context ID]>
	 *             <memoryBlockExpression label=[monitor label] address=[base address]/>
	 *             <memoryBlockExpression ...>
	 *             ...
	 *         </memoryBlockExpressionList>
	 *         ...
     *         <memoryBlockExpressionList context=...>
     *             ...
     *         </memoryBlockExpressionList>"
	 *  />
	 */

	//-------------------------------------------------------------------------
	// Memory blocks memento tags
    //-------------------------------------------------------------------------

	// These 2 really belong in the DSF launch configuration class...
	private static final String DSF_LAUNCH_ID = "org.eclipse.dsf.launch"; //$NON-NLS-1$
	private static final String ATTR_DEBUGGER_MEMORY_BLOCKS = DSF_LAUNCH_ID + ".MEMORY_BLOCKS"; //$NON-NLS-1$
	
	private static final String MEMORY_BLOCK_EXPRESSION_LIST   = "memoryBlockExpressionList";   //$NON-NLS-1$
    private static final String ATTR_EXPRESSION_LIST_CONTEXT   = "context";                     //$NON-NLS-1$   
    private static final String MEMORY_BLOCK_EXPRESSION        = "memoryBlockExpression";       //$NON-NLS-1$
    private static final String ATTR_MEMORY_BLOCK_EXPR_LABEL   = "label";                       //$NON-NLS-1$
    private static final String ATTR_MEMORY_BLOCK_EXPR_ADDRESS = "address";                     //$NON-NLS-1$

    //-------------------------------------------------------------------------
    // Install persisted memory monitors
    //-------------------------------------------------------------------------

    /**
     * Restore the memory monitors from the memento in the launch configuration
     */
    public void initialize(final IMemoryDMContext memoryCtx) {
        try {
            final String memento = fLaunchConfig.getAttribute(ATTR_DEBUGGER_MEMORY_BLOCKS, ""); //$NON-NLS-1$
            if (memento != null && memento.trim().length() != 0) {
                // Submit the runnable to install the monitors on dispatch thread.
                getExecutor().submit(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            createBlocksFromConfiguration(memoryCtx, memento);
                        } catch (CoreException e) {
                            DsfPlugin.getDefault().getLog().log(e.getStatus());
                        }
                    }
                });
            }
        } catch (CoreException e) {
            DsfPlugin.getDefault().getLog().log(e.getStatus());
        }
	}

	/**
	 * Create memory blocks based on the given memento (obtained from the launch
	 * configuration) and add them to the platform's IMemoryBlockManager. The
	 * memento was previously created by {@link #getMemento()}
	 * 
	 * @since 2.1
	 */
	protected void createBlocksFromConfiguration(IMemoryDMContext memoryCtx, String memento) throws CoreException {

	    // Parse the memento and validate its type
        Element root = DebugPlugin.parseDocument(memento);
		if (!root.getNodeName().equalsIgnoreCase(MEMORY_BLOCK_EXPRESSION_LIST)) {
	        IStatus status = new Status(IStatus.ERROR, DsfPlugin.PLUGIN_ID, DebugPlugin.INTERNAL_ERROR,
	                "Memory monitor initialization: invalid memento", null);//$NON-NLS-1$
	        throw new CoreException(status);
		}
		    
        // Process the block list specific to this memory context
        // FIXME: (Bug228573) We only process the first entry...
	    if (root.getAttribute(ATTR_EXPRESSION_LIST_CONTEXT).equals(fContextString)) {
            List<IMemoryBlock> blocks = new ArrayList<IMemoryBlock>();
            NodeList expressionList = root.getChildNodes();
            int length = expressionList.getLength();
            for (int i = 0; i < length; ++i) {
                Node node = expressionList.item(i);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    Element entry = (Element) node;
                    if (entry.getNodeName().equalsIgnoreCase(MEMORY_BLOCK_EXPRESSION)) {
                        String label   = entry.getAttribute(ATTR_MEMORY_BLOCK_EXPR_LABEL);
                        String address = entry.getAttribute(ATTR_MEMORY_BLOCK_EXPR_ADDRESS);
                        BigInteger blockAddress = new BigInteger(address);
                        DsfMemoryBlock block = new DsfMemoryBlock(this, memoryCtx, fModelId, label, blockAddress, fWordSize, 0);
                        blocks.add(block);
                    }
                }
            }
            DebugPlugin.getDefault().getMemoryBlockManager().addMemoryBlocks( blocks.toArray(new IMemoryBlock[blocks.size()]));
	    }
	}

    // FIXME: (Bug228573) Each retrieval overwrites the previous one :-(
	
    // In theory, we should make this a Job since we are writing to the file system.
	// However, this would cause the same racing condition as Bug228308. Finally, we
	// don't care too much about the UI responsiveness since we are in the process of
	// shutting down :-)
	public void saveMemoryBlocks() {
		try {
			ILaunchConfigurationWorkingCopy wc = fLaunchConfig.getWorkingCopy();
			wc.setAttribute(ATTR_DEBUGGER_MEMORY_BLOCKS, getMemento());
			wc.doSave();
		}
		catch( CoreException e ) {
            DsfPlugin.getDefault().getLog().log(e.getStatus());
		}
	}

	/**
	 * Create a memento to represent all active blocks created by this retrieval
	 * object (blocks currently registered with the platform's
	 * IMemoryBlockManager). We will be expected to recreate the blocks in
	 * {@link #createBlocksFromConfiguration(IMemoryDMContext, String)}.
	 * 
	 * @return a string memento
	 * @throws CoreException
	 */
	public String getMemento() throws CoreException {
		IMemoryBlock[] blocks = DebugPlugin.getDefault().getMemoryBlockManager().getMemoryBlocks(this);
		Document document = DebugPlugin.newDocument();
		Element expressionList = document.createElement(MEMORY_BLOCK_EXPRESSION_LIST);
		expressionList.setAttribute(ATTR_EXPRESSION_LIST_CONTEXT, fContextString);
		for (IMemoryBlock block : blocks) {
	          if (block instanceof IMemoryBlockExtension) {
	                IMemoryBlockExtension memoryBlock = (IMemoryBlockExtension) block;
	                Element expression = document.createElement(MEMORY_BLOCK_EXPRESSION);
	                expression.setAttribute(ATTR_MEMORY_BLOCK_EXPR_LABEL,   memoryBlock.getExpression());
                    expression.setAttribute(ATTR_MEMORY_BLOCK_EXPR_ADDRESS, memoryBlock.getBigBaseAddress().toString());
                    expressionList.appendChild(expression);
				}
		}
		document.appendChild(expressionList);
		return DebugPlugin.serializeDocument(document);
	}

	///////////////////////////////////////////////////////////////////////////
	// Accessors
	///////////////////////////////////////////////////////////////////////////

	public DsfSession getSession() {
	    return fSession;
	}
	
	public DsfExecutor getExecutor() {
		return fExecutor;
	}

	public ServiceTracker<IMemory, IMemory> getServiceTracker() {
		return fMemoryServiceTracker;
	}

	///////////////////////////////////////////////////////////////////////////
	// Launch/Target specific information
	///////////////////////////////////////////////////////////////////////////

	public ILaunch getLaunch() {
		return fLaunch;
	}

	public IDebugTarget getDebugTarget() {
		return fDebugTarget;
	}

	public int getAddressSize() {
		return fAddressSize;
	}

	public int getAddressableSize() {
		return fWordSize;
	}

	public boolean supportsValueModification() {
		return fSupportsValueModification;
	}

	public boolean supportBaseAddressModification() {
		return fSupportBaseAddressModification;
	}

	///////////////////////////////////////////////////////////////////////////
	// IMemoryBlockRetrieval - obsoleted by IMemoryBlockRetrievalExtension
	///////////////////////////////////////////////////////////////////////////

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.IMemoryBlockRetrieval#supportsStorageRetrieval()
	 */
    @Override
	public boolean supportsStorageRetrieval() {
		return true;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.debug.core.model.IMemoryBlockRetrieval#getMemoryBlock(long, long)
	 */
    @Override
	public IMemoryBlock getMemoryBlock(final long startAddress,	final long length) throws DebugException {
	    throw new DebugException(new Status(
	        IStatus.ERROR, DsfPlugin.PLUGIN_ID, DebugException.NOT_SUPPORTED, 
	        "getMemoryBlock() not supported, use getExtendedMemoryBlock()", null)); //$NON-NLS-1$
	}

	///////////////////////////////////////////////////////////////////////////
	// IMemoryBlockRetrievalExtension
	///////////////////////////////////////////////////////////////////////////

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.IMemoryBlockRetrievalExtension#getExtendedMemoryBlock(java.lang.String,
	 *      java.lang.Object)
	 */
    @Override
	public IMemoryBlockExtension getExtendedMemoryBlock(String expression, Object context) throws DebugException {
        // Drill for the actual DMC
        IMemoryDMContext memoryDmc = null;
        IDMContext dmc = null;
        if (context instanceof IAdaptable) {
        	dmc = (IDMContext)((IAdaptable)context).getAdapter(IDMContext.class);
            if (dmc != null) {
                memoryDmc = DMContexts.getAncestorOfType(dmc, IMemoryDMContext.class);
            }
        }

        if (memoryDmc == null) {
            return null;
        }
        
		// The block start address (supports 64-bit processors)
		BigInteger blockAddress;

		/*
		 * See if the expression is a simple numeric value; if it is, we can
		 * avoid some costly processing (calling the back-end to resolve the
		 * expression and obtain an address)
		 */
		try {
			// First, assume a decimal address
			int base = 10;
			int offset = 0;

			// Check for "hexadecimality"
			if (expression.startsWith("0x") || expression.startsWith("0X")) { //$NON-NLS-1$//$NON-NLS-2$
				base = 16;
				offset = 2;
			}
			// Check for "binarity"
			else if (expression.startsWith("0b")) { //$NON-NLS-1$
				base = 2;
				offset = 2;
			}
			// Check for "octality"
			else if (expression.startsWith("0")) { //$NON-NLS-1$
				base = 8;
				offset = 1;
			}
			// Now, try to parse the expression. If a NumberFormatException is
			// thrown, then it wasn't a simple numerical expression and we go 
			// to plan B (attempt an expression evaluation)
			blockAddress = new BigInteger(expression.substring(offset), base);

		} catch (NumberFormatException nfexc) {
			// OK, expression is not a simple, absolute numeric value;
			// try to resolve as an expression.
			// In case of failure, simply return 'null'

			// Resolve the expression
			blockAddress = resolveMemoryAddress(dmc, expression);
			if (blockAddress == null) {
				return null;
			}
		}

		/*
		 * At this point, we only resolved the requested memory block
		 * start address and we have no idea of the block's length.
		 * 
		 * The renderer will provide this information when it calls
		 * getBytesFromAddress() i.e. after the memory block holder has
		 * been instantiated.
		 * 
		 * The down side is that every time we switch renderer, for the
		 * same memory block, a trip to the target could result. However,
		 * the memory request cache should save the day.
		 */

		 return new DsfMemoryBlock(this, memoryDmc, fModelId, expression, blockAddress, fWordSize, 0);
	}

	///////////////////////////////////////////////////////////////////////////
	// Helper functions
	///////////////////////////////////////////////////////////////////////////

	/**
	 * @since 2.1
	 */
	protected BigInteger resolveMemoryAddress(final IDMContext dmc, final String expression) throws DebugException {

		// Use a Query to "synchronize" the downstream calls
		Query<BigInteger> query = new Query<BigInteger>() {
			@Override
			protected void execute(final DataRequestMonitor<BigInteger> drm) {
				// Lookup for the ExpressionService
				final IExpressions expressionService = fExpressionServiceTracker.getService();
				if (expressionService != null) {
					// Create the expression
					final IExpressionDMContext expressionDMC = expressionService.createExpression(dmc, expression);
					String formatId = IFormattedValues.HEX_FORMAT;
					FormattedValueDMContext valueDmc = expressionService.getFormattedValueContext(expressionDMC, formatId);
	                expressionService.getFormattedExpressionValue(
	                	valueDmc, 
	                    new DataRequestMonitor<FormattedValueDMData>(getExecutor(), drm) {
	            			@Override
	            			protected void handleSuccess() {
	            				// Store the result
	            				try {
                					String value = getData().getFormattedValue().substring(2);	// Strip the "0x"
                					drm.setData(new BigInteger(value, 16));
	            				} catch (IndexOutOfBoundsException e) {
	            				    setFormatError(e);
	            				} catch (NumberFormatException e) {
	            				    setFormatError(e);
	            				}
	            				drm.done();
	            			}
	            			
	            			private void setFormatError(Exception e) {
	            			    drm.setStatus(new Status(
	            			        IStatus.ERROR, DsfPlugin.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR, 
	            			        "The result of expression evaluation \"" + getData().getFormattedValue() + "\" is not formatted correctly.", e)); //$NON-NLS-1$ //$NON-NLS-2$
	            			}
	                	}
	                );
				}
			}
		};
		fExecutor.execute(query);

		try {
			// The happy case
			return query.get();
		} catch (InterruptedException e) {
			throw new DebugException(new Status(IStatus.ERROR,
					DsfPlugin.PLUGIN_ID, DebugException.INTERNAL_ERROR,
					"Error evaluating memory address (InterruptedException).", e)); //$NON-NLS-1$

		} catch (ExecutionException e) {
			throw new DebugException(new Status(IStatus.ERROR,
					DsfPlugin.PLUGIN_ID, DebugException.INTERNAL_ERROR,
					"Error evaluating memory address (ExecutionException).", e)); //$NON-NLS-1$
		}
	}
	
	
	/**
	 * Return the model ID specified at construction 
	 * 
	 * @since 2.1
	 */
	protected String getModelId() {
		return fModelId;
	}

}
