/*******************************************************************************
 * Copyright (c) 2000, 2012 QNX Software 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:
 *     QNX Software Systems - Initial API and implementation
 *     Ericsson AB - Modified for new DSF Reference Implementation
 *     Ericsson AB - Reverted to byte[] and processed multi-line results
 *     Harish Dewan (Tensilica Inc) - Bug 369377: Output for command should include value of 'nr-bytes' field
 *******************************************************************************/

package org.eclipse.cdt.dsf.mi.service.command.output;

import java.math.BigInteger;

import org.eclipse.debug.core.model.MemoryByte;

/**
 * 
 * -data-read-memory result
 * 
 * (gdb)
 * nn-data-read-memory [command parameters]
 * nn^done,addr="ADDRESS",nr-bytes="NR_BYTES",total-bytes="TOTAL_BYTES",
 * next-row="NEXT_ROW",prev-row="PREV_ROW",next-page="NEXT_PAGE",
 * prev-page="PREV_PAGE",memory=[
 * {addr="addr1",data=["0x00","0x01", ...]},
 * {addr="addr2",data=["0x02","0x03", ...]},
 * {addr="addr3",data=["0x04","0x05", ...]},
 * ...]
 * (gdb)
 * 
 * where:
 * 
 * 'ADDRESS'
 *      Address (in hex) of the first byte fetched.
 *  
 * 'NR_BYTES'
 * 		Number of bytes read.
 *
 * 'TOTAL_BYTES'
 * 		Number of bytes requested (nr-rows * nr-columns * word-size).
 *
 * 'NEXT_ROW'
 * 		Address (in hex) of the next row.
 *
 * 'PREV_ROW'
 * 		Address (in hex) of the previous row.
 *
 * 'NEXT_PAGE'
 * 		Address (in hex) of the next page.
 *
 * 'PREV_PAGE'
 * 		Address (in hex) of the previous page.
 *
 * 'MEMORY'
 * 		Memory bytes retrieved, nr-rows of nr-columns words.
 * 
 */
public class MIDataReadMemoryInfo extends MIInfo {

	// The parsed values of interest
	BigInteger fAddress = new BigInteger("0"); //$NON-NLS-1$
	int fBytesRead;
	int fBytesRequested;
    MemoryByte[] fMemoryBlock;

    /**
     * Constructor
     * 
     * @param output
     */
    public MIDataReadMemoryInfo(MIOutput output) {

    	super(output);

    	fMemoryBlock = new MemoryByte[0];
        if (isDone()) {
        	parseResult(1);
        }
    }

    /**
     * Constructor
     * 
     * @param output
     */
    public MIDataReadMemoryInfo(MIOutput output, int word_size) {

    	super(output);

    	fMemoryBlock = new MemoryByte[0];
        if (isDone()) {
        	parseResult(word_size);
        }
    }

    /**
     *  Parse the back-end-result. The result is an array of the following form:
     *  
     *  [0] addr="address"
     *  [1] nr-bytes="x"
     *  [2] total-bytes="y"
     *  [3] next-row="address2"
     *  [4] prev-row="address3"
     *  [5] next-page="address4"
     *  [6] prev-page="address5"
     *  [7] memory=[{addr="addr1",data=["0x00","0x01",...]}]
     *  
     *  At this point, we only have interest in "memory".
     */
    private void parseResult(int word_size) {

		// Get the GDB/MI result record
    	MIOutput output = getMIOutput();
    	MIResultRecord record = output.getMIResultRecord();

    	// Parse the result record
    	if (record != null) {

    		// Parse the output results
        	// Note: we assume that the result respects the output format
    		//  i.e. nothing missing, nothing out of order.
    		MIResult[] results =  record.getMIResults();
    		for (int i = 0; i < results.length; i++) {

    			// Get the variable name
    			String var = results[i].getVariable();

    			// Parse 'addr="address"', the address of the first byte to read
    			if (var.equals("addr")) { //$NON-NLS-1$
    				MIValue value = results[i].getMIValue();
    				if (value instanceof MIConst) {
    					String address = ((MIConst) value).getCString();
    					fAddress = new BigInteger(address.substring(2), 16);	// Strip the "0x"
    				}
    			}

    			// Parse 'nr-bytes="x"', the number of bytes read
    			if (var.equals("nr-bytes")) { //$NON-NLS-1$
    				MIValue value = results[i].getMIValue();
    				if (value instanceof MIConst) {
    					String size = ((MIConst) value).getCString();
    					fBytesRead = Integer.parseInt(size);
    				}
    			}

    			// Parse '"total-bytes="y"', the number of bytes requested
    			// Instantiate the corresponding output buffer with invalid bytes
    			if (var.equals("total-bytes")) { //$NON-NLS-1$
    				MIValue value = results[i].getMIValue();
    				if (value instanceof MIConst) {
    					String size = ((MIConst) value).getCString();
    					fBytesRequested = Integer.parseInt(size);
    					fMemoryBlock = new MemoryByte[fBytesRequested];
    					for (int j = 0; j < fMemoryBlock.length; j++)
    						fMemoryBlock[j] = new MemoryByte((byte) 0, (byte) 0);
    				}
    			}

    			// Parse 'memory=[{addr="addr1",data=["0x00","0x01",...]}]'
    			if (var.equals("memory")) { //$NON-NLS-1$
     				MIValue value = results[i].getMIValue();
    				if (value instanceof MIList) {
    					parseMemoryLines((MIList) value, word_size);
    				}
    			}
    		}
    	}
    }

    /**
     *  Parse the actual memory lines of the general form:
     *  
     *  [{addr="addr1",data=["0x00","0x01",...]}]
     *  [{addr="addr2",data=["0x00","0x01",...]}]
     *  
     *  Since we haven't implemented coalescing yet, we conveniently simplify
     *  the processing by assuming that the memory block address matches the
     *  one of the request. Therefore, we only have to fill the memoryBlock[]
     *  with the incoming bytes.
     *  
     *  This will have to be revisited as soon as we start considering
     *  multiple (and possibly canceled) requests.
     */
	private void parseMemoryLines(MIList lines, int word_size) {

		// Parse each line and append the results to the result block
		MIValue[] lineValues = lines.getMIValues();
		for (int i = 0; i < lineValues.length; i++) {

			// Each line has 2 tuples: "addr" and "data"
			if (lineValues[i] instanceof MITuple) {
				MITuple tuple = (MITuple) lineValues[i];
				MIResult[] results = tuple.getMIResults();

				// The offset of this particular output line in the result buffer
				int offset = 0;

				// The 1st tuple ('addr="addr1"') gives us the address of the first byte read
				MIValue addrValue = results[0].getMIValue();
				if (addrValue instanceof MIConst) {
					String address = ((MIConst) addrValue).getCString();
					BigInteger startAddress = new BigInteger(address.substring(2), 16);	// Strip the "0x"
					offset = startAddress.subtract(fAddress).intValue();
				}

				// The 2nd tuple ("data=[...]") gives us the actual bytes
				MIValue value = results[1].getMIValue();
				if (value instanceof MIList) {
					MIList list = (MIList) value;
					MIValue[] values = list.getMIValues();

					MemoryByte[] byteValues = new MemoryByte[values.length * word_size];

					// Parse the result array
					for (int j = 0; j < values.length; j++) {
						if (values[j] instanceof MIConst) {
							String str = ((MIConst) values[j]).getCString();
							try {
								long word = Long.decode(str.trim()).longValue();
								for (int k = 0; k < word_size; k++) {
									int bit_shift =  (word_size - k - 1) * 8;
									byteValues[j * word_size + k] = new MemoryByte((byte) ((word >> bit_shift) % 256));
								}
							} catch (NumberFormatException e) {
								for (int k = 0; k < word_size; k++)
									byteValues[j * word_size + k] = new MemoryByte((byte) -1, (byte) 0);
							}
						}
					}
					// Copy the parsed line to the memory block
					System.arraycopy(byteValues, 0, fMemoryBlock, offset, byteValues.length);
				}
			}
		}
	}

	/**
     *  Return the memory block
     */
    public MemoryByte[] getMIMemoryBlock() {
		return fMemoryBlock;
	}

}
