package jadx.core.dex.nodes;

import jadx.api.IJadxArgs;
import jadx.api.ResourceFile;
import jadx.api.ResourceType;
import jadx.api.ResourcesLoader;
import jadx.core.clsp.ClspGraph;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.ResTableParser;
import jadx.core.xmlgen.ResourceStorage;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RootNode {
	private static final Logger LOG = LoggerFactory.getLogger(RootNode.class);

	private final ErrorsCounter errorsCounter = new ErrorsCounter();
	private final IJadxArgs args;

	private List<DexNode> dexNodes;
	private Map<Integer, String> resourcesNames = new HashMap<Integer, String>();
	@Nullable
	private String appPackage;
	private ClassNode appResClass;

	public RootNode(IJadxArgs args) {
		this.args = args;
	}

	public void load(List<InputFile> dexFiles) throws DecodeException {
		dexNodes = new ArrayList<DexNode>(dexFiles.size());
		for (InputFile dex : dexFiles) {
			DexNode dexNode;
			try {
				dexNode = new DexNode(this, dex);
			} catch (Exception e) {
				throw new DecodeException("Error decode file: " + dex, e);
			}
			dexNodes.add(dexNode);
		}
		for (DexNode dexNode : dexNodes) {
			dexNode.loadClasses();
		}
		initInnerClasses();
	}

	public void loadResources(List<ResourceFile> resources) {
		ResourceFile arsc = null;
		for (ResourceFile rf : resources) {
			if (rf.getType() == ResourceType.ARSC) {
				arsc = rf;
				break;
			}
		}
		if (arsc == null) {
			LOG.debug("'.arsc' file not found");
			return;
		}
		final ResTableParser parser = new ResTableParser();
		try {
			ResourcesLoader.decodeStream(arsc, new ResourcesLoader.ResourceDecoder() {
				@Override
				public Object decode(long size, InputStream is) throws IOException {
					parser.decode(is);
					return null;
				}
			});
		} catch (JadxException e) {
			LOG.error("Failed to parse '.arsc' file", e);
			return;
		}

		ResourceStorage resStorage = parser.getResStorage();
		resourcesNames = resStorage.getResourcesNames();
		appPackage = resStorage.getAppPackage();
	}

	public void initAppResClass() {
		ClassNode resCls;
		if (appPackage == null) {
			appResClass = makeClass("R");
			return;
		}
		String fullName = appPackage + ".R";
		resCls = searchClassByName(fullName);
		if (resCls != null) {
			appResClass = resCls;
		} else {
			appResClass = makeClass(fullName);
		}
	}

	private ClassNode makeClass(String clsName) {
		DexNode firstDex = dexNodes.get(0);
		ClassInfo r = ClassInfo.fromName(firstDex, clsName);
		return new ClassNode(firstDex, r);
	}

	public void initClassPath() throws DecodeException {
		try {
			if (!ArgType.isClspSet()) {
				ClspGraph clsp = new ClspGraph();
				clsp.load();

				List<ClassNode> classes = new ArrayList<ClassNode>();
				for (DexNode dexNode : dexNodes) {
					classes.addAll(dexNode.getClasses());
				}
				clsp.addApp(classes);

				ArgType.setClsp(clsp);
			}
		} catch (IOException e) {
			throw new DecodeException("Error loading classpath", e);
		}
	}

	private void initInnerClasses() {
		for (DexNode dexNode : dexNodes) {
			dexNode.initInnerClasses();
		}
	}

	public List<ClassNode> getClasses(boolean includeInner) {
		List<ClassNode> classes = new ArrayList<ClassNode>();
		for (DexNode dexNode : dexNodes) {
			for (ClassNode cls : dexNode.getClasses()) {
				if (includeInner) {
					classes.add(cls);
				} else {
					if (!cls.getClassInfo().isInner()) {
						classes.add(cls);
					}
				}
			}
		}
		return classes;
	}

	public ClassNode searchClassByName(String fullName) {
		for (DexNode dexNode : dexNodes) {
			ClassInfo clsInfo = ClassInfo.fromName(dexNode, fullName);
			ClassNode cls = dexNode.resolveClass(clsInfo);
			if (cls != null) {
				return cls;
			}
		}
		return null;
	}

	public List<DexNode> getDexNodes() {
		return dexNodes;
	}

	public ErrorsCounter getErrorsCounter() {
		return errorsCounter;
	}

	public Map<Integer, String> getResourcesNames() {
		return resourcesNames;
	}

	@Nullable
	public String getAppPackage() {
		return appPackage;
	}

	public ClassNode getAppResClass() {
		return appResClass;
	}

	public IJadxArgs getArgs() {
		return args;
	}
}
