// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ui;

import com.intellij.openapi.application.ex.ApplicationInfoEx;
import com.intellij.openapi.application.ex.ProgressSlide;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.ui.scale.ScaleContext;
import com.intellij.util.ImageLoader;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.intellij.util.ui.StartupUiUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.awt.*;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReferenceArray;

final class ProgressSlidePainter {
  private static final int PREFETCH_PARALLEL_COUNT = 5;
  private static final int PREFETCH_BUFFER_SIZE = 32;
  private static final Logger ourLogger = Logger.getInstance(ProgressSlidePainter.class);

  private final List<ProgressSlide> myProgressSlides;

  private final AtomicReferenceArray<Slide> myPrefetchedSlides;
  private final AtomicInteger myPrefetchSlideIndex;
  private volatile int myNextSlideIndex = 0;

  private static class Slide {
    private static final Slide Empty = new Slide(0, null);

    public final double progress;
    public final Image image;

    public boolean isEmpty() {
      return Empty.equals(this);
    }

    Slide(double progress, Image image) {
      this.progress = progress;
      this.image = image;
    }
  }

  ProgressSlidePainter(@NotNull ApplicationInfoEx appInfo) {
    myProgressSlides = appInfo.getProgressSlides();
    myProgressSlides.sort(Comparator.comparing(it -> it.progressRation));
    myPrefetchedSlides = new AtomicReferenceArray<>(myProgressSlides.size());
    myPrefetchSlideIndex = new AtomicInteger(0);
  }

  private boolean isFinished() {
    return myNextSlideIndex >= myPrefetchedSlides.length();
  }

  public void startPreloading() {
    var executorService = AppExecutorUtil.getAppExecutorService();
    for (int i = 0; i < PREFETCH_PARALLEL_COUNT; i++) {
      executorService.execute(() -> {
        while (true) {
          int slideIndex = myPrefetchSlideIndex.getAndIncrement();
          if (slideIndex >= myProgressSlides.size()) return;

          if (slideIndex < myNextSlideIndex)
          {
            // current slide will not be shown
            myPrefetchedSlides.set(slideIndex, Slide.Empty);
            continue;
          }

          while (slideIndex > myNextSlideIndex + PREFETCH_BUFFER_SIZE)
            Thread.onSpinWait();

          var slide = myProgressSlides.get(slideIndex);
          var image = ImageLoader.loadFromUrl(slide.url, Splash.class, ImageLoader.ALLOW_FLOAT_SCALING, null, ScaleContext.create());

          if (image == null) {
            ourLogger.error("Cannot load slide by url: " + slide.url);
            myPrefetchedSlides.set(slideIndex, Slide.Empty);
            continue;
          }

          myPrefetchedSlides.compareAndSet(slideIndex, null, new Slide(slide.progressRation, image));
        }
      });
    }
  }

  public void paintSlides(@NotNull Graphics g, double currentProgress) {
    if (isFinished()) return;

    do {
      var newSlide = tryGetNextSlide();
      if (newSlide == null) return;

      if (newSlide.isEmpty())
        return;

      if (newSlide.progress <= currentProgress) {
        StartupUiUtil.drawImage(g, newSlide.image, 0, 0, null);
      } else return;
    }
    while (true);
  }

  // on ui thread
  @Nullable
  private Slide tryGetNextSlide() {
    int index = myNextSlideIndex;
    if (index >= myPrefetchedSlides.length()) return null;

    var start = System.currentTimeMillis();
    do {
      var slide = myPrefetchedSlides.get(index);
      if (slide != null) {
        myPrefetchedSlides.set(index, Slide.Empty);
        //noinspection NonAtomicOperationOnVolatileField
        myNextSlideIndex++;
        return slide;
      } else if (System.currentTimeMillis() - start > 1) {
        ourLogger.warn("Cannot get the next slide for 1 ms");
        myPrefetchedSlides.set(index, Slide.Empty);
        //noinspection NonAtomicOperationOnVolatileField
        myNextSlideIndex++;
        return Slide.Empty;
      }

      Thread.onSpinWait();
    } while (true);
  }
}

@@ -3335,29 +3335,12 @@
 null
-) return;%0A%0A      if (
+ %7C%7C 
 newS
@@ -3354,24 +3354,16 @@
 Empty())
-%0A       
  return;
@@ -3692,63 +3692,8 @@
 l;%0A%0A
-    var start = System.currentTimeMillis();%0A    do %7B%0A  
     
@@ -3739,39 +3739,8 @@
 x);%0A
-      if (slide != null) %7B%0A    
     
@@ -3775,36 +3775,32 @@
 , Slide.Empty);%0A
-    
     //noinspecti
@@ -3828,36 +3828,32 @@
 OnVolatileField%0A
-    
     myNextSlideI
@@ -3860,28 +3860,24 @@
 ndex++;%0A    
-   
- 
 return slide
@@ -3882,350 +3882,8 @@
 de;%0A
-      %7D else if (System.currentTimeMillis() - start %3E 1) %7B%0A        ourLogger.warn(%22Cannot get the next slide for 1 ms%22);%0A        myPrefetchedSlides.set(index, Slide.Empty);%0A        //noinspection NonAtomicOperationOnVolatileField%0A        myNextSlideIndex++;%0A        return Slide.Empty;%0A      %7D%0A%0A      Thread.onSpinWait();%0A    %7D while (true);%0A
   %7D%0A

Simplify tryGetNextSlide