/**
 * A {@link SubdialogueCall} is a {@link VoiceXmlOutputTurn} that invokes another external dialogue.
 *
 * <p>Parameters may be passed to the subdialogue and return values may be retrieved if the invoked
 * subdialogue ends with a <code>&lt;return&gt;</code> instruction.
 *
 * @author Nu Echo Inc.
 * @see Parameter
 * @see <a
 *     href="http://www.w3.org/TR/voicexml20/#dml2.3.4">http://www.w3.org/TR/voicexml20/#dml2.3.4</a>
 */
public class SubdialogueCall extends VoiceXmlOutputTurn {
  public static final String SUBDIALOGUE_RESULT_VARIABLE_NAME = "subdialogue";

  private static final String SUBDIALOGUE_INVOCATION_TURN_TYPE = "subdialogue";

  private static final String POST_DIALOGUE_SCRIPT_PROPERTY = "postDialogueScript";
  private static final String SUBDIALOGUE_PARAMETERS_PROPERTY = "subdialogueParameters";
  private static final String SUBMIT_URI_PROPERTY = "uri";
  private static final String SUBMIT_PARAMETERS_PROPERTY = "submitParameters";
  private static final String SUBMIT_METHOD_PROPERTY = "submitMethod";
  private static final String FETCH_CONFIGURATION_PROPERTY = "fetchConfiguration";

  private final String mUri;
  private Collection<Parameter> mParameters = Collections.emptyList();
  private VariableList mSubmitParameters = new VariableList();
  private SubmitMethod mMethod = SubmitMethod.get;
  private DocumentFetchConfiguration mFetchConfiguration;
  private String mPostDialogueScript;

  /**
   * @param name The name of this turn. Not empty.
   * @param uri The URI of the subdialogue. Not empty.
   */
  public SubdialogueCall(String name, String uri) {
    super(name);
    Assert.notEmpty(uri, "uri");
    mUri = uri;
  }

  /**
   * @param parameters A list of {@link Parameter} that will be passed to the subdialogue. Not null.
   */
  public final void setSubdialogueParameters(Collection<Parameter> parameters) {
    Assert.noNullValues(parameters, "parameters");
    mParameters = new ArrayList<Parameter>(parameters);
  }

  /**
   * @param subdialogueParameters A list of {@link Parameter} that will be passed to the
   *     subdialogue. Not null.
   */
  public final void setSubdialogueParameters(Parameter... subdialogueParameters) {
    setSubdialogueParameters(asListChecked(subdialogueParameters));
  }

  /** @param submitParameters A list of variable to submit when invoking the URI. Not null. */
  public final void setSubmitParameters(VariableList submitParameters) {
    Assert.notNull(submitParameters, "submitParameters");
    mSubmitParameters = submitParameters;
  }

  /**
   * @param method The HTTP method used to invoke the subdialogue (GET or POST). <code>null</code>
   *     to use the VoiceXML platform default
   * @see SubmitMethod
   */
  public final void setMethod(SubmitMethod method) {
    mMethod = method;
  }

  /**
   * @param fetchConfiguration The {@link DocumentFetchConfiguration}. <code>null</code> to use the
   *     VoiceXML platform default.
   */
  public final void setFetchConfiguration(DocumentFetchConfiguration fetchConfiguration) {
    mFetchConfiguration = fetchConfiguration;
  }

  /** @param postDialogueScript The ECMAScript script to execute after subdialogue invocation. */
  public final void setPostDialogueScript(String postDialogueScript) {
    mPostDialogueScript = postDialogueScript;
  }

  public final String getUri() {
    return mUri;
  }

  public final Collection<Parameter> getVoiceXmlParameters() {
    return unmodifiableCollection(mParameters);
  }

  public final VariableList getSubmitParameters() {
    return mSubmitParameters;
  }

  public final SubmitMethod getMethod() {
    return mMethod;
  }

  public final String getPostDialogueScript() {
    return mPostDialogueScript;
  }

  public final DocumentFetchConfiguration getFetchConfiguration() {
    return mFetchConfiguration;
  }

  @Override
  protected final String getOuputTurnType() {
    return SUBDIALOGUE_INVOCATION_TURN_TYPE;
  }

  @Override
  protected void addTurnProperties(JsonObjectBuilder builder) {
    JsonUtils.add(builder, SUBMIT_URI_PROPERTY, mUri);
    JsonUtils.add(builder, SUBMIT_METHOD_PROPERTY, mMethod.name());
    JsonUtils.add(builder, SUBMIT_PARAMETERS_PROPERTY, mSubmitParameters);
    JsonUtils.add(builder, SUBDIALOGUE_PARAMETERS_PROPERTY, JsonUtils.toJson(mParameters));
    JsonUtils.add(builder, FETCH_CONFIGURATION_PROPERTY, mFetchConfiguration);
    JsonUtils.add(builder, POST_DIALOGUE_SCRIPT_PROPERTY, getPostDialogueScript());
  }

  @Override
  protected void fillVoiceXmlDocument(
      Document document, Element formElement, VoiceXmlDialogueContext dialogueContext)
      throws VoiceXmlDocumentRenderingException {

    List<String> submitNameList = new ArrayList<String>();
    VariableList submitVariableList = mSubmitParameters;
    if (submitVariableList != null) {
      addVariables(formElement, submitVariableList);

      for (Entry<String, String> entry : mSubmitParameters) {
        submitNameList.add(entry.getKey());
      }
    }

    Element subdialogueElement = DomUtils.appendNewElement(formElement, SUBDIALOG_ELEMENT);
    subdialogueElement.setAttribute(NAME_ATTRIBUTE, SUBDIALOGUE_FORM_ITEM_NAME);
    subdialogueElement.setAttribute(SRC_ATTRIBUTE, mUri);

    if (!submitNameList.isEmpty()) {
      subdialogueElement.setAttribute(NAME_LIST_ATTRIBUTE, StringUtils.join(submitNameList, " "));
    }

    for (Parameter parameter : mParameters) {
      Element paramElement = DomUtils.appendNewElement(subdialogueElement, PARAM_ELEMENT);
      paramElement.setAttribute(NAME_ATTRIBUTE, parameter.getName());
      setAttribute(paramElement, VALUE_ATTRIBUTE, parameter.getValue());
      setAttribute(paramElement, EXPR_ATTRIBUTE, parameter.getExpression());
    }

    SubmitMethod submitMethod = mMethod;
    if (submitMethod != null) {
      subdialogueElement.setAttribute(METHOD_ATTRIBUTE, submitMethod.name());
    }

    DocumentFetchConfiguration fetchConfiguration = mFetchConfiguration;
    if (fetchConfiguration != null) {
      applyFetchAudio(subdialogueElement, fetchConfiguration.getFetchAudio());
      applyRessourceFetchConfiguration(subdialogueElement, fetchConfiguration);
    }

    Element filledElement = DomUtils.appendNewElement(subdialogueElement, FILLED_ELEMENT);

    createVarElement(
        filledElement, SUBDIALOGUE_RESULT_VARIABLE_NAME, "dialog." + SUBDIALOGUE_FORM_ITEM_NAME);

    if (mPostDialogueScript != null) {
      createScript(filledElement, mPostDialogueScript);
    }

    createScript(
        filledElement,
        RIVR_SCOPE_OBJECT + ".addValueResult(" + SUBDIALOGUE_RESULT_VARIABLE_NAME + ");");
    createGotoSubmit(filledElement);
  }

  /**
   * {@link SubdialogueCall} parameter, can be created with a string value, a {@link JsonValue} or
   * an expression.
   */
  public static final class Parameter implements JsonSerializable {
    private static final String NAME_PROPERTY = "name";
    private static final String VALUE_PROPERTY = "value";
    private static final String EXPRESSION_PROPERTY = "expression";

    private final String mName;
    private String mExpression;
    private String mValue;

    /**
     * @param name The name of the parameter. Not empty.
     * @param value The string value of the parameter. Not null.
     * @return The newly created subdialogue parameter
     */
    public static Parameter createWithValue(String name, String value) {
      Assert.notNull(value, "value");

      Parameter parameter = new Parameter(name);
      parameter.mValue = value;
      return parameter;
    }

    /**
     * @param name The name of the parameter. Not empty.
     * @param json The JSON value of the parameter. Not null.
     * @return The newly created subdialogue parameter
     */
    public static Parameter createWithJson(String name, JsonValue json) {
      Assert.notNull(json, "json");
      return createWithExpression(name, json.toString());
    }

    /**
     * @param name The name of the parameter. Not empty.
     * @param expression The ECMAScript expression of the parameter. Not null.
     * @return The newly created subdialogue parameter
     */
    public static Parameter createWithExpression(String name, String expression) {
      Assert.notNull(expression, "expression");

      Parameter parameter = new Parameter(name);
      parameter.mExpression = expression;
      return parameter;
    }

    private Parameter(String name) {
      Assert.notEmpty(name, "name");
      mName = name;
    }

    public String getName() {
      return mName;
    }

    public String getExpression() {
      return mExpression;
    }

    public String getValue() {
      return mValue;
    }

    @Override
    public JsonValue asJson() {
      JsonObjectBuilder builder = JsonUtils.createObjectBuilder();
      JsonUtils.add(builder, NAME_PROPERTY, mName);
      JsonUtils.add(builder, EXPRESSION_PROPERTY, mExpression);
      JsonUtils.add(builder, VALUE_PROPERTY, mValue);
      return builder.build();
    }
  }

  /** Builder used to ease the creation of instances of {@link SubdialogueCall} . */
  public static class Builder {

    private final String mName;
    private String mUri;
    private final List<Parameter> mParameters = Collections.emptyList();
    private final VariableList mSubmitParameters = new VariableList();
    private SubmitMethod mMethod = SubmitMethod.get;
    private DocumentFetchConfiguration mFetchConfiguration;
    private String mPostDialogueScript;

    public Builder(String name) {
      mName = name;
    }

    public Builder uri(String uri) {
      Assert.notEmpty(uri, "uri");
      mUri = uri;
      return this;
    }

    public Builder addVoiceXmlParameter(Parameter parameter) {
      Assert.notNull(parameter, "parameter");
      mParameters.add(parameter);
      return this;
    }

    public Builder addSubmitParameterExpression(String name, String expression) {
      Assert.notEmpty(name, "name");
      mSubmitParameters.addWithExpression(name, expression);
      return this;
    }

    public Builder addSubmitParameterString(String name, String string) {
      Assert.notEmpty(name, "name");
      mSubmitParameters.addWithString(name, string);
      return this;
    }

    public Builder setMethod(SubmitMethod method) {
      Assert.notNull(method, "method");
      mMethod = method;
      return this;
    }

    public Builder setFetchConfiguration(DocumentFetchConfiguration fetchConfiguration) {
      mFetchConfiguration = fetchConfiguration;
      return this;
    }

    public Builder setPostDialogueScript(String postDialogueScript) {
      mPostDialogueScript = postDialogueScript;
      return this;
    }

    public SubdialogueCall build() {
      SubdialogueCall subdialogueCall = new SubdialogueCall(mName, mUri);
      subdialogueCall.setMethod(mMethod);
      subdialogueCall.setPostDialogueScript(mPostDialogueScript);
      subdialogueCall.setFetchConfiguration(mFetchConfiguration);
      subdialogueCall.setSubdialogueParameters(mParameters);
      subdialogueCall.setSubmitParameters(mSubmitParameters);
      return subdialogueCall;
    }
  }
}
 public List<? extends AudioItem> getAcknowledgeAudioItems() {
   return Collections.unmodifiableList(mAcknowledgeAudioItems);
 }
  /** Builder used to ease the creation of instances of {@link SubdialogueCall} . */
  public static class Builder {

    private final String mName;
    private String mUri;
    private final List<Parameter> mParameters = Collections.emptyList();
    private final VariableList mSubmitParameters = new VariableList();
    private SubmitMethod mMethod = SubmitMethod.get;
    private DocumentFetchConfiguration mFetchConfiguration;
    private String mPostDialogueScript;

    public Builder(String name) {
      mName = name;
    }

    public Builder uri(String uri) {
      Assert.notEmpty(uri, "uri");
      mUri = uri;
      return this;
    }

    public Builder addVoiceXmlParameter(Parameter parameter) {
      Assert.notNull(parameter, "parameter");
      mParameters.add(parameter);
      return this;
    }

    public Builder addSubmitParameterExpression(String name, String expression) {
      Assert.notEmpty(name, "name");
      mSubmitParameters.addWithExpression(name, expression);
      return this;
    }

    public Builder addSubmitParameterString(String name, String string) {
      Assert.notEmpty(name, "name");
      mSubmitParameters.addWithString(name, string);
      return this;
    }

    public Builder setMethod(SubmitMethod method) {
      Assert.notNull(method, "method");
      mMethod = method;
      return this;
    }

    public Builder setFetchConfiguration(DocumentFetchConfiguration fetchConfiguration) {
      mFetchConfiguration = fetchConfiguration;
      return this;
    }

    public Builder setPostDialogueScript(String postDialogueScript) {
      mPostDialogueScript = postDialogueScript;
      return this;
    }

    public SubdialogueCall build() {
      SubdialogueCall subdialogueCall = new SubdialogueCall(mName, mUri);
      subdialogueCall.setMethod(mMethod);
      subdialogueCall.setPostDialogueScript(mPostDialogueScript);
      subdialogueCall.setFetchConfiguration(mFetchConfiguration);
      subdialogueCall.setSubdialogueParameters(mParameters);
      subdialogueCall.setSubmitParameters(mSubmitParameters);
      return subdialogueCall;
    }
  }