public class SARulesAgent extends Agent {
  private static final long serialVersionUID = -6154538349034427858L;
  public static final int MINIMUM_WIDTH = 300;
  public static final int MINIMUM_HEIGHT = 100;
  public static final String DEFAULT_AGENT_SRC =
      "// Reglas de máxima prioridad para salir del laberinto\n"
    + "DOWN OFFLIMITS -> MOVE DOWN.\n"
    + "RIGHT OFFLIMITS -> MOVE RIGHT.\n"
    + "LEFT OFFLIMITS -> MOVE LEFT.\n"
    + "UP OFFLIMITS -> MOVE UP.\n\n"

    + "// Reglas para moverse al primer sitio no visitado donde haya un hueco\n"
    + "// Siempre intenta acercarse a la esquina inferior derecha\n"
    + "DOWN FREE & DOWN ~VISITED => GO DOWN.\n"
    + "RIGHT FREE & RIGHT ~VISITED => GO RIGHT.\n"
    + "LEFT FREE & LEFT ~VISITED => GO LEFT.\n" + "UP FREE & UP ~VISITED => GO UP.\n\n"

    + "// Reglas para mover al agente si todo alrededor está visitado u\n"
    + "// ocupado. Utilizamos varias operaciones lógicas para demostrar\n"
    + "// la flexibilidad del lenguaje. Se pueden traducir como:\n"
    + "//     <dirección> FREE -> MOVE <dirección>.\n"
    + "Up Not Wall and Up ~Agent -> MOVE up.\n"
    + "not (left wall OR left agent) -> move left.\n"
    + "right !wall And right !agent -> move right.\n"
    + "!(down wall or down agent) -> move down.\n";

  private String m_code;
  private transient SituationActionErrorHandler m_error_handler;
  private transient ArrayList <SituationActionRule> m_rules;
  private transient boolean [][] m_visited;

  /**
   * Crea un agente basado en reglas de situación-acción con el comportamiento
   * por defecto.
   *
   * @param env
   *          Entorno donde se sitúa el agente.
   */
  public SARulesAgent (Environment env) {
    super(env);
    m_error_handler = new SituationActionErrorHandler();
    m_rules = new ArrayList <SituationActionRule>();
    m_code = DEFAULT_AGENT_SRC;
    compileCode();
  }

  /**
   * @return El código fuente actualmente cargado en el agente.
   */
  public String getCode () {
    return m_code;
  }

  /**
   * Cambia el código fuente cargado. Se debe llamar a {@link #compileCode()}
   * para que el nuevo código pueda ser ejecutado por el agente.
   * @param code
   *          Nuevo código fuente.
   */
  public void setCode (String code) {
    m_code = code;
  }

  /**
   * Convierte el código fuente guardado en m_code en la representación de las
   * reglas de situación-acción.
   *
   * @return {@code true} si la compilación fue exitosa y {@code false} si no.
   */
  public boolean compileCode () {
    m_error_handler.resetErrorList();

    ArrayList <SituationActionRule> rules = new ArrayList <SituationActionRule>();
    InputStream stream = new ByteArrayInputStream(m_code.getBytes(StandardCharsets.UTF_8));

    try {
      ANTLRInputStream input = new ANTLRInputStream(stream);
      SituationActionLexer lexer = new SituationActionLexer(input);
      CommonTokenStream tokens = new CommonTokenStream(lexer);
      SituationActionParser parser = new SituationActionParser(tokens);

      lexer.removeErrorListeners();
      parser.removeErrorListeners();
      lexer.addErrorListener(m_error_handler);
      parser.addErrorListener(m_error_handler);

      rules = new ArrayList <SituationActionRule>();
      for (Sa_ruleContext i: parser.program().sa_rule())
        rules.add(SituationActionRule.createFromTree(i));
    }
    catch (Exception e) {
      return false;
    }

    if (m_error_handler.hasErrors())
      return false;
    else {
      m_rules = rules;
      return true;
    }
  }

  /**
   * @return La lista de errores de compilación.
   */
  public ArrayList<String> getCompilationErrors () {
    return m_error_handler.getErrors();
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#setEnvironment(Environment)
   */
  public void setEnvironment (Environment env) {
    super.setEnvironment(env);
    Maze maze = m_env.getMaze();
    m_visited = new boolean [maze.getHeight()] [maze.getWidth()];
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#getAlgorithmName()
   */
  @Override
  public String getAlgorithmName () {
    return "Situation-Action Rules";
  }

  /*
   * (non-Javadoc)
   *
   * @see es.ull.mazesolver.agent.Agent#getAlgorithmColor()
   */
  @Override
  public Color getAlgorithmColor () {
    return Color.MAGENTA;
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#getNextMovement()
   */
  @Override
  public Direction getNextMovement () {
    // Recorremos las reglas y nos quedamos con la primera acción para la
    // que se cumple la situación (arquitectura de subsunción).
    for (SituationActionRule r: m_rules) {
      RuleAction act = r.getAction(this);
      if (act != null)
        return act.getDirection();
    }
    return Direction.NONE;
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#doMovement(maze.Direction)
   */
  @Override
  public void doMovement (Direction dir) {
    // Marcamos la celda actual como visitada
    m_visited[m_pos.y][m_pos.x] = true;
    super.doMovement(dir);
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#resetMemory()
   */
  public void resetMemory () {
    for (boolean [] i: m_visited)
      for (int j = 0; j < i.length; j++)
        i[j] = false;
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#getConfigurationPanel()
   */
  @Override
  public AgentConfigurationPanel getConfigurationPanel () {
    return new SARulesAgentConfigurationPanel(this);
  }

  /**
   * Indica si una celda adyacente al agente ha sido visitada ya por el mismo.
   *
   * @param dir
   *          Dirección en la que hay que mirar.
   * @return Si la celda adyacente en esa dirección ha sido visitada o no.
   */
  public boolean hasVisited (Direction dir) {
    Point p = dir.movePoint(m_pos);
    Maze maze = m_env.getMaze();

    if (!maze.containsPoint(p))
      return false;

    return m_visited[p.y][p.x];
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#duplicate()
   */
  @Override
  public Object clone () {
    SARulesAgent ag = new SARulesAgent(m_env);
    ag.setAgentColor(getAgentColor());
    ag.m_code = m_code;
    ag.m_rules = new ArrayList <SituationActionRule>(m_rules.size());
    for (SituationActionRule r: m_rules)
      ag.m_rules.add((SituationActionRule) r.clone());

    return ag;
  }

  /**
   * Extrae la información del objeto a partir de una forma serializada del
   * mismo.
   *
   * @param input
   *          Flujo de entrada con la información del objeto.
   * @throws ClassNotFoundException
   *           Si se trata de un objeto de otra clase.
   * @throws IOException
   *           Si no se puede leer el flujo de entrada.
   */
  private void readObject (ObjectInputStream input) throws ClassNotFoundException, IOException {
    input.defaultReadObject();

    m_error_handler = new SituationActionErrorHandler();
    m_rules = new ArrayList <SituationActionRule>();
    compileCode();
  }
} public static class AndContext extends SituationContext {
  public List <SituationContext> situation () {
    return getRuleContexts(SituationContext.class);
  }

  public SituationContext situation (int i) {
    return getRuleContext(SituationContext.class, i);
  }

  public TerminalNode AND () {
    return getToken(SituationActionParser.AND, 0);
  }

  public AndContext (SituationContext ctx) {
    copyFrom(ctx);
  }

  @Override
  public void enterRule (ParseTreeListener listener) {
    if (listener instanceof SituationActionListener)
      ((SituationActionListener) listener).enterAnd(this);
  }

  @Override
  public void exitRule (ParseTreeListener listener) {
    if (listener instanceof SituationActionListener)
      ((SituationActionListener) listener).exitAnd(this);
  }
} public static class DirectionContext extends ParserRuleContext {
  public TerminalNode LEFT () {
    return getToken(SituationActionParser.LEFT, 0);
  }

  public TerminalNode UP () {
    return getToken(SituationActionParser.UP, 0);
  }

  public TerminalNode RIGHT () {
    return getToken(SituationActionParser.RIGHT, 0);
  }

  public TerminalNode DOWN () {
    return getToken(SituationActionParser.DOWN, 0);
  }

  public DirectionContext (ParserRuleContext parent, int invokingState) {
    super(parent, invokingState);
  }

  @Override
  public int getRuleIndex () {
    return RULE_direction;
  }

  @Override
  public void enterRule (ParseTreeListener listener) {
    if (listener instanceof SituationActionListener)
      ((SituationActionListener) listener).enterDirection(this);
  }

  @Override
  public void exitRule (ParseTreeListener listener) {
    if (listener instanceof SituationActionListener)
      ((SituationActionListener) listener).exitDirection(this);
  }
} public static class OrContext extends SituationContext {
  public List <SituationContext> situation () {
    return getRuleContexts(SituationContext.class);
  }

  public SituationContext situation (int i) {
    return getRuleContext(SituationContext.class, i);
  }

  public TerminalNode OR () {
    return getToken(SituationActionParser.OR, 0);
  }

  public OrContext (SituationContext ctx) {
    copyFrom(ctx);
  }

  @Override
  public void enterRule (ParseTreeListener listener) {
    if (listener instanceof SituationActionListener)
      ((SituationActionListener) listener).enterOr(this);
  }

  @Override
  public void exitRule (ParseTreeListener listener) {
    if (listener instanceof SituationActionListener)
      ((SituationActionListener) listener).exitOr(this);
  }
} public static class ParensContext extends SituationContext {
  public SituationContext situation () {
    return getRuleContext(SituationContext.class, 0);
  }

  public TerminalNode NOT () {
    return getToken(SituationActionParser.NOT, 0);
  }

  public TerminalNode RIGHTPAR () {
    return getToken(SituationActionParser.RIGHTPAR, 0);
  }

  public TerminalNode LEFTPAR () {
    return getToken(SituationActionParser.LEFTPAR, 0);
  }

  public ParensContext (SituationContext ctx) {
    copyFrom(ctx);
  }

  @Override
  public void enterRule (ParseTreeListener listener) {
    if (listener instanceof SituationActionListener)
      ((SituationActionListener) listener).enterParens(this);
  }

  @Override
  public void exitRule (ParseTreeListener listener) {
    if (listener instanceof SituationActionListener)
      ((SituationActionListener) listener).exitParens(this);
  }
} public static class SingleTermContext extends SituationContext {
  public TermContext term () {
    return getRuleContext(TermContext.class, 0);
  }

  public SingleTermContext (SituationContext ctx) {
    copyFrom(ctx);
  }

  @Override
  public void enterRule (ParseTreeListener listener) {
    if (listener instanceof SituationActionListener)
      ((SituationActionListener) listener).enterSingleTerm(this);
  }

  @Override
  public void exitRule (ParseTreeListener listener) {
    if (listener instanceof SituationActionListener)
      ((SituationActionListener) listener).exitSingleTerm(this);
  }
} public static class SituationContext extends ParserRuleContext {
  public SituationContext (ParserRuleContext parent, int invokingState) {
    super(parent, invokingState);
  }

  @Override
  public int getRuleIndex () {
    return RULE_situation;
  }

  public SituationContext () {
  }

  public void copyFrom (SituationContext ctx) {
    super.copyFrom(ctx);
  }
} public static class TermContext extends ParserRuleContext {
  public TerminalNode NOT () {
    return getToken(SituationActionParser.NOT, 0);
  }

  public TerminalNode FREE () {
    return getToken(SituationActionParser.FREE, 0);
  }

  public TerminalNode AGENT () {
    return getToken(SituationActionParser.AGENT, 0);
  }

  public TerminalNode OFFLIMITS () {
    return getToken(SituationActionParser.OFFLIMITS, 0);
  }

  public TerminalNode WALL () {
    return getToken(SituationActionParser.WALL, 0);
  }

  public DirectionContext direction () {
    return getRuleContext(DirectionContext.class, 0);
  }

  public TerminalNode VISITED () {
    return getToken(SituationActionParser.VISITED, 0);
  }

  public TermContext (ParserRuleContext parent, int invokingState) {
    super(parent, invokingState);
  }

  @Override
  public int getRuleIndex () {
    return RULE_term;
  }

  @Override
  public void enterRule (ParseTreeListener listener) {
    if (listener instanceof SituationActionListener)
      ((SituationActionListener) listener).enterTerm(this);
  }

  @Override
  public void exitRule (ParseTreeListener listener) {
    if (listener instanceof SituationActionListener)
      ((SituationActionListener) listener).exitTerm(this);
  }
} public class MazeCell implements Serializable {
  private static final long serialVersionUID = 4328443829621010840L;

  private short m_cell;

  /**
   * Constructor por defecto. La celda creada está rodeada de muros.
   */
  public MazeCell () {
    m_cell |= Direction.UP.val;
    m_cell |= Direction.DOWN.val;
    m_cell |= Direction.RIGHT.val;
    m_cell |= Direction.LEFT.val;
  }

  /**
   * Cambia el estado de la dirección especificada. Si no había muro, ahora lo
   * hay y viceversa.
   *
   * @param dir
   *          Lado de la celda que se quiere modificar.
   */
  public void toggleWall (Direction dir) {
    if (hasWall(dir))
      unsetWall(dir);
    else
      setWall(dir);
  }

  /**
   * Pone un muro si no lo hay en la dirección especificada.
   *
   * @param dir
   *          Lado de la celda que se quiere modificar.
   */
  public void setWall (Direction dir) {
    m_cell |= dir.val;
  }

  /**
   * Quita el muro si lo hay en la dirección especificada.
   *
   * @param dir
   *          Lado de la celda que se quiere modificar.
   */
  public void unsetWall (Direction dir) {
    m_cell &= ~dir.val;
  }

  /**
   * Elimina todas las paredes de la celda.
   */
  public void removeWalls () {
    m_cell = 0;
  }

  /**
   * Indica si hay un muro en la dirección indicada.
   *
   * @param dir
   *          Lado de la celda que se quiere consultar.
   * @return Si hay una celda en esa dirección o no.
   */
  public boolean hasWall (Direction dir) {
    return (m_cell & dir.val) != 0;
  }

  /**
   * Enumeración de los diferentes estados que puede tener una celda de cara a
   * un agente cualquiera.
   */
  public static enum Vision {
    /**
     * Este estado significa que la celda está vacía y no hay una pared entre
     * las 2 celdas.
     */
    EMPTY,

    /**
     * Este estado significa que hay una pared entre la posición actual del
     * agente y la celda.
     */
    WALL,

    /**
     * Este estado significa que hay un agente sobre esa celda actualmente y no
     * hay nada interponiéndose en medio.
     */
    AGENT,

    /**
     * Este estado significa que la celda referenciada está fuera del laberinto
     * y además no hay una pared en medio.
     */
    OFFLIMITS;
  }

} public enum Direction implements Serializable {
  NONE  ((short) 0x00),
  UP    ((short) 0x01),
  DOWN  ((short) 0x02),
  LEFT  ((short) 0x04),
  RIGHT ((short) 0x08);

  /**
   * Número máximo de direcciones.
   */
  public static int MAX_DIRECTIONS = 5;

  /**
   * Valor asociado a una dirección (campo de bits).
   */
  public short val;

  private static Direction [] values = Direction.values();

  private Direction (short val) {
    this.val = val;
  }

  /**
   * Transforma un short en dirección, comparando sus valores directamente.
   *
   * @param value
   *          Valor (dentro de los valores posibles de dirección).
   * @return Dirección asociada a ese valor.
   */
  public static Direction fromValue (short value) {
    for (Direction i: values)
      if (i.val == value)
        return i;

    return null;
  }

  /**
   * Devuelve la dirección asociada a un índice.
   *
   * @param index
   *          Índice de la dirección. El orden es el siguiente:
   *          <ol start="0">
   *            <li>NONE</li>
   *            <li>UP</li>
   *            <li>DOWN</li>
   *            <li>LEFT</li>
   *            <li>RIGHT</li>
   *          </ol>
   * @return Dirección asociada al índice.
   */
  public static Direction fromIndex (int index) {
    return values[index];
  }

  /**
   * Extrae la dirección asociada al paso entre 2 posiciones contiguas.
   *
   * @param p1
   *          Posición de inicio.
   * @param p2
   *          Posición de destino.
   * @return La dirección que une los 2 puntos contiguos o {@code null} si los
   *         puntos no están contiguos,
   */
  public static Direction fromPoints (Point p1, Point p2) {
    Point diff = new Point(p2.x - p1.x, p2.y - p1.y);
    switch (diff.x) {
      case -1: // izquierda
        return diff.y == 0? Direction.LEFT : null;
      case 1: // derecha
        return diff.y == 0? Direction.RIGHT : null;
      case 0: // misma posición en X
        switch (diff.y) {
          case -1: // arriba
            return Direction.UP;
          case 1: // abajo
            return Direction.DOWN;
          case 0: // misma posición
            return Direction.NONE;
        }
      default:
        return null;
    }
  }

  /**
   * Crea una dirección de forma aleatoria.
   *
   * @return Una dirección aleatoria. No va a ser {@code Direction.NONE}.
   */
  public static Direction random () {
    return values[1 + (int) (Math.random() * 4.0)];
  }

  /**
   * Descompone la dirección en sus componentes x e y, con una magnitud de 1.
   *
   * @return Pareja con la descomposición de la dirección (x, y).
   */
  public Pair <Integer, Integer> decompose () {
    Pair <Integer, Integer> p = new Pair <Integer, Integer>(0, 0);
    switch (this) {
      case NONE:
        break;
      case UP:
        p.second = -1;
        break;
      case DOWN:
        p.second = 1;
        break;
      case LEFT:
        p.first = -1;
        break;
      case RIGHT:
        p.first = 1;
        break;
    }
    return p;
  }

  /**
   * Invierte la posición actual.
   *
   * @return Dirección contraria de la actual.
   */
  public Direction getOpposite () {
    switch (this) {
      case UP:
        return DOWN;
      case DOWN:
        return UP;
      case LEFT:
        return RIGHT;
      case RIGHT:
        return LEFT;
      default:
        return NONE;
    }
  }

  /**
   * Rota la dirección absoluta actual en el sentido indicado.
   *
   * @param rot
   *          Sentido de rotación que aplicar.
   * @return La nueva dirección rotada.
   */
  public Direction rotate (Rotation rot) {
    switch (this) {
      case UP:
        return rot == Rotation.CLOCKWISE? RIGHT : LEFT;
      case DOWN:
        return rot == Rotation.CLOCKWISE? LEFT : RIGHT;
      case LEFT:
        return rot == Rotation.CLOCKWISE? UP : DOWN;
      case RIGHT:
        return rot == Rotation.CLOCKWISE? DOWN : UP;
      default:
        return NONE;
    }
  }

  /**
   * Desplaza un punto en la dirección y lo guarda en un punto nuevo.
   *
   * @param p
   *          Punto que se desea mover en esta dirección.
   * @return Nuevo punto equivalente al indicado desplazado en esta dirección.
   */
  public Point movePoint (final Point p) {
    Pair <Integer, Integer> desp = decompose();
    return new Point(p.x + desp.first, p.y + desp.second);
  }

  /**
   * @return Si la dirección es vertical.
   */
  public boolean isVertical () {
    return this == UP || this == DOWN;
  }

  /**
   * @return Si la dirección es horizontal.
   */
  public boolean isHorizontal () {
    return this == LEFT || this == RIGHT;
  }

}
import es.ull.mazesolver.agent.rules.parser.SituationActionParser.SingleTermContext;
import es.ull.mazesolver.agent.rules.parser.SituationActionParser.SituationContext;
import es.ull.mazesolver.agent.rules.parser.SituationActionParser.TermContext;
import es.ull.mazesolver.maze.MazeCell;
import es.ull.mazesolver.util.Direction;
import es.ull.mazesolver.agent.SARulesAgent;
import es.ull.mazesolver.agent.rules.parser.SituationActionParser.AndContext;
import es.ull.mazesolver.agent.rules.parser.SituationActionParser.DirectionContext;
import es.ull.mazesolver.agent.rules.parser.SituationActionParser.OrContext;
import es.ull.mazesolver.agent.rules.parser.SituationActionParser.ParensContext;
/*
 * This file is part of MazeSolver.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright (c) 2014 MazeSolver
 * Sergio M. Afonso Fumero <theSkatrak@gmail.com>
 * Kevin I. Robayna Hernández <kevinirobaynahdez@gmail.com>
 */

/**
 * @file RulePredicate.java
 * @date 31/10/2014
 */
package es.ull.mazesolver.agent.rules;


/**
 * Representa una situación o predicado dentro de una regla de situación-acción.
 */
public abstract class RulePredicate implements Cloneable {
  protected boolean m_negated = false;

  /**
   * Distintos conectores de reglas que soporta el lenguaje.
   */
  private static enum RuleConnector {
    OR, AND;
  }

  /**
   * Construye un predicado a partir de una "situación" del árbol de parseo.
   *
   * @param ctx
   *          Nodo del árbol de parseo con un predicado o situación que se
   *          quiere procesar.
   * @return Predicado creado a partir del árbol.
   */
  public static RulePredicate createFromTree (SituationContext ctx) {
    // Lo que hay... (v_v)
    if (ctx instanceof OrContext)
      return createFromTree((OrContext) ctx);
    if (ctx instanceof AndContext)
      return createFromTree((AndContext) ctx);
    if (ctx instanceof ParensContext)
      return createFromTree((ParensContext) ctx);
    if (ctx instanceof SingleTermContext)