package jadx.core.deobf;

import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

	private static final String MAP_FILE_CHARSET = "UTF-8";

	private final Deobfuscator deobfuscator;
	private final File deobfMapFile;

	private final Map<String, String> clsPresetMap = new HashMap<String, String>();
	private final Map<String, String> fldPresetMap = new HashMap<String, String>();
	private final Map<String, String> mthPresetMap = new HashMap<String, String>();

	public DeobfPresets(Deobfuscator deobfuscator, File deobfMapFile) {
		this.deobfuscator = deobfuscator;
		this.deobfMapFile = deobfMapFile;
	}

	/**
	 * Loads deobfuscator presets
	 */
	public void load() {
		if (!deobfMapFile.exists()) {
			return;
		}
		LOG.info("Loading obfuscation map from: {}", deobfMapFile.getAbsoluteFile());
		try {
			List<String> lines = FileUtils.readLines(deobfMapFile, MAP_FILE_CHARSET);
			for (String l : lines) {
				l = l.trim();
				if (l.isEmpty() || l.startsWith("#")) {
					continue;
				}
				String[] va = splitAndTrim(l);
				if (va.length != 2) {
					continue;
				}
				String origName = va[0];
				String alias = va[1];
				if (l.startsWith("p ")) {
					deobfuscator.addPackagePreset(origName, alias);
				} else if (l.startsWith("c ")) {
					clsPresetMap.put(origName, alias);
				} else if (l.startsWith("f ")) {
					fldPresetMap.put(origName, alias);
				} else if (l.startsWith("m ")) {
					mthPresetMap.put(origName, alias);
				}
			}
		} catch (IOException e) {
			LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.getAbsolutePath(), e);
		}
	}

	private static String[] splitAndTrim(String str) {
		String[] v = str.substring(2).split("=");
		for (int i = 0; i < v.length; i++) {
			v[i] = v[i].trim();
		}
		return v;
	}

	public void save(boolean forceSave) {
		try {
			if (deobfMapFile.exists()) {
				if (forceSave) {
					dumpMapping();
				} else {
					LOG.warn("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
							deobfMapFile.getAbsolutePath());
				}
			} else {
				dumpMapping();
			}
		} catch (IOException e) {
			LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.getAbsolutePath(), e);
		}
	}

	/**
	 * Saves DefaultDeobfuscator presets
	 */
	private void dumpMapping() throws IOException {
		List<String> list = new ArrayList<String>();
		// packages
		for (PackageNode p : deobfuscator.getRootPackage().getInnerPackages()) {
			for (PackageNode pp : p.getInnerPackages()) {
				dfsPackageName(list, p.getName(), pp);
			}
			if (p.hasAlias()) {
				list.add(String.format("p %s = %s", p.getName(), p.getAlias()));
			}
		}
		// classes
		for (DeobfClsInfo deobfClsInfo : deobfuscator.getClsMap().values()) {
			if (deobfClsInfo.getAlias() != null) {
				list.add(String.format("c %s = %s",
						deobfClsInfo.getCls().getClassInfo().getFullName(), deobfClsInfo.getAlias()));
			}
		}
		for (FieldInfo fld : deobfuscator.getFldMap().keySet()) {
			list.add(String.format("f %s = %s", fld.getFullId(), fld.getAlias()));
		}
		for (MethodInfo mth : deobfuscator.getMthMap().keySet()) {
			list.add(String.format("m %s = %s", mth.getFullId(), mth.getAlias()));
		}
		Collections.sort(list);
		FileUtils.writeLines(deobfMapFile, MAP_FILE_CHARSET, list);
		list.clear();
	}

	private static void dfsPackageName(List<String> list, String prefix, PackageNode node) {
		for (PackageNode pp : node.getInnerPackages()) {
			dfsPackageName(list, prefix + '.' + node.getName(), pp);
		}
		if (node.hasAlias()) {
			list.add(String.format("p %s.%s = %s", prefix, node.getName(), node.getAlias()));
		}
	}

	public String getForCls(ClassInfo cls) {
		return clsPresetMap.get(cls.getFullName());
	}

	public String getForFld(FieldInfo fld) {
		return fldPresetMap.get(fld.getFullId());
	}

	public String getForMth(MethodInfo mth) {
		return mthPresetMap.get(mth.getFullId());
	}

	public void clear() {
		clsPresetMap.clear();
		fldPresetMap.clear();
		mthPresetMap.clear();
	}

	public Map<String, String> getClsPresetMap() {
		return clsPresetMap;
	}

	public Map<String, String> getFldPresetMap() {
		return fldPresetMap;
	}

	public Map<String, String> getMthPresetMap() {
		return mthPresetMap;
	}
}
