/*******************************************************************************
 * Copyright (c) 2006 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.debug.internal.ui.elements.adapters;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Vector;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.DebugException;
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.MemoryByte;
import org.eclipse.debug.internal.ui.DebugUIMessages;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
import org.eclipse.debug.internal.ui.memory.provisional.AbstractAsyncTableRendering;
import org.eclipse.debug.internal.ui.memory.provisional.MemoryViewPresentationContext;
import org.eclipse.debug.internal.ui.viewers.provisional.AsynchronousContentAdapter;
import org.eclipse.debug.internal.ui.viewers.provisional.IPresentationContext;
import org.eclipse.debug.internal.ui.views.memory.MemoryViewUtil;
import org.eclipse.debug.internal.ui.views.memory.renderings.MemorySegment;
import org.eclipse.debug.internal.ui.views.memory.renderings.TableRenderingContentDescriptor;
import org.eclipse.debug.ui.memory.IMemoryRendering;

public class MemoryBlockContentAdapter extends AsynchronousContentAdapter {

	// Cache to allow the content provider to comppute change information
	// Cache is taken by copying the lineCache after a suspend event
	// or change event from the the memory block.
	protected Hashtable contentCache;
	
	public MemoryBlockContentAdapter()
	{
		contentCache = new Hashtable();
	}

	protected Object[] getChildren(Object parent, IPresentationContext context)
			throws CoreException {
		
		if (!(parent instanceof IMemoryBlock))
			return new Object[0];
		
		if (!(context instanceof MemoryViewPresentationContext))
			return new Object[0];
		
		MemoryViewPresentationContext memoryViewContext = (MemoryViewPresentationContext)context; 
		IMemoryRendering rendering = memoryViewContext.getRendering();
		
		if (!(rendering instanceof AbstractAsyncTableRendering))
			return new Object[0];
			
		try 
		{
			return getMemoryFromMemoryBlock(memoryViewContext);
		} catch (DebugException e) {
			throw e;
		}			

	}

	protected boolean hasChildren(Object element, IPresentationContext context)
			throws CoreException {
		
		if (context instanceof MemoryViewPresentationContext)
		{
			if (((MemoryViewPresentationContext)context).getRendering() != null)
				return true;
		}
		
		return false;
	}

	protected boolean supportsPartId(String id) {
		return true;
	}
	
	
	private Object[] getMemoryFromMemoryBlock(MemoryViewPresentationContext context) throws DebugException {
		IMemoryBlock memoryBlock = context.getRendering().getMemoryBlock();
		if (memoryBlock instanceof IMemoryBlockExtension)
		{
			return loadContentForExtendedMemoryBlock(context);
		}

		return loadContentForSimpleMemoryBlock(context);
		
	}

	/**
	 * @throws DebugException
	 */
	public Object[] loadContentForSimpleMemoryBlock(MemoryViewPresentationContext context) throws DebugException {
		AbstractAsyncTableRendering rendering = getTableRendering(context);
		if (rendering != null)
		{
			IMemoryBlock memoryBlock = rendering.getMemoryBlock();
			long startAddress = memoryBlock.getStartAddress();
			BigInteger address = BigInteger.valueOf(startAddress);
			long length = memoryBlock.getLength();
			long numLines = length / rendering.getBytesPerLine();
			return getMemoryToFitTable(address, numLines,  context);
		}
		return EMPTY;
	}

	/**
	 * @throws DebugException
	 */
	public Object[] loadContentForExtendedMemoryBlock(MemoryViewPresentationContext context) throws DebugException {
		
		AbstractAsyncTableRendering rendering = getTableRendering(context);
		if (rendering != null)
		{
			TableRenderingContentDescriptor descriptor = (TableRenderingContentDescriptor)rendering.getAdapter(TableRenderingContentDescriptor.class);
			
			if (descriptor == null)
				return new Object[0];
			
			// calculate top buffered address
			BigInteger loadAddress = descriptor.getLoadAddress();
			if (loadAddress == null)
			{
				loadAddress = new BigInteger("0"); //$NON-NLS-1$
			}
			
			BigInteger mbStart = descriptor.getStartAddress();
			BigInteger mbEnd = descriptor.getEndAddress();
			
			// check that the load address is within range
			if (loadAddress.compareTo(mbStart) < 0 || loadAddress.compareTo(mbEnd) > 0)
			{
				// default load address to memory block base address
				loadAddress = ((IMemoryBlockExtension)descriptor.getMemoryBlock()).getBigBaseAddress();
				descriptor.setLoadAddress(loadAddress);
			}
			
			// if address is still out of range, throw an exception
			if (loadAddress.compareTo(mbStart) < 0 || loadAddress.compareTo(mbEnd) > 0)
			{
				throw new DebugException(DebugUIPlugin.newErrorStatus(DebugUIMessages.TableRenderingContentProvider_0 + loadAddress.toString(16), null));
			}
			
			int addressableUnitsPerLine = rendering.getAddressableUnitPerLine();
			BigInteger bufferStart = loadAddress.subtract(BigInteger.valueOf(descriptor.getPreBuffer()*addressableUnitsPerLine));
			BigInteger bufferEnd = loadAddress.add(BigInteger.valueOf(descriptor.getPostBuffer()*addressableUnitsPerLine));
			bufferEnd = bufferEnd.add(BigInteger.valueOf(descriptor.getNumLines()*addressableUnitsPerLine));
			
			// TODO:  should rely on input to tell us what to load
			// instead of having the content adapter override the setting
			if (descriptor.isDynamicLoad())
			{
				if (bufferStart.compareTo(mbStart) < 0)
					bufferStart = mbStart;
				
				if (bufferEnd.compareTo(mbEnd) > 0)
					bufferEnd = mbEnd;
				
				// buffer end must be greater than buffer start
				if (bufferEnd.compareTo(bufferStart) <= 0)
					throw new DebugException(DebugUIPlugin.newErrorStatus(DebugUIMessages.TableRenderingContentProvider_1, null));
				
				int numLines = bufferEnd.subtract(bufferStart).divide(BigInteger.valueOf(addressableUnitsPerLine)).intValue()+1;		
				// get stoarage to fit the memory view tab size
				return getMemoryToFitTable(bufferStart, numLines,context);
			}
			else
			{
				if (bufferStart.compareTo(mbStart) < 0)
					bufferStart = mbStart;
				
				if (bufferEnd.compareTo(mbEnd) > 0)
				{
					bufferStart = mbEnd.subtract(BigInteger.valueOf((descriptor.getNumLines()-1)*addressableUnitsPerLine));
				}
				
				// buffer end must be greater than buffer start
				if (bufferEnd.compareTo(bufferStart) <= 0)
					throw new DebugException(DebugUIPlugin.newErrorStatus(DebugUIMessages.TableRenderingContentProvider_2, null));
				
				int numLines = descriptor.getNumLines();	
				// get stoarage to fit the memory view tab size
				return getMemoryToFitTable(bufferStart, numLines,  context);
			}
		}
		return EMPTY;
	}
	
	/**
	 * Get memory to fit table
	 * @param startingAddress
	 * @param numberOfLines
	 * @param updateDelta
	 * @throws DebugException
	 */
	public Object[]  getMemoryToFitTable(BigInteger startAddress, long numberOfLines, MemoryViewPresentationContext context) throws DebugException
	{
		AbstractAsyncTableRendering tableRendering = getTableRendering(context);	
		if (tableRendering == null)
		{
			DebugException e = new DebugException(DebugUIPlugin.newErrorStatus(DebugUIMessages.MemoryViewContentProvider_Unable_to_retrieve_content, null));
			throw e;
		}
		
		TableRenderingContentDescriptor descriptor = (TableRenderingContentDescriptor)tableRendering.getAdapter(TableRenderingContentDescriptor.class);
		if(descriptor == null)
			return new Object[0];
		
		// do not ask for memory from memory block if the debug target
		// is already terminated
		IDebugTarget target = descriptor.getMemoryBlock().getDebugTarget();
		
		if (target.isDisconnected() || target.isTerminated())
			return new Object[0];
		
		boolean error = false;
		DebugException dbgEvt = null;
		
		String adjustedAddress = startAddress.toString(16);

		// align to the closest boundary based on addressable size per line
		if (descriptor.isAlignAddressToBoundary() &&  descriptor.getMemoryBlock() instanceof IMemoryBlockExtension)
		{
			startAddress = MemoryViewUtil.alignToBoundary(startAddress, tableRendering.getAddressableUnitPerLine());
		}

		IMemoryBlockExtension extMemoryBlock = null;
		MemoryByte[] memoryBuffer = null;
		
		long reqNumBytes = 0;
		try
		{
			
			if (descriptor.getMemoryBlock() instanceof IMemoryBlockExtension)
			{
				reqNumBytes = tableRendering.getBytesPerLine() * numberOfLines;
				// get memory from memory block
				extMemoryBlock = (IMemoryBlockExtension) descriptor.getMemoryBlock();
				
				long reqNumberOfUnits = tableRendering.getAddressableUnitPerLine() * numberOfLines;
						
				memoryBuffer =	extMemoryBlock.getBytesFromAddress(startAddress,	reqNumberOfUnits);
		
				if(memoryBuffer == null)
				{
					DebugException e = new DebugException(DebugUIPlugin.newErrorStatus(DebugUIMessages.MemoryViewContentProvider_Unable_to_retrieve_content, null));
					throw e;
				}
			}
			else 
			{				
				// get memory from memory block
				byte[] memory = descriptor.getMemoryBlock().getBytes();
				
				if (memory == null)
				{
					DebugException e = new DebugException(DebugUIPlugin.newErrorStatus(DebugUIMessages.MemoryViewContentProvider_Unable_to_retrieve_content, null));	
					throw e;					
				}
				
				int prefillNumBytes = 0;
				
				// number of bytes need to prefill
				if (!startAddress.toString(16).endsWith("0")) //$NON-NLS-1$
				{
					adjustedAddress = startAddress.toString(16).substring(0, adjustedAddress.length() - 1);
					adjustedAddress += "0"; //$NON-NLS-1$
					BigInteger adjustedStart = new BigInteger(adjustedAddress, 16);
					prefillNumBytes = startAddress.subtract(adjustedStart).intValue();
					startAddress = adjustedStart;
				}
				reqNumBytes = descriptor.getMemoryBlock().getLength() + prefillNumBytes;
				
				// figure out number of dummy bytes to append
				while (reqNumBytes % tableRendering.getBytesPerLine() != 0)
				{
					reqNumBytes ++;
				}
				
				numberOfLines = reqNumBytes / tableRendering.getBytesPerLine();
				
				// create memory byte for IMemoryBlock
				memoryBuffer = new MemoryByte[(int)reqNumBytes];
				
				// prefill buffer to ensure double-word alignment
				for (int i=0; i<prefillNumBytes; i++)
				{
					MemoryByte tmp = new MemoryByte();
					tmp.setValue((byte)0);
					tmp.setWritable(false);
					tmp.setReadable(false);
					tmp.setEndianessKnown(false);
					memoryBuffer[i] = tmp;
				}
				
				// fill buffer with memory returned by debug adapter
				int j = prefillNumBytes; 							// counter for memoryBuffer
				for (int i=0; i<memory.length; i++)
				{
					MemoryByte tmp = new MemoryByte();
					tmp.setValue(memory[i]);
					tmp.setReadable(true);
					tmp.setWritable(true);
					tmp.setEndianessKnown(false);
					memoryBuffer[j] = tmp;
					j++;
				}
				
				// append to buffer to fill up the entire line
				for (int i=j; i<memoryBuffer.length; i++)
				{
					MemoryByte tmp = new MemoryByte();
					tmp.setValue((byte)0);
					tmp.setWritable(false);
					tmp.setReadable(false);
					tmp.setEndianessKnown(false);
					memoryBuffer[i] = tmp;
				}
			}
		}
		catch (DebugException e)
		{
			memoryBuffer = makeDummyContent(numberOfLines, tableRendering.getBytesPerLine());
			
			// finish creating the content provider before throwing an event
			error = true; 
			dbgEvt = e;
		}
		catch (Throwable e)
		{
			// catch all errors from this process just to be safe
			memoryBuffer = makeDummyContent(numberOfLines, tableRendering.getBytesPerLine());
			
			// finish creating the content provider before throwing an event
			error = true; 
			dbgEvt = new DebugException(DebugUIPlugin.newErrorStatus(e.getMessage(), e));
		}
		
		// if debug adapter did not return enough memory, create dummy memory
		if (memoryBuffer.length < reqNumBytes)
		{
			ArrayList newBuffer = new ArrayList();
			
			for (int i=0; i<memoryBuffer.length; i++)
			{
				newBuffer.add(memoryBuffer[i]);
			}
			
			for (int i=memoryBuffer.length; i<reqNumBytes; i++)
			{
				MemoryByte mb = new MemoryByte();
				mb.setReadable(false);
				mb.setWritable(false);
				mb.setEndianessKnown(false);
				newBuffer.add(mb);
			}
			
			memoryBuffer = (MemoryByte[])newBuffer.toArray(new MemoryByte[newBuffer.size()]);
			
		}

		boolean manageDelta = true;
		
		// If change information is not managed by the memory block
		// The view tab will manage it and calculate delta information
		// for its content cache.
		if (descriptor.getMemoryBlock() instanceof IMemoryBlockExtension)
		{
			manageDelta = !((IMemoryBlockExtension)descriptor.getMemoryBlock()).supportsChangeManagement();
		}
			
		if (error){
			throw dbgEvt;
		}
		
		// put memory information into MemoryViewLine
		return organizeLines(numberOfLines, memoryBuffer, startAddress, manageDelta, context);
		

	}

	private Object[] organizeLines(long numberOfLines,  MemoryByte[] memoryBuffer, BigInteger address, boolean manageDelta, MemoryViewPresentationContext context) 
	{
		Vector lineCache = new Vector();
		IMemoryRendering rendering = context.getRendering();
		if (!(rendering instanceof AbstractAsyncTableRendering))
			return lineCache.toArray();
		
		AbstractAsyncTableRendering tableRendering = (AbstractAsyncTableRendering)rendering;
		int addressableUnit = tableRendering.getBytesPerLine()/tableRendering.getAddressableSize();
		
		for (int i = 0; i < numberOfLines; i++)
		{   
			int bytesPerLine = tableRendering.getBytesPerLine();
			MemoryByte[] memory = new MemoryByte[bytesPerLine];
			
			// counter for memory, starts from 0 to number of bytes per line
			int k = 0;
			// j is the counter for memArray, memory returned by debug adapter
			for (int j = i * bytesPerLine;
				j < i * bytesPerLine + bytesPerLine;
				j++)
			{
				
				byte changeFlag = memoryBuffer[j].getFlags();
				if (manageDelta)
				{
					// turn off both change and known bits to make sure that
					// the change bits returned by debug adapters do not take
					// any effect
					
					changeFlag |= MemoryByte.HISTORY_KNOWN;
					changeFlag ^= MemoryByte.HISTORY_KNOWN;
					
					changeFlag |= MemoryByte.CHANGED;
					changeFlag ^= MemoryByte.CHANGED;
				}
				
				MemoryByte newByteObj = new MemoryByte(memoryBuffer[j].getValue(), changeFlag);
				memory[k] =  newByteObj;
				k++;
			}
			
			MemorySegment newLine = new MemorySegment(address, memory, addressableUnit);
			lineCache.add(newLine);
			address = address.add(BigInteger.valueOf(addressableUnit));
		}
		return lineCache.toArray();
	}
	
	/**
	 * @param numberOfLines
	 * @return an array of dummy MemoryByte
	 */
	private MemoryByte[] makeDummyContent(long numberOfLines, int bytesPerLine) {
		MemoryByte[] memoryBuffer;
		// make up dummy memory, needed for recovery in case the debug adapter
		// is capable of retrieving memory again

		int numBytes = (int)(bytesPerLine * numberOfLines);
		memoryBuffer = new MemoryByte[numBytes];
		
		for (int i=0; i<memoryBuffer.length; i++){
			memoryBuffer[i] = new MemoryByte();
			memoryBuffer[i].setValue((byte)0);
			memoryBuffer[i].setWritable(false);
			memoryBuffer[i].setReadable(false);
			memoryBuffer[i].setEndianessKnown(false);
		}
		return memoryBuffer;
	}
	
	protected AbstractAsyncTableRendering getTableRendering(MemoryViewPresentationContext context)
	{
		IMemoryRendering memRendering = context.getRendering();
		if (memRendering != null && memRendering instanceof AbstractAsyncTableRendering)
		{
			return (AbstractAsyncTableRendering)memRendering;
		}
		return null;
	}
}
