/*
 * Copyright 2009 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.safehtml.rebind;

import com.google.gwt.thirdparty.guava.common.base.Preconditions;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
 * A representation of a parsed HTML template.
 *
 * <p>A parsed template is represented as a sequence of template chunks.
 *
 * <p>A chunk may correspond to a literal string or to a formal template
 * parameter.
 *
 * <p>Parameter chunks have an attribute that refers to the index of the
 * corresponding template method parameter, as well as an attribute that
 * represents the HTML context the template parameter appeared in.
 */
final class ParsedHtmlTemplate {

  /**
   * A representation of the context of a point within a HTML document. A
   * context consists of a type (such as "plain text", "attribute"), as well as
   * a HTML tag and attribute name where applicable.
   */
  static final class HtmlContext {
    /**
     * The possible types of HTML context.
     */
    static enum Type {
      /**
       * Regular inner text HTML context.
       */
      TEXT,
      /**
       * Value of a HTML attribute.
       */
      ATTRIBUTE_VALUE,
      /**
       * At the very start of a URL-valued attribute.
       */
      URL_ATTRIBUTE_START,
      /**
       * A template parameter that comprises an entire URL-valued attribute.
       */
      URL_ATTRIBUTE_ENTIRE,
      /**
       * CSS (style) context.
       */
      CSS,
      /**
       * CSS (style) attribute context.
       */
      CSS_ATTRIBUTE,
      /**
       * At the very start of a CSS (style) attribute context.
       */
      CSS_ATTRIBUTE_START
    }

    private final Type type;
    private final String tag;
    private final String attribute;

    /**
     * Creates a HTML context.
     *
     * @param type the {@link Type} of this context
     */
    public HtmlContext(Type type) {
      this(type, null, null);
    }

    /**
     * Creates a HTML context.
     *
     * @param type the {@link Type} of this context
     * @param tag the HTML tag this context corresponds to, if applicable; null
     *        otherwise
     * @param attribute the HTML attribute this context corresponds to, if
     *        applicable; null otherwise
     */
    public HtmlContext(Type type, String tag, String attribute) {
      Preconditions.checkArgument((type != Type.TEXT)
          || ((tag == null) && (attribute == null)),
          "tag and attribute must be null for context \"TEXT\"");
      this.type = type;
      this.tag = tag;
      this.attribute = attribute;
    }

    /**
     * Returns the attribute of this HTML context.
     */
    public String getAttribute() {
      return attribute;
    }

    /**
     * Returns the tag of this HTML context.
     */
    public String getTag() {
      return tag;
    }

    /**
     * Returns the type of this HTML context.
     */
    public Type getType() {
      return type;
    }

    @Override
    public String toString() {
      return "(" + getType() + "," + getTag() + "," + getAttribute() + ")";
    }
  }

  /**
   * Represents a template chunk corresponding to a literal string.
   */
  static final class LiteralChunk implements TemplateChunk {
    private final StringBuilder chunk;

    /**
     * Creates a literal chunk corresponding to an empty string.
     */
    public LiteralChunk() {
      chunk = new StringBuilder();
    }

    /**
     * Creates a literal chunk corresponding to the provided string.
     */
    public LiteralChunk(String literal) {
      chunk = new StringBuilder(literal);
    }

    public Kind getKind() {
      return Kind.LITERAL;
    }

    public String getLiteral() {
      return chunk.toString();
    }

    @Override
    public String toString() {
      return String.format("L(%s)", getLiteral());
    }

    void append(String s) {
      chunk.append(s);
    }
  }

  /**
   * Represents a template chunk corresponding to a template parameter.
   */
  static final class ParameterChunk implements TemplateChunk {

    private final HtmlContext context;
    private final int parameterIndex;

    /**
     * Creates a parameter chunk.
     *
     * @param context the HTML context this parameter appears in
     * @param parameterIndex the index of the template parameter that this chunk
     *        refers to
     */
    public ParameterChunk(HtmlContext context, int parameterIndex) {
      this.context = context;
      this.parameterIndex = parameterIndex;
    }

    /**
     * Returns the HTML context this parameter appears in.
     */
    public HtmlContext getContext() {
      return context;
    }

    public Kind getKind() {
      return Kind.PARAMETER;
    }

    /**
     * Returns the index of the template parameter that this chunk refers to.
     */
    public int getParameterIndex() {
      return parameterIndex;
    }

    @Override
    public String toString() {
      return String.format("P(%s,%d)", getContext(), getParameterIndex());
    }
  }

  /**
   * Represents a parsed chunk of a template.
   *
   * <p>There are two kinds of chunks: Those representing literal strings and
   * those representing template parameters.
   */
  interface TemplateChunk {
    /**
     * Distinguishes different kinds of {@link TemplateChunk}.
     */
    public static enum Kind {
      LITERAL, PARAMETER,
    }

    /**
     * Returns the {@link Kind} of this template chunk.
     */
    Kind getKind();
  }

  /*
   * The chunks of this parsed template.
   */
  private final LinkedList<TemplateChunk> chunks;

  /**
   * Initializes an empty parsed HTML template.
   */
  public ParsedHtmlTemplate() {
    chunks = new LinkedList<TemplateChunk>();
  }

  /**
   * Adds a literal string to the template.
   *
   * If the currently last chunk of the parsed template is a literal chunk, the
   * provided string literal will be appended to that chunk. I.e., consecutive
   * literal chunks are automatically coalesced.

   * @param literal the string to be added as a literal chunk
   */
  public void addLiteral(String literal) {
    if (chunks.isEmpty()
        || (chunks.getLast().getKind() != TemplateChunk.Kind.LITERAL)) {
      chunks.add(new LiteralChunk(literal));
    } else {
      ((LiteralChunk) chunks.getLast()).append(literal);
    }
  }

  /**
   * Adds a parameter chunk to the template.
   *
   * @param chunk the chunk to be added
   */
  public void addParameter(ParameterChunk chunk) {
    chunks.add(chunk);
  }

  /**
   * Returns the chunks of this parsed template.
   *
   * <p>The returned list is unmodifiable.
   */
  public List<TemplateChunk> getChunks() {
    return Collections.unmodifiableList(chunks);
  }

  @Override
  public String toString() {
    return chunks.toString();
  }
}
