/*                     __                                               *\
**     ________ ___   / /  ___     Scala API                            **
**    / __/ __// _ | / /  / _ |    (c) 2003-2009, LAMP/EPFL             **
**  __\ \/ /__/ __ |/ /__/ __ |    http://scala-lang.org/               **
** /____/\___/_/ |_/____/_/ | |                                         **
**                          |/                                          **
\*                                                                      */

package scala.io

import java.io.{ BufferedInputStream, InputStream, PrintStream, File => JFile }
import java.io.{ BufferedReader, InputStreamReader }
import java.net.{ URI, URL }

import collection.mutable.ArrayBuffer
import Path.fail

/** Traits for objects which can be represented as Streams.
 *
 *  @author Paul Phillips
 *  @since  2.8
 */

object Streamable
{
  /** Traits which can be viewed as a sequence of bytes.  Source types
   *  which know their length should override def length: Long for more
   *  efficient method implementations.
   */
  trait Bytes {
    def inputStream(): InputStream
    def length: Long = -1
    
    def bufferedInput() = new BufferedInputStream(inputStream())
    def bytes(): Iterator[Byte] = bytesAsInts() map (_.toByte)
    def bytesAsInts(): Iterator[Int] = {
      val in = bufferedInput()
      Iterator continually in.read() takeWhile (_ != -1)
    }

    /** This method aspires to be the fastest way to read
     *  a stream of known length into memory.
     */
    def toByteArray(): Array[Byte] = {
      // if we don't know the length, fall back on relative inefficiency
      if (length == -1L)
        return (new ArrayBuffer[Byte]() ++ bytes()).toArray
      
      val arr = new Array[Byte](length.toInt)
      val len = arr.length
      lazy val in = bufferedInput()
      var offset = 0
    
      def loop() {
        if (offset < len) {
          val read = in.read(arr, offset, len - offset)
          if (read >= 0) {
            offset += read
            loop()
          }
        }
      }
      try loop()
      finally in.close()
    
      if (offset == arr.length) arr
      else fail("Could not read entire source (%d of %d bytes)".format(offset, len))
    }
  }

  /** For objects which can be viewed as Chars.  The abstract creationCodec
   *  can safely be defined as null and will subsequently be ignored.
   */
  trait Chars extends Bytes {
    def creationCodec: Codec
    private def failNoCodec() = fail("This method requires a Codec to be chosen explicitly.")

    /** The general algorithm for any call to a method involving byte<->char
     *  transformations is: if a codec is supplied (explicitly or implicitly),
     *  use that; otherwise if a codec was defined when the object was created,
     *  use that; otherwise, use Codec.default.
     * 
     *  Note that getCodec takes a codec argument rather than having methods
     *  always default to getCodec() and use the argument otherwise, so that
     *  method implementations can, where desired, identify the case where no
     *  codec was ever explicitly supplied.  If allowDefault = false, an
     *  exception will be thrown rather than falling back on Codec.default.
     */
    def getCodec(givenCodec: Codec = null, allowDefault: Boolean = true) =
      if (givenCodec != null) givenCodec
      else if (creationCodec != null) creationCodec
      else if (allowDefault) Codec.default
      else failNoCodec()
  
    def chars(codec: Codec = getCodec()): Source = (Source fromInputStream inputStream())(codec)
    def lines(codec: Codec = getCodec()): Iterator[String] = chars(codec).getLines()
  
    /** Obtains an InputStreamReader wrapped around a FileInputStream.
     */
    def reader(codec: Codec = getCodec()) = new InputStreamReader(inputStream, codec.charSet)
  
    /** Wraps a BufferedReader around the result of reader().
     */
    def bufferedReader(codec: Codec = getCodec()) = new BufferedReader(reader(codec))
  
    /** Convenience function to import entire file into a String.
     */
    def slurp(codec: Codec = getCodec()) = chars(codec).mkString
  }
}