/**
 * /** FunctionDefinitionBase provides a common base for {@link
 * org.apache.openaz.xacml.pdp.policy.FunctionDefinition}s. The child classes derived from this are
 * of two types:
 *
 * <UL>
 *   <LI>Functions returning a single simple value of a type defined in {@link
 *       org.apache.openaz.xacml.std.datatypes.DataTypes}. These functions will all derive from
 *       {@link org.apache.openaz.xacml.pdp.std.functions.FunctionDefinitionSimple}.
 *   <LI>Functions returning a single bag with elements of a single type.
 *       <UL>
 *         <p>This base class contains the following components:
 *         <UL>
 *           <LI>The Identity for this function.
 *           <LI>The DataType of the data returned from this function. For Bags this means the
 *               DataType of the elements in the bag, or null if that is ambiguous.
 *           <LI>Commonly-used values.
 *         </UL>
 *
 * @param <O> the java class for the Output data type returned by the <code>FunctionDefinition
 *     </code>
 * @param <I> the java class for the Input data type expected in the arguments to the <code>
 *     FunctionDefinition
 *            </code>. Some functions have non-homogeneous arguments but may still have a main
 *     'type'.
 */
public abstract class FunctionDefinitionBase<O, I> implements FunctionDefinition {

  // The XACML identifier string for this particular function
  private Identifier id;

  // each function derived from this returns a single non-bag data value of the following type, or a
  // Bag
  // containing elements of this type
  private DataType<O> dataTypeReturn;

  // All functions have input arguments and expect them to be of a given type.
  // In some instances the argument gets multiple values of different types, but when the function
  // has a
  // 'type' associated with it's name
  // specific ones of the input must be of this type.
  // When an argument Input to the function is a Bag, the elements in that bag will be of this type.
  // This corresponds most closely to the 'type' in the function name (as in 'type'-bag or
  // 'type'-equals).
  private DataType<I> dataTypeArgs;

  // true = the return value from this function is a bag; false = return value is a single-value
  // DataType
  // object
  private boolean returnsBag;

  /*
   * For functions that return a Boolean result we create a single instance of the True/False return values
   * that they can share
   */
  protected static final ExpressionResult ER_TRUE =
      ExpressionResult.newSingle(DataTypeBoolean.AV_TRUE);
  protected static final ExpressionResult ER_FALSE =
      ExpressionResult.newSingle(DataTypeBoolean.AV_FALSE);

  /**
   * Creates a new <code>FunctionDefinitionBase</code> with the {@link
   * org.apache.openaz.xacml.api.Identifier} <code>idIn</code> as the function id.
   *
   * @param idIn the <code>Identifier</code> for this <code>FunctionDefinitionBase</code>
   */
  protected FunctionDefinitionBase(
      Identifier idIn,
      DataType<O> returnDataTypeIn,
      DataType<I> argumentDataTypeIn,
      boolean returnsBagIn) {
    this.id = idIn;
    this.dataTypeReturn = returnDataTypeIn;
    this.dataTypeArgs = argumentDataTypeIn;
    this.returnsBag = returnsBagIn;
  }

  /**
   * Returns a shortened version of the Id for this function, primarilly for use with error messages
   * to prevent them from becoming too long. This is a simple convenience method to reduce code
   * bloat.
   *
   * @return
   */
  public String getShortFunctionId() {
    return this.getId()
        .getUri()
        .toString()
        .substring(this.getId().getUri().toString().indexOf("function:"));
  }

  /**
   * Returns a shortened version of the given DataType Id, primarily for use with error messages to
   * prevent them from becoming too long. This is a simple convenience method to reduce code bloat.
   *
   * @param identifier expected to have '#' in it, and if no '#' should have ":data-type:"
   * @return
   */
  public String getShortDataTypeId(Identifier identifier) {
    String idString = identifier.stringValue();
    int index = idString.indexOf("#");
    if (index < 0) {
      index = idString.indexOf(":data-type:");
      if (index < 0) {
        return idString;
      } else {
        return idString.substring(index + 11);
      }
    } else {
      return idString.substring(index + 1);
    }
  }

  /**
   * Return a new Status that includes the name of this function in front of the original status'
   * message. This is a convenience method to reduce code bloat.
   *
   * @param originalStatu
   * @return
   */
  public Status getFunctionStatus(Status originalStatus) {
    return new StdStatus(
        originalStatus.getStatusCode(),
        getShortFunctionId() + " " + originalStatus.getStatusMessage());
  }

  //
  // Getters for the internal variables
  //

  @Override
  public Identifier getId() {
    return this.id;
  }

  @Override
  public Identifier getDataTypeId() {
    if (this.dataTypeReturn == null) {
      return null;
    } else {
      return this.dataTypeReturn.getId();
    }
  }

  public DataType<O> getDataType() {
    return this.dataTypeReturn;
  }

  /**
   * Return the Identifier for the Input Argument(s) DataType.
   *
   * @return
   */
  public DataType<I> getDataTypeArgs() {
    return this.dataTypeArgs;
  }

  @Override
  public boolean returnsBag() {
    return returnsBag;
  }
}
  @Override
  public ExpressionResult evaluate(
      EvaluationContext evaluationContext, List<FunctionArgument> arguments) {

    if (arguments == null || arguments.size() < 2) {
      return ExpressionResult.newError(
          new StdStatus(
              StdStatusCode.STATUS_CODE_PROCESSING_ERROR,
              getShortFunctionId()
                  + " Expected 2 or more arguments, got "
                  + ((arguments == null) ? "null" : arguments.size())));
    }

    // get the string to search for
    ConvertedArgument<URI> uriArgument =
        new ConvertedArgument<URI>(arguments.get(0), DataTypes.DT_ANYURI, false);
    if (!uriArgument.isOk()) {
      Status decoratedStatus =
          new StdStatus(
              uriArgument.getStatus().getStatusCode(),
              uriArgument.getStatus().getStatusMessage() + " at arg index 0");
      return ExpressionResult.newError(getFunctionStatus(decoratedStatus));
    }
    String uriString = uriArgument.getValue().toString();

    // remaining arguments are strings
    String[] stringValues = new String[arguments.size() - 1];

    for (int i = 1; i < arguments.size(); i++) {

      ConvertedArgument<String> stringArgument =
          new ConvertedArgument<String>(arguments.get(i), DataTypes.DT_STRING, false);
      if (!stringArgument.isOk()) {
        Status decoratedStatus =
            new StdStatus(
                stringArgument.getStatus().getStatusCode(),
                stringArgument.getStatus().getStatusMessage() + " at arg index " + i);
        return ExpressionResult.newError(getFunctionStatus(decoratedStatus));
      }

      stringValues[i - 1] = stringArgument.getValue();
    }

    // add each of the strings onto the URI
    for (int i = 0; i < stringValues.length; i++) {
      uriString += stringValues[i];
    }

    URI resultURI = null;
    try {
      resultURI = DataTypes.DT_ANYURI.convert(uriString);
    } catch (DataTypeException e) {
      String message = e.getMessage();
      if (e.getCause() != null) {
        message = e.getCause().getMessage();
      }
      return ExpressionResult.newError(
          new StdStatus(
              StdStatusCode.STATUS_CODE_SYNTAX_ERROR,
              getShortFunctionId() + " Final string '" + uriString + "' not URI, " + message));
    }

    return ExpressionResult.newSingle(
        new StdAttributeValue<URI>(XACML.ID_DATATYPE_ANYURI, resultURI));
  }