package jadx.core.dex.nodes.parser;

import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.nodes.DexNode;
import jadx.core.utils.exceptions.DecodeException;

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

import com.android.dex.Dex.Section;
import com.android.dex.Leb128;

public class EncValueParser {

	private static final int ENCODED_BYTE = 0x00;
	private static final int ENCODED_SHORT = 0x02;
	private static final int ENCODED_CHAR = 0x03;
	private static final int ENCODED_INT = 0x04;
	private static final int ENCODED_LONG = 0x06;
	private static final int ENCODED_FLOAT = 0x10;
	private static final int ENCODED_DOUBLE = 0x11;
	private static final int ENCODED_STRING = 0x17;
	private static final int ENCODED_TYPE = 0x18;
	private static final int ENCODED_FIELD = 0x19;
	private static final int ENCODED_ENUM = 0x1b;
	private static final int ENCODED_METHOD = 0x1a;
	private static final int ENCODED_ARRAY = 0x1c;
	private static final int ENCODED_ANNOTATION = 0x1d;
	private static final int ENCODED_NULL = 0x1e;
	private static final int ENCODED_BOOLEAN = 0x1f;

	protected final Section in;

	private final DexNode dex;

	public EncValueParser(DexNode dex, Section in) {
		this.in = in;
		this.dex = dex;
	}

	public Object parseValue() throws DecodeException {
		int argAndType = readByte();
		int type = argAndType & 0x1F;
		int arg = (argAndType & 0xE0) >> 5;
		int size = arg + 1;

		switch (type) {
			case ENCODED_NULL:
				return null;

			case ENCODED_BOOLEAN:
				return arg == 1;
			case ENCODED_BYTE:
				return in.readByte();

			case ENCODED_SHORT:
				return (short) parseNumber(size, true);
			case ENCODED_CHAR:
				return (char) parseUnsignedInt(size);
			case ENCODED_INT:
				return (int) parseNumber(size, true);
			case ENCODED_LONG:
				return parseNumber(size, true);

			case ENCODED_FLOAT:
				return Float.intBitsToFloat((int) parseNumber(size, false, 4));
			case ENCODED_DOUBLE:
				return Double.longBitsToDouble(parseNumber(size, false, 8));

			case ENCODED_STRING:
				return dex.getString(parseUnsignedInt(size));

			case ENCODED_TYPE:
				return dex.getType(parseUnsignedInt(size));

			case ENCODED_METHOD:
				return MethodInfo.fromDex(dex, parseUnsignedInt(size));

			case ENCODED_FIELD:
			case ENCODED_ENUM:
				return FieldInfo.fromDex(dex, parseUnsignedInt(size));

			case ENCODED_ARRAY:
				int count = Leb128.readUnsignedLeb128(in);
				List<Object> values = new ArrayList<Object>(count);
				for (int i = 0; i < count; i++) {
					values.add(parseValue());
				}
				return values;

			case ENCODED_ANNOTATION:
				return AnnotationsParser.readAnnotation(dex, in, false);
		}
		throw new DecodeException("Unknown encoded value type: 0x" + Integer.toHexString(type));
	}

	private int parseUnsignedInt(int byteCount) {
		return (int) parseNumber(byteCount, false, 0);
	}

	private long parseNumber(int byteCount, boolean isSignExtended) {
		return parseNumber(byteCount, isSignExtended, 0);
	}

	private long parseNumber(int byteCount, boolean isSignExtended, int fillOnRight) {
		long result = 0;
		long last = 0;
		for (int i = 0; i < byteCount; i++) {
			last = readByte();
			result |= last << i * 8;
		}
		if (fillOnRight != 0) {
			for (int i = byteCount; i < fillOnRight; i++) {
				result <<= 8;
			}
		} else {
			if (isSignExtended && (last & 0x80) != 0) {
				for (int i = byteCount; i < 8; i++) {
					result |= (long) 0xFF << i * 8;
				}
			}
		}
		return result;
	}

	private int readByte() {
		return in.readByte() & 0xFF;
	}
}
