package jadx.gui.treemodel;

import jadx.api.JavaPackage;
import jadx.gui.JadxWrapper;
import jadx.gui.utils.Utils;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class JSources extends JNode {
	private static final long serialVersionUID = 8962924556824862801L;

	private static final ImageIcon ROOT_ICON = Utils.openIcon("packagefolder_obj");

	private final JadxWrapper wrapper;
	private final boolean flatPackages;

	public JSources(JRoot jRoot, JadxWrapper wrapper) {
		this.flatPackages = jRoot.isFlatPackages();
		this.wrapper = wrapper;
		update();
	}

	public final void update() {
		removeAllChildren();
		if (flatPackages) {
			for (JavaPackage pkg : wrapper.getPackages()) {
				add(new JPackage(pkg));
			}
		} else {
			// build packages hierarchy
			List<JPackage> rootPkgs = getHierarchyPackages(wrapper.getPackages());
			for (JPackage jPackage : rootPkgs) {
				jPackage.update();
				add(jPackage);
			}
		}
	}

	/**
	 * Convert packages list to hierarchical packages representation
	 *
	 * @param packages input packages list
	 * @return root packages
	 */
	List<JPackage> getHierarchyPackages(List<JavaPackage> packages) {
		Map<String, JPackage> pkgMap = new HashMap<String, JPackage>();
		for (JavaPackage pkg : packages) {
			addPackage(pkgMap, new JPackage(pkg));
		}
		// merge packages without classes
		boolean repeat;
		do {
			repeat = false;
			for (JPackage pkg : pkgMap.values()) {
				if (pkg.getInnerPackages().size() == 1 && pkg.getClasses().isEmpty()) {
					JPackage innerPkg = pkg.getInnerPackages().get(0);
					pkg.getInnerPackages().clear();
					pkg.getInnerPackages().addAll(innerPkg.getInnerPackages());
					pkg.getClasses().addAll(innerPkg.getClasses());
					pkg.setName(pkg.getName() + "." + innerPkg.getName());

					innerPkg.getInnerPackages().clear();
					innerPkg.getClasses().clear();

					repeat = true;
					break;
				}
			}
		} while (repeat);

		// remove empty packages
		for (Iterator<Map.Entry<String, JPackage>> it = pkgMap.entrySet().iterator(); it.hasNext(); ) {
			JPackage pkg = it.next().getValue();
			if (pkg.getInnerPackages().isEmpty() && pkg.getClasses().isEmpty()) {
				it.remove();
			}
		}
		// use identity set for collect inner packages
		Set<JPackage> innerPackages = Collections.newSetFromMap(new IdentityHashMap<JPackage, Boolean>());
		for (JPackage pkg : pkgMap.values()) {
			innerPackages.addAll(pkg.getInnerPackages());
		}
		// find root packages
		List<JPackage> rootPkgs = new ArrayList<JPackage>();
		for (JPackage pkg : pkgMap.values()) {
			if (!innerPackages.contains(pkg)) {
				rootPkgs.add(pkg);
			}
		}
		Collections.sort(rootPkgs);
		return rootPkgs;
	}

	private void addPackage(Map<String, JPackage> pkgs, JPackage pkg) {
		String pkgName = pkg.getName();
		JPackage replaced = pkgs.put(pkgName, pkg);
		if (replaced != null) {
			pkg.getInnerPackages().addAll(replaced.getInnerPackages());
			pkg.getClasses().addAll(replaced.getClasses());
		}
		int dot = pkgName.lastIndexOf('.');
		if (dot > 0) {
			String prevPart = pkgName.substring(0, dot);
			String shortName = pkgName.substring(dot + 1);
			pkg.setName(shortName);
			JPackage prevPkg = pkgs.get(prevPart);
			if (prevPkg == null) {
				prevPkg = new JPackage(prevPart);
				addPackage(pkgs, prevPkg);
			}
			prevPkg.getInnerPackages().add(pkg);
		}
	}

	@Override
	public Icon getIcon() {
		return ROOT_ICON;
	}

	@Override
	public JClass getJParent() {
		return null;
	}

	@Override
	public String makeString() {
		return "Source code";
	}
}
