/*
 * Copyright 2000-2013 JetBrains s.r.o.
 * Copyright 2014-2015 AS3Boyan
 * Copyright 2014-2014 Elias Ku
 * Copyright 2018 Eric Bishton
 * Copyright 2018 Ilya Malanin
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.plugins.haxe.model.type;

import com.intellij.plugins.haxe.model.HaxeClassModel;
import com.intellij.plugins.haxe.model.HaxeProjectModel;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public abstract class SpecificTypeReference {
  public static final String VOID = "Void";
  public static final String BOOL = "Bool";
  public static final String INT = "Int";
  public static final String FLOAT = "Float";
  public static final String STRING = "String";
  public static final String ARRAY = "Array";
  public static final String DYNAMIC = "Dynamic";
  public static final String UNKNOWN = "Unknown";
  public static final String ITERATOR = "Iterator";
  public static final String INVALID = "@@Invalid";
  public static final String MAP = "Map";

  final protected PsiElement context;

  public SpecificTypeReference(@NotNull PsiElement context) {
    this.context = context;
  }

  public static SpecificTypeReference createArray(@NotNull ResultHolder elementType) {
    final PsiElement context = elementType.getElementContext();
    final HaxeClassReference classReference = getStdClassReference(ARRAY, context);
    return SpecificHaxeClassReference.withGenerics(classReference, new ResultHolder[]{elementType}, null);
  }

  public static SpecificTypeReference createMap(@NotNull ResultHolder keyType, @NotNull ResultHolder valueType) {
    final PsiElement context = keyType.getElementContext();
    final HaxeClassReference classReference = getStdClassReference(MAP, context);
    return SpecificHaxeClassReference.withGenerics(classReference, new ResultHolder[]{keyType, valueType}, null);
  }

  public static SpecificHaxeClassReference getVoid(@NotNull PsiElement context) {
    return primitive(VOID, context);
  }

  public static SpecificHaxeClassReference getBool(@NotNull PsiElement context) {
    return primitive(BOOL, context);
  }

  public static SpecificHaxeClassReference getInt(@NotNull PsiElement context) {
    return primitive(INT, context);
  }

  public static SpecificHaxeClassReference getInt(@NotNull PsiElement context, int value) {
    return primitive(INT, context, value);
  }

  public static SpecificHaxeClassReference getDynamic(@NotNull PsiElement context) {
    return primitive(DYNAMIC, context);
  }

  public static SpecificHaxeClassReference getUnknown(@NotNull PsiElement context) {
    return primitive(UNKNOWN, context);
  }

  public static SpecificHaxeClassReference getInvalid(@NotNull PsiElement context) {
    return SpecificHaxeClassReference.withoutGenerics(new HaxeClassReference(INVALID, context));
  }

  public static SpecificHaxeClassReference getIterator(SpecificHaxeClassReference type) {
    final PsiElement context = type.getElementContext();
    final HaxeClassReference classReference = getStdClassReference(ITERATOR, context);
    return SpecificHaxeClassReference.withGenerics(classReference, new ResultHolder[]{type.createHolder()});
  }

  public static SpecificHaxeClassReference primitive(String name, @NotNull PsiElement context) {
    return SpecificHaxeClassReference.withoutGenerics(getStdClassReference(name, context));
  }

  public static SpecificHaxeClassReference primitive(String name, @NotNull PsiElement context, Object constant) {
    return SpecificHaxeClassReference.withoutGenerics(getStdClassReference(name, context), constant);
  }

  public SpecificTypeReference withRangeConstraint(HaxeRange range) {
    return this;
  }

  final public boolean isUnknown() {
    return this.toStringWithoutConstant().equals(UNKNOWN);
  }

  final public boolean isDynamic() {
    return this.toStringWithoutConstant().equals(DYNAMIC);
  }

  final public boolean isInvalid() {
    return this.toStringWithoutConstant().equals(INVALID);
  }

  final public boolean isVoid() {
    return this.toStringWithoutConstant().equals(VOID);
  }

  final public boolean isInt() {
    return this.toStringWithoutConstant().equals(INT);
  }

  final public boolean isNumeric() {
    return isInt() || isFloat();
  }

  final public boolean isBool() {
    return this.toStringWithoutConstant().equals(BOOL);
  }

  final public boolean isFloat() {
    return this.toStringWithoutConstant().equals(FLOAT);
  }

  final public boolean isString() {
    return this.toStringWithoutConstant().equals(STRING);
  }

  final public boolean isArray() {
    if (this instanceof SpecificHaxeClassReference) {
      final SpecificHaxeClassReference reference = (SpecificHaxeClassReference)this;
      return reference.getHaxeClassReference().getName().equals(ARRAY);
    }
    return false;
  }

  final public ResultHolder getArrayElementType() {
    if (isArray()) {
      final ResultHolder[] specifics = ((SpecificHaxeClassReference)this).getSpecifics();
      if (specifics.length >= 1) return specifics[0];
    }
    return getUnknown(context).createHolder();
  }

  final public ResultHolder getIterableElementType(SpecificTypeReference iterable) {
    if (isArray()) {
      return getArrayElementType();
    }
    // @TODO: Must implement it (it is not int always)
    return getInt(iterable.getElementContext()).createHolder();
  }

  abstract public SpecificTypeReference withConstantValue(Object constantValue);

  final public SpecificTypeReference withoutConstantValue() {
    return withConstantValue(null);
  }

  public boolean isConstant() {
    return this.getConstant() != null;
  }

  public HaxeRange getRangeConstraint() {
    return null;
  }

  public Object getConstant() {
    return null;
  }

  final public boolean getConstantAsBool() {
    return HaxeTypeUtils.getBoolValue(getConstant());
  }

  final public double getConstantAsDouble() {
    return HaxeTypeUtils.getDoubleValue(getConstant());
  }

  final public int getConstantAsInt() {
    return HaxeTypeUtils.getIntValue(getConstant());
  }

  @NotNull
  final public PsiElement getElementContext() {
    return context;
  }

  abstract public String toString();

  abstract public String toStringWithoutConstant();

  public String toStringWithConstant() {
    return toString();
  }

  @Nullable
  public ResultHolder access(String name, HaxeExpressionEvaluatorContext context, HaxeGenericResolver resolver) {
    return null;
  }

  final public boolean canAssign(SpecificTypeReference type2) {
    return HaxeTypeCompatible.canAssignToFrom(this, type2);
  }

  final public boolean canAssign(ResultHolder type2) {
    return HaxeTypeCompatible.canAssignToFrom(this, type2);
  }

  public ResultHolder createHolder() {
    return new ResultHolder(this);
  }

  public abstract boolean canBeTypeVariable();

  @NotNull
  private static HaxeClassReference getStdClassReference(String className, PsiElement context) {
    final HaxeClassModel model = getStdTypeModel(className, context);
    HaxeClassReference classReference;
    if (model != null) {
      classReference = new HaxeClassReference(model, context);
    } else {
      classReference = new HaxeClassReference(className, context);
    }
    return classReference;
  }

  @Nullable
  private static HaxeClassModel getStdTypeModel(String name, PsiElement context) {
    return HaxeProjectModel.fromElement(context).getStdPackage().getClassModel(name);
  }
}

@@ -117,16 +117,21 @@
 ght 2018
+-2019
  Eric Bi
@@ -5125,24 +5125,176 @@
 isArray() %7B%0A
+    return isNamedType(ARRAY);%0A  %7D%0A%0A  final public boolean isMap() %7B%0A    return isNamedType(MAP);%0A  %7D%0A%0A  private boolean isNamedType(String typeName) %7B%0A
     if (this
@@ -5484,21 +5484,24 @@
 .equals(
-ARRAY
+typeName
 );%0A    %7D

Add SpecificTypeReference.isMap().