buildscript {
    repositories {
        jcenter()
		maven { url 'https://plugins.gradle.org/m2/' }
        maven { url 'https://dl.bintray.com/content/aalmiray/kordamp' }
    }
    dependencies {
		classpath 'org.kordamp.gradle:clirr-gradle-plugin:0.2.2'
		classpath "org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.3.1"
		classpath "biz.aQute.bnd:biz.aQute.bnd.gradle:6.0.0"
    }
}

plugins {
    id 'ru.vyarus.animalsniffer' version '1.5.0'
	id 'net.ltgt.errorprone' version '1.1.1'
	// Use e.g. "gradle <task> taskTree" to show its dependency tree.
	id 'com.dorongold.task-tree' version '1.5'
}

allprojects {
	apply plugin: 'java-library'
	apply plugin: 'java-test-fixtures'
	apply plugin: 'eclipse'
	apply plugin: 'idea'
	apply plugin: 'jacoco'
	apply plugin: 'net.ltgt.errorprone'

	version readVersionFile()

	ext {
		isSnapshot = version.endsWith('-SNAPSHOT')
		gitCommit = getGitCommit()
		javadocAllDir = new File(buildDir, 'javadoc')
		documentationDir = new File(projectDir, 'documentation')
		releasedocsDir = new File(buildDir, 'releasedocs')
		rootConfigDir = new File(rootDir, 'config')
		sonatypeCredentialsAvailable = project.hasProperty('sonatypeUsername') && project.hasProperty('sonatypePassword')
		isReleaseVersion = !isSnapshot
		isContinuousIntegrationEnvironment = Boolean.parseBoolean(System.getenv('CI'))
		signingRequired = !(isSnapshot || isContinuousIntegrationEnvironment)
		sonatypeSnapshotUrl = 'https://oss.sonatype.org/content/repositories/snapshots'
		sonatypeStagingUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2'
		// Returns only the date in yyyy-MM-dd format, as otherwise, with
		// hh:mm:ss information, the manifest files would change with every
		// build, causing unnecessary rebuilds.
		builtDate = (new java.text.SimpleDateFormat("yyyy-MM-dd")).format(new Date())
		oneLineDesc = 'An Open Source XMPP (Jabber) client library'
		integrationTestProjects = [
			':smack-integration-test',
			':smack-omemo-signal-integration-test',
		].collect{ project(it) }
		javadocAllProjects = subprojects - integrationTestProjects
		// A dirty hack used for Gradle's jacoco plugin, since is not
		// hable to handle the case when a (sub)project has no unit
		// tests. :-(
		projectsWithoutUnitTests = [
			':smack-android',
			':smack-android-extensions',
			':smack-bosh',
			':smack-debug',
			':smack-debug-slf4j',
			':smack-java8',
			':smack-jingle-old',
			':smack-resolver-dnsjava',
			':smack-resolver-javax',
			':smack-resolver-minidns',
			':smack-omemo-signal-integration-test',
		].collect{ project(it) }
		projectsWithUnitTests = subprojects - projectsWithoutUnitTests
		androidProjects = [
			':smack-tcp',
			':smack-bosh',
			':smack-core',
			':smack-im',
			':smack-resolver-minidns',
			':smack-sasl-provided',
			':smack-extensions',
			':smack-experimental',
			':smack-omemo',
			':smack-omemo-signal',
			':smack-openpgp',
			':smack-xmlparser',
			':smack-xmlparser-xpp3',
		].collect{ project(it) }
		androidBootClasspathProjects = [
			':smack-android',
			':smack-android-extensions',
		].collect{ project(it) }
		androidOptionalProjects = [
			':smack-tcp',
			':smack-extensions',
			':smack-experimental',
			':smack-bosh',
			':smack-omemo',
			':smack-omemo-signal',
		].collect{ project(it) }
		gplLicensedProjects = [
			':smack-omemo-signal',
			':smack-omemo-signal-integration-test',
			':smack-repl'
		].collect{ project(it) }
		// Lazily evaluate the Android bootClasspath and offline
		// Javadoc using a closure, so that targets which do not
		// require it are still able to succeed without an Android
		// SDK.
		androidBootClasspath = { getAndroidRuntimeJar() }
		androidJavadocOffline = { getAndroidJavadocOffline() }
		junit4Projects = [
			':smack-core',
			':smack-im',
			':smack-omemo',
			':smack-omemo-signal',
			':smack-openpgp',
		].collect { project(it) }

		// When using dynamic versions for those, do *not* use [1.0,
		// 2.0), since this will also pull in 2.0-alpha1. Instead use
		// [1.0, 1.0.99].
		// See also:
		// - https://issues.apache.org/jira/browse/MNG-6232
		// - https://issues.igniterealtime.org/browse/SMACK-858
		jxmppVersion = '[1.0.0, 1.0.999]'
		miniDnsVersion = '[1.0.0, 1.0.999]'
		smackMinAndroidSdk = 19
		junitVersion = '5.7.1'
		commonsIoVersion = '2.6'
		bouncyCastleVersion = '1.65'

		if (project.hasProperty("useSonatype")) {
			useSonatype = project.getProperty("useSonatype").toBoolean()
		} else {
			// Default to true
			useSonatype = true
		}
		javaCompatilibity = JavaVersion.VERSION_1_8
		javaMajor = javaCompatilibity.getMajorVersion()
	}
	group = 'org.igniterealtime.smack'
	sourceCompatibility = javaCompatilibity
	targetCompatibility = sourceCompatibility

	test {
		useJUnitPlatform()

		maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1

		// Enable full stacktraces of failed tests. Especially handy
		// for environments like Travis.
		testLogging {
			events "failed"
			exceptionFormat "full"
		}
	}

	ext.sharedManifest = manifest {
		attributes('Implementation-Version': version,
				   'Implementation-GitRevision': ext.gitCommit,
				   'Built-Date': ext.builtDate,
				   'Built-JDK': System.getProperty('java.version'),
				   'Built-Gradle': gradle.gradleVersion,
				   'Built-By': System.getProperty('user.name')
				  )
	}

	eclipse {
		classpath {
			downloadJavadoc = true
		}
	}

	repositories {
		mavenLocal()
		mavenCentral()
	}

	tasks.withType(JavaCompile) {
		// Some systems may not have set their platform default
		// converter to 'utf8', but we use unicode in our source
		// files. Therefore ensure that javac uses unicode
		options.encoding = 'UTF-8'
		options.compilerArgs = [
			'-Xlint:all',
			// Set '-options' because a non-java7 javac will emit a
			// warning if source/traget is set to 1.7 and
			// bootclasspath is *not* set.
			// TODO implement a sound heuristic to determine a java7
			// rt.jar on the build host. And if none is found,
			// fallback to using a environment variable,
			// e.g. JAVA7_HOME. See SMACK-651.
			'-Xlint:-options',
			'-Werror',
		]
		options.errorprone {
			error(
				"UnusedVariable",
				"UnusedMethod",
				"MethodCanBeStatic",
			)
			errorproneArgs = [
				// Disable errorprone checks
				'-Xep:TypeParameterUnusedInFormals:OFF',
				// Disable errorpone StringSplitter check, as it
				// recommends using Splitter from Guava, which we don't
				// have (nor want to use in Smack).
				'-Xep:StringSplitter:OFF',
				'-Xep:JdkObsolete:OFF',
				// Disabled because sinttest re-uses BeforeClass from junit.
				// TODO: change sinttest so that it has it's own
				// BeforeClass and re-enable this check.
				'-Xep:JUnit4ClassAnnotationNonStatic:OFF',
				// Disabled but should be re-enabled at some point
				//'-Xep:InconsistentCapitalization:OFF',
				'-Xep:MixedMutabilityReturnType:OFF',
			]
		}
	}

	tasks.withType(ScalaCompile) {
		scalaCompileOptions.additionalParameters = [
			'-Xfatal-warnings',
			'-feature',
		]
	}

	jacoco {
		toolVersion = "0.8.3"
	}

	jacocoTestReport {
		dependsOn test
		getSourceDirectories().setFrom(project.files(sourceSets.main.allSource.srcDirs))
		getClassDirectories().setFrom(project.files(sourceSets.main.output))
		reports {
			xml.enabled true
		}
	}

	if (JavaVersion.current().isJava8Compatible()) {
		tasks.withType(Javadoc) {
			// The '-quiet' as second argument is actually a hack,
			// since the one paramater addStringOption doesn't seem to
			// work, we extra add '-quiet', which is added anyway by
			// gradle.
			options.addStringOption('Xdoclint:all', '-quiet')

			// Treat warnings as errors.
			// See also https://bugs.openjdk.java.net/browse/JDK-8200363
			options.addStringOption('Xwerror', '-quiet')
		}
	}

	if (JavaVersion.current().isJava9Compatible()) {
		tasks.withType(Javadoc) {
			options.addStringOption('-release', javaMajor)

			// Fix for javadoc search. If not set, the search result would direct to
			// javadoc/undefined/org/jivesoftware/smack/altconnections/HttpLookupMethod.html
			// instead of
			// javadoc/org/jivesoftware/smack/altconnections/HttpLookupMethod.html
			// https://stackoverflow.com/a/53732633/194894
			options.addBooleanOption("-no-module-directories", true)
		}
		tasks.withType(JavaCompile) {
			options.compilerArgs.addAll([
				'--release', javaMajor,
			])
		}
	}

tasks.withType(Javadoc) {
		options.charSet = "UTF-8"
		options.encoding = 'UTF-8'
	}

	dependencies {
		testFixturesApi "org.junit.jupiter:junit-jupiter-api:$junitVersion"
		testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion"
		testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"

		// The smack-extensions subproject uses mockito in its fest
		// fixtures, and we want to have mockito also available in
		// test, so we use API here.
		testFixturesApi "org.mockito:mockito-core:3.3.3"
		testImplementation 'com.jamesmurty.utils:java-xmlbuilder:1.2'

		errorprone 'com.google.errorprone:error_prone_core:2.3.4'
		errorproneJavac('com.google.errorprone:javac:9+181-r4173-1')
	}

	// Make all project's 'test' target depend on javadoc, so that
	// javadoc is also linted.
	test { dependsOn javadoc }
}

configure (junit4Projects) {
	dependencies {
		testImplementation "org.junit.vintage:junit-vintage-engine:$junitVersion"
	}
}

// We need to evaluate the child projects first because
// - javadocAll needs the smack-core child to have already resolved
//   the jXMPP/MiniDNS dependencies, so that we can the resovled
//   version to link to those project's javadoc.
// - We use the child's project description as description for the
//   Maven POM.
evaluationDependsOnChildren()
task javadocAll(type: Javadoc) {
	source javadocAllProjects.collect {project ->
		project.sourceSets.main.allJava.findAll {
			// Filter out symbolic links to avoid
			// "warning: a package-info.java file has already been seen for package"
			// javadoc warnings.
			!java.nio.file.Files.isSymbolicLink(it.toPath())
		}
	}
	destinationDir = javadocAllDir
	// Might need a classpath
	classpath = files(subprojects.collect {project ->
		project.sourceSets.main.compileClasspath})
	classpath += files(androidBootClasspath)
	def staticJxmppVersion = getResolvedVersion('org.jxmpp:jxmpp-core')
	def staticMiniDnsVersion = getResolvedVersion('org.minidns:minidns-core')
	options {
		linkSource = true
		use = true
		links = [
		"https://docs.oracle.com/javase/${javaMajor}/docs/api/",
		"https://jxmpp.org/releases/${staticJxmppVersion}/javadoc/",
		"https://minidns.org/releases/${staticMiniDnsVersion}/javadoc/",
		] as String[]
		overview = "$projectDir/resources/javadoc-overview.html"
	}

	// Finally copy the javadoc doc-files from the subprojects, which
	// are potentially generated, to the javadocAll directory. Note
	// that we use a copy *method* and not a *task* because the inputs
	// of copy tasks is determined within the configuration phase. And
	// since some of the inputs are generated, they will not get
	// picked up if we used a copy method. See also
	// https://stackoverflow.com/a/40518516/194894
	doLast {
		copy {
			javadocAllProjects.each {
				from ("${it.projectDir}/src/javadoc") {
					include '**/doc-files/*.*'
				}
			}

			into javadocAllDir
		}
	}
}

import org.apache.tools.ant.filters.ReplaceTokens
task prepareReleasedocs(type: Copy) {
	from 'resources/releasedocs'
	into releasedocsDir
	filter(ReplaceTokens, tokens: [version: version, releasedate: builtDate, targetCompatibility: targetCompatibility.toString()])
}

task distributionZip(type: Zip, dependsOn: [javadocAll, prepareReleasedocs]) {
	classifier builtDate
	into ('javadoc') {
		from(javadocAllDir)
	}
	into ('releasedocs') {
		from(releasedocsDir)
	}
	into ('releasedocs/documentation') {
		from(documentationDir)
	}
}

task maybeCheckForSnapshotDependencies {
	// Don't check for Snapshot dependencies if this is a snapshot.
	onlyIf { isReleaseVersion }
	// Run in the execution phase, not in configuration phase, as the
	// 'each' forces the runtime configuration to be resovled, which
	// causes "Cannot change dependencies of configuration after it
	// has been included in dependency resolution." errors.
	// See https://discuss.gradle.org/t/23153
	doLast {
		allprojects { project ->
			project.configurations.runtime.each {
				if (it.toString().contains("-SNAPSHOT"))
					throw new Exception("Release build contains snapshot dependencies: " + it)
			}
		}
	}
}

test { dependsOn maybeCheckForSnapshotDependencies }

jar {
	// Root project should not create empty jar artifact
	enabled = false
}

// Disable upload archives for the root project
uploadArchives.enabled = false

description = """\
Smack ${version}
${oneLineDesc}."""

subprojects {
	apply plugin: 'maven-publish'
	apply plugin: 'signing'
	apply plugin: 'checkstyle'
	apply plugin: 'org.kordamp.gradle.clirr'
	apply plugin: 'biz.aQute.bnd.builder'

	checkstyle {
		toolVersion = '8.27'
	}
	task sourcesJar(type: Jar, dependsOn: classes) {
		classifier = 'sources'
		from sourceSets.main.allSource
	}
	task javadocJar(type: Jar, dependsOn: javadoc) {
		classifier = 'javadoc'
		from javadoc.destinationDir
	}
	task testsJar(type: Jar, dependsOn: testClasses) {
		classifier = 'tests'
		from sourceSets.test.output
	}

	artifacts {
		// See http://stackoverflow.com/a/21946676/194894
		testRuntime testsJar
	}

	publishing {
		publications {
			mavenJava(MavenPublication) {
				from components.java
				artifact sourcesJar
				artifact javadocJar
				artifact testsJar
				pom {
					name = 'Smack'
					packaging = 'jar'
					inceptionYear = '2003'
					url = 'http://www.igniterealtime.org/projects/smack/'
					description = project.description

					issueManagement {
						system = 'JIRA'
						url = 'https://igniterealtime.org/issues/browse/SMACK'
					}

					scm {
						url = 'https://github.com/igniterealtime/Smack'
						connection =  'scm:git:https://github.com/igniterealtime/Smack.git'
						developerConnection =  'scm:git:https://github.com/igniterealtime/Smack.git'
					}

					developers {
						developer {
							id = 'flow'
							name = 'Florian Schmaus'
							email = 'flow@igniterealtime.org'
						}
					}
				}
			}
		}
		repositories {
			if (sonatypeCredentialsAvailable && useSonatype) {
				maven {
					url isSnapshot ? sonatypeSnapshotUrl : sonatypeStagingUrl
					credentials {
						username = sonatypeUsername
						password = sonatypePassword
					}
				}
			}
			// Use
			// gradle publish -P customRepoUrl=https://www.igniterealtime.org/archiva/repository/maven -P customRepoUsername=bamboo -P customRepoPassword=hidden -P useSonatype=false
			// to deploy to this repo.
			if (project.hasProperty("customRepoUrl")) {
				maven {
					name 'customRepo'
					url customRepoUrl
					if (project.hasProperty("customRepoUsername")) {
						credentials {
							username customRepoUsername
							password customRepoPassword
						}
					}
				}
			}
		}
	}
	rootProject.distributionZip {
		dependsOn build
		from(buildDir) {
			include "$libsDirName/*${version}.jar"
			include "$libsDirName/*${version}-javadoc.jar"
			include "$libsDirName/*${version}-sources.jar"
		}
	}

	// Workaround for gpg signatory not supporting the 'required' option
	// See https://github.com/gradle/gradle/issues/5064#issuecomment-381924984
	// Note what we use 'signing.gnupg.keyName' instead of 'signing.keyId'.
	tasks.withType(Sign) {
		onlyIf {
			project.hasProperty('signing.gnupg.keyName')
		}
	}
	signing {
		useGpgCmd()
		required { signingRequired }
		sign publishing.publications.mavenJava
	}

	clirr {
		// 2018-08-14: Disabled Clirr because
		// - It reports an breaking change in android.jar (seems right, but there is nothing we can do about it)
		// - Only the first smack-* projects are correctly checked,
		//   the other ones have the output of a clirr report from a previous project
		//   (Look at the clirr reports).
		enabled false
		semver false
	}

	// Work around https://github.com/gradle/gradle/issues/4046
	task copyJavadocDocFiles(type: Copy) {
		from('src/javadoc')
		into 'build/docs/javadoc'
		include '**/doc-files/*.*'
	}
	javadoc.dependsOn copyJavadocDocFiles

	// Make sure root projects 'javadocAll' depends on the
	// subproject's javadoc, to ensure that all all doc-files/ are
	// generated and up-to-date. Obviously this means that the
	// javadocAll task will also create the individual javadoc's of the
	// subprojects.
	javadocAll.dependsOn javadoc
}

// The smack-java8-full project generates the dot and png files of the
// current state graph. Ensure they are generated before copied.
configure (project(':smack-java8-full')) {
	copyJavadocDocFiles.dependsOn convertModularXmppClientToServerConnectionStateGraphDotToPng
}

configure (androidProjects + androidBootClasspathProjects) {
	apply plugin: 'ru.vyarus.animalsniffer'
	dependencies {
		signature "net.sf.androidscents.signature:android-api-level-${smackMinAndroidSdk}:4.4.2_r4@signature"
	}
	animalsniffer {
		sourceSets = [sourceSets.main]
	}
}

// There is no need to ever clirr integration test projects and the
// smack-repl project.
configure(integrationTestProjects + project(':smack-repl')) {
	clirr {
		enabled false
	}
}

// Disable clirr on omemo modules
project(':smack-omemo').clirr.enabled = false
project(':smack-omemo-signal').clirr.enabled = false

subprojects*.jar {
	manifest {
		from sharedManifest
	}
	bundle {
		bnd(
				'-removeheaders': 'Tool, Bnd-*',
				'-exportcontents': '*',
		)
	}
}

configure(subprojects - gplLicensedProjects) {
	checkstyle {
		configProperties.checkstyleLicenseHeader = "header"
	}
	publishing {
		publications {
			mavenJava(MavenPublication) {
				pom {
					licenses {
						license {
							name = 'The Apache Software License, Version 2.0'
							url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
							distribution = 'repo'
						}
					}
				}
			}
		}
	}
}

configure(gplLicensedProjects) {
	checkstyle {
		configProperties.checkstyleLicenseHeader = "${project.name}-gplv3-license-header"
	}
	publishing {
		publications {
			mavenJava(MavenPublication) {
				pom {
					licenses {
						license {
							name = 'GNU General Public License, version 3 or any later version'
							url = 'https://www.gnu.org/licenses/gpl.txt'
							distribution = 'repo'
						}
					}
				}
			}
		}
	}
}

configure(androidBootClasspathProjects) {
	compileJava {
		options.bootstrapClasspath = files(androidBootClasspath)
	}
	javadoc {
		classpath += files(androidBootClasspath)
	}
}

apply plugin: "com.github.kt3k.coveralls"
coveralls {
	sourceDirs = files(subprojects.sourceSets.main.allSource.srcDirs).files.absolutePath
}

task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) {
	dependsOn = projectsWithUnitTests.jacocoTestReport
	getSourceDirectories().setFrom(files(projectsWithUnitTests.sourceSets.main.allSource.srcDirs))
	getClassDirectories().setFrom(files(projectsWithUnitTests.sourceSets.main.output))
	getExecutionData().setFrom(files(projectsWithUnitTests.jacocoTestReport.executionData))
	reports {
		xml.enabled true
		xml.destination file("${buildDir}/reports/jacoco/test/jacocoTestReport.xml")
	}
	// We could remove the following setOnlyIf line, but then
	// jacocoRootReport would silently be SKIPPED if something with
	// the projectsWithUnitTests is wrong (e.g. a project is missing
	// in there).
	setOnlyIf { true }
}

// Important to specify this task after the subprojects block
task clirrRootReport(type: org.kordamp.gradle.clirr.ClirrReportTask) {
	dependsOn = subprojects.tasks.clirr
	reports = files((subprojects.findAll { it.clirr.enabled == true }).tasks.clirr.xmlReport)
}

task integrationTest {
	description 'Verify correct functionality of Smack by running some integration tests.'
	dependsOn project(':smack-integration-test').tasks.run
}

task omemoSignalIntTest {
	description 'Run integration tests of the smack-omemo module in combination with smack-omemo-signal.'
	dependsOn project(':smack-omemo-signal-integration-test').tasks.run
}

task sinttestAll {
	description 'Run all of Smack\'s integration tests.'
	dependsOn {[
		integrationTest,
		omemoSignalIntTest,
	]}
}

def getGitCommit() {
	def dotGit = new File("$projectDir/.git")
	if (!dotGit.isDirectory()) return 'non-git build'

	def projectDir = dotGit.getParentFile()
	def cmd = 'git describe --always --tags --dirty=+'
	def proc = cmd.execute(null, projectDir)
	proc.waitForOrKill(10 * 1000)

	def gitCommit = proc.text.trim()
	assert !gitCommit.isEmpty()

    def srCmd = 'git symbolic-ref --short HEAD'
    def srProc = srCmd.execute(null, projectDir)
    srProc.waitForOrKill(10 * 1000)
    if (srProc.exitValue() == 0) {
        // Only add the information if the git command was
        // successful. There may be no symbolic reference for HEAD if
        // e.g. in detached mode.
        def symbolicReference = srProc.text.trim()
        assert !symbolicReference.isEmpty()
        gitCommit += "-$symbolicReference"
    }

	gitCommit
}

def getAndroidRuntimeJar() {
	def androidApiLevel = ext.smackMinAndroidSdk
	def androidHome = getAndroidHome()
	def androidJar = new File("$androidHome/platforms/android-${androidApiLevel}/android.jar")
	if (androidJar.isFile()) {
		return androidJar
	} else {
		throw new Exception("Can't find android.jar for API level ${androidApiLevel}. Please install corresponding SDK platform package")
	}
}

def getAndroidJavadocOffline() {
	def androidHome = getAndroidHome()
	return androidHome.toString() + "/docs/reference"
}

def getAndroidHome() {
	def androidHomeEnv = System.getenv("ANDROID_HOME")
	if (androidHomeEnv == null) {
		throw new Exception("ANDROID_HOME environment variable is not set")
	}
	def androidHome = new File(androidHomeEnv)
	if (!androidHome.isDirectory()) throw new Exception("Environment variable ANDROID_HOME is not pointing to a directory")
	return androidHome
}

def readVersionFile() {
	def versionFile = new File(rootDir, 'version')
	if (!versionFile.isFile()) {
		throw new Exception("Could not find version file")
	}
	if (versionFile.text.isEmpty()) {
		throw new Exception("Version file does not contain a version")
	}
	versionFile.text.trim()
}

def getResolvedVersion(queriedProject = 'smack-core', component) {
	def configuration = project(queriedProject)
		.configurations
		.compileClasspath

	def artifact = configuration
		.resolvedConfiguration
		.resolvedArtifacts
		.findAll {
			// 'it' is of type ResolvedArtifact, 'id' of
			// Component*Artifcat*Identifier, and we check the
			// ComponentIdentifier.
			it.id.getComponentIdentifier() instanceof org.gradle.api.artifacts.component.ModuleComponentIdentifier
		}
	    .find {
			it.id.getComponentIdentifier().toString().startsWith(component + ':')
		}

	artifact.getModuleVersion().getId().getVersion()
}
