/*
 * Copyright 2000-2009 JetBrains s.r.o.
 *
 * 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.intellij.formatting;

import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;

/**
 * The indent setting for a formatting model block. Indicates how the block is indented
 * relative to its parent block.
 * <p/>
 * <b>Relative indents</b>
 * <p/>
 * Number of factory methods of this class use {@code 'indent relative to direct parent'} flag. It specified anchor parent block
 * to use to apply indent.
 * <p/>
 * Consider the following situation:
 * <p/>
 * <pre>
 *     return a == 0
                  && (b == 0
                          || c == 0);
 * </pre>
 * <p/>
 * Here is the following blocks hierarchy (going from child to parent):
 * <p/>
 * <ul>
 *   <li>{@code '|| c == 0`};</li>
 *   <li>{@code 'b == 0 || c == 0'};</li>
 *   <li>{@code '(b == 0 || c == 0)'};</li>
 *   <li>{@code 'a == 0 && (b == 0 || c == 0)'};</li>
 *   <li>{@code 'return a == 0 && (b == 0 || c == 0)'};</li>
 * </ul>
 * <p/>
 * By default formatter applies block indent to the first block ancestor (direct or indirect) that starts on a new line. That means
 * that such an ancestor for both blocks {@code '|| c == 0'} and {@code '&& (b == 0 || c == 0)'}
 * is {@code 'return a == 0 && (b == 0 || c == 0)'}. That means that the code above is formatted as follows:
 * <p/>
 * <pre>
 *    return a == 0
 *        && (b == 0
 *        || c == 0);
 * </pre>
 * <p/>
 * In contrast, it's possible to specify that direct parent block that starts on a line before target child block is used as an anchor.
 * Initial formatting example illustrates such approach.
 * <p/>
 * <b>Enforcing indent to children</b>
 * <p/>
 * It's possible to configure indent to enforce parent block indent to its children that start new line. Consider the following situation:
 * <pre>
 *   foo("test", new Runnable() {
 *           public void run() { 
 *           }                   
 *       },                      
 *       new Runnable() {        
 *           public void run() { 
 *           }                   
 *       }                       
 *   );                          
 * </pre>
 * We want the first {@code 'new Runnable() {...}'} block here to be indented to the method expression list element. However, formatter
 * uses indents only if the block starts new line. Here the block doesn't start new line ({@code 'new Runnable() ...'}), hence
 * we need to define {@code 'enforce indent to children'} flag in order to instruct formatter to apply parent indent to the sub-blocks.
 *
 * @see com.intellij.formatting.Block#getIndent()
 * @see com.intellij.formatting.ChildAttributes#getChildIndent() 
 */

public abstract class Indent {
  private static IndentFactory myFactory;

  public abstract Type getType();

  static void setFactory(IndentFactory factory) {
    myFactory = factory;
  }

  /**
   * Returns an instance of a regular indent, with the width specified
   * in "Project Code Style | General | Indent".
   * <p/>
   * <b>Note:</b> returned indent is not set to be {@code 'relative'} to it's direct parent block
   *
   * @return the indent instance.
   * @see #getNormalIndent(boolean)
   */
  public static Indent getNormalIndent() {
    return myFactory.getNormalIndent(false);
  }

  /**
   * Returns an instance of a regular indent, with the width specified
   * in "Project Code Style | General | Indent" and given {@code 'relative to direct parent'} flag
   *
   * @param relativeToDirectParent    flag the indicates if current indent object anchors direct block parent (feel free
   *                                  to get more information about that at class-level javadoc)
   * @return                          newly created indent instance configured in accordance with the given parameter
   */
  public static Indent getNormalIndent(boolean relativeToDirectParent) {
    return myFactory.getNormalIndent(relativeToDirectParent);
  }

  /**
   * Returns the standard "empty indent" instance, indicating that the block is not
   * indented relative to its parent block.
   *
   * @return the empty indent instance.
   */
  public static Indent getNoneIndent() {
    return myFactory.getNoneIndent();
  }

  /**
   * Returns the "absolute none" indent instance, indicating that the block will
   * be placed at the leftmost column in the document.
   *
   * @return the indent instance.
   */
  public static Indent getAbsoluteNoneIndent() {
    return myFactory.getAbsoluteNoneIndent();
  }

  /**
   * Returns the "absolute label" indent instance, indicating that the block will be
   * indented by the number of spaces indicated in the "Project Code Style | General |
   * Label indent" setting from the leftmost column in the document.
   *
   * @return the indent instance.
   */
  public static Indent getAbsoluteLabelIndent() {
    return myFactory.getAbsoluteLabelIndent();
  }

  /**
   * Returns the "label" indent instance, indicating that the block will be indented by
   * the number of spaces indicated in the "Project Code Style | General | Label indent"
   * setting relative to its parent block.
   *
   * @return the indent instance.
   */
  public static Indent getLabelIndent() {
    return myFactory.getLabelIndent();
  }

  /**
   * Returns the "continuation" indent instance, indicating that the block will be indented by
   * the number of spaces indicated in the "Project Code Style | General | Continuation indent"
   * setting relative to its parent block.
   * <p/>
   * <b>Note:</b> returned indent is not set to be {@code 'relative'} to it's direct parent block
   *
   * @return the indent instance.
   * @see #getContinuationIndent(boolean)
   */
  public static Indent getContinuationIndent() {
    return myFactory.getContinuationIndent(false);
  }

  /**
   * Returns the "continuation" indent instance, indicating that the block will be indented by
   * the number of spaces indicated in the "Project Code Style | General | Continuation indent"
   * setting relative to its parent block  and given {@code 'relative to direct parent'} flag.
   *
   * @param relativeToDirectParent    flag the indicates if current indent object anchors direct block parent (feel free
   *                                  to get more information about that at class-level javadoc)
   * @return                          newly created indent instance configured in accordance with the given parameter
   */
  public static Indent getContinuationIndent(boolean relativeToDirectParent) {
    return myFactory.getContinuationIndent(relativeToDirectParent);
  }

  /**
   * Returns the "continuation without first" indent instance, indicating that the block will
   * be indented by the number of spaces indicated in the "Project Code Style | General | Continuation indent"
   * setting relative to its parent block, unless this block is the first of the children of its
   * parent having the same indent type. This is used for things like parameter lists, where the first parameter
   * does not have any indent and the remaining parameters are indented by the continuation indent.
   * <p/>
   * <b>Note:</b> returned indent is not set to be {@code 'relative'} to it's direct parent block
   *
   * @return the indent instance.
   * @see #getContinuationWithoutFirstIndent(boolean)
   */
  public static Indent getContinuationWithoutFirstIndent() {//is default
    return myFactory.getContinuationWithoutFirstIndent(false);
  }

  /**
   * Returns the "continuation without first" indent instance, indicating that the block will
   * be indented by the number of spaces indicated in the "Project Code Style | General | Continuation indent"
   * setting relative to its parent block, unless this block is the first of the children of its
   * parent having the same indent type. This is used for things like parameter lists, where the first parameter
   * does not have any indent and the remaining parameters are indented by the continuation indent  and given
   * {@code 'relative to direct parent'} flag.
   *
   * @param relativeToDirectParent    flag the indicates if current indent object anchors direct block parent (feel free
   *                                  to get more information about that at class-level javadoc)
   * @return                          newly created indent instance configured in accordance with the given parameter
   */
  public static Indent getContinuationWithoutFirstIndent(boolean relativeToDirectParent) {
    return myFactory.getContinuationWithoutFirstIndent(relativeToDirectParent);
  }

  /**
   * Returns an indent with the specified width.
   * <p/>
   * <b>Note:</b> returned indent is not set to be {@code 'relative'} to it's direct parent block
   *
   * @param spaces the number of spaces in the indent.
   * @return the indent instance.
   * @see #getSpaceIndent(int, boolean)
   */
  public static Indent getSpaceIndent(final int spaces) {
    return myFactory.getSpaceIndent(spaces, false);
  }

  /**
   * Returns an indent with the specified width  and given {@code 'relative to direct parent'} flag.
   *
   * @param spaces                    the number of spaces in the indent
   * @param relativeToDirectParent    flag the indicates if current indent object anchors direct block parent (feel free
   *                                  to get more information about that at class-level javadoc)
   * @return                          newly created indent instance configured in accordance with the given parameter
   */
  public static Indent getSpaceIndent(final int spaces, final boolean relativeToDirectParent) {
    return myFactory.getSpaceIndent(spaces, relativeToDirectParent);
  }

  /**
   * Base factory method for {@link Indent} objects construction, i.e. all other methods may be expressed in terms of this method.
   * 
   * @param type                      indent type
   * @param relativeToDirectParent    flag the indicates if current indent object anchors direct block parent (feel free
   *                                  to get more information about that at class-level javadoc)
   * @param enforceIndentToChildren   flag the indicates if current indent object should be enforced for multiline block children
   *                                  (feel free to get more information about that at class-level javadoc)
   * @return                          newly created indent configured in accordance with the given arguments
   */
  public static Indent getIndent(@NotNull Type type, boolean relativeToDirectParent, boolean enforceIndentToChildren) {
    return myFactory.getIndent(type, relativeToDirectParent, enforceIndentToChildren);
  }

  /**
   * Base factory method for {@link Indent} objects construction, i.e. all other methods may be expressed in terms of this method.
   *
   * @param type                      indent type
   * @param spaces                    the number of spaces in the indent
   * @param relativeToDirectParent    flag the indicates if current indent object anchors direct block parent (feel free
   *                                  to get more information about that at class-level javadoc)
   * @param enforceIndentToChildren   flag the indicates if current indent object should be enforced for multiline block children
   *                                  (feel free to get more information about that at class-level javadoc)
   * @return                          newly created indent configured in accordance with the given arguments
   */
  public static Indent getIndent(@NotNull Type type, int spaces, boolean relativeToDirectParent, boolean enforceIndentToChildren) {
    return myFactory.getIndent(type, spaces, relativeToDirectParent, enforceIndentToChildren);
  }

  public static Indent getSmartIndent(Type type) {
    return myFactory.getSmartIndent(type);
  }

  public static class Type {
    private final String myName;


    private Type(@NonNls final String name) {
      myName = name;
    }

    public static final Type SPACES = new Type("SPACES");
    public static final Type NONE = new Type("NONE");
    public static final Type LABEL = new Type("LABEL");
    public static final Type NORMAL = new Type("NORMAL");
    public static final Type CONTINUATION = new Type("CONTINUATION");
    public static final Type CONTINUATION_WITHOUT_FIRST = new Type("CONTINUATION_WITHOUT_FIRST");

    public String toString() {
      return myName;
    }
  }
}
