// Copyright 2012 Google Inc. All Rights Reserved.
//
// 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.collide.client.code.autocomplete;

import javax.annotation.Nonnull;

import com.google.collide.client.code.autocomplete.AutocompleteProposals.ProposalWithContext;
import com.google.collide.client.documentparser.DocumentParser;
import com.google.collide.client.editor.selection.SelectionModel;
import com.google.collide.client.util.PathUtil;
import com.google.collide.codemirror2.SyntaxType;
import com.google.common.base.Preconditions;

/**
 * Base class for language-specific autocompleters.
 *
 * <p>State transitions:<ol>
 * <li> {@link #LanguageSpecificAutocompleter} -> (2)
 * <li> {@link #attach} -> (3) or (8)
 * <li> {@link #getExplicitAction} -> (4) or (7) or (8)
 * <li> {@link #start} -> (5) or (8)
 * <li> {@link #findAutocompletions} -> (5) or (6) or (7) or (8)
 * <li> {@link #computeAutocompletionResult} -> (5) or (6) or (7) or (8)
 * <li> {@link #pause} -> (3) or (8)
 * <li> {@link #detach()}
 * </ol>
 */
public abstract class LanguageSpecificAutocompleter {

  /**
   * Enumeration of types of actions preformed on keypress.
   */
  public enum ExplicitActionType {
    /**
     * Default behaviour (just append char).
     */
    DEFAULT,

    /**
     * Append char and open autocompletion box.
     */
    DEFERRED_COMPLETE,

    /**
     * Do not append char, use explicit autocompletion.
     */
    EXPLICIT_COMPLETE,

    /**
     * Append char and close autocompletion box.
     */
    CLOSE_POPUP
  }

  /**
   * Bean that holds explicit action type, and optional explicit autocompletion.
   */
  public static class ExplicitAction {

    public static final ExplicitAction DEFAULT = new ExplicitAction(
        ExplicitActionType.DEFAULT, null);

    public static final ExplicitAction DEFERRED_COMPLETE = new ExplicitAction(
        ExplicitActionType.DEFERRED_COMPLETE, null);

    public static final ExplicitAction CLOSE_POPUP = new ExplicitAction(
        ExplicitActionType.CLOSE_POPUP, null);

    private final ExplicitActionType type;
    private final AutocompleteResult explicitAutocompletion;

    public ExplicitAction(AutocompleteResult explicitAutocompletion) {
      this(ExplicitActionType.EXPLICIT_COMPLETE, explicitAutocompletion);
    }

    public ExplicitAction(ExplicitActionType type, AutocompleteResult explicitAutocompletion) {
      this.type = type;
      this.explicitAutocompletion = explicitAutocompletion;
    }

    public ExplicitActionType getType() {
      return type;
    }

    public AutocompleteResult getExplicitAutocompletion() {
      return explicitAutocompletion;
    }
  }

  private AutocompleteController controller;
  private boolean isPaused;
  private final SyntaxType mode;
  private DocumentParser documentParser;

  protected LanguageSpecificAutocompleter(SyntaxType mode) {
    Preconditions.checkNotNull(mode);
    this.mode = mode;
  }

  /**
   * Computes the full autocompletion for a selected proposal.
   *
   * <p>A full autocompletion may include such things as closing tags or braces.
   * In complex substitution case the number of indexes the caret should jump
   * after the autocompletion is complete is also computed.
   *
   * @param proposal proposal selected by user
   * @return value object with information for applying changes
   */
  public abstract AutocompleteResult computeAutocompletionResult(ProposalWithContext proposal);

  /**
   * Finds autocompletions for a given completion query.
   *
   * @param selection used to obtain current cursor position and selection
   * @param trigger used to request different lists of proposals
   * @return POJO holding array of autocompletion proposals.
   */
  public abstract AutocompleteProposals findAutocompletions(
      SelectionModel selection, SignalEventEssence trigger);

  /**
   * Cleanup before instance is dismissed.
   */
  public abstract void cleanup();

  /**
   * Prepare instance for the new autocompletion life cycle.
   *
   * <p>No completions are requested yet, but the completer can prepare
   * itself (e.g. pre-fetch data).
   */
  protected void attach(
      DocumentParser parser, AutocompleteController controller, PathUtil filePath) {
    Preconditions.checkNotNull(parser);
    documentParser = parser;
    this.controller = controller;
    isPaused = true;
  }

  /**
   * Specifies the behavior of this controller on signal events (not text
   * changes).
   *
   * <p>Ctrl-space event is processed directly and not passed to this
   * method.
   *
   * <p>Key press in not applied to document yet, so be careful when analyse
   * text around cursor.
   */
  protected ExplicitAction getExplicitAction(SelectionModel selectionModel,
      SignalEventEssence signal, boolean popupIsShown) {
    return ExplicitAction.DEFAULT;
  }

  protected void start() {
    isPaused = false;
  }

  protected void pause() {
    isPaused = true;
  }

  /**
   * Indicates the end of this autocompleter lifecycle.
   *
   * <p>User switched to other file and is not willing to see any proposals
   * from this completer.
   */
  protected void detach() {
    pause();
    this.controller = null;
  }

  /**
   * Invoked by implementations to provide asynchronously obtained proposals.
   */
  protected final void scheduleRequestForUpdatedProposals() {
    if (!isPaused) {
      controller.scheduleRequestForUpdatedProposals();
    }
  }

  protected SyntaxType getMode() {
    return mode;
  }

  @Nonnull
  protected DocumentParser getParser() {
    return Preconditions.checkNotNull(documentParser);
  }
}
