package org.jmotor.sbt.metadata

import org.apache.maven.artifact.versioning.ArtifactVersion
import org.jmotor.artifact.exception.ArtifactNotFoundException
import org.jmotor.artifact.metadata.MetadataLoader
import org.jmotor.artifact.metadata.loader.IvyPatternsMetadataLoader
import org.jmotor.sbt.concurrent.MultiFuture
import sbt.librarymanagement.Constant
import sbt.librarymanagement.Patch
import sbt.librarymanagement.{ Binary, Disabled, Full, ModuleID }

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{ ExecutionContext, Future, Promise }
import scala.util.{ Failure, Success }

/**
 * Component:
 * Description:
 * Date: 2018/3/1
 *
 * @author AI
 */
class MetadataLoaderGroup(scalaVersion: String, scalaBinaryVersion: String, loaders: Seq[MetadataLoader]) {

  def getVersions(module: ModuleID, sbtSettings: Option[(String, String)]): Future[Seq[ArtifactVersion]] = {
    if (loaders.lengthCompare(1) > 0) {
      firstCompletedOf(loaders.map { loader ⇒
        val (artifactId, attrs) = getArtifactIdAndAttrs(loader, module, sbtSettings)
        loader.getVersions(module.organization, artifactId, attrs)
      })
    } else {
      loaders.headOption.fold(Future.successful(Seq.empty[ArtifactVersion])) { loader ⇒
        val (artifactId, attrs) = getArtifactIdAndAttrs(loader, module, sbtSettings)
        loader.getVersions(module.organization, artifactId, attrs)
      }
    }
  }

  private[metadata] def firstCompletedOf(futures: TraversableOnce[Future[Seq[ArtifactVersion]]])
    (implicit executor: ExecutionContext): Future[Seq[ArtifactVersion]] = {
    val p = Promise[Seq[ArtifactVersion]]()
    val multiFuture = new MultiFuture[Seq[ArtifactVersion]](p, futures.size, Seq.empty)
    futures foreach { future ⇒
      future.onComplete {
        case Success(r) if r.nonEmpty              ⇒ p trySuccess r
        case Success(_)                            ⇒ multiFuture.tryComplete()
        case Failure(_: ArtifactNotFoundException) ⇒ multiFuture.tryComplete()
        case Failure(t)                            ⇒ multiFuture.tryComplete(t)
      }(scala.concurrent.ExecutionContext.Implicits.global)
    }
    p.future
  }

  private[metadata] def getArtifactIdAndAttrs(loader: MetadataLoader, module: ModuleID,
                                              sbtSettings: Option[(String, String)]): (String, Map[String, String]) = {
    val remapVersion = module.crossVersion match {
      case _: Disabled        ⇒ None
      case _: Binary          ⇒ Option(scalaBinaryVersion)
      case _: Full            ⇒ Option(scalaVersion)
      case _: Patch           ⇒ Option(scalaVersion)
      case constant: Constant ⇒ Option(constant.value)
      case _                  ⇒ None
    }
    val name = remapVersion.map(v ⇒ s"${module.name}_$v").getOrElse(module.name)
    loader match {
      case _: IvyPatternsMetadataLoader if sbtSettings.isDefined ⇒
        val settings = sbtSettings.get
        name -> Map("sbtVersion" -> settings._1, "scalaVersion" -> settings._2)
      case _ ⇒ name -> Map.empty
    }
  }

}

object MetadataLoaderGroup {

  def apply(scalaVersion: String, scalaBinaryVersion: String, loaders: MetadataLoader*): MetadataLoaderGroup = {
    new MetadataLoaderGroup(scalaVersion, scalaBinaryVersion, loaders)
  }

}