/*
   Copyright 2010 Aaron J. Radke

   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 cc.drx

import scala.language.existentials

object JailBreak{
  //--wrap objects with jailbreak configuration
  def apply[A](obj:A):JailBreak[A] = JailBreak(obj,obj.getClass)
  def apply[A](obj:A,className:String):JailBreak[A] = JailBreak(obj,forName(className))

  //--construct new objection
  def companion(className:String):JailBreak[_] = apply(className + "$")
  def apply(className:String):JailBreak[_] =
    apply(className,None)
  def apply(className:String, constructorName:String):JailBreak[_] =
    apply(className,Some(constructorName))
  def apply(className:String, constructor:Option[String]=None):JailBreak[_] = {
    val klass = forName(className)
    val obj = if(className endsWith "$") klass.getField("MODULE$").get(())
              else constructor match{
                case None => klass.getConstructor().newInstance() //java8: klass.newInstance
                case Some(name) => klass.getMethod(name).invoke(null)
              }
    JailBreak(obj,klass)
  }

  //TODO base JailBreak around a Class instead of an object
  private def forName(className:String) = java.lang.Class.forName(className)
  private def findClass(className:String):Option[java.lang.Class[_]] = //a type hole exists explictly becuase we don't reflectively know what it actually is
    Try{forName(className)}.toOption
  def field[A](name:String):Try[A] = Try{
    val args = name.split('.')
    val className = args.init.mkString(".")
    val fieldName = args.last
    val c = forName(className)
    c.getField(fieldName).get(c).asInstanceOf[A]
  }
}
/**Simple scala type inferencing wrapper around the java reflection api to break into protected fields
 * 
 * */
case class JailBreak[A](obj:A,klass:Class[_]){
 private def field(fieldName:String):java.lang.reflect.Field = {
   val reflectedField = klass.getDeclaredField(fieldName)
   reflectedField.setAccessible(true) //we need to be able to set this private method
   reflectedField
 }
 private def method(name:String,numArgs:Int):java.lang.reflect.Method = {
   val mopt = methods.filter(_.getName == name).find(_.getParameterTypes.size == numArgs)
   // println(s"found method $mopt")
   if(mopt.isEmpty) throw(new java.lang.RuntimeException(
      s"method name '$name' with $numArgs arguments does not exist in $this"
   ))
   val m = mopt.get
   m.setAccessible(true) //we need to be able to set this private method
   m
 }
 def fields = klass.getDeclaredFields
 lazy val methods = klass.getDeclaredMethods
 def update[B](fieldName:String,value:B):Unit = field(fieldName).set(obj,value)
 def apply[B](fieldName:String):B = field(fieldName).get(obj).asInstanceOf[B]
 // def /[B](fieldName:Symbol):B = field(fieldName.name).get(obj).asInstanceOf[B]

 //supper simple access, assumes field exists and the first name found matches the arguments... warning this may causes some things to break badly if these assumptiosn are not met, but using jailbreak itself is a dangerous thing so if you decide to be dangerous lets make it easy
 def invoke[B](methodName:String):B = method(methodName, 0).invoke(obj).asInstanceOf[B]
 def invoke[B](methodName:String,arg0:AnyRef):B = method(methodName,1).invoke(obj,arg0).asInstanceOf[B] //scala.AnyRef => java.lang.Object
 def invoke[B](methodName:String,arg0:AnyRef,arg1:AnyRef):B = method(methodName, 2).invoke(obj,arg0,arg1).asInstanceOf[B]
}