/*
 * 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.validator;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;

import org.antlr.runtime.Token;
import org.apache.commons.lang3.StringUtils;
import org.apache.uima.UIMAFramework;
import org.apache.uima.cas.CAS;
import org.apache.uima.resource.ResourceManager;
import org.apache.uima.resource.impl.ResourceManager_impl;
import org.apache.uima.resource.metadata.FeatureDescription;
import org.apache.uima.resource.metadata.TypeDescription;
import org.apache.uima.resource.metadata.TypeSystemDescription;
import org.apache.uima.resource.metadata.impl.FeatureDescription_impl;
import org.apache.uima.ruta.ide.RutaIdeUIPlugin;
import org.apache.uima.ruta.ide.core.IRutaKeywords;
import org.apache.uima.ruta.ide.core.RutaCorePreferences;
import org.apache.uima.ruta.ide.core.RutaExtensionManager;
import org.apache.uima.ruta.ide.core.RutaKeywordsManager;
import org.apache.uima.ruta.ide.core.builder.RutaProjectUtils;
import org.apache.uima.ruta.ide.core.extensions.IIDEActionExtension;
import org.apache.uima.ruta.ide.core.extensions.IIDEBlockExtension;
import org.apache.uima.ruta.ide.core.extensions.IIDEBooleanFunctionExtension;
import org.apache.uima.ruta.ide.core.extensions.IIDEConditionExtension;
import org.apache.uima.ruta.ide.core.extensions.IIDENumberFunctionExtension;
import org.apache.uima.ruta.ide.core.extensions.IIDEStringFunctionExtension;
import org.apache.uima.ruta.ide.core.extensions.IIDETypeFunctionExtension;
import org.apache.uima.ruta.ide.core.extensions.IRutaExtension;
import org.apache.uima.ruta.ide.parser.ast.FeatureMatchExpression;
import org.apache.uima.ruta.ide.parser.ast.ForEachBlock;
import org.apache.uima.ruta.ide.parser.ast.NullExpression;
import org.apache.uima.ruta.ide.parser.ast.RutaAction;
import org.apache.uima.ruta.ide.parser.ast.RutaBlock;
import org.apache.uima.ruta.ide.parser.ast.RutaCondition;
import org.apache.uima.ruta.ide.parser.ast.RutaDeclareDeclarationsStatement;
import org.apache.uima.ruta.ide.parser.ast.RutaExpression;
import org.apache.uima.ruta.ide.parser.ast.RutaFeatureDeclaration;
import org.apache.uima.ruta.ide.parser.ast.RutaFunction;
import org.apache.uima.ruta.ide.parser.ast.RutaImportStatement;
import org.apache.uima.ruta.ide.parser.ast.RutaImportTypesStatement;
import org.apache.uima.ruta.ide.parser.ast.RutaListExpression;
import org.apache.uima.ruta.ide.parser.ast.RutaMacroDeclaration;
import org.apache.uima.ruta.ide.parser.ast.RutaPackageDeclaration;
import org.apache.uima.ruta.ide.parser.ast.RutaRegExpRule;
import org.apache.uima.ruta.ide.parser.ast.RutaRule;
import org.apache.uima.ruta.ide.parser.ast.RutaRuleElement;
import org.apache.uima.ruta.ide.parser.ast.RutaStatementConstants;
import org.apache.uima.ruta.ide.parser.ast.RutaStringExpression;
import org.apache.uima.ruta.ide.parser.ast.RutaStructureAction;
import org.apache.uima.ruta.ide.parser.ast.RutaTypeConstants;
import org.apache.uima.ruta.ide.parser.ast.RutaTypeDeclaration;
import org.apache.uima.ruta.ide.parser.ast.RutaVariableDeclaration;
import org.apache.uima.ruta.ide.parser.ast.RutaVariableReference;
import org.apache.uima.util.InvalidXMLException;
import org.apache.uima.util.XMLInputSource;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.dltk.ast.ASTListNode;
import org.eclipse.dltk.ast.ASTNode;
import org.eclipse.dltk.ast.ASTVisitor;
import org.eclipse.dltk.ast.declarations.MethodDeclaration;
import org.eclipse.dltk.ast.expressions.Expression;
import org.eclipse.dltk.ast.expressions.NumericLiteral;
import org.eclipse.dltk.ast.expressions.StringLiteral;
import org.eclipse.dltk.ast.references.SimpleReference;
import org.eclipse.dltk.ast.statements.Statement;
import org.eclipse.dltk.compiler.problem.IProblem;
import org.eclipse.dltk.compiler.problem.IProblemReporter;
import org.eclipse.dltk.compiler.problem.ProblemSeverity;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.builder.ISourceLineTracker;
import org.eclipse.jface.preference.IPreferenceStore;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

public class LanguageCheckerVisitor extends ASTVisitor {

  private IProblemReporter pr;

  private ISourceLineTracker linetracker;

  private ISourceModule sourceModule;

  private Map<String, IIDEConditionExtension> conditionExtensions;

  private Map<String, IIDEActionExtension> actionExtensions;

  private Map<String, IIDENumberFunctionExtension> numberFunctionExtensions;

  private Map<String, IIDEBooleanFunctionExtension> booleanFunctionExtensions;

  private Map<String, IIDEStringFunctionExtension> stringFunctionExtensions;

  private Map<String, IIDETypeFunctionExtension> typeFunctionExtensions;

  private Map<String, IIDEBlockExtension> blockExtensions;

  /**
   * Mapping from short type name (e.g. {@code W}) to their disambiguated long type names (e.g.
   * {@code org.apache.uima.ruta.type.W}).
   */
  private Map<String, String> namespaces;

  /**
   * Mapping from ambiguous short type names to all their possible long type names.
   */
  private Map<String, Set<String>> ambiguousTypeAlias;

  /**
   * Known variables for each block environment
   */
  private final Stack<Map<String, Integer>> knownLocalVariables;

  /**
   * Name of each block
   */
  private final Stack<String> blocks;

  /**
   * each (inlined) rules
   */
  private final Stack<RutaRule> rules;

  /**
   * Caching the matched type of a rule element
   */
  private String matchedType;

  /**
   * Caching the declared package of the script
   */
  private String packageName = "";

  /**
   * Mapping all long type names of the type system to their type description
   */
  private Map<String, TypeDescription> typeDescriptionMap;

  /**
   * Mapping all long type names of all declared feature description
   */
  private Map<String, Set<FeatureDescription>> featureDescriptionMap;

  /**
   * The type system description of the script file
   */
  private TypeSystemDescription typeSystemDescription;

  private final String implicitString = "Implicit";

  private Set<String> finalTypes;

  private boolean reportWarningOnShortNames;

  private RutaCheckerProblemFactory problemFactory;

  private ResourceManager resourceManager;

  private Set<String> allLongTypeNames;

  private String parentTypeInDeclaration;

  private ClassLoader classLoader;

  private String packagePathString;

  private boolean packageChecked = false;

  private Collection<String> currentLabels = new HashSet<>();

  public LanguageCheckerVisitor(IProblemReporter problemReporter, ISourceLineTracker linetracker,
          ISourceModule sourceModule, ClassLoader classLoader) {
    super();
    pr = problemReporter;
    this.linetracker = linetracker;
    this.sourceModule = sourceModule;
    this.classLoader = classLoader;
    problemFactory = new RutaCheckerProblemFactory(sourceModule.getElementName(), linetracker);

    namespaces = new TreeMap<>();
    ambiguousTypeAlias = new TreeMap<>();
    allLongTypeNames = new HashSet<>();
    knownLocalVariables = new Stack<>();
    knownLocalVariables.push(new HashMap<>());
    blocks = new Stack<>();
    rules = new Stack<>();
    packagePathString = "";

    initializePredefinedInformation();
    initializeExtensionInformation();

    IProject project = sourceModule.getScriptProject().getProject();
    IPath location = sourceModule.getResource().getLocation();
    try {
      IPath packagePath = RutaProjectUtils.getPackagePath(location, project);
      if (packagePath != null) {
        packagePathString = packagePath.toPortableString().replaceAll("/", ".");
      }
    } catch (CoreException e) {
      RutaIdeUIPlugin.error(e);
    }

    IPreferenceStore store = RutaIdeUIPlugin.getDefault().getPreferenceStore();
    reportWarningOnShortNames = !store
            .getBoolean(RutaCorePreferences.BUILDER_IGNORE_DUPLICATE_SHORTNAMES);

  }

  @Override
  public boolean visit(Statement s) throws Exception {
    if (s instanceof RutaPackageDeclaration) {
      packageName = ((RutaPackageDeclaration) s).getName();
      checkPackage(s);
      return false;
    }
    if (s instanceof RutaMacroDeclaration) {
      RutaMacroDeclaration decl = (RutaMacroDeclaration) s;
      Map<Token, Token> definition = decl.getDefinition();
      String name = decl.getName();
      int kind = decl.getKind();
      if (kind == RutaTypeConstants.RUTA_TYPE_A) {
        actionExtensions.put(name, null);
      } else if (kind == RutaTypeConstants.RUTA_TYPE_C) {
        conditionExtensions.put(name, null);
      }
      Set<Entry<Token, Token>> entrySet = definition.entrySet();
      Map<String, Integer> map = new HashMap<>();
      for (Entry<Token, Token> entry : entrySet) {
        String varName = entry.getKey().getText();
        String varType = entry.getValue().getText();
        int vt = getType(varType);
        map.put(varName, vt);
      }
      knownLocalVariables.push(map);
    }
    if (s instanceof RutaImportTypesStatement) {
      RutaImportTypesStatement stmt = (RutaImportTypesStatement) s;
      SimpleReference tsExpr = (SimpleReference) stmt.getExpression();
      Token typeToken = stmt.getTypeToken();
      Token pkgToken = stmt.getPkgToken();
      Token aliasToken = stmt.getAliasToken();
      if (tsExpr != null) {
        String localPath = tsExpr.getName();
        processCompleteTypeSystemImport(tsExpr, localPath, typeToken, pkgToken, aliasToken);
      } else {
        // TODO package import not supported in Workbench
      }

    } else if (s instanceof RutaImportStatement) {
      // handle type system imports
      if (((RutaImportStatement) s).getType() == RutaStatementConstants.S_IMPORT_TYPESYSTEM) {
        SimpleReference sRef = (SimpleReference) ((RutaImportStatement) s).getExpression();
        String localPath = sRef.getName();
        processCompleteTypeSystemImport(sRef, localPath);
        return false;
      }
      // handle script-imports
      if (((RutaImportStatement) s).getType() == RutaStatementConstants.S_IMPORT_SCRIPT) {
        SimpleReference sRef = (SimpleReference) ((RutaImportStatement) s).getExpression();
        String localPath = sRef.getName();

        // HOTFIX Peter add also the imported types of the imported type system!
        try {
          URL url = null;
          IFile file = RutaCheckerUtils.checkScriptImport(localPath,
                  sourceModule.getScriptProject());
          if (file == null) {
            String typesystemSuffix = RutaProjectUtils
                    .getTypeSystemSuffix(sourceModule.getScriptProject().getProject());
            url = RutaCheckerUtils.checkImportExistence(localPath + typesystemSuffix, "xml",
                    classLoader);
          }
          if (file == null && url == null) {
            pr.reportProblem(problemFactory.createFileNotFoundProblem(sRef, localPath));
          } else {
            IProject referredProject = sourceModule.getScriptProject().getProject();
            if (file != null) {
              // script in other project? use that if the file was found in the workspace
              referredProject = file.getProject();
              IPath typeSystemDescriptorPath = RutaProjectUtils.getTypeSystemDescriptorPath(
                      file.getLocation(), referredProject, classLoader);
              TypeSystemDescription tsDesc = importCompleteTypeSystem(typeSystemDescriptorPath,
                      url);

              List<String> checkDuplicateShortNames = checkOnAmbiguousShortNames(tsDesc);
              if (!checkDuplicateShortNames.isEmpty()) {
                pr.reportProblem(problemFactory.createDuplicateShortNameInImported(sRef, localPath,
                        checkDuplicateShortNames, ProblemSeverity.WARNING));
              }
            }
          }
        } catch (IOException e) {
          pr.reportProblem(problemFactory.createFileNotFoundProblem(sRef, localPath));
        }
        return false;
      }
    }

    if (s instanceof RutaDeclareDeclarationsStatement) {
      RutaDeclareDeclarationsStatement dds = (RutaDeclareDeclarationsStatement) s;
      ASTNode parent = dds.getParent();
      if (parent != null && parent instanceof RutaVariableReference) {
        RutaVariableReference p = (RutaVariableReference) parent;
        String name = p.getName();
        name = expand(name);
        parentTypeInDeclaration = name;
        // TODO remember name for new types and their features!
        if (finalTypes.contains(name)) {
          IProblem problem = problemFactory.createInheritenceFinalProblem(p);
          pr.reportProblem(problem);
        }
      }
      return true;
    }
    if (s instanceof RutaTypeDeclaration) {
      RutaTypeDeclaration newType = (RutaTypeDeclaration) s;
      String shortName = newType.getName();
      String longName = getLongNameOfNewType(shortName);

      if (namespaces.values().contains(longName)) {
        IProblem problem = problemFactory.createIdConflictsWithTypeProblem(newType);
        pr.reportProblem(problem);
        return false;
      }

      if (reportWarningOnShortNames && namespaces.containsKey(shortName)) {
        IProblem problem = problemFactory.createDuplicateShortName(newType,
                ProblemSeverity.WARNING);
        pr.reportProblem(problem);
        return false;
      }
      if (knowsVariable(shortName)) {
        IProblem problem = problemFactory.createIdConflictsWithVariableProblem(newType);
        pr.reportProblem(problem);
        return false;
      }
      List<RutaFeatureDeclaration> features = newType.getFeatures();
      Set<FeatureDescription> feats = new HashSet<>();
      if (parentTypeInDeclaration != null) {
        Set<FeatureDescription> set = featureDescriptionMap.get(parentTypeInDeclaration);
        if (set != null) {
          feats.addAll(set);
        }
      }
      if (features != null) {
        for (RutaFeatureDeclaration each : features) {
          // TODO create correct feature description! Works right now because the type is not
          // checked
          String type = each.getType();
          type = translate(type);
          type = expand(type);
          FeatureDescription f = new FeatureDescription_impl(each.getName(), "", type);
          feats.add(f);
          if (type.equals("INT") || type.equals("STRING") || type.equals("DOUBLE")
                  || type.equals("FLOAT") || type.equals("BOOLEAN")) {
            continue;
          }
          if (!namespaces.keySet().contains(type) && !namespaces.values().contains(type)) {
            IProblem problem = problemFactory.createUnknownFeatureTypeProblem(each);
            pr.reportProblem(problem);
          }

        }
        featureDescriptionMap.put(longName, feats);
      }
      addDeclaredType(shortName);
      return false;
    }
    if (s instanceof RutaVariableDeclaration) {
      RutaVariableDeclaration newVar = (RutaVariableDeclaration) s;
      if (knowsVariable(newVar.getName())) {
        IProblem problem = problemFactory.createIdConflictsWithVariableProblem(newVar);
        pr.reportProblem(problem);
        return false;
      }
      if (namespaces.containsKey(newVar.getName())) {
        IProblem problem = problemFactory.createIdConflictsWithTypeProblem(newVar);
        pr.reportProblem(problem);
        return false;
      }
      knownLocalVariables.peek().put(newVar.getName(), newVar.getKind());
      return false;
    }
    if (s instanceof RutaRegExpRule) {
      RutaRegExpRule rule = (RutaRegExpRule) s;
      Map<Expression, Map<Expression, Expression>> faMap = rule.getFeats();
      Set<Entry<Expression, Map<Expression, Expression>>> typeEntrySet = faMap.entrySet();
      for (Entry<Expression, Map<Expression, Expression>> entry : typeEntrySet) {
        Expression struct = entry.getKey();
        String structure = "";
        if (struct != null) {
          structure = sourceModule.getSource().substring(struct.sourceStart(), struct.sourceEnd());
          structure = expand(structure);
        }
        Map<Expression, Expression> fmap = entry.getValue();
        Set<Expression> keySet = fmap.keySet();
        for (Expression fkey : keySet) {
          if (fkey instanceof RutaExpression && fkey.getKind() == RutaTypeConstants.RUTA_TYPE_S) {
            String feat = fkey.toString();
            feat = getFeatureName(fkey, feat);
            boolean findFeature = findFeature(structure, feat, -1);
            if (!findFeature) {
              IProblem problem = problemFactory.createUnknownFeatureProblem(fkey, structure);
              pr.reportProblem(problem);
            }
          }
        }
      }
    }
    if (s instanceof RutaRule) {
      if (rules.isEmpty()) {
        collectAllLabels((RutaRule) s);
      }
      rules.push((RutaRule) s);
    }
    return true;
  }

  private void collectAllLabels(RutaRule rule) {
    try {
      RuleElementLabelVisitor visitor = new RuleElementLabelVisitor();
      rule.traverse(visitor);
      currentLabels = visitor.getLabels();
    } catch (Exception e) {
      RutaIdeUIPlugin.error(e);
    }
  }

  private void checkPackage(ASTNode node) {
    if (!StringUtils.equals(packageName, packagePathString) && !packageChecked) {
      pr.reportProblem(problemFactory.createWrongPackageProblem(node));
    }
    packageChecked = true;
  }

  private void processCompleteTypeSystemImport(SimpleReference sRef, String localPath)
          throws CoreException {
    processCompleteTypeSystemImport(sRef, localPath, null, null, null);
  }

  private void processCompleteTypeSystemImport(SimpleReference sRef, String localPath,
          Token typeToken, Token pkgToken, Token aliasToken) throws CoreException {
    try {
      URL url = null;
      IFile file = RutaCheckerUtils.checkTypeSystemImport(localPath,
              sourceModule.getScriptProject());
      if (file == null) {
        url = RutaCheckerUtils.checkImportExistence(localPath, "xml", classLoader);
      }
      if (file == null && url == null) {
        pr.reportProblem(problemFactory.createFileNotFoundProblem(sRef, localPath));
      } else {
        IPath path = file == null ? null : file.getLocation();
        TypeSystemDescription tsDesc = importTypeSystem(path, url, typeToken, pkgToken, aliasToken);
        if (reportWarningOnShortNames) {
          List<String> checkDuplicateShortNames = checkOnAmbiguousShortNames(tsDesc);
          if (!checkDuplicateShortNames.isEmpty()) {
            pr.reportProblem(problemFactory.createDuplicateShortNameInImported(sRef, localPath,
                    checkDuplicateShortNames, ProblemSeverity.WARNING));
          }
        }
      }
    } catch (IOException e) {
      pr.reportProblem(problemFactory.createFileNotFoundProblem(sRef, localPath));
    } catch (InvalidXMLException e) {
      pr.reportProblem(problemFactory.createXMLProblem(sRef, localPath));
    }
  }

  private List<String> checkOnAmbiguousShortNames(TypeSystemDescription tsDesc) {
    List<String> checkDuplicateShortNames = new ArrayList<>();
    for (TypeDescription each : tsDesc.getTypes()) {
      String longName = each.getName();
      String shortName = getShortName(longName);
      Set<String> set = ambiguousTypeAlias.get(shortName);
      if (set != null && set.size() > 1) {
        checkDuplicateShortNames.addAll(set);
      }
    }
    return checkDuplicateShortNames;
  }

  private TypeSystemDescription importTypeSystem(IPath path, URL url, Token typeToken,
          Token pkgToken, Token aliasToken)
          throws InvalidXMLException, IOException, MalformedURLException, CoreException {
    TypeSystemDescription tsDesc = null;
    if (path != null) {
      tsDesc = UIMAFramework.getXMLParser()
              .parseTypeSystemDescription(new XMLInputSource(path.toFile()));
    } else {
      tsDesc = UIMAFramework.getXMLParser().parseTypeSystemDescription(new XMLInputSource(url));
    }

    ResourceManager resMgr = getResourceManager(classLoader);
    tsDesc.resolveImports(resMgr);
    for (TypeDescription each : tsDesc.getTypes()) {
      String longName = each.getName();
      String shortName = getShortName(longName);
      if (pkgToken != null) {
        String pkg = pkgToken.getText();
        if (!longName.startsWith(pkg + ".")) {
          continue;
        }
      }
      if (typeToken != null) {
        String type = typeToken.getText();
        if (!longName.equals(type)) {
          continue;
        }
      }
      if (aliasToken != null) {
        String alias = aliasToken.getText();
        if (typeToken == null) {
          shortName = alias + "." + shortName;
        } else {
          shortName = alias;
        }
      }
      importType(longName, shortName);
    }
    return tsDesc;
  }

  private ResourceManager getResourceManager(ClassLoader classloader)
          throws MalformedURLException, CoreException {
    if (resourceManager == null) {
      resourceManager = new ResourceManager_impl(classloader);
      List<IFolder> folders = RutaProjectUtils
              .getAllDescriptorFolders(sourceModule.getScriptProject().getProject());
      StringBuilder sb = new StringBuilder();
      Iterator<IFolder> iterator = folders.iterator();
      while (iterator.hasNext()) {
        IFolder iFolder = iterator.next();
        sb.append(iFolder.getLocation().toPortableString());
        if (iterator.hasNext()) {
          sb.append(System.getProperty("path.separator"));
        }
      }
      resourceManager.setDataPath(sb.toString());
    }
    return resourceManager;
  }

  @Override
  public boolean visit(Expression s) throws Exception {

    if (s instanceof RutaRuleElement) {
      RutaRuleElement re = (RutaRuleElement) s;
      Expression head = re.getHead();
      if (head instanceof FeatureMatchExpression) {
        FeatureMatchExpression fme = (FeatureMatchExpression) head;
        String text = fme.getFeature().getText();
        int lastIndexOf = text.lastIndexOf('.');
        String twf = text.substring(0, lastIndexOf);
        Integer variableType = getVariableType(twf);
        if (variableType != null && variableType == RutaTypeConstants.RUTA_TYPE_AT) {
          matchedType = twf;
        } else {
          twf = expand(twf);
          matchedType = isFeatureMatch(twf);
        }
      } else if (head != null) {
        matchedType = sourceModule.getSource().substring(head.sourceStart(), head.sourceEnd());
      }
      // cache long name
      matchedType = expand(matchedType);
      if (matchedType == null) {
        matchedType = "uima.tcas.Annotation";
      }
    }
    if (s instanceof FeatureMatchExpression) {
      FeatureMatchExpression fme = (FeatureMatchExpression) s;
      String featText = fme.getFeature().getText();
      // HOTFIX: parser creates wrong AST element
      if (allLongTypeNames.contains(featText)) {
        return true;
      }
      if (namespaces.keySet().contains(featText)) {
        // wrong ast elements, alias interpreted as feature expression
        return false;
      }
      checkTypeOfFeatureMatch(featText, fme);
      return true;
    }
    if (s instanceof RutaVariableReference) {
      if (s instanceof NullExpression) {
        return false;
      }
      RutaVariableReference ref = (RutaVariableReference) s;
      if (ref.getType() == RutaTypeConstants.RUTA_TYPE_WT
              || ref.getType() == RutaTypeConstants.RUTA_TYPE_WL) {
        if (StringUtils.isBlank(ref.getName())) {
          // declaration with a string expression: do not check
          return false;
        }
      }
      if ((ref.getType() & RutaTypeConstants.RUTA_TYPE_AT) != 0) {
        // types
        String name = ref.getName();
        if (name.equals("Document")) {
          return false;
        }

        Set<String> set = ambiguousTypeAlias.get(name);
        if (set != null && !set.isEmpty()) {
          pr.reportProblem(
                  problemFactory.createAmbiguousShortName(ref, set, ProblemSeverity.ERROR));
          return false;
        }
        if (namespaces.keySet().contains(name) || namespaces.values().contains(name)
                || allLongTypeNames.contains(name)) {
          return false;
        }
        Integer variableType = getVariableType(name);
        if (variableType != null && (variableType == RutaTypeConstants.RUTA_TYPE_AT
                || variableType == RutaTypeConstants.RUTA_TYPE_UA
                || variableType == RutaTypeConstants.RUTA_TYPE_UAL)) {
          return false;
        }
        if (isFeatureMatch(name) != null) {
          return false;
        }
        if (isLabel(name)) {
          return false;
        }
        if (name.indexOf(".") != -1) {
          String[] split = name.split("[.]");
          if (StringUtils.equals(split[split.length - 1], "ct")
                  || StringUtils.equals(split[split.length - 1], "coveredText")) {
            return false;
          }
          Integer prefixType = getVariableType(split[0]);
          if (prefixType == RutaTypeConstants.RUTA_TYPE_UA
                  || prefixType == RutaTypeConstants.RUTA_TYPE_UAL) {
            return false;
          }
        }

        pr.reportProblem(problemFactory.createTypeProblem(ref, sourceModule));
        return false;
      }
      if (!isVariableDeclared(ref)) {
        return false;
      }
      checkTypeOfVariable(ref);
      return false;
    }
    // check assign types
    if (s instanceof RutaAction) {
      RutaAction tma = (RutaAction) s;

      String actionName = sourceModule.getSource().substring(tma.getNameStart(), tma.getNameEnd());
      String[] keywords = RutaKeywordsManager.getKeywords(IRutaKeywords.ACTION);
      List<String> asList = Arrays.asList(keywords);
      if (!StringUtils.isEmpty(actionName) && !"-".equals(actionName)
              && !asList.contains(actionName) && !implicitString.equals(tma.getName())
              && !actionExtensions.keySet().contains(actionName)) {
        IProblem problem = problemFactory.createUnknownActionProblem(tma);
        pr.reportProblem(problem);
      }

      IRutaExtension extension = actionExtensions.get(actionName);
      if (extension != null) {
        extension.checkSyntax(tma, problemFactory, pr);
      }

      if (tma.getName().equals("GETFEATURE") || tma.getName().equals("SETFEATURE")) {
        List<?> childs = tma.getChilds();
        RutaStringExpression stringExpr = (RutaStringExpression) childs.get(0);
        String feat = stringExpr.toString();
        feat = getFeatureName(stringExpr, feat);
        boolean featureFound = findFeature(matchedType, feat, -1);
        if (!featureFound) {
          IProblem problem = problemFactory.createUnknownFeatureProblem(stringExpr, matchedType);
          pr.reportProblem(problem);
        }
      }

      if (s instanceof RutaStructureAction) {
        RutaStructureAction sa = (RutaStructureAction) s;
        Expression struct = sa.getStructure();
        String structure = null;
        if (struct != null) {
          structure = sourceModule.getSource().substring(struct.sourceStart(), struct.sourceEnd());
          structure = expand(structure);
        }
        Map<Expression, Expression> assignments = sa.getAssignments();
        // hotfix... correct name in ast
        String action = sourceModule.getSource().substring(sa.getNameStart(), sa.getNameEnd());
        if (assignments != null && !action.equals("TRIE")) {
          for (Expression each : assignments.keySet()) {
            // TODO refactor to visitor?
            String feat = each.toString();
            // List<?> childs = each.getChilds();
            feat = getFeatureName(each, feat);
            boolean featureFound = findFeature(structure, feat, -1);
            if (!featureFound) {
              IProblem problem = problemFactory.createUnknownFeatureProblem(each, structure);
              pr.reportProblem(problem);
            }
          }
        } else if (assignments != null && action.equals("TRIE")) {
          for (Expression each : assignments.values()) {
            if (each instanceof RutaListExpression) {
              RutaListExpression rle = (RutaListExpression) each;
              List<?> childs = rle.getExprs().getChilds();
              if (childs.size() != 2 && childs.size() != 3) {
                IProblem problem = problemFactory.createWrongNumberOfArgumentsProblem(actionName,
                        rle, 2);
                pr.reportProblem(problem);
              }
              Object arg1 = childs.get(0);
              if (arg1 instanceof RutaExpression) {
                RutaExpression e1 = (RutaExpression) arg1;
                if (e1.getKind() != RutaTypeConstants.RUTA_TYPE_AT) {
                  IProblem problem = problemFactory.createWrongArgumentTypeProblem(e1, "Type");
                  pr.reportProblem(problem);
                }
              }
              Object arg2 = childs.get(1);
              if (arg2 instanceof RutaExpression) {
                RutaExpression e2 = (RutaExpression) arg2;
                if (e2.getKind() != RutaTypeConstants.RUTA_TYPE_S) {
                  IProblem problem = problemFactory.createWrongArgumentTypeProblem(e2, "String");
                  pr.reportProblem(problem);
                }
              }
            }
          }
        }
      }
    }
    if (s instanceof RutaCondition) {
      RutaCondition cond = (RutaCondition) s;
      String conditionName = sourceModule.getSource().substring(cond.getNameStart(),
              cond.getNameEnd());
      String[] keywords = RutaKeywordsManager.getKeywords(IRutaKeywords.CONDITION);
      List<String> asList = Arrays.asList(keywords);
      if (!StringUtils.isEmpty(conditionName) && !"-".equals(conditionName)
              && !asList.contains(conditionName) && !implicitString.equals(cond.getName())
              && !conditionExtensions.keySet().contains(conditionName)) {
        IProblem problem = problemFactory.createUnknownConditionProblem(cond);
        pr.reportProblem(problem);
      }

      IRutaExtension extension = conditionExtensions.get(conditionName);
      if (extension != null) {
        // boolean checkSyntax =
        extension.checkSyntax(cond, problemFactory, pr);
      }

      if (conditionName.equals("FEATURE")) {
        if (matchedType != null) {
          List<?> args = cond.getChilds();
          RutaStringExpression se = (RutaStringExpression) args.get(0);
          String feat = se.toString();
          feat = getFeatureName(se, feat);
          boolean featureFound = findFeature(matchedType, feat, -1);
          if (!featureFound) {
            String featureMatch = isFeatureMatch(matchedType);
            if (featureMatch != null) {
              featureFound = findFeature(featureMatch, feat, -1);
            }
          }
          if (!featureFound) {
            IProblem problem = problemFactory.createUnknownFeatureProblem(se, matchedType);
            pr.reportProblem(problem);
          }
        }
      }
      if (conditionName.equals("CONTAINS")) {
        List<?> args = cond.getChilds();
        boolean valid = checkContainsArguments(args);
        if (!valid) {
          IProblem problem = problemFactory.createWrongArgumentTypeProblem(cond,
                  "different combination of arguments.");
          pr.reportProblem(problem);
        }
      }

    }
    if (s instanceof RutaFunction) {
      RutaFunction f = (RutaFunction) s;
      String name = f.getName();
      if (s.getKind() == RutaTypeConstants.RUTA_TYPE_AT) {
        IRutaExtension extension = typeFunctionExtensions.get(name);
        if (extension != null) {
          extension.checkSyntax(s, problemFactory, pr);
        }
      } else if (s.getKind() == RutaTypeConstants.RUTA_TYPE_B) {
        IRutaExtension extension = booleanFunctionExtensions.get(name);
        if (extension != null) {
          extension.checkSyntax(s, problemFactory, pr);
        }
      } else if (s.getKind() == RutaTypeConstants.RUTA_TYPE_N) {
        IRutaExtension extension = numberFunctionExtensions.get(name);
        if (extension != null) {
          extension.checkSyntax(s, problemFactory, pr);
        }
      } else if (s.getKind() == RutaTypeConstants.RUTA_TYPE_S) {
        IRutaExtension extension = stringFunctionExtensions.get(name);
        if (extension != null) {
          extension.checkSyntax(s, problemFactory, pr);
        }
      }
    }
    return true;
  }

  @SuppressWarnings("unused")
  private boolean checkContainsArguments(List<?> args) {
    if (args.size() == 1) {
      Object arg = args.get(0);
      // if (arg instanceof ITypeExpression) {
      return true;
      // }
    } else if (args.size() == 2) {
      Object arg1 = args.get(0);
      Object arg2 = args.get(1);
      // if (arg1 instanceof ListExpression) {
      return true;
      // }
    } else if (args.size() == 3) {
      Object arg1 = args.get(0);
      Object arg2 = args.get(1);
      Object arg3 = args.get(2);
      // if (arg1 instanceof ITypeExpression && arg2 instanceof INumberExpression
      // && arg3 instanceof INumberExpression) {
      return true;
      // }
    } else if (args.size() == 4) {
      Object arg1 = args.get(0);
      Object arg2 = args.get(1);
      Object arg3 = args.get(2);
      Object arg4 = args.get(3);
      // if (arg1 instanceof ITypeExpression && arg2 instanceof INumberExpression
      // && arg3 instanceof INumberExpression && arg4 instanceof IBooleanExpression) {
      return true;
      // }
    } else if (args.size() == 5) {
      Object arg1 = args.get(0);
      Object arg2 = args.get(1);
      Object arg3 = args.get(2);
      Object arg4 = args.get(3);
      Object arg5 = args.get(3);
      // if (arg1 instanceof ListExpression && arg3 instanceof INumberExpression
      // && arg4 instanceof INumberExpression && arg5 instanceof IBooleanExpression) {
      return true;
      // }
    }
    return false;
  }

  private boolean isLabel(String name) {

    if (currentLabels.contains(name)) {
      return true;
    }

    if (name.contains(".")) {
      String[] split = name.split("[.]");
      if (split.length > 0) {
        return currentLabels.contains(split[0]);
      }
    }

    return false;
  }

  private String expand(String shortName) {
    if (shortName == null) {
      return null;
    }
    String longName = shortName;
    String string = namespaces.get(shortName);
    if (string != null) {
      longName = string;
    }
    return longName;
  }

  private void checkTypeOfFeatureMatch(String featText, FeatureMatchExpression fme) {
    int lastIndexOf = featText.lastIndexOf(".");
    int firstIndexOf = featText.indexOf(".");
    if (lastIndexOf == -1) {
      return;
    }
    String bref = featText.substring(0, firstIndexOf);
    String aref = featText.substring(0, lastIndexOf);
    String fref = featText.substring(lastIndexOf + 1, featText.length());
    if (currentLabels.contains(aref) || currentLabels.contains(bref)) {
      return;
    }
    String match = isFeatureMatch(aref);
    Integer variableType1 = getVariableType(aref);
    Integer variableType2 = getVariableType(bref);
    if (match == null && variableType1 != null && variableType2 != null
            && (variableType1 == RutaTypeConstants.RUTA_TYPE_AT
                    || variableType2 == RutaTypeConstants.RUTA_TYPE_AT
                    || variableType1 == RutaTypeConstants.RUTA_TYPE_UA)) {
      // do not check on variables!
      return;
    }

    // match expression against local annotation variables cannot be checked
    if (variableType2 != null && (variableType2 == RutaTypeConstants.RUTA_TYPE_UA
            || variableType2 == RutaTypeConstants.RUTA_TYPE_UAL)) {
      return;
    }

    match = expand(match);
    if (match != null) {
      int kind = -1;
      if (fme.getValue() != null) {
        kind = fme.getValue().getKind();
        if (fme.getValue() instanceof StringLiteral) {
          kind = RutaTypeConstants.RUTA_TYPE_S;
        } else if (fme.getValue() instanceof NumericLiteral) {
          kind = RutaTypeConstants.RUTA_TYPE_N;
        } else if (fme.getValue() instanceof RutaVariableReference) {
          kind = ((RutaVariableReference) fme.getValue()).getType();
        } else if (fme.getValue() instanceof RutaFunction) {
          // check on function deactivates, requires correct parsing of AST with external factory
          kind = -1;
        }
      }
      boolean findFeature = findFeature(match, fref, kind);
      if (findFeature || fme.getValue() instanceof NullExpression) {

      } else {
        pr.reportProblem(problemFactory.createUnknownFeatureProblem(fme, aref));
      }
    } else {
      pr.reportProblem(problemFactory.createTypeProblem(fme, sourceModule));
      pr.reportProblem(problemFactory.createUnknownFeatureProblem(fme, aref));
    }
  }

  @Override
  public boolean endvisit(Expression s) throws Exception {
    if (s instanceof RutaRuleElement) {
      matchedType = null;
    }
    return super.endvisit(s);
  }

  @Override
  public boolean endvisit(Statement s) throws Exception {
    if (s instanceof RutaDeclareDeclarationsStatement) {
      parentTypeInDeclaration = null;
    }
    if (s instanceof ForEachBlock) {
      knownLocalVariables.pop();
    }
    if (s instanceof RutaMacroDeclaration) {
      knownLocalVariables.pop();
    }
    if (!packageChecked) {
      checkPackage(null);
    }
    if (s instanceof RutaRule) {
      rules.pop();
      if (rules.isEmpty()) {
        currentLabels.clear();
      }
    }

    return super.endvisit(s);
  }

  @Override
  public boolean endvisit(MethodDeclaration s) throws Exception {
    if (s instanceof RutaBlock) {
      knownLocalVariables.pop();
      blocks.pop();
    }
    return super.endvisit(s);
  }

  @Override
  public boolean visit(MethodDeclaration s) throws Exception {
    if (s instanceof RutaBlock) {
      RutaBlock b = (RutaBlock) s;
      String name = b.getName();
      HashMap<String, Integer> map = new HashMap<>();
      if (b instanceof ForEachBlock) {
        map.put(name, RutaTypeConstants.RUTA_TYPE_UA);
      }
      knownLocalVariables.push(map);
      blocks.push(name);
      // TODO add syntax check for block extensions
    }
    return true;
  }

  private boolean findFeature(String longTypeName, String featureName, int kind) {
    if (longTypeName == null) {
      return false;
    }
    if (longTypeName.equals("Document") || longTypeName.equals("org.apache.uima.ruta.type.Document")
            || longTypeName.equals("uima.tcas.DocumentAnnotation")) {
      if (featureName.equals("language") || featureName.equals("begin")
              || featureName.equals("end")) {
        return true;
      }
    }
    if (featureName.equals("begin") || featureName.equals("end")) {
      return kind == -1 || kind == RutaTypeConstants.RUTA_TYPE_N;
    }
    if (featureName.equals("ct") || featureName.equals("coveredText")) {
      return kind == -1 || kind == RutaTypeConstants.RUTA_TYPE_S;
    }
    Set<FeatureDescription> set = featureDescriptionMap.get(longTypeName);
    if (set != null) {
      for (FeatureDescription featureDescription : set) {
        String fName = featureDescription.getName();
        // TODO check on correct feature type, e.g., the type of the annotation
        if (fName.equals(featureName)
                && (kind == -1 || checkFeatureKind(featureDescription, kind))) {
          return true;
        }
      }
    }
    return false;
  }

  private boolean checkFeatureKind(FeatureDescription f, int kind) {
    if (kind == -1) {
      return true;
    }
    String t = f.getRangeTypeName();
    if (t.equals(CAS.TYPE_NAME_BOOLEAN) && RutaTypeConstants.RUTA_TYPE_B == kind) {
      return true;
    } else if (t.equals(CAS.TYPE_NAME_STRING) && RutaTypeConstants.RUTA_TYPE_S == kind) {
      return true;
    } else if ((t.equals(CAS.TYPE_NAME_BYTE) || t.equals(CAS.TYPE_NAME_DOUBLE)
            || t.equals(CAS.TYPE_NAME_FLOAT) || t.equals(CAS.TYPE_NAME_INTEGER)
            || t.equals(CAS.TYPE_NAME_LONG) || t.equals(CAS.TYPE_NAME_SHORT))
            && RutaTypeConstants.RUTA_TYPE_N == kind) {
      return true;
    } else if (RutaTypeConstants.RUTA_TYPE_AT == kind) {
      return true;
    }
    return false;
  }

  private void addDeclaredType(String shortName) {
    String longName = getLongNameOfNewType(shortName);
    importType(longName, shortName);
  }

  private String getLongNameOfNewType(String shortName) {
    String moduleName = sourceModule.getElementName();
    moduleName = moduleName.substring(0, moduleName.length() - 5);
    String packagePrefix = "";
    if (!packageName.isEmpty()) {
      packagePrefix = packageName + ".";
    }
    for (String each : blocks) {
      packagePrefix += each + ".";
    }
    String longName = packagePrefix + moduleName + "." + shortName;
    return longName;
  }

  /**
   * Import a type in the current namespace.
   *
   * @param longName
   *          Complete type name.
   * @param shortName
   *          Short type name (without namespace).
   */
  private void importType(String longName, String shortName) {
    if (allLongTypeNames.contains(longName)) {
      // TODO: in conflict with double import
      // pr.reportProblem(problemFactory.createIdenticalLongTypeNameProblem(longName,
      // sourceModule));
    } else {
      allLongTypeNames.add(longName);
    }
    Set<String> targets = ambiguousTypeAlias.get(shortName);
    if (targets != null) {
      // shortName is already ambiguous, add longName to its list of possible targets
      targets.add(longName);
    } else {
      String existing = namespaces.put(shortName, longName);

      if (existing != null && !existing.equals(longName)) {
        // shortName can now be resolved to "existing" or "longName"
        targets = new HashSet<>(2);
        targets.add(existing);
        targets.add(longName);

        // add existing mapping and longName to its list of possible targets
        ambiguousTypeAlias.put(shortName, targets);

        // remove shortName from the namespace because it is ambiguous
        namespaces.remove(shortName);
      }
    }
  }

  private boolean knowsVariable(String name) {
    for (Map<String, Integer> each : knownLocalVariables) {
      if (each.containsKey(name)) {
        return true;
      }
    }
    return false;
  }

  private Integer getVariableType(String name) {
    for (Map<String, Integer> each : knownLocalVariables) {
      Integer integer = each.get(name);
      if (integer != null) {
        return integer;
      }
    }
    return null;
  }

  private String getFeatureName(Expression expression, String defaultValue) {
    // TODO refactor AST. This is not a really straightforward!
    String result = defaultValue;
    List<?> childs = expression.getChilds();
    if (childs != null && !childs.isEmpty()) {
      Object object = childs.get(0);
      if (object instanceof ASTListNode) {
        List<?> childs2 = ((ASTListNode) object).getChilds();
        if (childs2 != null && !childs2.isEmpty()) {
          Object object2 = childs2.get(0);
          if (object2 instanceof StringLiteral) {
            StringLiteral sl = (StringLiteral) object2;
            result = sl.getValue().replaceAll("\"", "");
          }
        }
      }
    }
    return result;
  }

  private void initializePredefinedInformation() {

    typeDescriptionMap = new HashMap<>();
    featureDescriptionMap = new HashMap<>();

    try {
      typeSystemDescription = getTypeSystemOfScript();
      IProject project = sourceModule.getScriptProject().getProject();
      List<IFolder> descriptorFolders = RutaProjectUtils.getDescriptorFolders(project);
      boolean exists = false;
      for (IFolder iFolder : descriptorFolders) {
        IPath basicTSD = iFolder.getLocation().append("BasicTypeSystem.xml");
        exists = basicTSD.toFile().exists();
        if (exists) {
          importCompleteTypeSystem(basicTSD, null);
          break;
        }
      }
      if (!exists) {
        // not in a common ruta project
        // try to find the file in the classpath
        URL resource = classLoader.getResource("org/apache/uima/ruta/engine/BasicTypeSystem.xml");
        if (resource != null) {
          importCompleteTypeSystem(null, resource);
        }
      }
    } catch (Exception e) {
      RutaIdeUIPlugin.error(e);
    }
    if (typeSystemDescription != null) {
      TypeDescription[] descriptions = typeSystemDescription.getTypes();
      for (TypeDescription typeDescription : descriptions) {
        String typeName = typeDescription.getName();
        typeDescriptionMap.put(typeName, typeDescription);
      }

      for (TypeDescription typeDescription : descriptions) {
        Set<FeatureDescription> allFeatures = getAllDeclaredFeatures(typeDescription,
                typeDescriptionMap);
        featureDescriptionMap.put(typeDescription.getName(), allFeatures);
      }
    }

    List<String> uimaPredefTypes = Arrays.asList("uima.cas.Boolean", "uima.cas.Byte",
            "uima.cas.Short", "uima.cas.Integer", "uima.cas.Long", "uima.cas.Float",
            "uima.cas.Double", "uima.cas.String", "uima.cas.BooleanArray", "uima.cas.ByteArray",
            "uima.cas.ShortArray", "uima.cas.IntegerArray", "uima.cas.LongArray",
            "uima.cas.FloatArray", "uima.cas.DoubleArray", "uima.cas.StringArray",
            "uima.cas.FSArray", "uima.cas.AnnotationBase", "uima.tcas.Annotation",
            "uima.tcas.DocumentAnnotation", "uima.cas.FloatList", "uima.cas.IntegerList",
            "uima.cas.StringList", "uima.cas.FSList", "uima.cas.EmptyFloatList",
            "uima.cas.EmptyIntegerList", "uima.cas.EmptyStringList", "uima.cas.EmptyFSList",
            "uima.cas.NonEmptyFloatList", "uima.cas.NonEmptyIntegerList",
            "uima.cas.NonEmptyStringList", "uima.cas.NonEmptyFSList");
    for (String longName : uimaPredefTypes) {
      String shortName = getShortName(longName);
      importType(longName, shortName);
    }

    finalTypes = new HashSet<>();
    Set<String> uimaFinalTypes = new HashSet<>();
    uimaFinalTypes.addAll(Arrays.asList("uima.cas.Boolean", "uima.cas.Byte", "uima.cas.Short",
            "uima.cas.Integer", "uima.cas.Long", "uima.cas.Float", "uima.cas.Double",
            "uima.cas.BooleanArray", "uima.cas.ByteArray", "uima.cas.ShortArray",
            "uima.cas.IntegerArray", "uima.cas.LongArray", "uima.cas.FloatArray",
            "uima.cas.DoubleArray", "uima.cas.StringArray", "uima.cas.FSArray"));

    for (String string : uimaFinalTypes) {
      int indexOf = string.lastIndexOf('.');
      finalTypes.add(string);
      finalTypes.add(string.substring(indexOf + 1, string.length()));
    }
  }

  private TypeSystemDescription importCompleteTypeSystem(IPath path, URL url)
          throws InvalidXMLException, MalformedURLException, IOException, CoreException {
    return importTypeSystem(path, url, null, null, null);
  }

  private void initializeExtensionInformation() {
    conditionExtensions = new HashMap<>();
    actionExtensions = new HashMap<>();
    numberFunctionExtensions = new HashMap<>();
    booleanFunctionExtensions = new HashMap<>();
    stringFunctionExtensions = new HashMap<>();
    typeFunctionExtensions = new HashMap<>();
    blockExtensions = new HashMap<>();
    IIDEConditionExtension[] cextensions = RutaExtensionManager.getDefault()
            .getIDEConditionExtensions();
    for (IIDEConditionExtension each : cextensions) {
      String[] knownExtensions = each.getKnownExtensions();
      for (String string : knownExtensions) {
        conditionExtensions.put(string, each);
      }
    }
    IIDEActionExtension[] aextensions = RutaExtensionManager.getDefault().getIDEActionExtensions();
    for (IIDEActionExtension each : aextensions) {
      String[] knownExtensions = each.getKnownExtensions();
      for (String string : knownExtensions) {
        actionExtensions.put(string, each);
      }
    }
    IIDENumberFunctionExtension[] nfextensions = RutaExtensionManager.getDefault()
            .getIDENumberFunctionExtensions();
    for (IIDENumberFunctionExtension each : nfextensions) {
      String[] knownExtensions = each.getKnownExtensions();
      for (String string : knownExtensions) {
        numberFunctionExtensions.put(string, each);
      }
    }
    IIDEBooleanFunctionExtension[] bfextensions = RutaExtensionManager.getDefault()
            .getIDEBooleanFunctionExtensions();
    for (IIDEBooleanFunctionExtension each : bfextensions) {
      String[] knownExtensions = each.getKnownExtensions();
      for (String string : knownExtensions) {
        booleanFunctionExtensions.put(string, each);
      }
    }
    IIDEStringFunctionExtension[] sfextensions = RutaExtensionManager.getDefault()
            .getIDEStringFunctionExtensions();
    for (IIDEStringFunctionExtension each : sfextensions) {
      String[] knownExtensions = each.getKnownExtensions();
      for (String string : knownExtensions) {
        stringFunctionExtensions.put(string, each);
      }
    }
    IIDETypeFunctionExtension[] tfextensions = RutaExtensionManager.getDefault()
            .getIDETypeFunctionExtensions();
    for (IIDETypeFunctionExtension each : tfextensions) {
      String[] knownExtensions = each.getKnownExtensions();
      for (String string : knownExtensions) {
        typeFunctionExtensions.put(string, each);
      }
    }
    IIDEBlockExtension[] bextensions = RutaExtensionManager.getDefault().getIDEBlockExtensions();
    for (IIDEBlockExtension each : bextensions) {
      String[] knownExtensions = each.getKnownExtensions();
      for (String string : knownExtensions) {
        blockExtensions.put(string, each);
      }
    }

  }

  private Set<FeatureDescription> getAllDeclaredFeatures(TypeDescription typeDescription,
          Map<String, TypeDescription> typeMap) {
    Set<FeatureDescription> result = new HashSet<>();
    if (typeDescription == null) {
      return result;
    }
    FeatureDescription[] features = typeDescription.getFeatures();
    if (features == null) {
      return result;
    }
    result.addAll(Arrays.asList(features));
    String supertypeName = typeDescription.getSupertypeName();
    if (supertypeName != null) {
      TypeDescription parent = typeMap.get(supertypeName);
      result.addAll(getAllDeclaredFeatures(parent, typeMap));
    }
    return result;
  }

  private String isFeatureMatch(String text) {
    for (String each : namespaces.values()) {
      String t = checkFeatureMatch(text, each);
      if (t != null) {
        return t;
      }
    }
    for (String each : namespaces.keySet()) {
      String t = checkFeatureMatch(text, each);
      if (t != null) {
        return t;
      }
    }
    return null;
  }

  private String checkFeatureMatch(String name, String type) {
    if (name.startsWith(type)) {
      boolean foundAll = true;
      if (name.length() > type.length()) {
        String tail = name.substring(type.length() + 1);
        String[] split = tail.split("[.]");
        String typeToCheck = type;
        for (String feat : split) {
          typeToCheck = expand(typeToCheck);
          typeToCheck = checkFSFeatureOfType(feat, typeToCheck);
          if (StringUtils.contains(typeToCheck, CAS.TYPE_NAME_FS_ARRAY)) {
            // stop here because we do not know the type
            return CAS.TYPE_NAME_FS_ARRAY;
          }
          foundAll &= (typeToCheck != null);
          if (!foundAll) {
            return null;
          }
        }
        return typeToCheck;
      } else {
        return type;
      }
    } else {
      return null;
    }
  }

  private String checkFSFeatureOfType(String featureName, String longTypeName) {
    TypeDescription t = typeDescriptionMap.get(longTypeName);
    if (t == null) {
      return null;
    }
    FeatureDescription[] features = t.getFeatures();
    for (FeatureDescription featureDescription : features) {
      String name = featureDescription.getName();
      String rangeTypeName = featureDescription.getRangeTypeName();
      boolean isFS = isFeatureStructure(rangeTypeName);
      if (name.equals(featureName)) {
        if (isFS) {
          return rangeTypeName;
        } else if (StringUtils.equals(CAS.TYPE_NAME_FS_ARRAY, rangeTypeName)) {
          String elementType = featureDescription.getElementType();
          if (elementType == null) {
            return CAS.TYPE_NAME_FS_ARRAY;
          } else {
            return elementType;
          }
        }
      }
    }
    return null;
  }

  private boolean isFeatureStructure(String rangeTypeName) {
    if (rangeTypeName.equals("uima.tcas.Annotation") || rangeTypeName.equals("uima.cas.TOP")) {
      return true;
    }
    TypeDescription type = typeDescriptionMap.get(rangeTypeName);
    if (type == null) {
      return false;
    }
    String supertypeName = type.getSupertypeName();
    if (supertypeName != null) {
      return isFeatureStructure(supertypeName);
    }
    return false;
  }

  private boolean checkTypeOfVariable(RutaVariableReference ref) {
    Integer vt = getVariableType(ref.getName());
    if (vt == null) {
      IProblem problem = problemFactory.createUnknownVariableProblem(ref);
      pr.reportProblem(problem);
      return false;
    } else {
      int variableType = vt.intValue();
      int requiredType = ref.getType();
      // reject generic types
      if ((requiredType & RutaTypeConstants.RUTA_TYPE_G) != 0) {
        return true;
      }
      if ((variableType & requiredType) == 0) {
        String errMsg = "Variable \"" + ref.getName() + "\" has type "
                + RutaTypeConstants.typeStringOfInt.get(variableType) + ". But type "
                + RutaTypeConstants.typeStringOfInt.get(requiredType) + " is required.";
        IProblem problem = new RutaCheckerDefaultProblem(sourceModule.getElementName(), errMsg, ref,
                linetracker.getLineNumberOfOffset(ref.sourceStart()));
        pr.reportProblem(problem);
        return false;
      }
    }
    return true;
  }

  private boolean isVariableDeclared(RutaVariableReference ref) {
    if (!knowsVariable(ref.getName()) && !(ref instanceof NullExpression)) {
      // declared as type?
      if (namespaces.keySet().contains(ref.getName())) {
        String errMsg = "\"" + ref.getName() + "\" declared as a Type. Variable of type "
                + RutaTypeConstants.typeStringOfInt.get(ref.getType()) + " required.";
        IProblem problem = new RutaCheckerDefaultProblem(sourceModule.getElementName(), errMsg, ref,
                linetracker.getLineNumberOfOffset(ref.sourceStart()));
        pr.reportProblem(problem);
        return false;
      }
      String errMsgHead = "Variable \"";
      String errMsgTailDefault = " defined in this script or block!";

      // not found
      String errMsg = errMsgHead + ref.getName() + "\" not" + errMsgTailDefault;
      IProblem problem = new RutaCheckerDefaultProblem(sourceModule.getElementName(), errMsg, ref,
              linetracker.getLineNumberOfOffset(ref.sourceStart()));
      pr.reportProblem(problem);
      return false;
    }
    return true;
  }

  private String getShortName(String typeName) {
    String[] nameSpace = typeName.split("[.]");
    return nameSpace[nameSpace.length - 1];
  }

  private TypeSystemDescription getTypeSystemOfScript()
          throws InvalidXMLException, IOException, CoreException {
    IPath descriptorPath = RutaProjectUtils.getTypeSystemDescriptorPath(
            sourceModule.getResource().getLocation(), sourceModule.getScriptProject().getProject(),
            classLoader);
    if (descriptorPath == null) {
      return null;
    }

    TypeSystemDescription typeSysDescr = null;
    if (descriptorPath.toFile().exists()) {
      typeSysDescr = UIMAFramework.getXMLParser()
              .parseTypeSystemDescription(new XMLInputSource(descriptorPath.toPortableString()));
      ResourceManager resMgr = getResourceManager(classLoader);
      typeSysDescr.resolveImports(resMgr);
    } else {
      // backup: just search for the file named correctly
      String lastSegment = descriptorPath.lastSegment();
      PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(
              classLoader);
      String prefix = "classpath*:**/";
      String pattern = prefix + lastSegment;
      Resource[] resources = resolver.getResources(pattern);
      if (resources != null && resources.length != 0) {
        typeSysDescr = UIMAFramework.getXMLParser()
                .parseTypeSystemDescription(new XMLInputSource(resources[0].getURL()));
        ResourceManager resMgr = getResourceManager(classLoader);
        typeSysDescr.resolveImports(resMgr);
      }
    }
    return typeSysDescr;
  }

  private String translate(String name) {
    if (name == null) {
      return null;
    }
    if (name.equals("Annotation")) {
      return "uima.tcas.Annotation";
    } else if (name.equals("STRING")) {
      return CAS.TYPE_NAME_STRING;
    } else if (name.equals("INT")) {
      return CAS.TYPE_NAME_INTEGER;
    } else if (name.equals("DOUBLE")) {
      return CAS.TYPE_NAME_DOUBLE;
    } else if (name.equals("FLOAT")) {
      return CAS.TYPE_NAME_FLOAT;
    } else if (name.equals("BOOLEAN")) {
      return CAS.TYPE_NAME_BOOLEAN;
    } else if (name.equals("TYPE")) {
      return CAS.TYPE_NAME_STRING;
    }
    return name;
  }

  private int getType(String name) {
    if (name == null) {
      return 0;
    }
    // TODO reuse field in constants
    if (name.equals("STRING")) {
      return RutaTypeConstants.RUTA_TYPE_S;
    } else if (name.equals("STRINGLIST")) {
      return RutaTypeConstants.RUTA_TYPE_SL;
    } else if (name.equals("INT")) {
      return RutaTypeConstants.RUTA_TYPE_I;
    } else if (name.equals("INTLIST")) {
      return RutaTypeConstants.RUTA_TYPE_NL;
    } else if (name.equals("DOUBLE")) {
      return RutaTypeConstants.RUTA_TYPE_D;
    } else if (name.equals("DOUBLELIST")) {
      return RutaTypeConstants.RUTA_TYPE_NL;
    } else if (name.equals("FLOAT")) {
      return RutaTypeConstants.RUTA_TYPE_F;
    } else if (name.equals("FLOATLIST")) {
      return RutaTypeConstants.RUTA_TYPE_NL;
    } else if (name.equals("BOOLEAN")) {
      return RutaTypeConstants.RUTA_TYPE_B;
    } else if (name.equals("BOOLEANLIST")) {
      return RutaTypeConstants.RUTA_TYPE_BL;
    } else if (name.equals("TYPE")) {
      return RutaTypeConstants.RUTA_TYPE_AT;
    } else if (name.equals("TYPELIST")) {
      return RutaTypeConstants.RUTA_TYPE_TL;
    } else if (name.equals("ANNOTATION")) {
      return RutaTypeConstants.RUTA_TYPE_UA;
    } else if (name.equals("ANNOTATIONLIST")) {
      return RutaTypeConstants.RUTA_TYPE_UAL;
    }
    return 0;
  }

}
