package io.crnk.gen.gradle;

import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;

import io.crnk.gen.asciidoc.internal.AsciidocGeneratorModule;
import io.crnk.gen.base.GeneratorConfig;
import io.crnk.gen.base.GeneratorModule;
import io.crnk.gen.gradle.task.ForkedGenerateTask;
import io.crnk.gen.gradle.task.GeneratorTaskContract;
import io.crnk.gen.gradle.task.InMemoryGeneratorTask;
import org.gradle.api.DefaultTask;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.tasks.TaskContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class GeneratorPlugin implements Plugin<Project> {


	public static boolean APPLY_DOCLET_BY_DEFAULT = true;


	private static final Logger LOGGER = LoggerFactory.getLogger(GeneratorPlugin.class);

	private List<GeneratorModule> modules = new ArrayList<>();

	@Override
	public void apply(final Project project) {
		if(APPLY_DOCLET_BY_DEFAULT) {
			project.getPlugins().apply(DocletPlugin.class);
		}

		final Runnable initRunner = new Runnable() {

			private boolean initialized = false;

			@Override
			public void run() {
				if (!initialized) {
					initialized = true;
					init(project);

					for (GeneratorModule module : modules) {
						String taskName = getGenerateTaskName(module);
						Task task = project.getTasks().getByName(taskName);
						task.setEnabled(module.getConfig().isEnabled());
					}
				}
			}
		};
		final GeneratorExtension config = new GeneratorExtension(project, initRunner);

		ServiceLoader<GeneratorModule> loader = ServiceLoader.load(GeneratorModule.class);
		for (GeneratorModule module : loader) {
			LOGGER.debug("discovered generator module: {}", module.getName());
			modules.add(module);
			module.initDefaults(project.getBuildDir());
			config.getModuleConfig().put(module.getName(), module.getConfig());
		}
		LOGGER.debug("discovered {} modules", modules.size());

		project.getExtensions().add("crnkGen", config);
		project.afterEvaluate(project1 -> initRunner.run());
	}

	protected void init(Project project) {
		for (GeneratorModule module : modules) {
			setupCleanTask(project, module);
			Task generateTask = setupGenerateTask(project, module);

			if (module instanceof AsciidocGeneratorModule) {
				generateTask.dependsOn(DocletPlugin.TASK_NAME);
			}
		}
	}

	private Task setupCleanTask(Project project, GeneratorModule module) {
		Task task = project.getTasks().create(getCleanTaskName(module), DefaultTask.class);
		task.doFirst(task1 -> project.delete(module.getConfig().getGenDir()));
		return task;
	}

	private Task setupGenerateTask(Project project, GeneratorModule module) {
		GeneratorConfig config = project.getExtensions().getByType(GeneratorConfig.class);
		Class taskClass = config.isForked() ? ForkedGenerateTask.class : InMemoryGeneratorTask.class;
		Task task = project.getTasks().create(getGenerateTaskName(module), taskClass);

		((GeneratorTaskContract) task).setModule(module);

		setupRuntimeDependencies(project, task);

		// setup up-to-date checking
		String runtimeConfiguration = config.getRuntime().getConfiguration();
		Configuration compileConfiguration = project.getConfigurations().findByName(runtimeConfiguration);
		if (compileConfiguration != null) {
			task.getInputs().files(compileConfiguration.getFiles());
			task.getOutputs().dir(module.getGenDir());
		}

		return task;
	}

	private String getGenerateTaskName(GeneratorModule module) {
		return InMemoryGeneratorTask.NAME +
				Character.toUpperCase(module.getName().charAt(0)) + module.getName().substring(1);
	}

	private String getCleanTaskName(GeneratorModule module) {
		return "clean" +
				Character.toUpperCase(module.getName().charAt(0)) + module.getName().substring(1);
	}


	private void setupRuntimeDependencies(Project project, Task generateTask) {
		GeneratorConfig config = project.getExtensions().getByType(GeneratorConfig.class);
		String runtimeConfiguration = config.getRuntime().getConfiguration();
		if (runtimeConfiguration != null) {
			String runtimeConfigurationFirstUpper =
					Character.toUpperCase(runtimeConfiguration.charAt(0)) + runtimeConfiguration.substring(1);

			// make sure applications is compiled in order to startup and extract meta information
			String processResourcesName = "process" + runtimeConfigurationFirstUpper + "Resources";
			String compileJavaName = "compile" + runtimeConfigurationFirstUpper + "Java";
			TaskContainer tasks = project.getTasks();
			Task processResourceTask = tasks.findByName(processResourcesName);
			Task compileJavaTask = tasks.findByName(compileJavaName);
			if (processResourceTask != null) {
				generateTask.dependsOn(processResourceTask);
			}
			if (compileJavaTask != null) {
				generateTask.dependsOn(compileJavaTask, compileJavaTask);
			}
		}
	}
}