2APL

A Practical Agent Programming Language

Net2APL

Introduction

Net2APL is an BDI Agent Framework library, written and meant for Java. The source code can be found on https://bitbucket.org/goldenagents/net2apl.git

It’s a continuation of a previous framework called OO2APL, which in turn was originally an Object Oriented (/Java) based approach at an update of 2APL, a BDI Agent definition language.

net2apl.core

The OO2APL library, and by extension Net2APL, are based on the idea that Agents as a concept can make a valuable contribution to real-world applications.

For this purpose, the 2APL programming language was refurbished as an Object Oriented Design Pattern (as in the well known Gemma et. al. book), and subsequently reimplemented in Java as OO2APL.

This design pattern, the Object Oriented Agent Pattern, is described in section 3 of ‘Design Patterns for Multi-Agent programming’ [Dastani & Testerink, 2016].

Net2APL is a continuation of the OO2APL framework which can be distributed over multiple physical systems.

All of these where originally bases on the BDI (‘Believes, Desires, Intentions’) approach to Multi Agent Systems, which states that an Agent;

  • formulates intentions –plans of action–,
  • in order to fulfill its desires –statements that represent the various goals an Agent may have–,
  • but can only do so according to its beliefs –statements that represent the knowledge –

Intentions are represented by the ‘Plan’ class in Net2APL. A plan is a (relatively) small step that an Agent executes in pursuit of a desire (‘Goal’) or other ‘Trigger’ (such as a message).

‘Goal’s are the class that most directly represents the desires of an Agent. In a more general sense, the ’Trigger’ class, which is the superclass of ‘Goal’ could be said to represent everything analogous to an Agents desires, but sometimes cutting out the ‘middle-man’.

Reference

Core

Note that the ‘core’ packages are a library that contains only the M.A.S. functionality itself. Whenever possible, just use plain Java. Anything else should probably be somewhere else, away from ‘core’.

core.agent

This package contains the Agent-class, as well as most of the smaller concepts closely tied to it.

Agent

An Agent object is mostly a container to hold all the various different components an Agent consists of. Note that these are internals, of import to the Agent platform, and should not be confused with the more abstract ‘knowledge’ an Agent has, which is built on top of these. Most notably and of relevance to the explanation of the concept are the ContextContainer, the various lists of Trigger-s and Goal-s, the PlanSchemeBase, and the list of DeliberationStep.

  • The ContextContainer holds the various Contexts, each of which are a coherent collection of variables that represent the state of knowledge within an Agent.
  • A PlanShemeBase represents the ability of an Agent to make decisions, for which it checks against its current Goal(s), and/or which are precipitated by its current (internal or external) Triggers, or subclasses thereof, such as a message-class.
  • It’s current decided upon Plan-s are stored here as well, ready to be retrieved by the platform.

See (as always) the descriptions of those various classes for a more in-depth discussion of those concepts.

Note that, while an Agent of a new type can be created as a subclass, it’s not intended for the programmer to override methods or add variables directly. Instead, a class that extends AgentArguments handles all PlanScheme-s should be given as an argument (either directly, in the case of ‘new Agent’, or in the constructor as a super call in case a new class is derived). Variables that represent the knowledge of the new Agent can be added as one or more (classes derived from) Context in the constructor of (a subclass of) AgentArguments.

Note also that the Agent needs to have a Platform supplied. This is done so that the Agent can register itself during (the latter part of) creation, and also send messages via the Messenger supplied by the Platform.

Finally, and Agent also contains the DeliberationStep-s it will run through each deliberation-cycle. While its not expected that we’ll use this feature (in any other way than the default way) in practice, this was done so that different kinds of Agent (not to be confused with the types of subclass of Agent an implementor might create) can not only be created, but even run side-by-side within the same system. What is meant by ‘kinds’ in this paragraph is a (potentially) deep alteration of how an Agent goes about its deliberation-cycle; a different kind of Agent might not even have to be BDI!

This last part might be redundant, since the system as a whole is supposed to be BDI, and even if this changes, it should in that case very likely change for the system in it’s entirety. It’s a candidate for removal or at least refactoring, but doesn’t have priority, since it doesn’t hinder us, and it’s not clear how far it’s embedded into the current system (causing potential introduction of new bugs).

AgentArguments

Everything that makes an Agent a specific type of Agent, should be handled by subclasses of AgentArguments, which should be supplied to any new Agent upon construction. Mainly, these are PlanScheme-s and Context-s, which can be derived from those superclasses. PlanScheme-s represent what an Agent does, and can be added to AgentArguments with the various ‘AgentArguments.add[…]PlanScheme’ methods. A Context represents what an Agent knows, and any subclass of Context can be added to an Agent (via Arguments) with the ‘AgentArguments.addContext’ method. It is recommended that either of this is done in the constructor of any subclass of ArgentArguments, via a call to ‘super’.

AgentContextInterface

An AgentContextInterface represents all the current Agents Context to a SubPlanInterface, which is the part of a Plan that handles one specific type of Trigger.

AgentCreationFailedException

An Exception thrown when the creation of an Agent fails.

AgentDeathListener

The AgentDeathListener-interface can be implemented to be notified on the destruction of an Agent. Note that it’s used internally; programmers that make use of this library should make the Agent send a message instead.

AgentID

AgentID is an unique identifier of a single Agent, even across platforms. Mostly it consists of a random UUID, a host, and a port. The UUID is technically sufficient to guarantee uniqueness, but the host/port addition lets the AgentID also function as a kind of address.

User-defined properties can be added to the AgentID, for example a nickname, for easier debugging and administration/human readability purposes.

AgentKillSwitch

Represents the ability to ‘kill’ an Agent to the Platform class. Should not be used outside of the Platform-class.

Context

A Context is used to bundle the state of all knowledge an Agent knows about some closely related matters. Context is an empty interface that is supplied to make clear to the programmer what is expected, namely something that will mostly be a collection of variables, with perhaps some small convenience-methods ‘thrown in’.

An Agent can have multiple Context-s; for example, and Agent that is registered to a DirectoryFacilitator might have a DirectoryFacilitatorContext, to store the relevant information about that situation, but also it’s own specific subclass of Context for the specific type of Agent that it is.

All actual ‘business logic’ should be implemented by/via some sort of PlanScheme instead. Contexts can be added to an Agent via the AgentArguments a programmer is supposed to supply to a new Agent on construction (specifically, the ‘addContext’ method of AgentArguments).

Context can be retrieved for inspection, writes and updates during the execution of a plan via the ‘getContext’ method of PlanToAgentInterface, which is supplied by the Platform to the ‘to be executed’ plan-method.

ContextContainer

A set of Context-s, each of which can be retrieved by their class-type. Used (internally) to prevent lists of ‘Object’ and/or casting from more generic types.

Goal

Subclass of Trigger. A Goal is something the Agent works towards, and checks each deliberation-cycle whether it’s been achieved yet.

PlanToAgentInterface

Represents the Agent requesting the execution of the current plan to the plan-method. Mostly used to retrieve Context-s, but can be used for various other more complicated commands, such as ‘repeatWhile’, ‘getNotifiedWhenFinished’. Should probably have a way to send a message and/or retrieve the relevant messenger; the way this is handled now is to retrieve the Agent itself via a hack in the interface. (Either that or all useful methods should be subsumed by the Agent class itself, and PlanToAgentInterface could then be done away with. This would however expose the entire Agent class to the implementor of a plan, which may cause confusion over which methods are supposed to be used internally only, as opposed to freely available to any implementor.)

Trigger

A Trigger is the superclass of all events and issues an Agent is supposed to react to or work towards, whether those are internal Goal-s, external Messages, or some other type of actionable. Like Context, Trigger is an empty interface, merely there to guide the programmer to the correct meaning of certain situations.

Plans are always made ‘on behalf’ of some Trigger, after which those Plans become available for execution to (the deliberation-cycle of) the Platform.

core.defaults.context

Defaults contains all behaviors that the system adopts on default, when nothing else is specified.

DefaultConcurrencyContext

A ConcurrencyContext that only has a single thread, which should be enough for most implementation, since most of the heavier tasks are expected to be within Agents themselves, which can split those into multiple (single) runnables themselves.

core.defaults.deliberationstep

This sub-package contains all the default deliberation-steps.

ApplyExternalTriggerPlanSchemes

When executed, applies all extant external triggers for that Agent to the applicable PlanScheme-s in-order to see which ones should be executed during this step.

ApplyGoalPlanSchemes

When executed, checks the applicable PlanScheme-s if and how any Goals of this Agent should be pursued during this deliberation-cycle.

ApplyInternalTriggerPlanSchemes

When executed, applies all extant internal triggers for that Agent to the applicable PlanScheme-s in-order to see which ones should be executed during this step.

ApplyMessagePlanSchemes

When executed, checks the applicable PlanScheme-s if and how any Messages this Agent has received should be processed during this deliberation-cycle.

DefaultDeliberationStep

Since they are very similar, all Apply[…]PlanSchemes classes inherit from the DefaultDeliberationStep as a common superclass.

ExecutePlans

The final step in the deliberation-cycle process, this step does the actual execution of the currently applicable plans retrieved by the Apply[…]PlanSchemes ‘execute’ methods.

core.defaults.messenger

The default messenger, and related classes.

DefaultMessenger

The default messenger implements the Messenger class with the raw MessengerInterface, and as such can send any message that implements that interface (a subclass of Trigger). The downside of the DefaultMessenger is that the MessageInterface isn’t standardized, and, more importantly, the DefaultMessenger can only send messages locally!

We don’t rely on the DefaultMessenger, and there is some argument for it to be removed, in favor of FIPAMessenger.

MessageReceiverNotFoundException

This exception is thrown when a messenger, or processes called by it, (which could be any messenger, not just the default one) can’t find (one of) the recipient(s) of a message.

core.deliberation

This package contains the machinery that makes any potential deliberation-cycle work. See also core.defautls.deliberationstep, for the current (and future) default. (Which should perhaps be folded into here.)

DeliberationRunnable

Wraps/executes a deliberation-cycle into a single class.

On registering an Agent with a Platform (which in turn is done during the creation of an Agent, when the implementor supplies the Platform as an argument), the Platform will then immediately create a DelliberationRunnable, and feed it to the threadpool the Platform holds.

When scheduled for execution, the deliberation-cycle (of the agent) will run once. It will be rescheduled for sometime in the future if the agent is not done according to its ‘isDone’ method. The agent is killed in case it is done, or if a ‘DeliberationStepException’ occurs. In either which case, the Agent will be killed and removed from the Platform.

DeliberationStep

An interface each deliberation-step has to adhere to. A deliberation-step can basically be anything, but usually implies fetching some sort of Plan (which can best be seen as a sort of closure to be executed later) from the Agent, altough one of the DeliberationStep-s (the last one in ach cycle) is ‘execute all collected Plans of this Agent’.

See also the ‘default’ (should be ‘standard’ perhaps) DeliberationStep-s in the core.defaults.deliberationstep package.

DeliberationStepException

An exception that can be thrown when a DeliberationStep executes. An error grave enough that the offending Agent is to be ‘killed’ (removed from the threadpool), but gracefully, without of course destabilising the Platform, or the execution of other Agent-s.

Not actually thrown in practice.

SelfRescheduler

A convenience class to expose (to the Agent) the ability to reschedule the execution of its own plans. This is what is used to get out of sleep mode as well.

core.fipa, core.fipa.[acl|mts]

This sub-package is lifted almost wholesale from another open-source project that already implemented FIPA messaging. There are a few changes, like the encoding/decoding messages into byte-arrays (excepting ‘object-content’), and updating the performatives to be Enum-class, but that’s about it. As such, documentation is already available.

(However, a few classes have been remarked upon here anyway, in-order to display some hints.)

FIPAAgentState

Within FIPA, an Agent can have only a few states:

  • initiated, which is only true for an Agent that has just been created (and possibly still busy with setup),
  • active, the ‘normal’ state,
  • suspended, put to sleep by the system, ready to wake up on an event,
  • waiting, for a specific reply
  • transit, only when ‘in transit’ between two Platforms (the system currently lacks this capability).
FIPAMessenger

Implements Messenger.

MessageInterface

An extension to the Trigger interface that contains a few methods that every message-class should implement.

This is closely tied to the Messenger, and although the FIPA package uses it, it’s not clear what it’s doing here.

Performative

Any FIPA message must have any one of the 23 ‘Performatives’. This is closely linked to speech-action theory, which in turn is closely linked to BDI theory.

core.fipa.ams

A.M.S. stands for Agent Management System, and should contain all (FIPA defined) ‘management’ functions (apart from those basic ones already in Platform) that we wish to implement/adhere to.

ActionStatus

Not used. Should be implemented if closer adherence to FIPA is needed (‘A status indication delivered by a service showing the success or failure of an action’), but can be removed otherwise.

DirectoryFacilitator

DirectoryFacilitator (in conjunction with DirectoryFacilitatorContext) form the ‘Yellow Pages’ of the system, in that it administers (the DF itself) a ledger (DFContext) of which Agent is subscribed to which service (and which Agents are services in the first place). For a single DirectoryFascilitator, this task is for the local system only, but if a DF can be made aware of another DF (during creation/startup, or afterwards ‘as if it where’ a normal service), then it’ll forward any request to all known ‘colleage’ DFs on other systems (just once, so this message doesn’t keep bouncing around needlessly).

The DirectoryFacilitator is implemented as an Agent, so it’ll receive any requests for services or to subscribe as a Message.

See also DirectoryFacilitatorContext, which implements the ledger itself, as opposed to the more Agent based tasks, like the the communication with Agents.

DirectoryFacilitatorContext

This class stores the Yellow-Pages. That means that for every Agent subscribed to it in the local system, it has a ledger for which services it provides, and to which services it’s subscribed.

It has a fair number of convenience-methods to access the data in a way that presents the details of implementation as more high level variables. Also, since every (type of) service added has the potential to alter which Agents are subscribed to a (Agents that function as a type of) service, it’s just easier to have a ‘method’ in the context that, for example, adds a subscription, rather than to have to alter the minutiae of the ledger on the outside.

See also DirectoryFacilitator.

core.logging

Package contains all logging functionality for the core. This should be in core, especially since the core is still in development.

ConsoleLogger

Log to console. This is an option, not a default, not only because the implementor might want other logging capabilities (especially in a web-based application), but also since core is a library, and libraries should never log to console by default (for similar reasons).

Loggable

Wrapper for any log-able event.

MessageLogContext

A storage and retrieval class for all sent messages.

NullLogger

The implementor might not want to deal with logging (for example, in a small test application). NullLogger ignores any Loggable.

core.messaging

A package that (as it is now) should contain all generic interfaces for Messaging. MessageInterface should probably be moved to here, since it has very little to do with any FIPA detail, and is used by the Messenger interface class, but this is only relevant if we choose to keep the ability to have other types of Messenger than FIPAMessenger (and thus keep the distinction between the relevant packages). (Note that we don’t/shouldn’t depend on DefaultMessenger for any serious use.)

Messenger

An interface all classes that implement messaging should adhere to. Such a class should at least be capable of sending and receiving a certain type of Trigger (to be specified as a Generics-parameter during creation of any messenger). It should also at least be able to indicate whether it supports decoding/encoding these messages/Triggers as a bytestream, which will often depend on the type of message the Messenger is capable of sending. If this is the case, then the Messenger can also be used (in a process to distribute messages) non-locally (but see also NetNode for this issue).

Currently, we plan to use FIPAMessenger as the default, and so perhaps this interface could just be erased, and that class be used instead everywhere.

core.plan
Plan

A Plan is a ‘closure’-like or small executable function that was created by a PlanScheme, which is in turn generated by the ‘business-logic’ of the Agent. If the Plan was created to achieve a specific goal, then it will be canceled once the goal is achieved.

PlanExecutionError

Exception (supposed to be?) thrown (to the internals, where it’ll probably signal that the Agent is to be killed) when a Plan which doesn’t have its own error-handling, is executed that encountered an exception.

Comment from Bas on the only instance where the exception is actually thrown: “// TODO: design and implement a proper plan execution error that hints that the selector should ensure the correct type”

PlanScheme

Given any Trigger, return either a Plan or Plan.UNINSTANTIATED, to be executed later. An Agent can contain multiple PlanSchemes, of differing types (goal, internal, external and message). PlanSchemes can added to Agents with the add[…]PlanScheme methods, which take FunctionalPlanSchemeInterface, which can (and for clarity, probably should) be methods themselves.

PlanSchemeBase

PlanSchemeBase is a convenience class that collects all the different types of PlanSchemes of an Agent.

PlanSchemeBaseArguments

Not used. May not ever be if we don’t add different types of PlanSchemes next to the ones we already have.

TriggerInterceptor

Used internally to make sure ‘suspendToNextDeliberationCycle’ (and derivatives thereof, such as ‘repeatWhile’) in PlanToAgentInterface works correctly.

core.plan.builtin

The plan.builtin sub-package hosts some convenience-classes, so the implementor won’t need to reprogram these types of Plans each time they are needed. This is more important than it may seem on first read, since some of them (like FunctionalPlanScheme and SubPlanInterface) are involved in the current recommended way for implementors to implement Plans.

ConcurrencyContext

An interface, of which any implementation should allow an Agent to execute code concurrently to the rest of the agent. This is intended for heavy/long calculations which may block the agent’s execution. Adding any sort of ConcurrencyContext to an agent (in its AgentComponentFactory) enables the waitForX methods in the PlanToAgentInterface.

Bass says “By using a context it is easier to share for instance this service among agents so that not each agent spawns one or more threads, which might be problematic in a large multi-agent system.” However, our approach will likely be to have multiple Platforms running as programs (even on a single physical system) with just a few Agents, so it’s not clear whether we’d actually want to use it.

DecoupledPlan, DecoupledPlanBodyInterface, EnhancedTriggerInterceptor, EnhancedTriggerInterceptorBuilder, InstantiableRunOnceDecoupledPlan

These are all used internally to make the ‘waitFor[…]’ methods of PlanToAgentInterface work. It isn’t clear at the moment whether we’ll ever use this functionality, but I’d recommend against removing them at the moment.

FunctionalPlanScheme

Represents a ‘functional’ (in the sense of the functional programming paradigm) interface internally, allowing PlanSchemes to be thought of as closures as well (which produce the Plans that will eventually be executed, which may also be thought of as closures). Despite the seemingly rather convoluted explanation, this does make it easier (or at the very least more concise) for the implementor of Plan(Scheme)s. This is to be thought of as the go-to when developing new Plan(Scheme)s.

See also FunctionalPlanSchemeInterface.

FunctionalPlanSchemeInterface

Used to –either implicitly, as a lambda, or explicitly, by implementing the interface (see the Example), similar to how a Context is added to the Agent– to allow the implementor to implement a FunctionalPlanScheme.

InternalTriggerReturnValue

Used internally by the ‘getNotifiedWhenFinished’ (in PlanToAgentInterface) to facilitate the ‘return future’ functionality for Agent that make use of any (non-default) ConcurrencyContext.

NoConcurrencyContextException

An exception thrown when there is no ConcurrencyContext when ‘getConcurrencyContext’ is called in PlanToAgentInterface

RunOncePlan

‘RunOncePlan’ is an abstract class that facilitates executing a plan just once. This is very useful for plans that run once on start-up (or shut-down) of an Agent, and is also used (internally) when performing a ‘repeatWhile’ (in PlanToAgentInterface).

SubPlanInterface

Used to create ‘sub-plans’ that are executed across multiple deliberation cycles. We use this, since the ‘getPlan’ method of any FunctionalPlanSchemeInterface should deliver a SubPlanInterface (likely to make it possible to indeed execute across multiple cycles).

SuspensionTrigger

Used to suspend plans over one or more deliberation cycles. Used by ‘suspendToNextDeliberationCycle’ (and therefore ‘repeatWhile’) in PlanToAgentInterface. See also TriggerInterceptor.

core.platform

This package holds Platform and related classes, the backbone/glue of the system.

NetNode

NetNode wraps around any (local) Messenger capable of encoding and decoding messages, in-order to give the system the capability to send messages to other ports/hosts (other instances). Whenever a Platform is created, a Messenger should be provided as an argument.

The implementor won’t need to use a NetNode themselves: If the system is non-local, and the Messenger is capable of encoding/decoding messages, it’s wrapped in a NetNode.

Whenever a NetNode receives a message (see MessageInterface), it checks whether the address is on the same host/port as it is. If so, then the Message is handed to the wrapped Messenger. If not, the message is encoded, and sent to a NetNode on another system/instance, where the process repeats (and will be handed to the local Messenger over there, when sent directly).

Platform

Platform is the glue that holds (one instance) of the system together. It contains all agents on that instance, it starts the threadpool, it starts the DirectoryFacilitator, etc.

One of the ways we could continue, which we have discussed and was the plan for a while, is to create an instance for each Agent. In that case, the role of the Platform(-class) would be severely diminished, and may even be removed entirely eventually. Still, this raises some questions; how do we still get (efficient) DirectoryFascilitator/Yellow Pages services, and how will messaging be handled? Though we could perhaps just build-in those features into the Agent itself somehow.

PlatformNotFoundException

An exception currently only thrown when an Agent doesn’t have a reference to platform when the getPlatform method is called on that class. This can only happen when someone passed null as a parameter during Agent-creation, which will crash anyway. Perhaps remove?

Example

Main.java
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.Set;

import org.uu.nl.net2apl.core.*;
import org.uu.nl.net2apl.core.agent.Agent;
import org.uu.nl.net2apl.core.agent.AgentCreationFailedException;
import org.uu.nl.net2apl.core.agent.AgentID;
import org.uu.nl.net2apl.core.fipa.FIPAMessenger;
import org.uu.nl.net2apl.core.messaging.Messenger;
import org.uu.nl.net2apl.core.platform.Platform;

public class Main {
    
    public static void main(String[] args) {

        /* First we parse the command-line arguments.
         * It's not really all that relevant here (or part of the framework), but see the class if you want to know the details. 
         */
        ArgParser.ParsedArgs argStruct = ArgParser.parseArgs(args);
        
        /* In order to create a platform, we first need a messenger-object.
         * In this example, we need a messenger that can handle messages that can be (de)serialized, since the platforms may be on different machines.
         * (Note: This might just become the standard in the future, in which case you'd just create the platform.)
         */
        Messenger< ? > messenger = new FIPAMessenger();
        
        /* Platform creation.
         */
        Platform platform;
        if (argStruct.host != null || argStruct.listenPort >= 0 || argStruct.otherDfs.size() > 0) {
            /* If there is another platform that we know of at startup (because the admin. fed it to us as a command-line parameter in this case),
             *   the platform should be made aware of it, so it can initiate a handshake procedure with the other platform/directory facilitator. 
             */
            platform = Platform.newPlatform(2, messenger, argStruct.host, argStruct.listenPort, argStruct.otherDfs);
        } else {
            /* Otherwise, this is either a small stand-alone program, or the first platform (so we can't know any other directory facilitator yet). 
             */
            platform = Platform.newPlatform(2, messenger);
        }
        
        /* Create a yellow-pages agent. It's assumed that, in the case where Agents rely on each other as services (instead of being given the exact ID of other Agents on startup).
         * This will most often be the case when the system can have different configurations (more than one kind of a certain service for instance), or is distributed. 
         */
        try {
            Agent yellowPages = platform.newDirectoryFacilitator();
            
            /* We'll print out the ID of the new DF here, so it can be used to 'feed' the next program/platform. 
             */
            System.out.println("Created DirectoryFacilitator, AgentID:");
            System.out.println(yellowPages.getAID().toString());
            
        } catch (URISyntaxException | AgentCreationFailedException ex) {
            System.err.println("Failed to create yellow-pages (= directory facilitator) Agent.");
            System.exit(1);
        }
        
        /* Create two 'HelloWorld' Agents.
         * A hello world agent will register as a hello world service (via the yellow-pages),
         * then ask (that same/the) yellow-pages whether there are any other hello-world 'services' around,
         * lastly initiate a   
         */
        try {
            Agent helloA = new HelloWorldAgent(platform);
            Agent helloB = new HelloWorldAgent(platform);
        } catch (URISyntaxException ex) {
            System.err.println("Failed to create a Hello World Agent.");
            System.exit(1);
        }
        
        /* Note that the program does not exit, as the Agents are still active (albeit not doing anything after the initial messages, since no new hello-world agents are created.)   
         */
    }
}			
				
HelloWorldAgent.java
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;

import org.uu.nl.net2apl.core.agent.Agent;
import org.uu.nl.net2apl.core.agent.AgentArguments;
import org.uu.nl.net2apl.core.agent.AgentContextInterface;
import org.uu.nl.net2apl.core.agent.AgentCreationFailedException;
import org.uu.nl.net2apl.core.agent.AgentID;
import org.uu.nl.net2apl.core.agent.Context;
import org.uu.nl.net2apl.core.agent.PlanToAgentInterface;
import org.uu.nl.net2apl.core.agent.Trigger;
import org.uu.nl.net2apl.core.defaults.messenger.MessageReceiverNotFoundException;
import org.uu.nl.net2apl.core.fipa.acl.ACLMessage;
import org.uu.nl.net2apl.core.fipa.acl.Performative;
import org.uu.nl.net2apl.core.fipa.ams.DirectoryFacilitator;
import org.uu.nl.net2apl.core.fipa.mts.Envelope;
import org.uu.nl.net2apl.core.plan.Plan;
import org.uu.nl.net2apl.core.plan.PlanExecutionError;
import org.uu.nl.net2apl.core.plan.builtin.FunctionalPlanSchemeInterface;
import org.uu.nl.net2apl.core.plan.builtin.RunOncePlan;
import org.uu.nl.net2apl.core.plan.builtin.SubPlanInterface;
import org.uu.nl.net2apl.core.platform.Platform;
import org.uu.nl.net2apl.core.platform.PlatformNotFoundException;

/* The HelloWorldAgent-class is a convenience-class, holding together the (HelloWorld)AgentArguemnts and (HelloWorld)Context.
 * We could also have created a new Agent by performing something like 'Agent a = new Agent(platform, new XYZAgentArguments());'.
 */
public class HelloWorldAgent extends Agent {

    static String SERVICE_NAME = "HelloWorld";
    
    public HelloWorldAgent(Platform p) throws URISyntaxException {
        super(p, new HelloWorldAgentArguments());
    }
    
    /* A Context can be thought of as a set of knowledge an agent has about certain things.
     * Taken together, all Contexts an Agents has, should represent everything it knows
     * (and occasionally supporting methods for dealing elegantly with this knowledge,
     * or even capabilities, so things an agent may need to do at some point).
     * This could have been handled in the agent(s) itself,
     * but this allows for greater/easier modularity when building complex Agents with multiple contexts. 
     */
    static class HelloWorldContext implements Context {
        
        private Set alreadyContacted = new HashSet<>();
        private int replyCount = 0;
        
        public Set getNewContacts(Collection subscribers) {
            Set result = new HashSet<>();
            subscribers.forEach( (AgentID aid) -> { if (! alreadyContacted.contains(aid)) { result.add(aid); } } );
            return result;
        }
        
        public int nextReplyCount() {
            return ++replyCount;
        }
    }
    
    static class HelloWorldPlan implements FunctionalPlanSchemeInterface {
        
        /* The vast majority of plans any Agents will actually perform (other than the startup- and shutdown-plans) are precipitated by messages, mostly from other Agents.  
         */
        public SubPlanInterface getPlan(final Trigger trigger, final AgentContextInterface contextInterface) {
            
            if (trigger instanceof ACLMessage) {
                ACLMessage received = (ACLMessage) trigger;

                switch (received.getPerformative()) {
                default:
                    return SubPlanInterface.UNINSTANTIATED;
                    
                    /* The HelloWorld-agent should send a message back, with the AGREE performative, if an 'inform' message is received:
                     */
                case REQUEST:
                    /* As you may notice, we return a function/closure here, instead of performing the action 'ourselves'.
                     * All of the closures from all Agents are gathered for the current time-step, and only then are they executed.
                     */
                    return (planInterface) -> {

                        /* Since we're inside the closure here, constructed in a static method, we need the planInterface to get most relevant information. 
                         * Please note that the Nickname of an Agent doesn't count for equality tests between AgentID's.
                         */
                        String myName = planInterface.getAgentID().getShortLocalName();
                        
                        /* A lot of other necessary data is probably from the message that has just been received:
                         */
                        String theirName = received.getSender().getShortLocalName();
                        String greeting = received.getContent();

                        /* Often, some bit of info has to be recollected from, or remembered to, a context.
                         * In this case, both (via a convenience method in the relevant context itself.) 
                         */
                        int replyCount = planInterface.getContext(HelloWorldContext.class).nextReplyCount();
                        
                        System.out.println("I'm " + myName + " and " + theirName + ", after my request, just greeted me with the message '" + greeting + "'!");

                        try {
                            planInterface.getAgent().sendMessage(createMessage(planInterface.getAgentID(), received.getSender(), "I've received your message. You're the " + replyCount + "-st/nd/th HelloWorldAgent I've replied to!"));
                        } catch (MessageReceiverNotFoundException | PlatformNotFoundException e) {
                            System.err.println("Can't send message.");
                            System.exit(1);
                        }
                    };
                    
                    /* Confirmation that the other Agent got our message, and has responded. 
                     */
                case AGREE:
                    /* In a similar vein to what happend with the 'REQUEST' performative:
                     */
                    return (planInterface) -> {
                        String myName = planInterface.getAgentID().getShortLocalName();
                        String theirName = received.getSender().getShortLocalName();
                        String greeting = received.getContent();
                        
                        System.out.println("I'm " + myName + " and " + theirName + " just replied to me with the message '" + greeting + "'!");
                    };
                }
            }

            /* SubPanInterface.UNINSTANCIATED is a placeholder that lets the caller know no plan will need to be run in this cyclo for this Agent.
             */
            return SubPlanInterface.UNINSTANTIATED;
        }
        
        /* If a lot of fields are specified, sending a message can get quite verbose, so it's advisable to write a separate method if a lot of similar looking mesasges need to be sent. 
         */
        private static ACLMessage createMessage(AgentID aidMe, AgentID aidThem, String reply) {
            Envelope envelope = new Envelope();
            envelope.setFrom(aidMe);
            envelope.addTo(aidThem);
            envelope.addIntendedReceiver(aidThem);
            
            ACLMessage message = new ACLMessage(Performative.AGREE);
            message.addReceiver(aidThem);
            message.addReplyTo(aidMe);
            message.setSender(aidMe);
            message.setContent(reply);
            message.setEnvelope(envelope);

            return message;
        }
    }
    
    /* The ...Arguments subclass is really the glue that hangs a specific kind of Agent together.
     * Where 'Agent' class can be seen as instantiating a generic agent, 'AgentArguments' is everything that makes an agent a specific one.
     * This could have been handled by inheritance, but the current method was chosen to facilitate easier composition,
     * though this may change in the future as it makes the use of it less transparent to new users of the platform.
     * 
     *  You may notice that we inherit from 'RegisterableArguments' rather than 'AgentArguments'.
     *  'RegisterableArguments' is a convenience-class made to facilitate offering/subscribing to a Service easier. 
     */
    static class HelloWorldAgentArguments extends DFRegistration {

        static Random rnd = new Random(); 
        
        public HelloWorldAgentArguments() {
            super(SERVICE_NAME, SERVICE_NAME);
            
            super.addContext(new HelloWorldContext());
            
            /* We'd add any initial plans an Agent might have with 'super.addInitialPlan(...);' here.
             * There is a similar method for shut-down-plans.
             */
            super.addMessagePlanScheme(new HelloWorldPlan());
        }
    }   
}