/*
 * Copyright contributors to Hyperledger Besu.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

apply plugin: 'application'
apply plugin: 'java-library'
apply plugin: 'jacoco'

jar {
  archiveBaseName = 'besu-test-fuzz'
  manifest {
    attributes(
      'Specification-Title': archiveBaseName,
      'Specification-Version': project.version,
      'Implementation-Title': archiveBaseName,
      'Implementation-Version': calculateVersion(),
      'Commit-Hash': getGitCommitDetails(40).hash
      )
  }
}

dependencies {
  implementation project(':besu')
  implementation project(':crypto:algorithms')
  implementation project(':datatypes')
  implementation project(':ethereum:referencetests')
  implementation project(':evm')
  implementation project(':util')

  implementation 'com.fasterxml.jackson.core:jackson-databind'
  implementation 'com.google.guava:guava'
  implementation 'info.picocli:picocli'
  implementation 'io.tmio:tuweni-bytes'
  implementation 'org.jacoco:org.jacoco.agent'
  implementation 'org.jacoco:org.jacoco.core'
}

application {
  applicationName = 'BesuFuzz'
  mainClass = 'org.hyperledger.besu.testfuzz.BesuFuzz'
  applicationDefaultJvmArgs = [
    '-javaagent:$APP_HOME/lib/jacocoagent.jar'
  ]
}

def corpusDir = "${buildDir}/generated/corpus"

tasks.register("runFuzzer", JavaExec) {
  doNotTrackState("Produces no artifacts")
  classpath = sourceSets.main.runtimeClasspath
  mainClass = 'org.hyperledger.besu.testfuzz.BesuFuzz'

  args = [
    "eof-container",
    "--tests-dir=${projectDir}/../ethereum/referencetests/src/reference-test/external-resources/EOFTests",
    "--corpus-dir=${corpusDir}"
  ]
  doFirst {
    mkdir corpusDir
  }
}

// Adds guidance to the fuzzer but with a 90% performance drop.
tasks.register("fuzzGuided") {
  doLast {
    runFuzzer.args += "--guidance-regexp=org/(hyperledger/besu|apache/tuweni)"
    runFuzzer.args += "--new-corpus-dir=${corpusDir}/.."
  }
  finalizedBy("runFuzzer")
}

// This fuzzes besu as an external client.  Besu fuzzing as a local client is enabled by default.
tasks.register("fuzzBesu") {
  dependsOn(":installDist")
  doLast {
    runFuzzer.args += "--client=besu=../build/install/besu/bin/evmtool code-validate"
  }
  finalizedBy("runFuzzer")
}

tasks.register("fuzzEvmone") {
  doLast {
    runFuzzer.args += "--client=evm1=evmone-eofparse"
  }
  finalizedBy("runFuzzer")
}

tasks.register("fuzzEthereumJS") {
  doLast {
    runFuzzer.args += "--client=etjs=tsx ../../../ethereumjs/ethereumjs-monorepo/packages/evm/scripts/eofContainerValidator.ts"
  }
  finalizedBy("runFuzzer")
}

tasks.register("fuzzGeth") {
  doLast {
    runFuzzer.args += "--client=geth=eofdump eofparser"
  }
  finalizedBy("runFuzzer")
}

tasks.register("fuzzNethermind") {
  doLast {
    runFuzzer.args += "--client=neth=netheofparse -x"
  }
  finalizedBy("runFuzzer")
}

tasks.register("fuzzReth") {
  doLast {
    runFuzzer.args += "--client=revm=revme bytecode --eof-runtime"
  }
  finalizedBy("runFuzzer")
}

tasks.register("fuzzAll") {
  dependsOn fuzzEvmone, fuzzEthereumJS, fuzzGeth, fuzzNethermind, fuzzReth
}

jacoco {
  applyTo run
  applyTo runFuzzer
}

// Copies jacoco into the lib directory
tasks.register("copyJacoco", Copy) {
  // The jacocoagent.jar is embedded within the jar
  from zipTree(configurations.jacocoAgent.singleFile).filter { it.name == 'jacocoagent.jar' }.singleFile
  into layout.buildDirectory.dir("install/${application.applicationName}/lib")
}

installDist.finalizedBy copyJacoco

startScripts {
  defaultJvmOpts = [
    "-Dsecp256k1.randomize=false"
  ]
  unixStartScriptGenerator.template = resources.text.fromFile("${projectDir}/src/main/scripts/unixStartScript.txt")
  windowsStartScriptGenerator.template = resources.text.fromFile("${projectDir}/src/main/scripts/windowsStartScript.txt")
  doLast { tweakStartScript(startScripts) }
}

static def tweakStartScript(createScriptTask) {
  def shortenWindowsClasspath = { line ->
    line.replaceAll(/^set CLASSPATH=.*$/, "set CLASSPATH=%APP_HOME%/lib/*")
  }

  createScriptTask.unixScript.text = createScriptTask.unixScript.text.replace('BESU_HOME', '\$APP_HOME')
  createScriptTask.windowsScript.text = createScriptTask.windowsScript.text.replace('BESU_HOME', '%~dp0..')

  // Prevent the error originating from the 8191 chars limit on Windows
  createScriptTask.windowsScript.text =
    createScriptTask.windowsScript
    .readLines()
    .collect(shortenWindowsClasspath)
    .join('\r\n')
}
