package jp.sf.amateras.mirage.scala
import jp.sf.amateras.mirage.bean.PropertyWrapperImpl
import scala.tools.scalap.scalax.rules.scalasig.{ScalaSigParser, ScalaSigPrinter}
import java.io._
import java.lang.reflect._

class OptionFieldPropertyWrapper(name: String, field: Field) extends PropertyWrapperImpl(name, null, null, field) {

  override def get(instance: AnyRef): AnyRef = {
    val value = super.get(instance)
    value match {
      case Some(x: AnyRef) => x
      case None => null
    }
  }

  override def set(instance: AnyRef, value: AnyRef): Unit = {
    if(value == null){
      super.set(instance, None)
    } else {
      super.set(instance, Some(value))
    }
  }

  override def getType(): Class[_] = {
    val clazz = super.getType

    val optionType =
      if (detectScalapOnClasspath()) optionTypeFromScalaSig(getField)
      else throw new RuntimeException("scalap not found on classpath.")

    optionType match {
      case Some(x) => x
      case None => throw new RuntimeException("Failed to retreive Option type.")
    }
  }

  private def detectScalapOnClasspath(): Boolean = {
    try {
      Class.forName("scala.tools.scalap.scalax.rules.scalasig.ByteCode")
      true
    }catch{
      case cnfe : ClassNotFoundException => false
    }
  }

  private def optionTypeFromScalaSig(member: Member): Option[Class[_]] = {
    val scalaSigOption = ScalaSigParser.parse(member.getDeclaringClass())
    scalaSigOption flatMap { scalaSig =>
      val syms = scalaSig.topLevelClasses
      // Print classes
      val baos = new ByteArrayOutputStream
      val stream = new PrintStream(baos)
      val printer = new ScalaSigPrinter(stream, true)
      for (c <- syms) {
        if (c.path == member.getDeclaringClass().getName())
          printer.printSymbol(c)
      }
      val fullSig = baos.toString
      val matcher = """\s%s : scala.Option\[scala\.(\w+)\]?""".format(member.getName).r.pattern.matcher(fullSig)
      if (matcher.find) {
        matcher.group(1) match {
          case "Int" => Some(classOf[scala.Int])
          case "Short" => Some(classOf[scala.Short])
          case "Long" => Some(classOf[scala.Long])
          case "Double" => Some(classOf[scala.Double])
          case "Float" => Some(classOf[scala.Float])
          case "Boolean" => Some(classOf[scala.Boolean])
          case "Byte" => Some(classOf[scala.Byte])
          case "Char" => Some(classOf[scala.Char])
          case _ => None //Unknown scala primitive type?
        }
      } else
        None //Pattern was not found anywhere in the signature
    }
  }

}