/*
 * Copyright 2015 University of Basel, Graphics and Vision Research Group
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package scalismo.utils

import java.util.Map.Entry

class Memoize[-T, +R](f: T => R, cacheSizeHint: Int) extends (T => R) {

  private class Holder[X] {
    private var data: Option[X] = None

    def getOrPut(f: => X): X = {
      data match {
        case Some(v) => v
        case None =>
          this.synchronized {
            data match {
              case Some(v) => v
              case None =>
                data = Some(f)
                data.get
            }
          }
      }
    }
  }

  private[this] val cache = new java.util.LinkedHashMap[T, Holder[R]](64, 0.75f, false) {
    override def removeEldestEntry(eldest: Entry[T, Holder[R]]) = size() > cacheSizeHint
  }

  override def apply(x: T) = {
    val holder: Holder[R] = {
      cache.get(x) match {
        case h: Holder[R] => h
        case null =>
          cache.synchronized {
            cache.get(x) match {
              case h: Holder[R] => h
              case null =>
                val h = new Holder[R]
                cache.put(x, h)
                h
            }
          }
      }
    }
    holder.getOrPut(f(x))
  }

}

object Memoize {

  def apply[T, R](f: T => R, cacheSizeHint: Int) = new Memoize[T, R](f, cacheSizeHint)

  def memfun2[T, R, F](f: F, cacheSizeHint: Int)(implicit e: Tupler[F, T => R]): F =
    e.untupled(new Memoize(e.tupled(f), cacheSizeHint))

}

sealed class Tupler[U, T](val tupled: U => T, val untupled: T => U)

object Tupler {

  implicit def function0[R]: Tupler[() => R, Unit => R] =
    new Tupler((f: () => R) => (_: Unit) => f(), (f: Unit => R) => () => f(()))

  implicit def function1[T, R]: Tupler[T => R, T => R] = new Tupler(identity, identity)

  implicit def function2[T1, T2, R]: Tupler[(T1, T2) => R, ((T1, T2)) => R] =
    new Tupler(_.tupled, Function.untupled[T1, T2, R])

}