import de.heikoseeberger.sbtheader.HeaderPlugin.autoImport.headerLicense
import de.heikoseeberger.sbtheader.License
import org.scalafmt.sbt.ScalafmtPlugin.autoImport.scalafmtOnCompile
import sbt.Keys.{javaOptions, _}
import java.io.File
import sbt.{Def, IO, addCompilerPlugin, _}
import sbtassembly.AssemblyPlugin.autoImport.assemblyMergeStrategy
import sbtassembly.{MergeStrategy, PathList}
import bintray.BintrayKeys._

import scala.sys.process._

object SbtCommons {

  val scalaV = scalaVersion := "2.12.9"

  val commons = Seq(
    scalaV,
    version                              := "0.1.1",
    fork in Test                         := true,
    parallelExecution in Test            := false,
    fork in IntegrationTest              := true,
    parallelExecution in IntegrationTest := false,
    organizationName                     := "Fluence Labs Limited",
    organizationHomepage                 := Some(new URL("https://fluence.network")),
    startYear                            := Some(2019),
    licenses += ("Apache-2.0", new URL("https://www.apache.org/licenses/LICENSE-2.0.txt")),
    headerLicense := Some(License.ALv2("2019", organizationName.value)),
    publishMavenStyle   := true,
    scalafmtOnCompile   := true,
    bintrayOrganization := Some("fluencelabs"),
    bintrayRepository   := "releases",
    resolvers ++= Seq(Resolver.bintrayRepo("fluencelabs", "releases"), Resolver.sonatypeRepo("releases")),
    scalafmtOnCompile := true,
    // see good explanation https://gist.github.com/djspiewak/7a81a395c461fd3a09a6941d4cd040f2
    scalacOptions ++= Seq("-Ypartial-unification", "-deprecation"),
    javaOptions in Test ++= Seq(
      "-XX:MaxMetaspaceSize=4G",
      "-Xms4G",
      "-Xmx4G",
      "-Xss6M"
    ),
    javaOptions in IntegrationTest ++= Seq(
      "-XX:MaxMetaspaceSize=4G",
      "-Xms4G",
      "-Xmx4G",
      "-Xss6M"
    ),
    addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.0")
  )

  val mergeStrategy = Def.setting[String => MergeStrategy]({
    // a module definition fails compilation for java 8, just skip it
    case PathList("module-info.class", xs @ _*)  => MergeStrategy.first
    case "META-INF/io.netty.versions.properties" => MergeStrategy.first
    case x =>
      import sbtassembly.AssemblyPlugin.autoImport.assembly
      val oldStrategy = (assemblyMergeStrategy in assembly).value
      oldStrategy(x)
  }: String => MergeStrategy)

  val rustToolchain = "nightly-2019-09-23"

  def compileFrank() = {
    val projectRoot = file("").getAbsolutePath
    val frankFolder = s"$projectRoot/src/main/rust"
    val localCompileCmd = s"cargo +$rustToolchain build --manifest-path $frankFolder/Cargo.toml --release --lib"
    // TODO: cross build with manifest path doesn't work - so it needs to explicitly change a directory
    val crossCompileCmd = s"cd $frankFolder ; cross build --target x86_64-unknown-linux-gnu --release --lib"

    assert((localCompileCmd !) == 0, "Frank VM native compilation failed")
    assert((crossCompileCmd !) == 0, "Cross compilation to linux failed")
  }

  def compileFrankVMSettings(): Seq[Def.Setting[_]] =
    Seq(
      publishArtifact := false,
      test            := (test in Test).dependsOn(compile).value,
      compile := (compile in Compile)
        .dependsOn(Def.task {
          val log = streams.value.log

          log.info("Compiling Frank VM")
          compileFrank()
        })
        .value
    )

  def nativeResourceSettings(): Seq[Def.Setting[_]] =
    Seq(
      resourceGenerators in Compile += Def.task {
        val managedResource = s"${(resourceManaged in Compile).value}/native"

        val darwinLib = new File(managedResource + "/x86_64-darwin/libfrank.dylib")
        val linuxLib = new File(managedResource + "/x86_64-linux/libfrank.so")

        IO.copyFile(
          new File(s"${file("").getAbsolutePath}/src/main/rust/target/x86_64-unknown-linux-gnu/release/libfrank.so"),
          linuxLib
        )

        System.getProperty("os.name").toLowerCase match {
          case os if os.contains("linux") => linuxLib :: Nil
          case os if os.contains("mac") => {
            IO.copyFile(
              new File(s"${file("").getAbsolutePath}/src/main/rust/target/release/libfrank.dylib"),
              darwinLib
            )
            linuxLib :: darwinLib :: Nil
          }
          case os ⇒ {
            new RuntimeException(s"$os is unsupported, only *nix and MacOS OS are supported now")
            Nil
          }
        }
      }.taskValue
    )

  def downloadLlamadb(): Seq[Def.Setting[_]] =
    Seq(
      publishArtifact := false,
      test            := (test in Test).dependsOn(compile).value,
      compile := (compile in Compile)
        .dependsOn(Def.task {
          // by defaults, user.dir in sbt points to a submodule directory while in Idea to the project root
          val resourcesPath = System.getProperty("user.dir") + "/src/it/resources/"

          val log = streams.value.log
          val llamadbUrl = "https://github.com/fluencelabs/llamadb-wasm/releases/download/0.1.2/llama_db.wasm"
          val llamadbPreparedUrl =
            "https://github.com/fluencelabs/llamadb-wasm/releases/download/0.1.2/llama_db_prepared.wasm"

          log.info(s"Dowloading llamadb from $llamadbUrl to $resourcesPath")

          // -nc prevents downloading if file already exists
          val llamadbDownloadRet = s"wget -nc $llamadbUrl -O $resourcesPath/llama_db.wasm" !
          val llamadbPreparedDownloadRet = s"wget -nc $llamadbPreparedUrl -O $resourcesPath/llama_db_prepared.wasm" !

          // wget returns 0 of file was downloaded and 1 if file already exists
          assert(llamadbDownloadRet == 0 || llamadbDownloadRet == 1, s"Download failed: $llamadbUrl")
          assert(
            llamadbPreparedDownloadRet == 0 || llamadbPreparedDownloadRet == 1,
            s"Download failed: $llamadbPreparedUrl"
          )
        })
        .value
    )

  /* Common deps */

  val catsVersion = "2.0.0"
  val cats = "org.typelevel" %% "cats-core" % catsVersion
  val catsEffectVersion = "2.0.0"
  val catsEffect = "org.typelevel" %% "cats-effect" % catsEffectVersion

  // functional wrapper around 'lightbend/config'
  val ficus = "com.iheart" %% "ficus" % "1.4.5"

  // for ByteVector
  val scodecBits = "org.scodec" %% "scodec-bits" % "1.1.9"
  val scodecCore = "org.scodec" %% "scodec-core" % "1.11.3"

  // test deps
  val scalacheckShapeless = "com.github.alexarchambault" %% "scalacheck-shapeless_1.13" % "1.1.8"     % Test
  val catsTestkit = "org.typelevel"                      %% "cats-testkit"              % catsVersion % Test
  val disciplineScalaTest = "org.typelevel"              %% "discipline-scalatest"      % "1.0.0-M1"  % Test

  val scalaTest = "org.scalatest"            %% "scalatest"   % "3.0.8"  % Test
  val scalaIntegrationTest = "org.scalatest" %% "scalatest"   % "3.0.8"  % IntegrationTest
  val mockito = "org.mockito"                % "mockito-core" % "2.21.0" % Test
}