package com.danwatling.apkdecompiler.steps;

import com.danwatling.apkdecompiler.Logger;
import com.strobel.Procyon;
import com.strobel.decompiler.Decompiler;
import com.strobel.decompiler.DecompilerSettings;
import com.strobel.decompiler.PlainTextOutput;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;

/**
 * Runs Procyon.
 *
 * Unfortunately, Procyon does not do a good job decompiling .class files (problems: majority of resulting Java files are 0 bytes with no error output, excruciatingly slow processing time)
 *
 * Once Procyon is more mature I'll revisit this step.
 */
public class DecompileClasses extends BaseStep {
	private File workFolder = null;

	public DecompileClasses(File workFolder) {
		this.workFolder = workFolder;
	}

	public boolean run() {
		boolean result = true;

		File classesFolder = new File(this.workFolder + File.separator + "classes");
		File srcFolder = new File(this.workFolder + File.separator + "src");

		if (srcFolder.exists()) {
			Logger.info(srcFolder.getPath() + " already exists. Deleting.");
			if (!srcFolder.delete()) {
				Logger.info("\tFailed to delete " + srcFolder.getPath());
			}
		}

		List<File> classes = fetchAllClasses(classesFolder);
		decompileClasses(classes, srcFolder);

		return result;
	}

	private List<File> fetchAllClasses(File folder) {
		List<File> result = new ArrayList<>();
		File[] files = folder.listFiles();
		for (File f : files) {
			if (f.isDirectory()) {
				result.addAll(fetchAllClasses(f));
			} else {
				if (f.getName().endsWith(".class")) {
					result.add(f);
				}
			}
		}

		return result;
	}

	private void decompileClasses(List<File> classes, File srcFolder) {
		Logger.info("Decompiling classes");

		srcFolder.mkdirs();

		for (File classFile : classes) {
			decompile(classFile, srcFolder);
		}
	}
	private void decompile(File classFile, File srcFolder) {
		try {
			File outputFile = convertClassFileToOutputFile(classFile, srcFolder);
			outputFile.getParentFile().mkdirs();
			PlainTextOutput output = new PlainTextOutput(new OutputStreamWriter(new FileOutputStream(outputFile.getAbsolutePath())));
			DecompilerSettings settings = DecompilerSettings.javaDefaults();
			settings.setForceExplicitImports(true);
			settings.setOutputFileHeaderText("Generated with Procyon v" + Procyon.version());

			Decompiler.decompile(classFile.getAbsolutePath(), output, settings);
		} catch (IOException ex) {
			Logger.error("Unable to decompile " + classFile.getAbsolutePath(), ex);
		}
	}

	private File convertClassFileToOutputFile(File classFile, File srcFolder) {
		String[] tokens = classFile.getPath().split(Pattern.quote(File.separator));
		List<String> paths = new ArrayList<>();
		paths.addAll(Arrays.asList(tokens));

		paths.remove(0);		// workFolder
		paths.remove(0);		// classes
		paths.remove(paths.size()-1);		// Class file name

		paths.add(classFile.getName().substring(0, classFile.getName().lastIndexOf(".")) + ".java");		// Re-add class file name with .java extension

		File result = new File(srcFolder.getPath() + File.separator + String.join(File.separator, paths));

		return result;
	}
}