package jadx.api;

import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

public final class JavaClass implements JavaNode {

	private final JadxDecompiler decompiler;
	private final ClassNode cls;
	private final JavaClass parent;

	private List<JavaClass> innerClasses = Collections.emptyList();
	private List<JavaField> fields = Collections.emptyList();
	private List<JavaMethod> methods = Collections.emptyList();

	JavaClass(ClassNode classNode, JadxDecompiler decompiler) {
		this.decompiler = decompiler;
		this.cls = classNode;
		this.parent = null;
	}

	/**
	 * Inner classes constructor
	 */
	JavaClass(ClassNode classNode, JavaClass parent) {
		this.decompiler = null;
		this.cls = classNode;
		this.parent = parent;
	}

	public String getCode() {
		CodeWriter code = cls.getCode();
		if (code == null) {
			decompile();
			code = cls.getCode();
		}
		if (code == null) {
			return "";
		}
		return code.toString();
	}

	public void decompile() {
		if (decompiler == null) {
			return;
		}
		if (cls.getCode() == null) {
			decompiler.processClass(cls);
			load();
		}
	}

	ClassNode getClassNode() {
		return cls;
	}

	private void load() {
		int inClsCount = cls.getInnerClasses().size();
		if (inClsCount != 0) {
			List<JavaClass> list = new ArrayList<JavaClass>(inClsCount);
			for (ClassNode inner : cls.getInnerClasses()) {
				if (!inner.contains(AFlag.DONT_GENERATE)) {
					JavaClass javaClass = new JavaClass(inner, this);
					javaClass.load();
					list.add(javaClass);
				}
			}
			this.innerClasses = Collections.unmodifiableList(list);
		}

		int fieldsCount = cls.getFields().size();
		if (fieldsCount != 0) {
			List<JavaField> flds = new ArrayList<JavaField>(fieldsCount);
			for (FieldNode f : cls.getFields()) {
				if (!f.contains(AFlag.DONT_GENERATE)) {
					flds.add(new JavaField(f, this));
				}
			}
			this.fields = Collections.unmodifiableList(flds);
		}

		int methodsCount = cls.getMethods().size();
		if (methodsCount != 0) {
			List<JavaMethod> mths = new ArrayList<JavaMethod>(methodsCount);
			for (MethodNode m : cls.getMethods()) {
				if (!m.contains(AFlag.DONT_GENERATE)) {
					mths.add(new JavaMethod(this, m));
				}
			}
			Collections.sort(mths, new Comparator<JavaMethod>() {
				@Override
				public int compare(JavaMethod o1, JavaMethod o2) {
					return o1.getName().compareTo(o2.getName());
				}
			});
			this.methods = Collections.unmodifiableList(mths);
		}
	}

	private Map<CodePosition, Object> getCodeAnnotations() {
		decompile();
		return cls.getCode().getAnnotations();
	}

	public CodePosition getDefinitionPosition(int line, int offset) {
		Map<CodePosition, Object> map = getCodeAnnotations();
		if (map.isEmpty()) {
			return null;
		}
		Object obj = map.get(new CodePosition(line, offset));
		if (!(obj instanceof LineAttrNode)) {
			return null;
		}
		ClassNode clsNode = null;
		if (obj instanceof ClassNode) {
			clsNode = (ClassNode) obj;
		} else if (obj instanceof MethodNode) {
			clsNode = ((MethodNode) obj).getParentClass();
		} else if (obj instanceof FieldNode) {
			clsNode = ((FieldNode) obj).getParentClass();
		}
		if (clsNode == null) {
			return null;
		}
		clsNode = clsNode.getTopParentClass();
		JavaClass jCls = decompiler.findJavaClass(clsNode);
		if (jCls == null) {
			return null;
		}
		jCls.decompile();
		int defLine = ((LineAttrNode) obj).getDecompiledLine();
		if (defLine == 0) {
			return null;
		}
		return new CodePosition(jCls, defLine, 0);
	}

	public Integer getSourceLine(int decompiledLine) {
		decompile();
		return cls.getCode().getLineMapping().get(decompiledLine);
	}

	@Override
	public String getName() {
		return cls.getShortName();
	}

	@Override
	public String getFullName() {
		return cls.getFullName();
	}

	public String getPackage() {
		return cls.getPackage();
	}

	@Override
	public JavaClass getDeclaringClass() {
		return parent;
	}

	public AccessInfo getAccessInfo() {
		return cls.getAccessFlags();
	}

	public List<JavaClass> getInnerClasses() {
		decompile();
		return innerClasses;
	}

	public List<JavaField> getFields() {
		decompile();
		return fields;
	}

	public List<JavaMethod> getMethods() {
		decompile();
		return methods;
	}

	public int getDecompiledLine() {
		return cls.getDecompiledLine();
	}

	@Override
	public boolean equals(Object o) {
		return this == o || o instanceof JavaClass && cls.equals(((JavaClass) o).cls);
	}

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

	@Override
	public String toString() {
		return cls.getFullName() + "[ " + getFullName() + " ]";
	}
}
