/* sbt -- Simple Build Tool
 * Copyright 2009 Mark Harrah
 */
package sbt

import java.io.{ByteArrayInputStream, File, InputStream}
import java.net.{URI,URL}

object Hash
{
	private val BufferSize = 8192

	/** Converts an array of `bytes` to a hexadecimal representation String.*/
	def toHex(bytes: Array[Byte]): String =
	{
		val buffer = new StringBuilder(bytes.length * 2)
		for(i <- 0 until bytes.length)
		{
			val b = bytes(i)
			val bi: Int = if(b < 0) b + 256 else b
			buffer append toHex((bi >>> 4).asInstanceOf[Byte])
			buffer append toHex((bi & 0x0F).asInstanceOf[Byte])
		}
		buffer.toString
	}

	/** Converts the provided hexadecimal representation `hex` to an array of bytes.
	* The hexadecimal representation must have an even number of characters in the range 0-9, a-f, or A-F. */
	def fromHex(hex: String): Array[Byte] =
	{
		require((hex.length & 1) == 0, "Hex string must have length 2n.")
		val array = new Array[Byte](hex.length >> 1)
		for(i <- 0 until hex.length by 2)
		{
			val c1 = hex.charAt(i)
			val c2 = hex.charAt(i+1)
			array(i >> 1) = ((fromHex(c1) << 4) | fromHex(c2)).asInstanceOf[Byte]
		}
		array
	}

	/** Truncates the last half of `s` if the string has at least four characters.  Otherwise, the original string is returned. */
	def halve(s: String): String = if(s.length > 3) s.substring(0, s.length / 2) else s

	/** Computes the SHA-1 hash of `s` and returns the first `i` characters of the hexadecimal representation of the hash. */
	def trimHashString(s: String, i: Int): String = toHex(apply(s)).take(i)

	/** Computes the SHA-1 hash of `s` and truncates the hexadecimal representation of the hash via [[halve]]. */
	def halfHashString(s: String): String = halve(toHex(apply(s)))

	/** Calculates the SHA-1 hash of the given String.*/
	def apply(s: String): Array[Byte] = apply(s.getBytes("UTF-8"))
	/** Calculates the SHA-1 hash of the given Array[Byte].*/
	def apply(as: Array[Byte]): Array[Byte] = apply(new ByteArrayInputStream(as))
	/** Calculates the SHA-1 hash of the given file.*/
	def apply(file: File): Array[Byte] = Using.fileInputStream(file)(apply)
	/** Calculates the SHA-1 hash of the given resource.*/
	def apply(url: URL): Array[Byte] = Using.urlInputStream(url)(apply)

	/** If the URI represents a local file (the scheme is "file"),
	*  this method calculates the SHA-1 hash of the contents of that file.
	* Otherwise, this methods calculates the SHA-1 hash of the normalized string representation of the URI.*/
	def contentsIfLocal(uri: URI): Array[Byte] =
		if(uri.getScheme == "file") apply(uri.toURL) else apply(uri.normalize.toString)

	/** Calculates the SHA-1 hash of the given stream, closing it when finished.*/
	def apply(stream: InputStream): Array[Byte] =
	{
		import java.security.{MessageDigest, DigestInputStream}
		val digest = MessageDigest.getInstance("SHA")
		try
		{
			val dis = new DigestInputStream(stream, digest)
			val buffer = new Array[Byte](BufferSize)
			while(dis.read(buffer) >= 0) {}
			dis.close()
			digest.digest
		}
		finally { stream.close() }
	}

	private def toHex(b: Byte): Char =
	{
		require(b >= 0 && b <= 15, "Byte " + b + " was not between 0 and 15")
		if(b < 10)
			('0'.asInstanceOf[Int] + b).asInstanceOf[Char]
		else
			('a'.asInstanceOf[Int] + (b-10)).asInstanceOf[Char]
	}
	private def fromHex(c: Char): Int =
	{
		val b =
			if(c >= '0' && c <= '9')
				(c - '0')
			else if(c >= 'a' && c <= 'f')
				(c - 'a') + 10
			else if(c >= 'A' && c <= 'F')
				(c - 'A') + 10
			else
				throw new RuntimeException("Invalid hex character: '" + c + "'.")
		b
	}
}