/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.uima.ruta.ide.core.codeassist;

import java.util.ArrayList;
import java.util.List;

import org.apache.uima.ruta.ide.core.extensions.ICompletionExtension;
import org.apache.uima.ruta.ide.core.parser.RutaParseUtils;
import org.apache.uima.ruta.ide.parser.ast.RutaStatement;
import org.eclipse.dltk.ast.ASTNode;
import org.eclipse.dltk.ast.ASTVisitor;
import org.eclipse.dltk.ast.declarations.MethodDeclaration;
import org.eclipse.dltk.ast.declarations.ModuleDeclaration;
import org.eclipse.dltk.ast.declarations.TypeDeclaration;
import org.eclipse.dltk.ast.expressions.Expression;
import org.eclipse.dltk.ast.references.SimpleReference;
import org.eclipse.dltk.ast.statements.Block;
import org.eclipse.dltk.ast.statements.Statement;
import org.eclipse.dltk.codeassist.complete.CompletionNodeFound;
import org.eclipse.dltk.compiler.env.IModuleSource;

public class RutaCompletionParser extends RutaAssistParser {
  private static class RutaEmptyCompleteStatement extends RutaStatement {
    public RutaEmptyCompleteStatement(List expressions) {
      super(expressions);
    }
  }

  private ICompletionExtension[] extensions;

  public RutaCompletionParser(ICompletionExtension[] extensions) {
    this.extensions = extensions;
  }

  /**
   * Called then element could not be found.
   */
  public void handleNotInElement(ASTNode node, int position) {
    if (node != null && node.sourceStart() <= position && position <= node.sourceEnd()) {
      // this is empty module case
      if (node instanceof ModuleDeclaration) {
        ModuleDeclaration unit = (ModuleDeclaration) node;
        List exprs = new ArrayList();
        exprs.add(new SimpleReference(position, position, ""));
        RutaEmptyCompleteStatement statement = new RutaEmptyCompleteStatement(exprs);
        unit.addStatement(statement);
        this.parseBlockStatements(statement, unit, position);
      } else if (node instanceof MethodDeclaration) {
        // empty keyword like completion.
        MethodDeclaration method = (MethodDeclaration) node;
        List exprs = new ArrayList();
        exprs.add(new SimpleReference(position, position, ""));
        RutaEmptyCompleteStatement statement = new RutaEmptyCompleteStatement(exprs);
        method.getStatements().add(statement);
        this.parseBlockStatements(statement, method, position);
      } else if (node instanceof TypeDeclaration) {
        // empty keyword like completion.
        TypeDeclaration type = (TypeDeclaration) node;
        List exprs = new ArrayList();
        exprs.add(new SimpleReference(position, position, ""));
        RutaEmptyCompleteStatement statement = new RutaEmptyCompleteStatement(exprs);
        type.getStatements().add(statement);
        // this.assistNodeParent = node;
        this.parseBlockStatements(statement, type, position);
        // } else if (node instanceof RutaExecuteExpression) {
        // // empty keyword like completion.
        // List exprs = new ArrayList();
        // exprs.add(new SimpleReference(position, position, ""));
        // RutaEmptyCompleteStatement statement = new
        // RutaEmptyCompleteStatement(
        // exprs);
        // this.parseBlockStatements(statement, node, position);
      }
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.eclipse.dltk.codeassist.IAssistParser#parseBlockStatements(org.eclipse.dltk.ast.ASTNode,
   * org.eclipse.dltk.ast.ASTNode, int)
   */
  public void parseBlockStatements(ASTNode node, ASTNode inNode, int position) {
    // if (node instanceof RutaStatement) {
    // RutaStatement statement = (RutaStatement) node;
    // List expressions = statement.getExpressions();
    // int len = expressions.size();
    // boolean first = false;
    // String completionToken = null;
    // ASTNode completionNode = null;
    // for (int i = 0; i < len; ++i) {
    // ASTNode n = (ASTNode) expressions.get(i);
    // if (n.sourceStart() <= position && n.sourceEnd() >= position
    // || (node instanceof RutaEmptyCompleteStatement)) {
    // if (i == 0) {
    // first = true;
    // }
    // completionNode = n;
    // }
    // }
    // if (completionNode == null) {
    // // TODO: Add inner completion here.
    // if (len > 0) {
    // // ASTNode firstNode = (ASTNode) expressions.get(0);
    // // if (position > firstNode.sourceEnd()) {
    // // // This could be variable completion.
    // // boolean provideDollar =
    // // !checkVariableWithoutDollarCompletion(
    // // statement, position);
    // // this.assistNodeParent = inNode;
    // // SimpleReference ref = new SimpleReference(position,
    // // position, "");
    // // ASTNode nde = new CompletionOnVariable("", ref, node,
    // // inNode, true, provideDollar);
    // // throw new CompletionNodeFound(nde, null);
    // // }
    // String[] keywords = checkKeywords(completionToken, MODULE);
    // ASTNode nde = new CompletionOnKeywordArgumentOrFunctionArgument("",
    // (RutaStatement) node, keywords, position);
    // this.assistNodeParent = inNode;
    // throw new CompletionNodeFound(nde, null/* ((TypeDeclaration)inNode).scope */);
    //
    // } else {
    // completionToken = "";
    // }
    // } else if (completionNode instanceof SimpleReference) {
    // int maxLen = position - completionNode.sourceStart();
    // completionToken = ((SimpleReference) completionNode).getName();
    // // We need to cut some sumbols if node is begger then position.
    // if (completionToken.length() > maxLen && maxLen > 0) {
    // completionToken = completionToken.substring(0, maxLen);
    // }
    // } /*
    // * else if (completionNode instanceof RutaBlockExpression) { RutaBlockExpression
    // * block = (RutaBlockExpression) completionNode;
    // *
    // * List s = block.parseBlock(); if (s != null) { int slen = s.size(); for (int u = 0; u <
    // * slen; ++u) { ASTNode n = (ASTNode) s.get(u); n.setStart(n.sourceStart() -
    // * block.sourceStart()); n.setEnd(n.sourceEnd() - block.sourceStart());
    // * RutaASTUtil.extendStatement(n, block.getBlock()); n.setStart(n.sourceStart() +
    // * block.sourceStart()); n.setEnd(n.sourceEnd() + block.sourceStart()); if (n != null &&
    // * n.sourceStart() <= position && n.sourceEnd() >= position) { parseBlockStatements(n,
    // * inNode, position); } } } handleNotInElement(inNode, position); }
    // */
    // if (completionNode instanceof StringLiteral) {
    // int maxLen = position - completionNode.sourceStart();
    // int pos = maxLen;
    // SimpleReference tok = RutaParseUtils.extractVariableFromString(
    // (StringLiteral) completionNode, pos);
    // if (tok != null) {
    // this.assistNodeParent = inNode;
    // ASTNode nde = new CompletionOnVariable(tok.getName(), tok, node, inNode, false);
    // throw new CompletionNodeFound(nde, null);
    // } else {
    // this.assistNodeParent = inNode;
    // SimpleReference ref = new SimpleReference(position, position, "");
    // ASTNode nde = new CompletionOnVariable("", ref, node, inNode, true);
    // throw new CompletionNodeFound(nde, null);
    // }
    // }
    // // if (completionNode instanceof RutaExecuteExpression) {
    // // RutaExecuteExpression expr = (RutaExecuteExpression)
    // // completionNode;
    // // List exprs = expr.parseExpression();
    // // for (int i = 0; i < exprs.size(); ++i) {
    // // ASTNode n = (ASTNode) exprs.get(i);
    // // if (n.sourceStart() <= position
    // // && n.sourceEnd() >= position) {
    // // parseBlockStatements(n, expr, position);
    // // }
    // // }
    // // handleNotInElement(expr, position);
    // // }
    // if (completionToken != null && completionToken.startsWith("$")) {
    // // Argument name completion...
    // this.assistNodeParent = inNode;
    // ASTNode nde = new CompletionOnVariable(completionToken, completionNode, node, inNode, false);
    // throw new CompletionNodeFound(nde, null);
    // } else {
    // // This is keyword or function completion.
    // if (inNode instanceof ModuleDeclaration && completionNode != null && first) {
    // String[] keywords = checkKeywords(completionToken, MODULE);
    // ASTNode nde = new CompletionOnKeywordOrFunction(completionToken, completionNode, node,
    // keywords);
    // this.assistNodeParent = inNode;
    // throw new CompletionNodeFound(nde, ((ModuleDeclaration) inNode).scope);
    // } else if (inNode instanceof MethodDeclaration && completionNode != null && first) {
    // String[] keywords = checkKeywords(completionToken, FUNCTION);
    // ASTNode nde = new CompletionOnKeywordOrFunction(completionToken, completionNode, node,
    // keywords);
    // this.assistNodeParent = inNode;
    // throw new CompletionNodeFound(nde, ((MethodDeclaration) inNode).scope);
    // } else if (inNode instanceof TypeDeclaration && completionNode != null && first) {
    // String[] keywords = checkKeywords(completionToken, NAMESPACE);
    // ASTNode nde = new CompletionOnKeywordOrFunction(completionToken, completionNode, node,
    // keywords);
    // this.assistNodeParent = inNode;
    // throw new CompletionNodeFound(nde, null/* ((TypeDeclaration)inNode).scope */);
    // // } else if (inNode instanceof RutaExecuteExpression
    // // && completionNode != null && first) {
    // // String[] keywords = checkKeywords(completionToken,
    // // EXEC_EXPRESSION);
    // // ASTNode nde = new CompletionOnKeywordOrFunction(
    // // completionToken, completionNode, node, keywords);
    // // this.assistNodeParent = inNode;
    // // throw new CompletionNodeFound(nde, null/*
    // // ((TypeDeclaration)inNode).scope */);
    // } else {
    // if (completionNode != null) {
    // String[] keywords = checkKeywords(completionToken, MODULE);
    //
    // ASTNode nde = new CompletionOnKeywordArgumentOrFunctionArgument(completionToken,
    // completionNode, (RutaStatement) node, keywords);
    // this.assistNodeParent = inNode;
    // throw new CompletionNodeFound(nde, null/* ((TypeDeclaration)inNode).scope */);
    // } else {
    // String[] keywords = checkKeywords(completionToken, MODULE);
    // if (completionToken == null) {
    // completionToken = "";
    // }
    // ASTNode nde = new CompletionOnKeywordArgumentOrFunctionArgument(completionToken,
    // (RutaStatement) node, keywords, position);
    // this.assistNodeParent = inNode;
    // throw new CompletionNodeFound(nde, null/* ((TypeDeclaration)inNode).scope */);
    // }
    // }
    // }
    // // if (checkVariableWithoutDollarCompletion(statement, position)
    // // && completionToken != null) {
    // // this.assistNodeParent = inNode;
    // // SimpleReference ref = new SimpleReference(completionNode
    // // .sourceStart(), completionNode.sourceEnd(),
    // // completionToken);
    // // ASTNode nde = new CompletionOnVariable(completionToken, ref,
    // // node, inNode, true);
    // // throw new CompletionNodeFound(nde, null);
    // // }
    //
    // } else if (node instanceof MethodDeclaration) {
    // MethodDeclaration method = (MethodDeclaration) node;
    // List statements = method.getStatements();
    // boolean inStatement = false;
    // if (statements != null) {
    // int length = statements.size();
    // for (int i = 0; i < length; i++) {
    // ASTNode nde = (ASTNode) statements.get(i);
    // if (nde.sourceStart() <= position && nde.sourceEnd() >= position) {
    // inStatement = true;
    // parseBlockStatements(nde, method, position);
    // }
    // }
    // }
    // if (!inStatement) {
    // this.handleNotInElement(method, position);
    // }
    // } else {
    // visitElements(node, position);
    // }
  }

  public class CompletionVisitor extends ASTVisitor {
    protected int position;

    protected ModuleDeclaration module;

    public CompletionVisitor(int position, ModuleDeclaration module) {
      this.position = position;
      this.module = module;
    }

    @Override
    public boolean visit(Statement s) throws Exception {
      // if (s.sourceStart() <= position && s.sourceEnd() >= position) {
      // for (int i = 0; i < extensions.length; i++) {
      // extensions[i].visit(s, RutaCompletionParser.this,
      // position);
      // }
      // if (s instanceof RutaStatement) {
      // ASTNode inNode = RutaParseUtil.getScopeParent(module, s);
      // RutaCompletionParser.this.parseBlockStatements(s, inNode,
      // position);
      // }
      // if (s instanceof RutaPackageDeclaration) {
      // RutaPackageDeclaration decl =
      // (RutaPackageDeclaration) s;
      // if (decl.getNameStart() <= position
      // && position <= decl.getNameEnd()) {
      // ASTNode inNode = RutaParseUtil.getScopeParent(module, s);
      // assistNodeParent = inNode;
      // throw new CompletionNodeFound(decl, null/*
      // ((TypeDeclaration)inNode).scope */);
      // }
      // }
      // }
      return super.visit(s);
    }

    @Override
    public boolean visit(Expression s) throws Exception {
      if (s.sourceStart() <= position && s.sourceEnd() >= position) {
        for (int i = 0; i < extensions.length; i++) {
          extensions[i].visit(s, RutaCompletionParser.this, position);
        }
      }
      return super.visit(s);
    }

    @Override
    public boolean endvisit(Expression s) throws Exception {
      if (s instanceof Block && s.sourceStart() <= position && s.sourceEnd() >= position) {
        // We are in block, and no in node completion are done.
        String[] keywords = checkKeywords("", MODULE);
        ASTNode inNode = RutaParseUtils.getScopeParent(module, s);
        ASTNode nde = new CompletionOnKeywordOrFunction("", inNode, s, keywords);
        assistNodeParent = inNode;
        throw new CompletionNodeFound(nde, null);
      }
      return super.endvisit(s);
    }
  };

  protected CompletionVisitor createCompletionVisitor(int position) {
    return new CompletionVisitor(position, this.getModule());
  }

  private void visitElements(ASTNode node, int position) {
    if (!(node instanceof RutaStatement)) {
      CompletionVisitor visitor = createCompletionVisitor(position);
      try {
        node.traverse(visitor);
      } catch (CompletionNodeFound e) {
        throw e;
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  private boolean checkVariableWithoutDollarCompletion(RutaStatement statement, int position) {
    // TODO: Add more compecated check.
    Expression e = statement.getAt(0);
    if (e instanceof SimpleReference) {
      SimpleReference ref = (SimpleReference) e;
      String name = ref.getName();
      if (name.equals("set")) {
        return true;
      }
    }
    return false;
  }

  public String[] checkKeywords(String completionToken, int type) {
    // String[] keywords = RutaKeywordsManager.getKeywords(type);
    // // TODO: Possible require cases.
    // if (type == MODULE || type == FUNCTION || type == NAMESPACE
    // || type == EXEC_EXPRESSION) {
    // // Suppose we can handle all keywords.
    // String[] kw = new String[keywords.length];
    // for (int i = 0; i < keywords.length; ++i) {
    // kw[i] = keywords[i];
    // }
    // return kw;
    // }
    return null;
  }

  public void setAssistNodeParent(ASTNode prevParent) {
    this.assistNodeParent = prevParent;
  }

  public ModuleDeclaration parse(IModuleSource sourceModule) {
    ModuleDeclaration module = (ModuleDeclaration) this.parser.parse(sourceModule, null);
    module.rebuild();
    return module;
  }

}
