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

import java.io.{File, InputStream}
import java.util.zip.{ZipEntry, ZipFile}

object Main
{
	lazy val log: Logger = new ConsoleLogger
	
	def main(args: Array[String])
	{
		if( args contains "debug" )
			log.setLevel(Level.Debug)
		val result = OpenResource.zipFile.ioOption(FileUtilities.classLocationFile[Install], "processing", log)(process)
		for(msg <- result)
		{
			log.error(msg)
			System.exit(1)
		}
	}
	private[this] val packedGzip = ".pack.gz"
	private def isArchive(name: String) = name.endsWith(".gz") || name.endsWith(".zip")
	private def process(zip: ZipFile) =
	{
		val installEntry = zip.getEntry("install")
		if(installEntry == null)
			Some("Install commands not found.")
		else
		{
			val jarAndZip = wrap.Wrappers.toList(zip.entries).filter(entry => isArchive(entry.getName)).partition(_.getName.endsWith(packedGzip))
			jarAndZip match
			{
				case (Nil, _)=> Some("sbt loader not found.")
				case (_, Nil) => Some("Project to extract and build not found.")
				case (loaderEntry :: _, projectEntry :: _) => extractAndRun(zip, loaderEntry, projectEntry, installEntry)
			}
		}
	}
	private def extractAndRun(zip: ZipFile, loaderEntry: ZipEntry, projectEntry: ZipEntry, installEntry: ZipEntry) =
	{
		val zipResource = OpenResource.zipEntry(zip)
		
		import FileUtilities.{gunzip, readString, transfer, unzip, writeStream}
		val directory = new File(".", trimExtension(projectEntry.getName, ".zip"))
		assume(!directory.exists, "Could not extract project: directory " + projectEntry.getName + " exists.")
		
		val loaderBaseName = trimExtension(loaderEntry.getName, packedGzip)
		val loaderFile = new File(directory, loaderBaseName + ".jar")
		val tempLoaderFile = new File(directory, loaderBaseName + ".pack")
		
		def extractLoader() =
		{
			implicit def fileToPath(f: File) = Path.fromFile(f)
			val result =
				writeStream(tempLoaderFile, log) { out => zipResource.ioOption(loaderEntry, "reading", log)(gunzip(_, out, log)) } orElse
				Pack.unpack(tempLoaderFile, loaderFile, log)
			FileUtilities.clean(tempLoaderFile :: Nil, true, log)
			result.toLeft(loaderFile)
		}

		Control.thread(zipResource.io(installEntry, "reading", log)(readString(_, log))) { installString =>
			Control.thread(parseInstall(installString)) { install =>
				zipResource.io(projectEntry, "reading", log)(unzip(_, Path.fromFile(directory), log)).left.toOption orElse
				Control.thread(extractLoader()) { loaderFile =>
					run(loaderFile, directory, install)
				}
			}
		}
	}
	private def parseInstall(installString: String): Either[String, Install] =
	{
		installString.split(separator) match
		{
			case Array(allOptions, allActions) =>
				val options = allOptions.split("""\n""").toList
				val actions = allActions.split("""\n""").toList
				Right( Install(options, actions) )
			case _ => Left("Invalid install script (no separator found)")
		}
	}
	private def filterEmpty(list: List[String]) = list.filter(!_.isEmpty)
	private def run(loader: File, project: File, install: Install) =
	{
		val command = "java" :: "-jar" :: loader.getAbsolutePath :: filterEmpty(install.options) ::: filterEmpty(install.actions)
		val builder = new java.lang.ProcessBuilder(command.toArray : _*)
		builder.directory(project)
		val exitCode = (Process(builder) !<)
		if(exitCode == 0)
			None
		else
			Some("sbt exited with nonzero exit code: " + exitCode)
	}
	private def trimExtension(name: String, ext: String) =
	{
		if(name.endsWith(ext))
			name.substring(0, name.length - ext.length)
		else
			name
	}
	// keep this in sync with sbt.extract.SelfExtractingProject
	private def separator = "===================="
}
private final case class Install(options: List[String], actions: List[String]) extends NotNull