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

import Types.:+:
import sbinary.{ DefaultProtocol, Format, Input, Output => Out }
import DefaultProtocol.ByteFormat
import java.io.{ File, InputStream, OutputStream }

trait InputCache[I] {
  type Internal
  def convert(i: I): Internal
  def read(from: Input): Internal
  def write(to: Out, j: Internal): Unit
  def equiv: Equiv[Internal]
}
object InputCache {
  implicit def basicInputCache[I](implicit fmt: Format[I], eqv: Equiv[I]): InputCache[I] =
    new InputCache[I] {
      type Internal = I
      def convert(i: I) = i
      def read(from: Input): I = fmt.reads(from)
      def write(to: Out, i: I) = fmt.writes(to, i)
      def equiv = eqv
    }
  def lzy[I](mkIn: => InputCache[I]): InputCache[I] =
    new InputCache[I] {
      lazy val ic = mkIn
      type Internal = ic.Internal
      def convert(i: I) = ic convert i
      def read(from: Input): ic.Internal = ic.read(from)
      def write(to: Out, i: ic.Internal) = ic.write(to, i)
      def equiv = ic.equiv
    }
}

class BasicCache[I, O](implicit input: InputCache[I], outFormat: Format[O]) extends Cache[I, O] {
  def apply(file: File)(in: I) =
    {
      val j = input.convert(in)
      try { applyImpl(file, j) }
      catch { case e: Exception => Right(update(file)(j)) }
    }
  protected def applyImpl(file: File, in: input.Internal) =
    {
      Using.fileInputStream(file) { stream =>
        val previousIn = input.read(stream)
        if (input.equiv.equiv(in, previousIn))
          Left(outFormat.reads(stream))
        else
          Right(update(file)(in))
      }
    }
  protected def update(file: File)(in: input.Internal) = (out: O) =>
    {
      Using.fileOutputStream(false)(file) { stream =>
        input.write(stream, in)
        outFormat.writes(stream, out)
      }
    }
}