// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.util.xml.impl;

import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.util.CachedValue;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.xml.*;
import org.jetbrains.annotations.Nullable;

import java.util.List;

public class GetInvocation implements Invocation {
  private static final Key<CachedValue<List<Pair<Converter,Object>>>> DOM_VALUE_KEY = Key.create("Dom element value key");
  private final Converter myConverter;

  protected GetInvocation(final Converter converter) {
    assert converter != null;
    myConverter = converter;
  }

  @Override
  public Object invoke(final DomInvocationHandler handler, final Object[] args) throws Throwable {
    if (myConverter == Converter.EMPTY_CONVERTER) {
      return getValueInner(handler, myConverter);
    }

    CachedValue<List<Pair<Converter,Object>>> value = handler.getUserData(DOM_VALUE_KEY);
    if (value == null) {
      final DomManagerImpl domManager = handler.getManager();
      final Project project = domManager.getProject();
      final CachedValuesManager cachedValuesManager = CachedValuesManager.getManager(project);
      handler.putUserData(DOM_VALUE_KEY, value = cachedValuesManager.createCachedValue(() -> {
        List<Pair<Converter, Object>> list = ContainerUtil.createLockFreeCopyOnWriteList();
        return CachedValueProvider.Result
          .create(list, PsiModificationTracker.MODIFICATION_COUNT, domManager, ProjectRootManager.getInstance(project));
      }, false));
    }

    return getOrCalcValue(handler, value.getValue());
  }

  private @Nullable Object getOrCalcValue(final DomInvocationHandler handler, final List<Pair<Converter, Object>> list) {
    if (!list.isEmpty()) {
      //noinspection ForLoopReplaceableByForEach
      for (int i = 0; i < list.size(); i++) {
        Pair<Converter, Object> pair = list.get(i);
        if (pair.first == myConverter) return pair.second;
      }
    }
    final Object returnValue = getValueInner(handler, myConverter);
    list.add(Pair.create(myConverter, returnValue));
    return returnValue;
  }

  private static @Nullable Object getValueInner(DomInvocationHandler handler, Converter converter) {
    final SubTag annotation = handler.getAnnotation(SubTag.class);
    if (annotation != null && annotation.indicator()) {
      final boolean tagNotNull = handler.getXmlTag() != null;
      if (converter == Converter.EMPTY_CONVERTER) {
        return tagNotNull ? "" : null;
      }
      else {
        return tagNotNull;
      }
    }

    String tagValue = handler.getValue();
    ConvertContext context = ConvertContextFactory.createConvertContext(handler);

    for (DomReferenceInjector each : DomUtil.getFileElement(handler).getFileDescription().getReferenceInjectors()) {
      tagValue = each.resolveString(tagValue, context);
    }

    return converter.fromString(tagValue, context);
  }

}
