/*
 * Copyright 2008 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.common.css;

import com.google.common.base.Preconditions;

/**
 * Aids in the creation of source code locations. A builder can be used for
 * creating a single {@link SourceCodeLocation}.
 *
 */
public class SourceCodeLocationBuilder {
  private SourceCode sourceCode;
  private int beginCharacterIndex;
  private int beginLineNumber;
  private int beginIndexInLine;
  private int endCharacterIndex;
  private int endLineNumber;
  private int endIndexInLine;

  /**
   * {@code null} until the location gets created. After that, none of the set
   * methods should be called, only {@link #getSourceCodeLocation()} should be
   * called.
   */
  private SourceCodeLocation sourceCodeLocation = null;

  public SourceCodeLocationBuilder() {
    // We initialize to a unknown location.
    sourceCode = null;
    beginCharacterIndex = -1;
    beginLineNumber = 0;
    beginIndexInLine = 0;
    endCharacterIndex = -1;
    endLineNumber = 0;
    endIndexInLine = 0;
  }

  private void checkLocationIsNotAlreadyCreated() {
    Preconditions.checkState(sourceCodeLocation == null, "You cannot set " +
        "source code location properties after the object was created.");
  }

  public SourceCodeLocationBuilder setSourceCode(SourceCode newSourceCode) {
    checkLocationIsNotAlreadyCreated();
    Preconditions.checkNotNull(newSourceCode);
    this.sourceCode = newSourceCode;
    return this;
  }

  public boolean hasSourceCode() {
    return this.sourceCode != null;
  }

  public SourceCodeLocationBuilder setBeginLocation(int characterIndex,
      int lineNumber, int indexInLine) {
    checkLocationIsNotAlreadyCreated();
    Preconditions.checkArgument(characterIndex >= 0,
        "The passed location is not valid.");
    Preconditions.checkArgument(lineNumber >= 1,
        "The passed location is not valid.");
    Preconditions.checkArgument(indexInLine >= 1,
        "The passed location is not valid.");
    this.beginCharacterIndex = characterIndex;
    this.beginLineNumber = lineNumber;
    this.beginIndexInLine = indexInLine;
    return this;
  }

  public SourceCodeLocationBuilder setBeginLocation(SourceCodeLocation.SourceCodePoint begin) {
    checkLocationIsNotAlreadyCreated();
    Preconditions.checkNotNull(begin);
    setBeginLocation(begin.getCharacterIndex(), begin.getLineNumber(),
        begin.getIndexInLine());
    return this;
  }

  public boolean hasBeginLocation() {
    return this.beginCharacterIndex != -1;
  }

  public SourceCodeLocationBuilder setEndLocation(int characterIndex,
      int lineNumber, int indexInLine) {
    checkLocationIsNotAlreadyCreated();
    Preconditions.checkArgument(characterIndex >= 0,
        "The passed characterIndex is not valid.");
    Preconditions.checkArgument(lineNumber >= 1,
        "The passed lineNumber is not valid.");
    Preconditions.checkArgument(indexInLine >= 1,
        "The passed indexInLine is not valid.");
    this.endCharacterIndex = characterIndex;
    this.endLineNumber = lineNumber;
    this.endIndexInLine = indexInLine;
    return this;
  }

  public SourceCodeLocationBuilder setEndLocation(SourceCodeLocation.SourceCodePoint end) {
    checkLocationIsNotAlreadyCreated();
    Preconditions.checkNotNull(end);
    setEndLocation(end.getCharacterIndex(), end.getLineNumber(),
        end.getIndexInLine());
    return this;
  }

  public SourceCodeLocationBuilder setLocation(int characterIndex,
      int lineNumber, int indexInLine) {
    checkLocationIsNotAlreadyCreated();
    setBeginLocation(characterIndex, lineNumber, indexInLine);
    setEndLocation(characterIndex, lineNumber, indexInLine);
    return this;
  }

  public SourceCodeLocation getSourceCodeLocation() {
    if (sourceCodeLocation != null) {
      return sourceCodeLocation;
    }
    Preconditions.checkState(sourceCode != null,
        "You should specify a source code object");
    Preconditions.checkState(
        (beginCharacterIndex >= 0 && endCharacterIndex >= 0) ||
        (beginCharacterIndex == -1 && endCharacterIndex == -1),
        "Both the start and the end locations must be set.");
    Preconditions.checkState(hasSourceCode() || !hasBeginLocation(),
        "Must specify the source code if you specify a location.");
    try {
      sourceCodeLocation = new SourceCodeLocation(sourceCode,
          beginCharacterIndex, beginLineNumber, beginIndexInLine,
          endCharacterIndex, endLineNumber, endIndexInLine);
    } catch (IllegalArgumentException e) {
      throw new IllegalArgumentException(
          String.format(
              "with sourceCode = %s, beginCharacterIndex = %d, "
              + "beginLineNumber = %d, beginIndexInLine = %d, "
              + "endCharacterIndex = %d, endLineNumber = %d, "
              + "endIndexInLine = %d",
              sourceCode.getFileContents(), beginCharacterIndex,
              beginLineNumber, beginIndexInLine, endCharacterIndex,
              endLineNumber, endIndexInLine),
          e);
    }
    return sourceCodeLocation;
  }
}
