/*******************************************************************************
 * Copyright (c) 2004, 2006 Intel 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:
 *     Intel Corporation - Initial API and implementation
 *     Mark Mitchell, CodeSourcery - Bug 136896: View variables in binary format
 *******************************************************************************/
package org.jakstab.loader.elf;

import java.math.BigInteger;

public class Addr64 implements IAddress {

	public static final Addr64 ZERO = new Addr64("0"); //$NON-NLS-1$
	public static final Addr64 MAX = new Addr64("ffffffffffffffff", 16); //$NON-NLS-1$

	public static final BigInteger MAX_OFFSET = new BigInteger("ffffffffffffffff", 16); //$NON-NLS-1$

	private static final int BYTES_NUM = 8;
	private static final int DIGITS_NUM = BYTES_NUM * 2;
	private static final int CHARS_NUM = DIGITS_NUM + 2;
	private static final int BINARY_DIGITS_NUM = BYTES_NUM * 8;
	private static final int BINARY_CHARS_NUM = BINARY_DIGITS_NUM + 2;

	private final BigInteger address;

	public Addr64(byte[] addrBytes) {
		address = checkAddress(new BigInteger(1, addrBytes), true);
	}

	public Addr64(BigInteger rawaddress) {
		this(rawaddress, true);
	}

	public Addr64(BigInteger rawaddress, boolean truncate) {
		address = checkAddress(rawaddress, truncate);
	}

	public Addr64(String addr) {
		this(addr, true);
	}

	public Addr64(String addr, boolean truncate) {
		addr = addr.toLowerCase();
		if (addr.startsWith("0x")) { //$NON-NLS-1$
			address = checkAddress(new BigInteger(addr.substring(2), 16), truncate);
		} else {
			address = checkAddress(new BigInteger(addr, 10), truncate);
		}
	}

	public Addr64(String addr, int radix) {
		this(addr, radix, true);
	}

	public Addr64(String addr, int radix, boolean truncate) {
		this(new BigInteger(addr, radix), truncate);
	}

	private BigInteger checkAddress(BigInteger addr, boolean truncate) {
		if (addr.signum() == -1) {
			throw new IllegalArgumentException("Invalid Address, must be positive value"); //$NON-NLS-1$
		}
		if (addr.bitLength() > 64 ) {
			if (truncate) {
				return addr.and(MAX.getValue()); // truncate
			}
			else {
				throw (new NumberFormatException("Value out of range"));
			}
		}
		return addr;
	}
	
	
	public IAddress add(BigInteger offset) {
		return new Addr64(this.address.add(offset));
	}

	public IAddress add(long offset) {
		return new Addr64(this.address.add(BigInteger.valueOf(offset)));
	}

	public BigInteger getMaxOffset() {
		return MAX_OFFSET;
	}

	public BigInteger distanceTo(IAddress other) {
		if (! (other instanceof Addr64)) {
			throw new IllegalArgumentException();
		}
		return ((Addr64)other).address.add(address.negate());
	}

	public boolean isMax() {
		return address.equals(MAX.address);
	}

	public boolean isZero() {
		return address.equals(ZERO.address);
	}

	public BigInteger getValue() {
		return address;
	}

	public int compareTo(Object other) {
		return this.address.compareTo(((Addr64)other).address);
	}

	@Override
	public boolean equals(Object x) {
		if (x == this)
			return true;
		if (! (x instanceof Addr64))
			return false;
		return this.address.equals(((Addr64)x).address);
	}

	@Override
	public int hashCode() {
		return address.hashCode();
	}

	@Override
	public String toString() {
		return toString(10);
	}

	public String toString(int radix) {
		return address.toString(radix);
	}

	public String toHexAddressString() {
		String addressString = address.toString(16);
		StringBuffer sb = new StringBuffer(CHARS_NUM);
		int count = DIGITS_NUM - addressString.length();
		sb.append("0x"); //$NON-NLS-1$
		for (int i = 0; i < count; ++i) {
			sb.append('0');
		}
		sb.append(addressString);
		return sb.toString();
	}

	public String toBinaryAddressString() {
		String addressString = address.toString(2);
		StringBuffer sb = new StringBuffer(BINARY_CHARS_NUM);
		int count = BINARY_DIGITS_NUM - addressString.length();
		sb.append("0b"); //$NON-NLS-1$
		for (int i = 0; i < count; ++i) {
			sb.append('0');
		}
		sb.append(addressString);
		return sb.toString();
	}

	public int getCharsNum() {
		return CHARS_NUM;
	}

	public int getSize() {
		return BYTES_NUM;
	}
}

