/*
 * Copyright 2000-2015 JetBrains s.r.o.
 *
 * 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.psi;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.List;

/**
 * Copies PsiType structure with an ability to replace some inner components.
 *
 * @author peter
 */
public abstract class PsiTypeMapper extends PsiTypeVisitorEx<PsiType> {
  protected static final Logger LOG = Logger.getInstance("#com.intellij.psi.PsiTypeMapper");

  @Nullable
  public <T extends PsiType> T mapType(@NotNull T type) {
    //noinspection unchecked
    return (T)type.accept(this);
  }

  @Override
  public PsiType visitArrayType(final PsiArrayType type) {
    PsiType componentType = type.getComponentType();
    PsiType mappedComponent = mapType(componentType);
    if (mappedComponent == null) return null;
    if (mappedComponent == componentType) return type;
    return new PsiArrayType(mappedComponent, type.getAnnotationProvider());
  }

  @Override
  public PsiType visitEllipsisType(final PsiEllipsisType type) {
    PsiType componentType = type.getComponentType();
    PsiType mappedComponent = mapType(componentType);
    if (mappedComponent == null) return null;
    if (mappedComponent == componentType) return type;
    return new PsiEllipsisType(mappedComponent, type.getAnnotationProvider());
  }

  @Override
  public PsiType visitTypeVariable(final PsiTypeVariable var) {
    return var;
  }

  @Override
  public PsiType visitBottom(final Bottom bottom) {
    return bottom;
  }

  @Override
  public PsiType visitCapturedWildcardType(final PsiCapturedWildcardType type) {
    return type;
  }

  @Override
  public abstract PsiType visitClassType(final PsiClassType classType);

  @Override
  public PsiType visitPrimitiveType(final PsiPrimitiveType primitiveType) {
    return primitiveType;
  }

  @Override
  public PsiType visitType(final PsiType type) {
    LOG.error(type);
    return null;
  }

  @Override
  public PsiType visitWildcardType(final PsiWildcardType wildcardType) {
    PsiType bound = wildcardType.getBound();
    final PsiManager manager = wildcardType.getManager();
    if (bound == null) return PsiWildcardType.createUnbounded(manager);

    bound = mapType(bound);
    if (bound == null) return null;
    return wildcardType.isExtends() ? PsiWildcardType.createExtends(manager, bound) : PsiWildcardType.createSuper(manager, bound);
  }

  @Nullable
  @Override
  public PsiType visitIntersectionType(PsiIntersectionType intersectionType) {
    final List<PsiType> substituted = ContainerUtil.newSmartList();
    for (PsiType component : intersectionType.getConjuncts()) {
      PsiType mapped = mapType(component);
      if (mapped == null) return null;

      substituted.add(mapped);
    }
    return PsiIntersectionType.createIntersection(false, substituted.toArray(PsiType.EMPTY_ARRAY));
  }

  @Override
  public PsiType visitDisjunctionType(PsiDisjunctionType disjunctionType) {
    final List<PsiType> substituted = ContainerUtil.newSmartList();
    for (PsiType component : disjunctionType.getDisjunctions()) {
      PsiType mapped = mapType(component);
      if (mapped == null) return null;

      substituted.add(mapped);
    }
    return disjunctionType.newDisjunctionType(substituted);
  }

  @Override
  public PsiType visitDiamondType(PsiDiamondType diamondType) {
    return diamondType;
  }

}
