(* ::Package:: *)

BeginPackage["NetLogo`",{"JLink`"}]

SetAttributes[Headless, Protected];

$NLHome = "";

JLink`InstallJava`Private`$disablePrefs = False;

Headless::usage := "Headless is an option for  NLStart[] that specifies whether or not NetLogo should be run with a graphical interface.  By default, Headless is False."

NLStart::usage := "NLStart[NetLogoHome] starts the J/Link interface and launches the version of NetLogo (6.2 or later) installed in the directory NetLogoHome. By default, the NetLogo-Mathematica Link runs in GUI-mode. By specifying the option Headless->True, NetLogo may operate in headless mode. This is recommended for conducting large experiments";

NLQuit::usage := "NLQuit[] quits J/Link";


NLSequence::usage := \
"NLSequence[{\!\(\*SubscriptBox[
StyleBox[\"expr\",\nFontSlant->\"Italic\"], \"1\"]\), \!\(\*SubscriptBox[
StyleBox[\"expr\",\nFontSlant->\"Italic\"], \"2\"]\), ...}] splices a list of Mathematica expressions into a string readable by NetLogo. NetLogo-Mathematica Link functions, such as NLCommand[], NLReport[], and their variants automatically apply NLSequence to their command and reporter arguments.  All atoms are converted to a NetLogo-readable string. Real numbers are rounded to 6 decimals of precision. RGBColor[] and Hue[] expressions are converted to NetLogo color specifications.  Any Mathematica Lists contained in \!\(\*SubscriptBox[
StyleBox[\"expr\",\nFontSlant->\"Italic\"], \"1\"]\), \!\(\*SubscriptBox[
StyleBox[\"expr\",\nFontSlant->\"Italic\"], \"2\"]\), etc are recursively converted into NetLogo-formated lists. All data are eventually converted to strings followed by a blank space";


NLLoadModel::usage := \
"NLLoadModel[\!\(\*
StyleBox[\"path\",\nFontSlant->\"Italic\"]\)] opens the NetLogo model in the specified path";
NLCommand::usage := \
"NLCommand[\!\(\*
StyleBox[SubscriptBox[
StyleBox[\"command\",\nFontSlant->\"Italic\"], \"1\"],\nFontSlant->\"Italic\"]\)\!\(\*
StyleBox[\",\",\nFontSlant->\"Italic\"]\)\!\(\*
StyleBox[\" \",\nFontSlant->\"Italic\"]\)\!\(\*
StyleBox[SubscriptBox[
StyleBox[\"command\",\nFontSlant->\"Italic\"], \"2\"],\nFontSlant->\"Italic\"]\)\!\(\*
StyleBox[\",\",\nFontSlant->\"Italic\"]\)\!\(\*
StyleBox[\"...\",\nFontSlant->\"Italic\"]\)] tells NetLogo to execute a series of commands. The sequence of commands are automatically spliced into NetLogo expressions using NLSequence[].";
NLReport::usage := "NLReport[\!\(\*
StyleBox[\"reporter\",\nFontSlant->\"Italic\"]\)] reports the value of given string.";
NLReportList::usage := \
"NLReportList[{\!\(\*SubscriptBox[
StyleBox[\"reporter\",\nFontSlant->\"Italic\"], \"1\"]\), \!\(\*SubscriptBox[
StyleBox[\"reporter\",\nFontSlant->\"Italic\"], \"2\"]\), ...}] returns a list of values reportered by each reporter, \!\(\*SubscriptBox[
StyleBox[\"reporter\",\nFontSlant->\"Italic\"], \"1\"]\), \!\(\*SubscriptBox[
StyleBox[\"reporter\",\nFontSlant->\"Italic\"], \"2\"]\), \[Ellipsis].  If \!\(\*SubscriptBox[
StyleBox[\"reporter\",\nFontSlant->\"Italic\"],
StyleBox[\"i\",\nFontSlant->\"Italic\"]]\) is a list, it will automatically be spliced into a NetLogo string using NLSequence[].";

NLDoCommand::usage := \
"NLDoCommand[\!\(\*
StyleBox[\"command\",\nFontSlant->\"Italic\"]\), n] tells NetLogo to repeat a \!\(\*
StyleBox[\"command\",\nFontSlant->\"Italic\"]\) \!\(\*
StyleBox[\"n\",\nFontSlant->\"Italic\"]\) times.";
NLDoCommandWhile::usage = \
"NLDoCommandWhile[\!\(\*
StyleBox[\"command\",\nFontSlant->\"Italic\"]\), \!\(\*
StyleBox[\"condition\",\nFontSlant->\"Italic\"]\)] tells NetLogo to repeat a \!\(\*
StyleBox[\"command\",\nFontSlant->\"Italic\"]\) until the NetLogo \!\(\*
StyleBox[\"condition\",\nFontSlant->\"Italic\"]\) is met.";

NLDoReport::usage = \
"NLDoReport[\!\(\*
StyleBox[\"command\",\nFontSlant->\"Italic\"]\), \!\(\*
StyleBox[\"reporter\",\nFontSlant->\"Italic\"]\), \!\(\*
StyleBox[\"n\",\nFontSlant->\"Italic\"]\)] returns a list generated by instructing NetLogo to execute a \!\(\*
StyleBox[\"command\",\nFontSlant->\"Italic\"]\) and returning the result of \!\(\*
StyleBox[\"reporter\",\nFontSlant->\"Italic\"]\), \!\(\*
StyleBox[\"n\",\nFontSlant->\"Italic\"]\) times.";

NLDoReportList::usage = \
"NLDoReport[\!\(\*
StyleBox[\"command\",\nFontSlant->\"Italic\"]\)\!\(\*
StyleBox[\",\",\nFontSlant->\"Italic\"]\)\!\(\*
StyleBox[\" \",\nFontSlant->\"Italic\"]\){\!\(\*SubscriptBox[
StyleBox[\"reporter\",\nFontSlant->\"Italic\"], \"1\"]\), \!\(\*SubscriptBox[
StyleBox[\"reporter\",\nFontSlant->\"Italic\"], \"2\"]\), ...}, \!\(\*
StyleBox[\"n\",\nFontSlant->\"Italic\"]\)] returns a list generated by instructing NetLogo to execute a \!\(\*
StyleBox[\"command\",\nFontSlant->\"Italic\"]\) and returning the results given by a list of reporters, \!\(\*
StyleBox[\"n\",\nFontSlant->\"Italic\"]\) times.  If \!\(\*SubscriptBox[
StyleBox[\"reporter\",\nFontSlant->\"Italic\"],
StyleBox[\"i\",\nFontSlant->\"Italic\"]]\) is a list, it will automatically be spliced into a NetLogo string using NLSequence[].";

NLDoReportWhile::usage = "NLDoReport[\!\(\*
StyleBox[\"command\",\nFontSlant->\"Italic\"]\), \!\(\*
StyleBox[\"reporter\",\nFontSlant->\"Italic\"]\), \!\(\*
StyleBox[\"condition\",\nFontSlant->\"Italic\"]\)] returns a list generated by instructing NetLogo to execute a \!\(\*
StyleBox[\"command\",\nFontSlant->\"Italic\"]\) and returning the result of \!\(\*
StyleBox[\"reporter\",\nFontSlant->\"Italic\"]\) until the condition given by \!\(\*
StyleBox[\"test\",\nFontSlant->\"Italic\"]\) in NetLogo is satisfied.";
NLDoReportListWhile::usage = \
"NLDoReport[\!\(\*
StyleBox[\"command\",\nFontSlant->\"Italic\"]\), \!\(\*
StyleBox[\"{\",\nFontSlant->\"Italic\"]\)\!\(\*
StyleBox[SubscriptBox[
StyleBox[\"reporter\",\nFontSlant->\"Italic\"], \"1\"],\nFontSlant->\"Italic\"]\)\!\(\*
StyleBox[\",\",\nFontSlant->\"Italic\"]\)\!\(\*
StyleBox[\" \",\nFontSlant->\"Italic\"]\)\!\(\*
StyleBox[SubscriptBox[
StyleBox[\"reporter\",\nFontSlant->\"Italic\"], \"2\"],\nFontSlant->\"Italic\"]\)\!\(\*
StyleBox[\",\",\nFontSlant->\"Italic\"]\)\!\(\*
StyleBox[\" \",\nFontSlant->\"Italic\"]\)\!\(\*
StyleBox[\"...\",\nFontSlant->\"Italic\"]\)\!\(\*
StyleBox[\"}\",\nFontSlant->\"Italic\"]\), \!\(\*
StyleBox[\"n\",\nFontSlant->\"Italic\"]\)] returns a list generated by instructing NetLogo to execute a \!\(\*
StyleBox[\"command\",\nFontSlant->\"Italic\"]\) and returning the results given by a list of reporters, n times. If \!\(\*SubscriptBox[
StyleBox[\"reporter\",\nFontSlant->\"Italic\"],
StyleBox[\"i\",\nFontSlant->\"Italic\"]]\) is a list, it will automatically be spliced into a NetLogo string using NLSequence[].";


NLGetGraph::usage = \
"NLGetGraph[] returns the graph of a network as a list of pairs of who numbers of agents that have links belonging to the generic \!\(\*
StyleBox[\"link\",\nFontWeight->\"Bold\"]\) breed.
NLGetGraph[\!\(\*
StyleBox[\"linkSet\",\nFontSlant->\"Italic\"]\)] returns the graph of a network as a list of pairs of who numbers of agents that have links belonging to the \!\(\*
StyleBox[\"linkSet\",\nFontSlant->\"Italic\"]\) agentset.";

NLGetPatches::usage = \
"NLGetPatches[\!\(\*
StyleBox[\"patchVariable\",\nFontSlant->\"Italic\"]\)] returns a matrix whose entries are the variable \!\(\*
StyleBox[\"patchVariable\",\nFontSlant->\"Italic\"]\) of each patch in the world";

NLDiagnostics::usage = \
"NLDiagnostics[\!\(\*
StyleBox[\"nlHome\",\nFontSlant->\"Italic\"]\)] returns information useful in diagnosing problems with NetLogo Mathematica Link.
Pass the desired home directory to it as an argument to see where it's looking for files and whether they are found";

NLJavaDiagnostics::usage = \
"NLJavaDiagnostics[] returns information about the java environment used in running NetLogo";


Options[NLStart] = {Headless -> False};
(*$MessagePrePrint=.*)

Begin["`Private`"]




(* Mathematica to NetLogo conversion utilities *)
colorConversionRules = {RGBColor[r_,g_,b_]:>(" (rgb "<>ToString[255.*r]<>" "<>ToString[255.*g]<>" "<>ToString[255.*b]<>") "),
Hue[h_]:>("hsb "<>ToString[255.*h]<>" 1 1" ),
Hue[h_,s_,b_]:>(" hsb "<>ToString[255.*h]<>" "<>ToString[255.*h]<>" "<>ToString[255.*b]<>" ")};

NLSequence[l_] :=
    StringJoin[(If[
              StringTake[#, -1] === " ", #, # <>
                " "]) & /@ ((ToString[
                  If[! AtomQ[#], "[" <> NLSequence[#] <> "]",
                    If[NumericQ[#] && ! IntegerQ[#], N[#, 6], #]]] &) /@ (l /.colorConversionRules))];
NLSequence[s_String] := s;




(* Exception Handling *)
NetLogo::compilerException="`1`";
NetLogo::engineException="`1`";
NetLogo::unknownException="`1`";

NLCompilerExceptionQ[s_] := StringMatchQ[s, __ ~~"org.nlogo.compiler.Compiler.exception"~~___];
ParseCompilerException[s_] := StringReplace[s, error__~~" at position"~~___ :>  error];

NLEngineExceptionQ[s_] := StringMatchQ[s, ___ ~~ "org.nlogo.nvm.EngineException" ~~ ___];
ParseEngineException[s_]:= StringReplace[First[StringSplit[s,"\n"]], "org.nlogo.nvm.EngineException:" ~~ error__ :>  error];

(* executes an NetLogo-related function and handles exceptions*)

SetAttributes[HandleNLExceptions,HoldAll];
HandleNLExceptions[NLFunction_]:= Block[{exString, javaException, $JavaExceptionHandler, result, $MessagePrePrint},
	$MessagePrePrint =.;
	$JavaExceptionHandler = (exString = #3)&;
	result = NLFunction;
    javaException = GetJavaException[];
	If[UnsameQ[javaException, Null],
		ReleaseJavaObject[javaException];
		Which[
			NLCompilerExceptionQ[exString], Message[NetLogo::compilerException, ParseCompilerException[exString]],
			NLEngineExceptionQ[exString], Message[NetLogo::engineException, ParseEngineException[exString]],
			True, Message[NetLogo::unknownException, exString]
		];
		$Failed,
		result
	]
];


NLLoadModel::notfound = "The model located at `1` could not be found";
NLStart::netlogonotfound = "NetLogo could not be found in: `1`";
NLStart::nopath = "NetLogo cannot be started without a valid NetLogo 6.2 path";

NLLoadModel[path_String] := If[FileNames[path] == {},
	Message[NLLoadModel::notfound, path];,
	NLink@loadModel[path];
	];
NLCommand[c_String] := HandleNLExceptions[NLink@command[c]];
NLCommand[cl_List] := HandleNLExceptions[NLink@command[NLSequence[cl]]];
NLCommand[params___] := HandleNLExceptions[NLink@command[NLSequence[List[params]]]];
NLReport[reporter_] := HandleNLExceptions[NLink@report[NLSequence[reporter]]];
NLReportList[reporters_List] := HandleNLExceptions[NLink@report[NLSequence /@ reporters]];
NLDoCommand[c_, imax_] :=
    NLCommand[NLSequence[{"repeat ", imax, "[", c, "]"}]];
NLDoCommandWhile[c_, test_String] :=
    NLCommand[{"while [ ", test, "] [", c, "]"}];
NLDoReport[c_, reporter_, imax_] :=
  HandleNLExceptions[NLink@doReport[NLSequence[c], NLSequence[reporter], imax]];
NLDoReportList[c_, reporters_List, imax_] :=
  HandleNLExceptions[NLink@doReport[NLSequence[c], NLSequence /@ reporters, imax]];
NLDoReportWhile[c_, reporter_, test_] :=
 HandleNLExceptions[NLink@doReportWhile[NLSequence[c], NLSequence[reporter], test]];
NLDoReportListWhile[c_, reporters_List, test_] :=
 HandleNLExceptions[NLink@doReportWhile[NLSequence[c], NLSequence /@ reporters, test]];

NLGetGraph[linkAgentSet_String] := (#[[1]] -> #[[2]]) & /@
    (Floor/@NLReport["[list ([who] of end1) ([who] of end2)] of "<>linkAgentSet]);
NLGetGraph[] := NLGetGraph["links"];

NLGetPatches[patchVariable_] := Partition[NLReport["map [ [p] -> ["<>patchVariable<>"] of p ] sort patches"],Floor[NLReport["world-width"]]];

Clear[NLJarPaths];
Clear[NLDiagnostics];
Clear[NLJavaDiagnostics];
Clear[NLStart];

NLJarPaths[nlPath_] :=
  Module[{NLPath = nlPath, NLJarDir = If[StringMatchQ[$System,"*Mac*"], FileNameJoin[{nlPath,"Java"}], FileNameJoin[{nlPath,"app"}]]},
    List[SelectFirst[FileNames["netlogo-*.jar", NLJarDir], Not @* StringContainsQ["mac"]], FileNameJoin[{NLPath,"Mathematica Link","mathematica-link.jar"}]]];

Options[NLStart] = {Headless -> False};

NLStart[opts:OptionsPattern[]] := NLStart[$NLHome] /; ValueQ[$NLHome];
NLStart[opts:OptionsPattern[]] := NLStart[""] /; !ValueQ[$NLHome];

NLStart[NetLogoPath_String, opts:OptionsPattern[]] :=
  Module[{NLPath = NetLogoPath, alreadySetPathQ = False},
	(* No NetLogo path specified: ask user to locate path *)
	If[ NLPath == "",
		NLPath = SystemDialogInput["Directory", WindowTitle->"Please locate your NetLogo installation directory"];
		alreadySetPathQ = True;
	];

	(* NetLogo path specified is bogus in some way *)
	If[!alreadySetPathQ && !AllTrue[NLJarPaths[NLPath], FileExistsQ]
		&& ChoiceDialog["Mathematica could not find your NetLogo installation directory in: "<> NLPath <> ". Would you like to locate it?"],
        NLPath = SystemDialogInput["Directory", WindowTitle->"Please locate your NetLogo installation directory"];
	];

	If[NLPath == $Canceled, (* throw error and return if user does not specify a path *)
		Message[NLStart::nopath];
		Return[$Failed];
	];

	(* Report back error for users that still cannot get cannot find the right path *)
	If[! AllTrue[NLJarPaths[NLPath], FileExistsQ],
		Message[NLStart::netlogonotfound, NLPath];
		Return[$Failed];
	];

	(* Reinitialize NLink *)
  (* only set after all paths are legit *)
	$NLHome = NLPath;
	UninstallJava[];
	Apply[AddToClassPath, NLJarPaths[NLPath]];
	InstallJava[FilterRules[{opts}, Options[InstallJava]]];
	NLink = JavaNew[LoadJavaClass["NLink"], ! OptionValue[Headless]];
];

NLDiagnostics[NetLogoPath_String] :=
  Module[{nlPath = NetLogoPath},
    Module[{jarPaths = NLJarPaths[nlPath]},

      <| "providedPath"   -> nlPath,
         "jarLookupPaths" -> jarPaths,
         "foundJars"      -> Select[jarPaths, FileExistsQ],
         "missingJars"    -> Select[jarPaths, Not[FileExistsQ]] |>
    ]
  ];


NLJavaDiagnostics[opts:OptionsPattern[]] :=
  Module[{},
    UninstallJava[];
    InstallJava[FilterRules[{opts}, Options[InstallJava]]];
    LoadJavaClass["java.lang.System"];

    <| "javaVersion" -> System`getProperty["java.version"],
       "javaArchitecture" -> System`getProperty["sun.arch.data.model"] |>
  ];

NLQuit[] := UninstallJava[];

End[]

EndPackage[]
