// -----------------------------------------------------------------------------
//
//  Scalax - The Scala Community Library
//  Copyright (c) 2005-8 The Scalax Project. All rights reserved.
//
//  The primary distribution site is http://scalax.scalaforge.org/
//
//  This software is released under the terms of the Revised BSD License.
//  There is NO WARRANTY.  See the file LICENSE for the full text.
//
// -----------------------------------------------------------------------------

package scalax.io
import java.io._
import java.util.zip._
import java.net.URL
import java.nio.channels._
import java.nio.charset._
import java.util.regex._
import scalax.control._

abstract class ResourceFactory {
	/** Resource type */
	type RT
	
	def file(f : File) : RT
	
	def file(path : String) : RT = file(new File(path))
	
	def url(u : String) : RT
	
	def apply(path : String) = {
		if (path.startsWith("/") || path.startsWith("./")) file(new File(path))
		else url(path)
	}
}

abstract class CloseableResource[+C <: Closeable] extends ManagedResource[C] { self =>
	type Handle <: C
	final def translate(v : Handle) = v
	override def unsafeClose(r : Handle) = r.close()
	
	protected trait Wrapper {
		type Handle
		type SelfHandle = self.Handle
		
		def wrap(is : SelfHandle) : Handle
		
		def unsafeOpen() = {
			val is = self.unsafeOpen()
			try {
				wrap(is)
			} catch {
				case e =>
					self.unsafeCloseQuietly(is)
					throw e
			}
		}
	}
}

abstract class InputStreamResource[+I <: InputStream] extends CloseableResource[I] {
	def buffered : InputStreamResource[BufferedInputStream] =
		new InputStreamResource[BufferedInputStream] with Wrapper {
			type Handle = BufferedInputStream
			override def wrap(is : SelfHandle) = new BufferedInputStream(is)
			
			override def buffered = this
		}

	def slurp() = for (is <- this) yield StreamHelp.slurp(is)
	
	/* Obtains a Reader using the system default charset. */
	def reader =
		new ReaderResource[Reader] with Wrapper {
			type Handle = Reader
			// XXX: should be UTF-8 by default instead of OS default
			// practically, here in Russia I never used default charset
			override def wrap(is : SelfHandle) = new InputStreamReader(is)
		}
	
	def gunzip =
		new InputStreamResource[GZIPInputStream] with Wrapper {
			type Handle = GZIPInputStream
			override def wrap(is : SelfHandle) = new GZIPInputStream(is)
		}
	
	/** Obtains a Reader using the supplied charset. */
	def reader(charset : String) = {
		// Do this lookup before opening the file, since it might fail.
		val cs = Charset.forName(charset)
		new ReaderResource[Reader] with Wrapper {
			type Handle = Reader
			override def wrap(is : SelfHandle) = new InputStreamReader(is, cs)
		}
	}
	
	def lines = reader.lines
	
	def lines(charset : String) = reader(charset).lines
	
	def readLines() = reader.readLines()
	
	def readLine() = reader.readLine()
	
	def pumpTo[O <: OutputStream](osr : OutputStreamResource[O]) {
		// Note InputStream should be opened before OutputStream
		for (is <- this; os <- osr) StreamHelp.pump(is, os)
	}
}

object InputStreamResource extends ResourceFactory {
	override type RT = InputStreamResource[InputStream]
	
	def apply[I <: InputStream](is : => I) =
		new InputStreamResource[I] {
			type Handle = I
			def unsafeOpen() = is
		}
	
	def bytes(array : Array[Byte], offset : Int, length : Int) =
		apply(new ByteArrayInputStream(array, offset, length))
	
	def bytes(b : Array[Byte]) : InputStreamResource[ByteArrayInputStream] =
		bytes(b, 0, b.length)
	
	override def file(file : File) =
		apply(new FileInputStream(file))

	private def url(url : java.net.URL) = {
		def is = {
			// see org.springframework.core.io.UrlResource
			val conn = url.openConnection()
			conn.setUseCaches(false)
			conn.getInputStream()
		}
		apply(is)
	}
	
	def classpath(path : String): InputStreamResource[InputStream] =
		classpath(path, Thread.currentThread.getContextClassLoader)
	
	def classpath(path : String, classLoader: ClassLoader): InputStreamResource[InputStream] = {
		def is = {
			val is = classLoader.getResourceAsStream(path)
			if (is eq null) throw new FileNotFoundException("classpath resource " + path + " not found")
			is
		}
		apply(is)
	}

	private val CLASSPATH_URL_PREFIX = "classpath:"
	
	private val GZIP_URL_PREFIXES = List("gzip:", "gunzip:")
	
	override def url(u : String): InputStreamResource[InputStream] = {
		if (u startsWith CLASSPATH_URL_PREFIX) classpath(u.substring(CLASSPATH_URL_PREFIX.length))
		else {
			val gzippedO = GZIP_URL_PREFIXES.map(prefix => (prefix, u startsWith prefix))
					.find(_._2)
					.map(_._1)
			if (gzippedO.isDefined) {
				url(u.substring(gzippedO.get.length)).gunzip
			} else url(new URL(u))
		}
	}
	
}

abstract class ReaderResource[+R <: Reader] extends CloseableResource[R] {

	def slurp() = for (r <- this) yield StreamHelp.slurp(r)
	
	def buffered : ReaderResource[BufferedReader] =
		new ReaderResource[BufferedReader] with Wrapper {
			type Handle = BufferedReader
			override def wrap(r : SelfHandle) = new BufferedReader(r)
			
			override def buffered = this
		}
	
	def lines =
		new ManagedSequence[String] {
			type Handle = BufferedReader
			val resource = ReaderResource.this.buffered
			override def iterator(v : BufferedReader) = StreamHelp.lines(v)
		}
	
	def readLines(): Seq[String] = lines.toList
	
	/** First line or <code>""</code> if file is empty */
	def readLine() = lines.headOption.getOrElse("")
	
	def pumpTo[W <: Writer](wr : WriterResource[W]) {
		// Note Reader should be opened before Writer
		for (r <- this; w <- wr) StreamHelp.pump(r, w)
	}
}

object ReaderResource extends ResourceFactory  {
	override type RT = ReaderResource[Reader]

	def string(s : String) =
		apply(new StringReader(s))
	
	def apply[R <: Reader](r : => R) =
		new ReaderResource[R] {
			type Handle = R
			def unsafeOpen() = r
		}
	
	override def file(f : File) =
		InputStreamResource.file(f).reader
	
	override def url(u : String) =
		InputStreamResource.url(u).reader
	
	def classpath(p : String) =
		InputStreamResource.classpath(p).reader
}

abstract class OutputStreamResource[+O <: OutputStream] extends CloseableResource[O] {
	
	def buffered : OutputStreamResource[BufferedOutputStream] =
		new OutputStreamResource[BufferedOutputStream] with Wrapper {
			type Handle = BufferedOutputStream
			override def wrap(os : SelfHandle) = new BufferedOutputStream(os)
			
			override def buffered = this
		}
	
	// XXX: should use UTF-8, see comment above
	/** Obtains a Writer using the system default charset. */
	def writer =
		new WriterResource[Writer] with Wrapper {
			type Handle = Writer
			override def wrap(os : SelfHandle) = new OutputStreamWriter(os)
		}
	
	def gzip =
		new OutputStreamResource[GZIPOutputStream] with Wrapper {
			type Handle = GZIPOutputStream
			override def wrap(os : SelfHandle) = new GZIPOutputStream(os)
		}
	
	/** Obtains a Writer using the supplied charset. */
	def writer(charset : String) = {
		val cs = Charset.forName(charset)
		new WriterResource[Writer] with Wrapper {
			type Handle = Writer
			override def wrap(os : SelfHandle) = new OutputStreamWriter(os, cs)
		}
	}
	
	def writeLine(line : String) = writer.writeLine(line)
	
	def writeLines(lines : Seq[String]) = writer.writeLines(lines)
	
	def writeString(string : String) = writer.writeString(string)
	
	def pumpFrom[I <: InputStream](isr : InputStreamResource[I]) {
		isr pumpTo this
	}
}

object OutputStreamResource extends ResourceFactory {
	override type RT = OutputStreamResource[OutputStream]
	
	def apply[O <: OutputStream](os : => O) =
		new OutputStreamResource[O] {
			type Handle = O
			def unsafeOpen() = os
		}

	def fileAppend(file : File, append : Boolean) =
		apply(new FileOutputStream(file, append))
	
	def fileAppend(file : File) : OutputStreamResource[FileOutputStream] =
		fileAppend(file, true)
		
	override def file(file : File) =
		apply(new FileOutputStream(file))
		
	private val FILE_URL_PREFIX = "file:"
	private val GZIP_URL_PREFIX = "gzip:"
	
	override def url(u : String) : OutputStreamResource[OutputStream] = {
		if (u startsWith FILE_URL_PREFIX) file(new File(u substring FILE_URL_PREFIX.length))
		else if (u startsWith GZIP_URL_PREFIX) url(u substring GZIP_URL_PREFIX.length).gzip
		else throw new IllegalArgumentException("unknown url: " + u)
	}
	
}

abstract class WriterResource[+W <: Writer] extends CloseableResource[W] {
	
	def buffered : WriterResource[BufferedWriter] =
		new WriterResource[BufferedWriter] with Wrapper {
			type Handle = BufferedWriter
			override def wrap(w : SelfHandle) = new BufferedWriter(w)
			
			override def buffered = this
		}
	
	def printWriter: WriterResource[PrintWriter] =
		new WriterResource[PrintWriter] with Wrapper {
			type Handle = PrintWriter
			override def wrap(w : SelfHandle) = new PrintWriter(w)
			
			override def printWriter: WriterResource[PrintWriter] = this
		}
	
	def writeString(string : String) {
		for (w <- this) w.write(string)
	}
	
	/** Alias for <code>writeString</code> */
	def write(string : String) = writeString(string)
	
	/** Write strings adding line separator after each line */
	def writeLines(lines : Seq[String]) {
		for (w <- buffered; line <- lines) {
			w.write(line)
			w.write(FileHelp.lineSeparator)
		}
	}
	
	/** Write string followed by line separator */
	def writeLine(line : String) {
		writeLines(line :: Nil)
	}
	
	def pumpFrom[R <: Reader](rr : ReaderResource[R]) {
		rr pumpTo this
	}
}

object WriterResource extends ResourceFactory {
	override type RT = WriterResource[Writer]
	
	def apply[W <: Writer](w : => W) =
		new WriterResource[W] {
			type Handle = W
			def unsafeOpen() = w
		}
	
	override def file(f : File) =
		OutputStreamResource.file(f).writer
	
	override def url(u : String) =
		OutputStreamResource.url(u).writer
}

// vim: set ts=4 sw=4 noet: