/* sbt -- Simple Build Tool
 * Copyright 2011 Sanjin Sehic
 */

package sbt

import java.io.File
import java.net.URI

import BuildLoader.ResolveInfo
import RichURI.fromURI

object Resolvers
{
	type Resolver = BuildLoader.Resolver

	val local: Resolver = (info: ResolveInfo) => {
		val uri = info.uri
		val from = new File(uri)
		val to = uniqueSubdirectoryFor(uri, in = info.staging)

		if (from.isDirectory) Some {() => if(from.canWrite) from else creates(to) {IO.copyDirectory(from, to)}}
		else None
	}

	val remote: Resolver = (info: ResolveInfo) => {
		val url = info.uri.toURL
		val to = uniqueSubdirectoryFor(info.uri, in = info.staging)

		Some {() => creates(to) {IO.unzipURL(url, to)}}
	}

	val subversion: Resolver = (info: ResolveInfo) => {
		def normalized(uri: URI) = uri.copy(scheme = "svn")

		val uri = info.uri.withoutMarkerScheme
		val localCopy = uniqueSubdirectoryFor(normalized(uri), in = info.staging)
		val from = uri.withoutFragment.toASCIIString
		val to = localCopy.getAbsolutePath

		if (uri.hasFragment) {
			val revision = uri.getFragment
			Some {
				() => creates(localCopy) {
					run("svn", "checkout", "-q", "-r", revision, from, to)
				}
			}
		} else
			Some {
				() => creates(localCopy) {
					run("svn", "checkout", "-q", from, to)
				}
			}
	}

	val mercurial: Resolver = new DistributedVCS
	{
		override val scheme = "hg"

		override def clone(from: String, to: File)
		{
			run("hg", "clone", "-q", from, to.getAbsolutePath)
		}

		override def checkout(branch: String, in: File)
		{
			run(Some(in), "hg", "checkout", "-q", branch)
		}
	}.toResolver

	val git: Resolver = new DistributedVCS
	{
		override val scheme = "git"

		override def clone(from: String, to: File)
		{
			run("git", "clone", from, to.getAbsolutePath)
		}

		override def checkout(branch: String, in: File)
		{
			run(Some(in), "git", "checkout", "-q", branch)
		}
	}.toResolver

	abstract class DistributedVCS
	{
		val scheme: String

		def clone(from: String, to: File)

		def checkout(branch: String, in: File)

		def toResolver: Resolver = (info: ResolveInfo) => {
			val uri = info.uri.withoutMarkerScheme
			val localCopy = uniqueSubdirectoryFor(normalized(uri), in = info.staging)
			val from = uri.withoutFragment.toASCIIString

			if (uri.hasFragment) {
				val branch = uri.getFragment
				Some {
					() => creates(localCopy) {
						clone(from, to = localCopy)
						checkout(branch, in = localCopy)
					}
				}
			} else Some {() => creates(localCopy) {clone(from, to = localCopy)}}
		}

		private def normalized(uri: URI) = uri.copy(scheme = scheme)
	}

	private lazy val onWindows = {
		val os = System.getenv("OSTYPE")
		val isCygwin = (os != null) && os.toLowerCase.contains("cygwin")
		val isWindows = System.getProperty("os.name", "").toLowerCase.contains("windows")
		isWindows && !isCygwin
	}

	def run(command: String*)
	{
		run(None, command: _*)
	}

	def run(cwd: Option[File], command: String*)
	{
		val result = Process(
			if (onWindows) "cmd" +: "/c" +: command
			else command,
			cwd
		) !;
		if (result != 0)
			sys.error("Nonzero exit code (" + result + "): " + command.mkString(" "))
	}

	def creates(file: File)(f: => Unit) =
	{
		if (!file.exists)
			try {
				f
			} catch {
				case e: Throwable =>
					IO.delete(file)
					throw e
			}
		file
	}

	def uniqueSubdirectoryFor(uri: URI, in: File) = {
		in.mkdirs()
		val base = new File(in, Hash.halfHashString(uri.normalize.toASCIIString))
		val last = shortName(uri) match { case Some(n) => normalizeDirectoryName(n); case None => "root" }
		new File(base, last)
	}

	private[this] def shortName(uri: URI): Option[String] =
		Option(uri.withoutMarkerScheme.getPath).flatMap {
			_.split("/").map(_.trim).filterNot(_.isEmpty).lastOption
		}
	
	private[this] def normalizeDirectoryName(name: String): String =
		StringUtilities.normalize(dropExtensions(name))

	private[this] def dropExtensions(name: String): String = name.takeWhile(_ != '.')

}