package com.programmaticallyspeaking.ncd.nashorn

import java.lang.reflect.{InvocationHandler, Method}

import com.programmaticallyspeaking.ncd.boot.{Broker, BrokerConnection}
import org.slf4s.Logging

import scala.concurrent.Await
import scala.concurrent.duration.FiniteDuration

class AttachingHostProxy(broker: Broker, connectionTimeout: FiniteDuration) {

  def createHost(): NashornScriptHost = {
    val clazz = classOf[NashornScriptHost]
    java.lang.reflect.Proxy.newProxyInstance(clazz.getClassLoader, Array(clazz), new Handler).asInstanceOf[NashornScriptHost]
  }

  class Handler extends InvocationHandler with Logging {

    private var brokerConnection: Option[BrokerConnection] = None
    private object connectLock

    override def invoke(proxy: scala.Any, method: Method, args: Array[AnyRef]): AnyRef = {

      brokerConnection match {
        case Some(connection) =>
          val targetHost = connection.host
          val result = method.invoke(targetHost, args: _*)

          if (method.getName == "reset") {
            log.info("Detaching from target debugger")
            connection.disconnect()
            brokerConnection = None
          }

          result

        case None =>
          if (method.getName == "reset") {
            log.warn("Ignoring reset call when there is no connection!")
            return null
          }

          connectFirstThen { connection =>
            val targetHost = connection.host
            method.invoke(targetHost, args: _*)
          }
      }

    }

    private def connectFirstThen(f: BrokerConnection => AnyRef): AnyRef = {
      if (brokerConnection.isEmpty) {
        connectLock.synchronized {
          if (brokerConnection.isEmpty) {
            log.info("Attaching to debug target...")
            val futureConnection = broker.connect({
              _ =>
                // Do we need different handling depending on error or not?
                brokerConnection.foreach(_.disconnect())
                brokerConnection = None
            })
            val connection = Await.result(futureConnection, connectionTimeout)
            brokerConnection = Some(connection)
          }
        }
      }
      f(brokerConnection.get)
    }
  }
}