/*
 * Copyright 2012-2014 Sergey Ignatov
 *
 * 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 org.intellij.erlang.completion;

import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.completion.util.ParenthesesInsertHandler;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileFactory;
import com.intellij.psi.impl.source.tree.TreeUtil;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ProcessingContext;
import com.intellij.util.containers.ContainerUtil;
import org.intellij.erlang.ErlangLanguage;
import org.intellij.erlang.parser.ErlangLexer;
import org.intellij.erlang.parser.ErlangParserUtil;
import org.intellij.erlang.psi.ErlangColonQualifiedExpression;
import org.intellij.erlang.psi.ErlangExport;
import org.intellij.erlang.psi.ErlangFile;
import org.intellij.erlang.psi.ErlangStringLiteral;
import org.jetbrains.annotations.NotNull;

import java.util.Collection;
import java.util.Set;

import static com.intellij.patterns.PlatformPatterns.psiElement;
import static com.intellij.patterns.StandardPatterns.instanceOf;

public class ErlangKeywordsCompletionContributor extends CompletionContributor implements DumbAware {
  private static final Set<String> KEYWORDS_WITH_PARENTHESIS = ContainerUtil.immutableSet(
    "include", "include_lib", "module", "export", "export_type", "import",
    "define", "record", "behaviour", "behavior", "optional_callbacks"
  );

  public ErlangKeywordsCompletionContributor() {
    extend(CompletionType.BASIC, psiElement().inFile(instanceOf(ErlangFile.class)), new CompletionProvider<>() {
      @Override
      protected void addCompletions(@NotNull CompletionParameters parameters,
                                    @NotNull ProcessingContext context,
                                    @NotNull CompletionResultSet result) {
        PsiFile file = parameters.getOriginalFile();
        if (ErlangParserUtil.isApplicationConfigFileType(file)) return;
        PsiElement position = parameters.getPosition();
        if (PsiTreeUtil.getParentOfType(position, ErlangExport.class, ErlangColonQualifiedExpression.class, ErlangStringLiteral.class, PsiComment.class) != null) {
          return;
        }
        for (String keyword : suggestKeywords(position)) {
          result.addElement(createKeywordLookupElement(keyword));
        }
      }
    });
  }

  @NotNull
  private static Collection<String> suggestKeywords(@NotNull PsiElement position) {
    TextRange posRange = position.getTextRange();
    ErlangFile posFile = (ErlangFile) position.getContainingFile();
    TextRange range = new TextRange(0, posRange.getStartOffset());
    String text = range.isEmpty() ? CompletionInitializationContext.DUMMY_IDENTIFIER : range.substring(posFile.getText());

    PsiFile file = PsiFileFactory.getInstance(posFile.getProject()).createFileFromText("a.erl", ErlangLanguage.INSTANCE, text, true, false);
    int completionOffset = posRange.getStartOffset() - range.getStartOffset();
    ErlangParserUtil.CompletionState state = new ErlangParserUtil.CompletionState(completionOffset) {
      @Override
      public String convertItem(Object o) {
        if (o instanceof IElementType && ErlangLexer.KEYWORDS.contains((IElementType) o)) return o.toString();
        return o instanceof String ? (String) o : null;
      }
    };
    //noinspection StaticFieldReferencedViaSubclass
    file.putUserData(ErlangParserUtil.COMPLETION_STATE_KEY, state);
    TreeUtil.ensureParsed(file.getNode());
    return state.items;
  }

  @NotNull
  private static LookupElement createKeywordLookupElement(@NotNull String keyword) {
    boolean needHandler = KEYWORDS_WITH_PARENTHESIS.contains(keyword);
    boolean needQuotas = "include".equalsIgnoreCase(keyword) || "include_lib".equalsIgnoreCase(keyword);
    boolean needBrackets = "export".equalsIgnoreCase(keyword) || "export_type".equalsIgnoreCase(keyword) ||
                           "optional_callbacks".equalsIgnoreCase(keyword);
    return PrioritizedLookupElement.withPriority(LookupElementBuilder.create(keyword)
      .withInsertHandler(needHandler ? new ErlangKeywordInsertHandler(needQuotas, needBrackets) : null)
      .withTailText(needHandler ? "()" : null)
      .bold(), ErlangCompletionContributor.KEYWORD_PRIORITY);
  }

  private static class ErlangKeywordInsertHandler extends ParenthesesInsertHandler<LookupElement> {
    private final boolean myNeedQuotas;
    private final boolean myInsertBrackets;

    public ErlangKeywordInsertHandler(boolean needQuotas, boolean insertBrackets) {
      myNeedQuotas = needQuotas;
      myInsertBrackets = insertBrackets;
    }

    @Override
    protected boolean placeCaretInsideParentheses(InsertionContext context, LookupElement item) {
      return true;
    }

    @Override
    public void handleInsert(@NotNull InsertionContext context, @NotNull LookupElement item) {
      super.handleInsert(context, item);
      Editor editor = context.getEditor();

      Document document = editor.getDocument();
      if (!document.getText().substring(context.getTailOffset()).startsWith(".")) {
        document.insertString(context.getTailOffset(), ".");
      }

      if (insertQuotas()) doInsert(editor, document, "\"", "\"");
      if (insertBrackets()) doInsert(editor, document, "[", "]");
    }

    private static void doInsert(@NotNull Editor editor, @NotNull Document document, @NotNull String open, @NotNull String closed) {
      int offset = editor.getCaretModel().getOffset();
      document.insertString(offset, open);
      document.insertString(offset + 1, closed);
      editor.getCaretModel().moveToOffset(offset + 1);
    }

    private boolean insertBrackets() {
      return myInsertBrackets;
    }

    private boolean insertQuotas() {
      return myNeedQuotas;
    }
  }
}
