package org.nlogo.extensions.reflection

import org.nlogo.core.{ Program, Syntax }
import org.nlogo.core.Syntax.{ NumberType, ListType }
import org.nlogo.app.App
import org.nlogo.nvm.ExtensionContext
import org.nlogo.api.{ LogoListBuilder, Reporter, Command, Context, Argument, PrimitiveManager, DefaultClassManager, ExtensionException }
import org.nlogo.api.ScalaConversions._
import org.nlogo.headless.HeadlessWorkspace
import org.nlogo.lite.LiteWorkspace

class NetLogoReflectionScala extends DefaultClassManager {
  override def load(manager: PrimitiveManager) {
    manager.addPrimitive("globals", new Globals)
    manager.addPrimitive("breeds", new Breeds)
    manager.addPrimitive("procedures", new Procedures);
    manager.addPrimitive("current-procedure", new CurrentProcedure);
    manager.addPrimitive("callers", new Callers);
    manager.addPrimitive("turtles", new Turtles);
    //manager.addPrimitive("set-framerate", new SetFramerate);
  }
}

class Globals extends Reporter {
  override def getSyntax = Syntax.reporterSyntax(ret = ListType)
  override def report(args: Array[Argument], context: Context): AnyRef = {
    context match {
      case extContext: ExtensionContext => {
        if (extContext.workspace == null) {
          throw new ExtensionException(s"We need a workspace : $extContext");
        }
        val observer = extContext.workspace.world.observer
        (0 until observer.variables.length).map(observer.variableName(_)).toLogoList
      }
      case _ => throw new ExtensionException(s"Unknown context given : $context")
    }
  }
}

class Breeds extends Reporter {
  override def getSyntax = Syntax.reporterSyntax(ret = ListType)
  override def report(args: Array[Argument], context: Context): AnyRef = {
    context match {
      case extContext: ExtensionContext => {
        val breeds = new LogoListBuilder
        // add turtle vars as a separate tuple
        val turtleInfo = new LogoListBuilder
        turtleInfo.add("TURTLES")
        val workspace = extContext.workspace;
        val turtleVars = workspace.world.program.turtleVars.keys.toVector.toLogoList
        turtleInfo.add(turtleVars)
        breeds.add(turtleInfo.toLogoList)
        // now the rest of the breeds
        val otherBreeds = workspace.world.program.breeds.map {
          case (name, vars) =>
            val breedInfo = new LogoListBuilder
            breedInfo.add(name)
            breedInfo.add(vars.owns.toLogoList)
            breedInfo.toLogoList
        }
        otherBreeds.foreach(breeds.add)
        breeds.toLogoList
      }
      case _ => throw new ExtensionException(s"Unknown context given : $context")
    }
  }
}

class Turtles extends Reporter {
  override def getSyntax = Syntax.reporterSyntax(ret = ListType)
  override def report(args: Array[Argument], context: Context): AnyRef = {
    context match {
      case extContext: ExtensionContext => {
        val turtles = extContext.workspace.world.turtles.agents
        val allvariables = new LogoListBuilder
        turtles.forEach(turtle => {
          allvariables.add((0 until turtle.variables.length).map(turtle.getVariable(_).toString).toLogoList)          
        })
        allvariables.toLogoList
      }
      case _ => throw new ExtensionException(s"Unknown context given : $context")
    }
  }
}

/*
class SetFramerate extends Command {
  override def getSyntax = Syntax.reporterSyntax(ret = ListType)
  override def perform(args: Array[Argument], context: Context): AnyRef = {
    context match {
      case extContext: ExtensionContext => {
        if (extContext.workspace.isInstanceOf[HeadlessWorkspace]) {
          extContext.workspace.asInstanceOf[HeadlessWorkspace].frameRate(5.0)
        } else if (extContext.workspace.isInstanceOf[LiteWorkspace]) {
          extContext.workspace.asInstanceOf[LiteWorkspace].frameRate(5.0)
        }
      }
      case _ => throw new ExtensionException(s"Unknown context given : $context")
    }
  }
} 
*/

class Procedures extends Reporter {
  override def getSyntax = Syntax.reporterSyntax(ret = ListType)
  override def report(args: Array[Argument], context: Context): AnyRef = {
    context match {
      case extContext: ExtensionContext => {
        var workspace = extContext.workspace
        workspace.procedures.map {
          case (name, procedure) =>
            val procedureInfo = new LogoListBuilder
            procedureInfo.add(name)
            procedureInfo.add(if (procedure.isReporter) "REPORTER" else "COMMAND")
            procedureInfo.add(procedure.agentClassString)
            // args contains dummies (temp 'lets') so we don't include them.
            // localsCount contains number of lets so we just subtract that
            val argsCount = procedure.args.size - procedure.localsCount
            val args = procedure.args.take(argsCount).toLogoList
            procedureInfo.add(args)
            procedureInfo.toLogoList
        }.toVector.toLogoList
      }
      case _ => throw new ExtensionException(s"Unknown context given : $context")
    }
  }
}

class CurrentProcedure extends Reporter {
  override def getSyntax = Syntax.reporterSyntax(ret = ListType)
  override def report(args: Array[Argument], context: Context): AnyRef = {
    context match {
      case extContext: ExtensionContext => extContext.nvmContext.activation.procedure.name;
      case _ => throw new ExtensionException(s"Unknown context given : $context")
    }
  }
}

class Callers extends Reporter {
  override def getSyntax = Syntax.reporterSyntax(ret = ListType)
  override def report(args: Array[Argument], context: Context): AnyRef = {
    val callers = new LogoListBuilder
    context match {
      case extContext: ExtensionContext => {
        var activation = extContext.nvmContext.activation
        while (activation != null && activation.procedure != null) {
          callers.add(activation.procedure.name)
          activation = activation.parent
        }
        callers.toLogoList
      }
      case _ => throw new ExtensionException(s"Unknown context given : $context")
    }
  }
}
