import org.gradle.api.Project
import org.gradle.api.Plugin
import org.gradle.api.tasks.Copy

// configuration for the riskscape plugin projects
class RiskscapePluginSettings {

  // the project this settings belongs to - set by the plugin itself - don't mess!
  Project project

  // name of class that extends nz.org.riskscape.engine.Plugin - required
  String implementation

  // unique id for the plugin - defaults to directory name
  private String id

  // version number - defaults to project's riskscapeVersion
  private String version

  // a list of patterns to exclude things from the assembled plugin.  Can be libraries
  // or resources or whatever else would otherwise end up in build/install/{id}
  List<String> excludeFromDist = []

  // list of ids of other plugins that this plugin depends on - added to manifest metadata, doesn't affect
  // compile classpath
  List<String> depends = []

  // list of extra things to copy in to installdir - these are passed in to the distribution config
  // as 'from' directives
  List<String> pluginResources = []

  // set to true if you want this plugin to appear in the plugins install directory, rather than plugins-optional
  boolean enabledByDefault = false

  // directory where this plugin's jars are assembled
  File getInstallDir() {
    return project.file('build/install/' + project.projectDir.name)
  }

  // returns the plugin version, defaulting to the project's riskscape version
  String getVersion() {
    return version == null ? project.riskscapeVersion : version
  }

  // assign a non-standard id for this plugin's version
  String setVersion(String newVersion) {
    this.version = newVersion
  }

  // returns the plugin id for this project
  String getId() {
    return id == null ? project.projectDir.name : id
  }

  // assign a non-standard id for this plugin project
  String setId(String newId) {
    this.id = newId;
  }
}

// apply this to plugin projects via `apply :plugin RiskscapePluginProjectPlugin`
class RiskscapePluginProjectPlugin implements Plugin<Project> {

  void apply(Project project) {

    // add a plugin configuration for dependencies to be bundled with the plugin, e.g.
    // dependencies {
    //   plugin group: 'org.geotools.jdbc', name: 'gt-jdbc-postgis', version: project.property('geotoolsVersion')
    // }
    project.configurations {
      plugin
    }

    // we always depend on these
    project.apply plugin: 'java-library'
    project.apply plugin: 'distribution'

    // add an object in to the project dsl
    def extension = project.extensions.create('riskscapePlugin', RiskscapePluginSettings)
    // make sure the extension knows which project it came from
    extension.project = project

    // monkey with the project to add our standard bits once the user's project has been evaluated
    project.afterEvaluate {

      project.dependencies {
        // standard plugin dependencies
        implementation project.project(':engine:core')
        testImplementation project.project(':engine:core').sourceSets.test.output
        testImplementation project.project(':test:shared')

        // all plugin dependencies are also compile dependencies
        api project.configurations.plugin.dependencies
      }

      // write a manifest
      project.jar {
        // always name the jar 'plugin'
        archiveBaseName = 'plugin'
        manifest {
          attributes (
            'Riskscape-Plugin': extension.implementation,
            'Riskscape-Plugin-ID': extension.id,
            'Riskscape-Plugin-Version': extension.version,
            'Riskscape-Plugin-Dependency-IDs': extension.depends.join(',')
          )
        }
      }

      // get the cli project's assemble task to depend on this project's distPlugin tasks, otherwise
      // it's going to potentially run before it has run
      // XXX maybe we could make this a shouldRunBefore constraint?
      project.tasks.getByPath(':engine:app:assemblePlugins').dependsOn(project.tasks.named('installDist'))


      // a set of all the names of artifacts that are included with the engine core and api.
      // these end up in appRoot/lib, and do not need to be included by each plugin as well
      def engineDepNames = [':engine:core', ':engine:api'].stream()
        .map { project.rootProject.project(it) }
        .flatMap { it.configurations.compileClasspath.resolvedConfiguration.resolvedArtifacts.stream() }
        .map { it.name }
        .toSet()

      // this is the list of all the resolved artifacts that were included as part of
      // a `plugin` dependency.  We will add all of these to the distribution, excluding
      // those that are already included in the engine (via engineDepNames above)
      def pluginDependencies = project.configurations.plugin.resolvedConfiguration.resolvedArtifacts

      project.distributions {
        main {
          contents {
            // include the plugin.jar
            from project.tasks.getByPath('jar').outputs

            // include all dependencies from the plugin configuration that are not already
            // included by the engine.  If we were to just use
            // `from project.configurations.plugin` then it would include stuff that's
            // already there from engine:core and engine:api
            for (pluginDep in pluginDependencies) {
              if (!engineDepNames.contains(pluginDep.name)) {
                from pluginDep.file
              }
            }

            // plugin custom exclusions
            for (pattern in extension.excludeFromDist) {
              exclude pattern
            }

            // copy in any rando files that the plugin author specified
            for (resource in extension.pluginResources) {
              from resource
            }
          }
        }
      }
    }
  }
}
