/* ###
 * IP: GHIDRA
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package ghidra.program.model.listing;

import java.util.ArrayList;
import java.util.List;

import generic.algorithms.CRC64;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.UnknownRegister;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.util.LanguageTranslator;
import ghidra.util.exception.InvalidInputException;

/**
 * <code></code> encapsulates the ordered list of storage varnodes which correspond to a 
 * function parameter or local variable.  For big-endian the first element corresponds 
 * to the most-significant varnode, while for little-endian the first element 
 * corresponds to the least-significant varnode.
 */
public class VariableStorage implements Comparable<VariableStorage> {

	private static final String BAD = "<BAD>";
	private static final String UNASSIGNED = "<UNASSIGNED>";
	private static final String VOID = "<VOID>";

	/**
	 * <code>BAD_STORAGE</code> used to identify variable storage which is no longer
	 * valid.  This can be caused by various events such as significant language/processor
	 * changes or software bugs which prevent variable storage to be properly decoded.
	 */
	public static final VariableStorage BAD_STORAGE = new VariableStorage();

	/**
	 * <code>UNASSIGNED_STORAGE</code> used to identify parameter storage which is "unmapped"
	 * or could not be determined.
	 */
	public static final VariableStorage UNASSIGNED_STORAGE = new VariableStorage();

	/**
	 * <code>VOID_STORAGE</code> used to identify parameter/return storage which is "mapped"
	 * with a data-type of void.
	 */
	public static final VariableStorage VOID_STORAGE = new VariableStorage();

	protected final Varnode[] varnodes;
	protected final Program program;

	private List<Register> registers;
	private int size;
	private long hashcode;
	private String serialization;

	/**
	 * Construct an empty variable storage for reserved usage (i.e., BAD_STORAGE, UNMAPPED_STORAGE)
	 */
	protected VariableStorage() {
		this.program = null;
		this.varnodes = null;
	}

	/**
	 * Construct variable storage
	 * @param program
	 * @param varnodes one or more ordered storage varnodes
	 * @throws InvalidInputException if specified varnodes violate storage restrictions
	 */
	public VariableStorage(Program program, Varnode... varnodes) throws InvalidInputException {
		this.program = program;
		this.varnodes = varnodes.clone();
		checkVarnodes();
	}

	/**
	 * Construct register variable storage
	 * @param program
	 * @param registers one or more ordered registers
	 * @throws InvalidInputException if specified registers violate storage restrictions
	 */
	public VariableStorage(Program program, Register... registers) throws InvalidInputException {
		this(program, getVarnodeList(registers));
	}

	/**
	 * Construct stack variable storage
	 * @param program
	 * @param stackOffset stack offset
	 * @param size stack element size
	 * @throws InvalidInputException if specified registers violate storage restrictions
	 */
	public VariableStorage(Program program, int stackOffset, int size) throws InvalidInputException {
		this(program, new Varnode(program.getAddressFactory().getStackSpace().getAddress(
			stackOffset), size));
	}

	private static Varnode[] getVarnodeList(Register[] registers) {
		Varnode[] varnodes = new Varnode[registers.length];
		for (int i = 0; i < registers.length; i++) {
			varnodes[i] = new Varnode(registers[i].getAddress(), registers[i].getMinimumByteSize());
		}
		return varnodes;
	}

	/**
	 * Construct variable storage
	 * @param program
	 * @param varnodes one or more ordered storage varnodes
	 * @throws InvalidInputException if specified varnodes violate storage restrictions
	 */
	public VariableStorage(Program program, List<Varnode> varnodes) throws InvalidInputException {
		this.program = program;
		this.varnodes = varnodes.toArray(new Varnode[varnodes.size()]);
		checkVarnodes();
	}

	/**
	 * Construct variable storage
	 * @param program
	 * @param address
	 * @param size
	 * @throws InvalidInputException
	 */
	public VariableStorage(Program program, Address address, int size) throws InvalidInputException {
		this(program, new Varnode(address, size));
	}

	/**
	 * Construct variable storage
	 * @param program
	 * @param serialization storage serialization string
	 * @throws InvalidInputException
	 */
	public static VariableStorage deserialize(Program program, String serialization)
			throws InvalidInputException {
		if (serialization == null || UNASSIGNED.equals(serialization)) {
			return UNASSIGNED_STORAGE;
		}
		if (VOID.equals(serialization)) {
			return VOID_STORAGE;
		}
		if (BAD.equals(serialization)) {
			return BAD_STORAGE;
		}
		List<Varnode> varnodes = getVarnodes(program.getAddressFactory(), serialization);
		if (varnodes == null) {
			return BAD_STORAGE;
		}
		return new VariableStorage(program, varnodes);
	}

	/**
	 * @return program for which this storage is associated
	 */
	public Program getProgram() {
		return program;
	}

	/**
	 * @return the total size of corresponding storage varnodes
	 */
	public int size() {
		return size;
	}

	private void checkVarnodes() throws InvalidInputException {
		if (varnodes.length == 0) {
			throw new IllegalArgumentException("A minimum of one varnode must be specified");
		}

		AddressFactory addrFactory = program.getAddressFactory();
		size = 0;
		for (int i = 0; i < varnodes.length; i++) {
			Varnode varnode = varnodes[i];
			if (varnode == null) {
				throw new InvalidInputException("Null varnode not permitted");
			}
			if (varnode.getSize() <= 0) {
				throw new InvalidInputException("Unsupported varnode size: " + varnode.getSize());
			}

			boolean isRegister = false;
			Address storageAddr = varnode.getAddress();
			if (storageAddr.isHashAddress() || storageAddr.isUniqueAddress() ||
				storageAddr.isConstantAddress()) {
				if (varnodes.length != 1) {
					throw new InvalidInputException(
						"Hash, Unique and Constant storage may only use a single varnode");
				}
			}
			else {
				AddressSpace space = addrFactory.getAddressSpace(varnode.getSpace());
				AddressSpace varnodeSpace = varnode.getAddress().getAddressSpace();
				if (space != varnodeSpace) {
					throw new InvalidInputException(
						"Invalid varnode address for specified program: " +
							varnode.getAddress().toString(true));
				}
			}

			if (!storageAddr.isStackAddress()) {
				Register reg = program.getRegister(storageAddr, varnode.getSize());
				if (reg != null && !(reg instanceof UnknownRegister)) {
					isRegister = true;
					if (registers == null) {
						registers = new ArrayList<Register>();
					}
					registers.add(reg);
				}
// The decompiler can create special varnodes in upper bytes of a register
// so we are unable to prevent such varnodes
//				else if (storageAddr.isRegisterAddress()) {
//					throw new InvalidInputException("Variable register not found at " +
//						storageAddr.toString(true) + ", size=" + varnode.getSize() + ")");
//				}
			}
			else {
				long stackOffset = storageAddr.getOffset();
				if (stackOffset < 0 && -stackOffset < varnode.getSize()) {
					// do not allow stack varnode to span the 0-offset 
					// i.e., maintain separation of locals and params
					throw new InvalidInputException(
						"Stack varnode violates stack frame constraints (stack offset=" +
							stackOffset + ", size=" + varnode.getSize());
				}
			}
			if (i < (varnodes.length - 1) && !isRegister) {
				throw new InvalidInputException(
					"Compound storage must use registers accept for last varnode");
			}
			size += varnode.getSize();
		}
		for (int i = 0; i < varnodes.length; i++) {
			for (int j = i + 1; j < varnodes.length; j++) {
				if (varnodes[i].intersects(varnodes[j])) {
					throw new InvalidInputException("One or more conflicting varnodes");
				}
			}
		}
	}

	/**
	 * Attempt to clone variable storage for use in a different program.
	 * Dynamic storage characteristics will not be preserved.
	 * @param newProgram target program
	 * @return cloned storage
	 * @throws InvalidInputException 
	 */
	public VariableStorage clone(Program newProgram) throws InvalidInputException {
		if (program == null || newProgram == program) {
			if (getClass().equals(VariableStorage.class)) {
				return this; // only reuse if simple VariableStorage instance
			}
			if (isUnassignedStorage()) {
				return UNASSIGNED_STORAGE;
			}
			if (isBadStorage()) {
				return BAD_STORAGE;
			}
			return new VariableStorage(newProgram, varnodes);
		}
		if (!newProgram.getLanguage().equals(program.getLanguage())) {
			throw new IllegalArgumentException(
				"Variable storage incompatible with program language: " +
					newProgram.getLanguage().toString());
		}
		AddressFactory newAddressFactory = newProgram.getAddressFactory();
		Varnode[] v = getVarnodes();
		Varnode[] newVarnodes = new Varnode[v.length];
		for (int i = 0; i < v.length; i++) {
			AddressSpace newSpace = newAddressFactory.getAddressSpace(v[i].getSpace());
			AddressSpace curSpace = v[i].getAddress().getAddressSpace();
			if (newSpace == null) {
				throw new InvalidInputException(
					"Variable storage incompatible with program, address space not found: " +
						curSpace.getName());
			}
			newVarnodes[i] =
				new Varnode(newSpace.getAddress(v[i].getOffset()), v[i].getSize());
		}
		return new VariableStorage(newProgram, newVarnodes);
	}

	@Override
	public String toString() {
		if (isBadStorage()) {
			return BAD;
		}
		if (isUnassignedStorage()) {
			return UNASSIGNED;
		}
		if (isVoidStorage()) {
			return VOID;
		}
		StringBuilder builder = new StringBuilder();
		Varnode varnode = varnodes[0];
		addVarnodeInfo(builder, varnode);
		for (int i = 1; i < varnodes.length; i++) {
			builder.append(",");
			addVarnodeInfo(builder, varnodes[i]);
		}
		return builder.toString();
	}

	private void addVarnodeInfo(StringBuilder builder, Varnode varnode) {
		Address address = varnode.getAddress();
		builder.append(getAddressString(address, varnode.getSize()));
		builder.append(":");
		builder.append(varnode.getSize());
	}

	private String getAddressString(Address address, int size) {
		if (address.isRegisterAddress() || address.isMemoryAddress()) {
			Register register = program.getRegister(address, size);
			if (register != null) {
				return register.toString();
			}
		}
		return address.toString();
	}

	/**
	 * @return the number of varnodes associated with this variable storage
	 */
	public int getVarnodeCount() {
		if (varnodes == null) {
			return 0;
		}
		return varnodes.length;
	}

	/**
	 * @return ordered varnodes associated with this variable storage
	 */
	public Varnode[] getVarnodes() {
		if (varnodes == null) {
			return new Varnode[0];
		}
		return varnodes.clone();
	}

	/**
	 * Associated with auto-parameters.  Parameters whose existence is dictated
	 * by a calling-convention may automatically inject additional hidden
	 * parameters.  If this storage is associated with a auto-parameter, this
	 * method will return true.   
	 * @return true if this storage is associated with an auto-parameter, else false
	 */
	public boolean isAutoStorage() {
		return false;
	}

	/**
	 * If this storage corresponds to a auto-parameter, return the type associated
	 * with the auto-parameter.
	 * @return auto-parameter type or null if not applicable
	 */
	public AutoParameterType getAutoParameterType() {
		return null;
	}

	/**
	 * If this storage corresponds to parameter which was forced by the associated calling 
	 * convention to be passed as a pointer instead of its raw type.
	 * @return true if this parameter was forced to be passed as a pointer instead of its raw type
	 */
	public boolean isForcedIndirect() {
		return false;
	}

	/**
	 * @return true if this storage is bad (could not be resolved)
	 */
	public boolean isBadStorage() {
		return this == BAD_STORAGE;
	}

	/**
	 * @return true if storage has not been assigned (no varnodes)
	 */
	public boolean isUnassignedStorage() {
		return this == UNASSIGNED_STORAGE;
	}

	/**
	 * @return true if storage is assigned and is not BAD
	 */
	public boolean isValid() {
		return !isUnassignedStorage() && !isBadStorage();
	}

	/**
	 * @return true if storage corresponds to the VOID_STORAGE instance
	 * @see #VOID_STORAGE
	 */
	public boolean isVoidStorage() {
		return this == VOID_STORAGE;
	}

	/**
	 * @return first varnode within the ordered list of varnodes
	 */
	public Varnode getFirstVarnode() {
		return (varnodes == null || varnodes.length == 0) ? null : varnodes[0];
	}

	/**
	 * @return last varnode within the ordered list of varnodes
	 */
	public Varnode getLastVarnode() {
		return (varnodes == null || varnodes.length == 0) ? null : varnodes[varnodes.length - 1];
	}

	/**
	 * @return true if storage consists of a single stack varnode
	 */
	public boolean isStackStorage() {
		if (varnodes == null || varnodes.length == 0) {
			return false;
		}
		// check first varnode for stack use
		Address storageAddr = getFirstVarnode().getAddress();
		return storageAddr.isStackAddress();
	}

	/**
	 * @return true if the last varnode for simple or compound storage is a stack varnode
	 */
	public boolean hasStackStorage() {
		if (varnodes == null || varnodes.length == 0) {
			return false;
		}
		// check last varnode for stack use
		Address storageAddr = getLastVarnode().getAddress();
		return storageAddr.isStackAddress();
	}

	/**
	 * @return true if this is a simple variable consisting of a single register varnode
	 * which will be returned by either the {@link Variable#getFirstStorageVarnode()} or 
	 * {@link Variable#getLastStorageVarnode()} methods.  The register can be obtained using the 
	 * {@link #getRegister()} method.  Keep in mind that registers
	 * may exist in a memory space or the register space.
	 */
	public boolean isRegisterStorage() {
		return varnodes != null && varnodes.length == 1 && registers != null;
	}

	/**
	 * @return first storage register associated with this register or compound storage, else
	 * null is returned.
	 * @see Variable#isRegisterVariable()
	 */
	public Register getRegister() {
		return registers != null ? registers.get(0) : null;
	}

	/**
	 * @return storage register(s) associated with this register or compound storage, else
	 * null is returned.
	 * @see Variable#isRegisterVariable()
	 * @see #isCompoundStorage()
	 */
	public List<Register> getRegisters() {
		return registers;
	}

	/**
	 * @return the stack offset associated with simple stack storage or compound 
	 * storage where the last varnode is stack, see {@link #hasStackStorage()}. 
	 * @throws UnsupportedOperationException if storage does not have a stack varnode
	 */
	public int getStackOffset() {
		if (varnodes != null && varnodes.length != 0) {
			Address storageAddr = getLastVarnode().getAddress();
			if (storageAddr.isStackAddress()) {
				return (int) storageAddr.getOffset();
			}
		}
		throw new UnsupportedOperationException("Storage does not have a stack varnode");
	}

	/**
	 * @return the minimum address corresponding to the first varnode of this storage
	 * or null if this is a special empty storage: {@link #isBadStorage()}, {@link #isUnassignedStorage()},
	 * {@link #isVoidStorage()}
	 */
	public Address getMinAddress() {
		if (varnodes == null || varnodes.length == 0) {
			return null;
		}
		return varnodes[0].getAddress();
	}

	/**
	 * @return true if storage consists of a single memory varnode which does not correspond
	 * to a register.
	 */
	public boolean isMemoryStorage() {
		if (varnodes == null || varnodes.length == 0) {
			return false;
		}
		Address storageAddr = varnodes[0].getAddress();
		return storageAddr.isMemoryAddress() && (registers == null);
	}

	/**
	 * @return true if storage consists of a single constant-space varnode which is used when storing
	 * local function constants.
	 */
	public boolean isConstantStorage() {
		if (varnodes == null || varnodes.length == 0) {
			return false;
		}
		Address storageAddr = varnodes[0].getAddress();
		return storageAddr.isConstantAddress();
	}

	/**
	 * @return true if storage consists of a single hash-space varnode which is used when storing
	 * local unique function variables.
	 */
	public boolean isHashStorage() {
		if (varnodes == null || varnodes.length == 0) {
			return false;
		}
		Address storageAddr = varnodes[0].getAddress();
		return storageAddr.isHashAddress();
	}

	/**
	 * @return true if storage consists of a single unique-space varnode which is used during
	 * function analysis.  This type of storage is not suitable for database-stored function
	 * variables.  This type of storage must be properly converted to Hash storage when 
	 * storing unique function variables.
	 */
	public boolean isUniqueStorage() {
		if (varnodes == null || varnodes.length == 0) {
			return false;
		}
		Address storageAddr = varnodes[0].getAddress();
		return storageAddr.isUniqueAddress();
	}

	/**
	 * @return true if storage consists of two or more storage varnodes
	 */
	public boolean isCompoundStorage() {
		return varnodes != null && varnodes.length > 1;
	}

	public long getLongHash() {
		if (hashcode == 0) {
			// WARNING! This can not be changed since this hash is used to 
			// locate existing storage records which have previously 
			// stored this hash
			CRC64 crc = new CRC64();
			byte[] bytes = getSerializationString().getBytes();
			crc.update(bytes, 0, bytes.length);
			hashcode = crc.finish();
		}
		return hashcode;
	}

	@Override
	public int hashCode() {
		return (int) getLongHash();
		//return (varnodes == null || varnodes.length == 0) ? 0 : varnodes[0].hashCode();
	}

	/**
	 * This storage is considered equal if it consists of the same storage varnodes.
	 */
	@Override
	public boolean equals(Object obj) {
		if (!(obj instanceof VariableStorage)) {
			return false;
		}
		VariableStorage otherVS = (VariableStorage) obj;
		if (isAutoStorage() != otherVS.isAutoStorage()) {
			return false;
		}
		if (isForcedIndirect() != otherVS.isForcedIndirect()) {
			return false;
		}
		if (isBadStorage() != otherVS.isBadStorage()) {
			return false;
		}
		if (isUnassignedStorage() != otherVS.isUnassignedStorage()) {
			return false;
		}
		if (isVoidStorage() != otherVS.isVoidStorage()) {
			return false;
		}
		return compareTo(otherVS) == 0;
	}

	/**
	 * Determine if this variable storage intersects the specified variable storage
	 * @param variableStorage
	 * @return true if any intersection exists between this storage and the specified
	 * variable storage
	 */
	public boolean intersects(VariableStorage variableStorage) {
		Varnode[] otherVarnodes = variableStorage.varnodes;
		if (varnodes == null || otherVarnodes == null) {
			return false;
		}
		for (int i = 0; i < varnodes.length; i++) {
			for (int j = 0; j < otherVarnodes.length; j++) {
				if (varnodes[i].intersects(otherVarnodes[j])) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Determine if this storage intersects the specified address set
	 * @param set address set
	 * @return true if this storage intersects the specified address set
	 */
	public boolean intersects(AddressSetView set) {
		if (varnodes == null || set == null || set.isEmpty()) {
			return false;
		}
		for (int i = 0; i < varnodes.length; i++) {
			if (varnodes[i].intersects(set)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Determine if this storage intersects the specified register
	 * @param reg the register
	 * @return true if this storage intersects the specified register
	 */
	public boolean intersects(Register reg) {
		if (varnodes == null || reg == null) {
			return false;
		}
		Varnode regVarnode = new Varnode(reg.getAddress(), reg.getMinimumByteSize());
		for (int i = 0; i < varnodes.length; i++) {
			if (varnodes[i].intersects(regVarnode)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Determine if the specified address is contained within this storage
	 * @param address
	 * @return
	 */
	public boolean contains(Address address) {
		if (varnodes == null) {
			return false;
		}
		for (int i = 0; i < varnodes.length; i++) {
			if (varnodes[i].contains(address)) {
				return true;
			}
		}
		return false;
	}

	private static final int PRECEDENCE_MAPPED = 1;
	private static final int PRECEDENCE_UNMAPPED = 2;
	private static final int PRECEDENCE_BAD = 3;

	private static int getPrecedence(VariableStorage storage) {
		if (storage.isUnassignedStorage()) {
			return PRECEDENCE_UNMAPPED;
		}
		if (storage.varnodes != null && storage.varnodes.length != 0) {
			return PRECEDENCE_MAPPED;
		}
		return PRECEDENCE_BAD;
	}

	/**
	 * Compare this variable storage with another.  A value of 0 indicates 
	 * that the two objects are equal
	 * @see java.lang.Comparable#compareTo(java.lang.Object)
	 */
	@Override
	public int compareTo(VariableStorage otherStorage) {
		int myPrecedence = getPrecedence(this);
		int otherPrecedence = getPrecedence(otherStorage);
		int diff = myPrecedence - otherPrecedence;
		if (diff != 0 || myPrecedence != PRECEDENCE_MAPPED) {
			return diff;
		}
		int compareIndexCnt = Math.min(varnodes.length, otherStorage.varnodes.length);
		for (int i = 0; i < compareIndexCnt; i++) {
			Address myStorageAddr = varnodes[i].getAddress();
			Address otherStorageAddr = otherStorage.varnodes[i].getAddress();
			diff = myStorageAddr.compareTo(otherStorageAddr);
			if (diff != 0) {
				return diff;
			}
			diff = varnodes[i].getSize() - otherStorage.varnodes[i].getSize();
			if (diff != 0) {
				return diff;
			}
		}
		return varnodes.length - otherStorage.varnodes.length;
	}

	/**
	 * Return a serialization form of this variable storage.
	 * @return storage serialization string useful for subsequent reconstruction
	 */
	public String getSerializationString() {

		if (serialization != null) {
			return serialization;
		}

		if (isBadStorage()) {
			serialization = BAD;
		}
		else if (isUnassignedStorage()) {
			serialization = UNASSIGNED;
		}
		else if (isVoidStorage()) {
			serialization = VOID;
		}
		else {
			serialization = getSerializationString(varnodes);
		}
		return serialization;
	}

	/**
	 * Generate VariableStorage serialization string
	 * @param varnodes
	 * @return storage serialization string useful for subsequent reconstruction
	 * of a VariableStorage object
	 */
	public static String getSerializationString(Varnode... varnodes) {
		if (varnodes == null || varnodes.length == 0) {
			throw new IllegalArgumentException("varnodes may not be null or empty");
		}
		StringBuilder strBuilder = new StringBuilder();
		for (Varnode v : varnodes) {
			if (strBuilder.length() != 0) {
				strBuilder.append(",");
			}
			strBuilder.append(v.getAddress().toString(true));
			strBuilder.append(":");
			strBuilder.append(Integer.toString(v.getSize()));
		}
		return strBuilder.toString();
	}

	/**
	 * Parse a storage serialization string to produce an array or varnodes
	 * @param addrFactory
	 * @param serialization
	 * @return array of varnodes or null if invalid
	 */
	public static List<Varnode> getVarnodes(AddressFactory addrFactory, String serialization)
			throws InvalidInputException {
		if (BAD.equals(serialization)) {
			return null;
		}
		ArrayList<Varnode> list = new ArrayList<Varnode>();
		String[] varnodeStrings = serialization.split(",");
		try {
			for (String piece : varnodeStrings) {
				int index = piece.lastIndexOf(':');
				if (index <= 0) {
					list = null;
					break;
				}
				String addrStr = piece.substring(0, index);
				String sizeStr = piece.substring(index + 1);
				Address addr = addrFactory.getAddress(addrStr);
				if (addr == null) {
					list = null;
					break;
				}
				if (addr == Address.NO_ADDRESS) {
					return null;
				}
				int size = Integer.parseInt(sizeStr);
				list.add(new Varnode(addr, size));
			}
		}
		catch (NumberFormatException e) {
			list = null;
		}
		if (list == null) {
			throw new InvalidInputException("Invalid varnode serialization: '" + serialization +
				"'");
		}
		return list;
	}

	/**
	 * Perform language translations on VariableStorage serialization string
	 * @param translator language translator
	 * @param serialization VariableStorage serialization string
	 * @return translated serialization string
	 * @throws InvalidInputException if serialization has invalid format
	 */
	public static String translateSerialization(LanguageTranslator translator, String serialization)
			throws InvalidInputException {
		if (serialization == null || UNASSIGNED.equals(serialization)) {
			return null;
		}
		if (VOID.equals(serialization)) {
			return VOID;
		}
		if (BAD.equals(serialization)) {
			return BAD;
		}

		StringBuilder strBuilder = new StringBuilder();
		String[] varnodeStrings = serialization.split(",");
		for (String piece : varnodeStrings) {
			int index = piece.lastIndexOf(':');
			if (index <= 0) {
				strBuilder = null;
				break;
			}

			if (strBuilder.length() != 0) {
				strBuilder.append(",");
			}

			String addrStr = piece.substring(0, index);
			String sizeStr = piece.substring(index + 1);

			index = addrStr.indexOf(':');
			if (index > 0) {
				String spaceName = addrStr.substring(0, index);
				String offsetStr = addrStr.substring(index + 1);
				AddressSpace space = translator.getNewAddressSpace(spaceName);
				if (space != null) {

					// Handle register movement within register space only
					// Assumes all translators will map register space properly
					if (space.isRegisterSpace()) {
						long offset = Long.parseUnsignedLong(offsetStr, 16);
						int size = Integer.parseInt(sizeStr);
						Address oldRegAddr =
							translator.getOldLanguage().getAddressFactory().getRegisterSpace().getAddress(
								offset);
						String newOffsetStr =
							translateRegisterVarnodeOffset(oldRegAddr, size, translator, space);
						if (newOffsetStr != null) {
							// if mapping failed - leave it unchanged
							offsetStr = newOffsetStr;
						}
					}
					strBuilder.append(space.getName());
					strBuilder.append(':');
					strBuilder.append(offsetStr);
					strBuilder.append(':');
					strBuilder.append(sizeStr);
					continue;
				}
				// else don't translate varnode - assume overlay and leave it alone
			}
			// else don't translate varnode - assume Stack or other special encoding

			// no translation needed
			strBuilder.append(piece);
		}

		if (strBuilder == null) {
			throw new InvalidInputException("Invalid varnode serialization: '" + serialization +
				"'");
		}

		return strBuilder.toString();
	}

	/**
	 * Translate register varnode address offsetStr
	 * @param translator
	 * @param space
	 * @param offsetStr
	 * @param sizeStr
	 * @return translated offsetStr or null if BAD translation
	 */
	private static String translateRegisterVarnodeOffset(Address oldRegAddr, int varnodeSize,
			LanguageTranslator translator, AddressSpace newRegisterSpace) {
		// Handle register movement within register space only
		// Assumes all translators will map register space properly
		// If old or new register not found no adjustment is made
		// The original addrStr may refer to an offcut location within a register 
		long offset = oldRegAddr.getOffset();
		Register oldReg = translator.getOldRegister(oldRegAddr, varnodeSize);
		if (oldReg == null) {
			// possible offcut register varnode
			oldReg = translator.getOldRegisterContaining(oldRegAddr);
		}
		if (oldReg != null && !(oldReg instanceof UnknownRegister)) {
			Register newReg = translator.getNewRegister(oldReg);
			if (newReg != null) { // assume reg endianess unchanged
				// NOTE: could produce bad results if not careful with mapping
				int origByteShift = (int) offset - oldReg.getOffset();
				offset = newReg.getOffset() + origByteShift;
				if (newReg.isBigEndian()) {
					// maintain right alignment for BE
					int regSizeDiff = newReg.getMinimumByteSize() - oldReg.getMinimumByteSize();
					offset += regSizeDiff;
					if (offset < newReg.getOffset()) {
						return null; // BE - did not fit
					}
				}
				else if ((origByteShift + varnodeSize) > newReg.getMinimumByteSize()) {
					return null; // LE - did not fit
				}
				return Long.toHexString(offset);
			}
		}
		return null; // translation failed
	}

}