/*
 * $Id: BeanShellExpressionValidator.java,v 1.1 2006/09/28 04:59:19 swampwallaby Exp $
 *
 * Copyright (c) 2008-2013 by Brent Easton
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License (LGPL) as published by the Free Software Foundation.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, copies are available
 * at http://www.opensource.org.
 */
package bsh;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

/**
 * Validate a single line BeanShell expression. 
 * Build a list of variable references in the expression.
 * 
 * This Class must be defined in package bsh to allow access to 
 * package visible elements in the bsh library.
 *
 */
public class BeanShellExpressionValidator {
  
  protected String expression;
  protected List<String> variables = new ArrayList<String>();
  protected List<String> methods = new ArrayList<String>();
  protected String error;
  protected boolean valid = false;
  
  /**
   * Build a new Validator and validate the expression
   * @param expression Expression to validate
   */
  public BeanShellExpressionValidator(String expression) {
    this.expression = expression; 
    valid = validate();
  }
  
  /**
   * Is the expression valid?
   * @return valid
   */
  public boolean isValid() {
    return valid;
  }
  
  protected void setValid(boolean b) {
    valid = b;
  }
  
  /**
   * Return a list of Variable references in the expression
   * @return List of variables
   */
  public List<String> getVariables() {
    return variables;
  }
  
  /**
   * Return a list of Methods called by the expression
   * @return List of Methods
   */
  public List<String> getMethods() {
    return methods;
  }
  
  /**
   * Return an Error Message if no valid
   * @return
   */
  public String getError() {
    return error;
  }
  
  protected void setError(String s) {
    error = s;
  }
  
  /**
   * Validate the expression
   * 
   * @return Expression validity
   */
  protected boolean validate() {
    final String expr = stripBraces(expression);
    
    setError("");
    try {
      Parser p = new Parser(new StringReader(expr + ";"));
      for (;;) {
        if (p.Line()) {
          return true;
        } 
        else {
          final SimpleNode node = p.popNode();
          if (! processNode(node)) {
            return false;
          }
        }
      }
    }
    catch (ParseException e) {
      setError(e.getMessage());
      return false;
    }
    catch (TokenMgrError e) {
      setError(e.getMessage());
      return false;
    }
  }
  
  /**
   * If the expression is surrounded by Vassal expression braces {}
   * replace them with spaces so that it will validate and report errors
   * in the correct location
   * 
   * @param s Expression
   * @return stripped expression
   */
  public static String stripBraces(String s) {
    String expr = s;
    if (s.trim().startsWith("{") && s.trim().endsWith("}")) {
      final int start = s.indexOf("{");
      final int end = s.lastIndexOf("}");
      StringBuffer buffer = new StringBuffer(s.length());
      for (int i = 0; i < s.length(); i++) {
        if (i == start || i == end) {
          buffer.append(' ');
        }
        else {
          buffer.append(s.charAt(i));        
        }
      }
      expr = buffer.toString();
    }
    return expr;
  }
  
  /**
   * Process a Parser Node and extract any Variable and Method references.
   * Assignemnts are not allowed in an expression, so flag as an error
   * @param node Parser Node
   */
  protected boolean processNode (SimpleNode node) {
    if (node == null) {
      return true;
    }
    
    if (node instanceof BSHAmbiguousName) {
      final String name = ((BSHAmbiguousName) node).getText().trim();
      if ((node.parent instanceof BSHMethodInvocation)) {
        if (! methods.contains(name)) {
          methods.add(name);
        }
      }
      else if (! variables.contains(name)) {
        variables.add(name);
      }
    }
    else if (node instanceof BSHAssignment) {
      setError("Assignemts (=) not allowed in Expressions. See Help");
      return false;
    }
    else {
      if (node.children != null) {
        for (int i = 0; i < node.children.length; i++) {
          if (! processNode(node.getChild(i))) {
            return false;
          }
        }
      }
    }
    return true;
  }  
  
}