import java.nio.file.FileAlreadyExistsException
import java.nio.file.Files
import java.nio.file.Path
import java.time.Duration
import java.time.Instant

void downloadFileUsingWget(
    String url,
    String outputFilePath
) {
  println("Downloading ${url} into ${outputFilePath}")

  String command = "curl -L -H 'Accept:application/octet-stream' -o ${outputFilePath} ${url}"
  //  println("# " + command)

  def execResult = exec {
    commandLine 'bash', '-c', command
  }

  if (execResult.exitValue != 0) {
    execResult.rethrowFailure()
  }
}

ext.architectureResourceDirMapping = [
  "darwin_arm64" : "darwin-aarch64",
  "darwin_x86_64": "darwin-x86-64",
  "linux_arm64"  : "linux-aarch64",
  "linux_amd64"  : "linux-x86-64",
  "linux_x86_64" : "linux-x86-64"
]

private String downloadAssetIfNotPresent(
    String libsZipUrl,
    String releaseTag,
    String outputDir
) {
  def fileName = releaseTag + ".zip"
  def outputFilePath = Path.of(outputDir).resolve(fileName)

  if (Files.exists(outputFilePath)) {
    println("Skipping download, file already exists at ${outputFilePath}")
    return outputFilePath.toString()
  } else if (!Files.exists(outputFilePath.getParent())) {
    try {
      Files.createDirectory(outputFilePath.getParent())
    } catch (FileAlreadyExistsException e) {
      // ignore, other task might have created the directory in the meantime
    }
  }

  downloadFileUsingWget(libsZipUrl, outputFilePath.toString())

  return outputFilePath.toString()
}

def getBinaryResourceFolder(
    String libFile
) {
  def destResource = architectureResourceDirMapping.find {
    libFile.contains(it.key.toString())
  }
  if (destResource == null) {
    throw new GradleException("No architecture found for ${libFile}")
  }

  return destResource.value.toString()
}

def getBinaryResourceFileName(
    String libFile,
    String libName
) {
  def versionPattern = ~/v\d+\.\d+\.\d+/
  def matcher = versionPattern.matcher(libFile)
  def version = null
  if (matcher.find()) {
    version = matcher.group(0)
  }
  def dstBinaryName = "lib${libName}_jna_${version}.${libFile.split("\\.").last()}"
  return dstBinaryName
}

def lazyUnzipWithRetry(
    Path zipFile,
    Path outputDir = zipFile.parent.resolve(zipFile.getFileName().toString().replaceFirst("\\.zip", "")).toString(),
    Duration timeout
) {
  if (outputDir.toFile().exists()) {
    println("Skipping unzip, directory already exists at ${outputDir.toString()}")
  }
  def unziped = false
  def startTime = Instant.now()
  def error = null
  while (!unziped && Duration.between(startTime, Instant.now()).compareTo(timeout) < 0) {
    try {
      error = null
      copy {
        from zipTree(zipFile)
        into outputDir
      }
      unziped = true
    } catch (Exception e) {
      error = e
      // wait for a second before retrying
      // another process might be using the file
      Thread.sleep(1000)
    }
  }

  if (error != null) {
    throw new GradleException("Failed to unzip ${zipFile} to ${outputDir}", error)
  }
}

def extractLibToResources(
    Path zipFile,
    Path outputUnzipDir,
    String libName
) {
  lazyUnzipWithRetry(zipFile, outputUnzipDir, Duration.ofSeconds(60))
  fileTree(outputUnzipDir.toFile())
      .filter { it.name.contains(libName) && (it.name.endsWith(".so") || it.name.endsWith(".dylib")) }
      .each { File file ->
        def javaResourcesLibDir = getBinaryResourceFolder(file.name)
        def destResourceFileName = getBinaryResourceFileName(file.name, libName)
        def destResourcesPath = Path.of(project.layout.projectDirectory
            .file("src/main/resources/${javaResourcesLibDir}").asFile.absolutePath)
        def destFilePath = destResourcesPath.resolve(destResourceFileName)

        if (destFilePath.toFile().exists()) {
          println("Skipping: ${file} already found at ${destResourcesPath}/${destResourceFileName}")
        } else {
          println("Copying ${file} to ${destResourcesPath}/${destResourceFileName}")
        }
        copy {
          from file
          into destResourcesPath
          rename {
            destResourceFileName
          }
        }
      }
}

def downloadZipReleaseAndExtractToResources(
    String libsZipUrl,
    String releaseTag,
    String libName,
    String outputDir
) {
  def zipFile = downloadAssetIfNotPresent(libsZipUrl, releaseTag, outputDir)
  def outputUnzipDir = Path.of(outputDir).resolve(releaseTag)
  extractLibToResources(Path.of(zipFile), outputUnzipDir, libName)
}

ext.fetchLibFromZip = { String libsZipUrl, String libName, String outputDir ->
  def releaseTag = libsZipUrl.split("/").last().replace(".zip", "")
  downloadZipReleaseAndExtractToResources(libsZipUrl, releaseTag, libName, outputDir)
}
