/*
 * 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.Iterator;
import java.util.List;

import org.apache.uima.ruta.ide.core.parser.RutaParseUtils;
import org.eclipse.dltk.ast.ASTNode;
import org.eclipse.dltk.ast.ASTVisitor;
import org.eclipse.dltk.ast.Modifiers;
import org.eclipse.dltk.ast.declarations.FieldDeclaration;
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.statements.Block;
import org.eclipse.dltk.ast.statements.Statement;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.IField;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.IParent;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.IType;
import org.eclipse.dltk.core.ModelException;

public class RutaResolver {
  private IResolveElementParent resolver;

  private ModuleDeclaration moduleDeclaration;

  private ISourceModule sourceModule;

  public RutaResolver(ISourceModule sourceModule, ModuleDeclaration moduleDeclaration,
          IResolveElementParent resolver) {
    this(sourceModule, moduleDeclaration);
    this.resolver = resolver;
  }

  public RutaResolver(ISourceModule sourceModule, ModuleDeclaration moduleDeclaration) {
    this.sourceModule = sourceModule;
    this.moduleDeclaration = moduleDeclaration;
  }

  public IModelElement findModelElementFrom(ASTNode node) {
    List statements = moduleDeclaration.getStatements();
    List elements = new ArrayList();
    searchAddElementsTo(statements, node, sourceModule, elements);
    if (elements.size() == 1) {
      return (IModelElement) elements.get(0);
    }
    return null;
  }

  public interface IResolveElementParent {
    IModelElement findElementParent(ASTNode node, String name, IParent parent);
  }

  public void searchAddElementsTo(List statements, final ASTNode node, IParent element,
          List selectionElements) {
    if (statements == null || element == null) {
      return;
    }
    Iterator i = statements.iterator();
    while (i.hasNext()) {
      ASTNode nde = (ASTNode) i.next();
      if (nde.equals(node)) {
        if (node instanceof MethodDeclaration) {
          String oName = ((MethodDeclaration) node).getName();
          if (oName.indexOf("::") != -1) {
            String pName = oName.substring(0, oName.lastIndexOf("::"));
            pName = pName.replaceAll("::", "\\$");

            if (pName.startsWith("$")) {
              if (pName.equals("$")) {
                element = sourceModule;
              } else {
                try {
                  element = findTypeFrom(sourceModule.getChildren(), "", pName, '$');
                } catch (ModelException e) {
                  if (DLTKCore.DEBUG) {
                    e.printStackTrace();
                  }
                }
              }
            } else {
              pName = "$" + pName;
              try {
                element = findTypeFrom(element.getChildren(), "", pName, '$');
                if (element == null) {
                  return;
                }
              } catch (ModelException e) {
                e.printStackTrace();
                return;
              }
            }
          }
        }
        String nodeName = getNodeChildName(node);
        if (nodeName != null) {
          IModelElement e = null;
          if (nodeName.startsWith("::")) {
            nodeName = nodeName.substring(2);
            e = findChildrenByName(nodeName, sourceModule);
          } else {
            e = findChildrenByName(nodeName, element);
          }
          if (e == null && resolver != null) {
            e = resolver.findElementParent(node, nodeName, element);

          }
          if (e != null) {
            List toRemove = new ArrayList();
            for (int k = 0; k < selectionElements.size(); ++k) {
              IModelElement ke = (IModelElement) selectionElements.get(k);
              String keName = ke.getElementName();
              if (keName.equals(nodeName)) {
                toRemove.add(ke);
              }
            }
            for (int k = 0; k < toRemove.size(); ++k) {
              selectionElements.remove(toRemove.get(k));
            }
            selectionElements.add(e);
          }
        }
        return;
      }
      if (nde.sourceStart() <= node.sourceStart() && node.sourceEnd() <= nde.sourceEnd()) {
        if (element instanceof IParent) {
          if (nde instanceof TypeDeclaration) {
            TypeDeclaration type = (TypeDeclaration) nde;
            String typeName = getNodeChildName(type);
            IModelElement e = findChildrenByName(typeName, element);
            if (e == null && type.getName().startsWith("::")) {
              try {
                e = (IModelElement) findTypeFrom(sourceModule.getChildren(), "", type.getName()
                        .replaceAll("::", "\\$"), '$');
              } catch (ModelException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
              }
            }
            if (e instanceof IParent) {
              // was: if (e != null || e instanceof IParent)
              List stats = ((TypeDeclaration) nde).getStatements();
              searchAddElementsTo(stats, node, (IParent) e, selectionElements);
            }
          } else if (nde instanceof MethodDeclaration) {
            searchInMethod(node, element, nde, selectionElements);
          } /*
             * else if (nde instanceof RutaStatement) { RutaStatement s = (RutaStatement) nde;
             * Expression commandId = s.getAt(0); final IParent e = element; if (commandId != null
             * && commandId instanceof SimpleReference) { String qname = ((SimpleReference)
             * commandId) .getName(); } }
             */
          else {
            final IParent e = element;
            List statements2 = findExtractBlocks(nde);
            if (statements2.size() > 0) {
              searchAddElementsTo(statements2, node, e, selectionElements);
            }
          }
        }
        return;
      }
    }
  }

  public static IModelElement findChildrenByName(String childName, IParent element) {
    try {
      if (element == null) {
        return null;
      }
      String nextName = null;
      int pos;
      if ((pos = childName.indexOf("::")) != -1) {
        nextName = childName.substring(pos + 2);
        childName = childName.split("::")[0];
      }
      IModelElement[] children = element.getChildren();
      if (children != null) {
        for (int i = 0; i < children.length; ++i) {
          String name = children[i].getElementName();
          if (children[i] instanceof IField && name.indexOf('(') != -1) {
            name = name.substring(0, name.indexOf('('));
          }
          if (name.equals(childName)) {
            if (nextName == null) {
              return children[i];
            } else if (children[i] instanceof IParent) {
              return findChildrenByName(nextName, (IParent) children[i]);
            }
          }
        }
      }
    } catch (ModelException e) {
      if (DLTKCore.DEBUG) {
        e.printStackTrace();
      }
    }
    return null;
  }

  public static IParent findTypeFrom(IModelElement[] childs, String name, String parentName,
          char delimiter) {
    try {
      for (int i = 0; i < childs.length; ++i) {
        if (childs[i] instanceof IType) {
          if ((((IType) childs[i]).getFlags() & Modifiers.AccNameSpace) == 0) {
            continue;
          }
          IType type = (IType) childs[i];
          String qname = name + delimiter + type.getElementName();
          if (qname.equals(parentName)) {
            return type;
          }
          IParent val = findTypeFrom(type.getChildren(), qname, parentName, delimiter);
          if (val != null) {
            return val;
          }
        }
      }
    } catch (ModelException e) {
      if (DLTKCore.DEBUG) {
        e.printStackTrace();
      }
    }
    return null;
  }

  public static String getNodeChildName(ASTNode node) {
    if (node instanceof MethodDeclaration) {
      MethodDeclaration method = (MethodDeclaration) node;
      String name = method.getName();
      if (name.indexOf("::") != -1) {
        return name.substring(name.lastIndexOf("::") + 2);
      }
      return name;
    } else if (node instanceof TypeDeclaration) {
      TypeDeclaration type = (TypeDeclaration) node;
      String name = type.getName();
      /*
       * if (name.startsWith("::")) { return name.substring(2); }
       */
      return name;
    } else if (node instanceof Statement) {
      String[] var = RutaParseUtils.returnVariable((Statement) node);
      if (var != null) {
        return var[0];
      }
    } else if (node instanceof FieldDeclaration) {
      return ((FieldDeclaration) node).getName();
    }
    return null;
  }

  public void searchInMethod(final ASTNode node, IParent element, ASTNode nde,
          List selectionElements) {
    MethodDeclaration method = (MethodDeclaration) nde;
    String methodName = method.getName();
    if (methodName.indexOf("::") != -1) {
      String pName = methodName.substring(0, methodName.lastIndexOf("::"));
      pName = pName.replaceAll("::", "\\$");
      if (pName.equals("$")) {
        element = sourceModule;
      } else {
        try {
          element = RutaResolver.findTypeFrom(sourceModule.getChildren(), "", pName, '$');
          if (element == null) {
            return;
          }
        } catch (ModelException e) {
          e.printStackTrace();
          return;
        }
      }
      methodName = RutaResolver.getNodeChildName(nde);
    }
    IModelElement e = RutaResolver.findChildrenByName(methodName, element);
    if (e != null && e instanceof IParent) {
      List stats = ((MethodDeclaration) nde).getStatements();
      searchAddElementsTo(stats, node, (IParent) e, selectionElements);
    }
  }

  public static List findExtractBlocks(ASTNode node) {
    final List statements2 = new ArrayList();
    ASTVisitor visitor = new ASTVisitor() {
      @Override
      public boolean visit(Expression s) throws Exception {
        if (s instanceof Block) {
          statements2.addAll(((Block) s).getStatements());
        }
        return super.visit(s);
      }
    };
    try {
      node.traverse(visitor);
    } catch (Exception e) {
      if (DLTKCore.DEBUG) {
        e.printStackTrace();
      }
    }
    return statements2;
  }
}
