/*
 * Copyright 2000-2016 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.vcs.log.ui.frame;

import com.intellij.ide.ui.laf.IntelliJLaf;
import com.intellij.openapi.ui.GraphicsConfig;
import com.intellij.ui.ColorUtil;
import com.intellij.ui.Gray;
import com.intellij.ui.JBColor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.AsyncProcessIcon;
import com.intellij.util.ui.GraphicsUtil;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.geom.Area;
import java.awt.geom.Path2D;
import java.util.List;

public abstract class ProgressStripeIcon implements Icon {
  private static final int TRANSLATE = 1;
  private static final int HEIGHT = 3;
  @NotNull
  private final JComponent myReferenceComponent;
  private final int myShift;

  private ProgressStripeIcon(@NotNull JComponent component, int shift) {
    myReferenceComponent = component;
    myShift = shift;
  }

  public abstract int getChunkWidth();

  protected abstract void paint(@NotNull Graphics2D g2, int x, int y, int shift);

  @Override
  public void paintIcon(Component c, Graphics g, int x, int y) {
    GraphicsConfig config = GraphicsUtil.setupAAPainting(g);
    Graphics2D g2 = (Graphics2D)g;

    int shift = myShift - getChunkWidth();
    while (shift < getIconWidth()) {
      paint(g2, x, y, shift);
      shift += getChunkWidth();
    }

    config.restore();
  }

  @Override
  public int getIconWidth() {
    return myReferenceComponent.getWidth();
  }

  @Override
  public int getIconHeight() {
    return getHeight();
  }

  public static int getHeight() {
    return JBUI.scale(HEIGHT);
  }

  private static class StripeIcon extends ProgressStripeIcon {
    private static final double ALPHA = 0.8;
    private static final JBColor BG_COLOR = new JBColor(ColorUtil.withAlpha(Gray._165, ALPHA), ColorUtil.withAlpha(Gray._110, ALPHA));
    private static final int WIDTH = 16;

    private StripeIcon(@NotNull JComponent component, int shift) {
      super(component, shift);
    }

    @Override
    public int getChunkWidth() {
      return JBUI.scale(WIDTH);
    }

    @Override
    protected void paint(@NotNull Graphics2D g2, int x, int y, int shift) {
      g2.setColor(BG_COLOR);

      Path2D.Double path = new Path2D.Double();
      int height = JBUI.scale(HEIGHT);
      float incline = height / 2.0f;
      float length = JBUI.scale(WIDTH) / 2.0f;
      float start = length / 2.0f;
      path.moveTo(x + shift + start, y + height);
      path.lineTo(x + shift + start + incline, y);
      path.lineTo(x + shift + start + incline + length, y);
      path.lineTo(x + shift + start + length, y + height);
      path.lineTo(x + shift + start, y + height);
      path.closePath();

      g2.fill(new Area(path));
    }
  }

  // this icon is not used under darcula
  @SuppressWarnings("UseJBColor")
  private static class GradientIcon extends ProgressStripeIcon {
    private static final double ALPHA = 0.5;
    private static final Color DARK_BLUE = new Color(0x4d9ff8);
    private static final Color DARK_GRAY = Gray._165;
    private static final Color LIGHT_BLUE = ColorUtil.withAlpha(new Color(0x90c2f8), ALPHA);
    private static final Color LIGHT_GRAY = ColorUtil.withAlpha(new Color(0xdbdbdb), ALPHA);
    private static final int GRADIENT = 128;
    private static final int GRADIENT_HEIGHT = 2;

    private GradientIcon(@NotNull JComponent component, int shift) {
      super(component, shift);
    }

    public int getChunkWidth() {
      return 2 * JBUI.scale(GRADIENT);
    }

    public void paint(@NotNull Graphics2D g2, int x, int y, int shift) {
      Color dark;
      Color light;
      if (IntelliJLaf.isGraphite()) {
        dark = DARK_GRAY;
        light = LIGHT_GRAY;
      }
      else {
        dark = DARK_BLUE;
        light = LIGHT_BLUE;
      }
      g2.setPaint(new GradientPaint(x + shift, y, dark, x + shift + JBUI.scale(GRADIENT), y, light));
      g2.fill(new Rectangle(x + shift, y, JBUI.scale(GRADIENT), getIconHeight()));
      g2.setPaint(new GradientPaint(x + shift + JBUI.scale(GRADIENT), y, light, x + shift + 2 * JBUI.scale(GRADIENT), y, dark));
      g2.fill(new Rectangle(x + shift + JBUI.scale(GRADIENT), y, JBUI.scale(GRADIENT), getIconHeight()));
    }

    @Override
    public int getIconHeight() {
      return JBUI.scale(GRADIENT_HEIGHT);
    }
  }

  @NotNull
  public static AsyncProcessIcon generateIcon(@NotNull JComponent component) {
    List<Icon> result = ContainerUtil.newArrayList();
    if (UIUtil.isUnderAquaBasedLookAndFeel() && !UIUtil.isUnderDarcula()) {
      for (int i = 0; i < 2 * JBUI.scale(GradientIcon.GRADIENT); i += JBUI.scale(TRANSLATE)) {
        result.add(new GradientIcon(component, i));
      }
    }
    else {
      for (int i = 0; i < JBUI.scale(StripeIcon.WIDTH); i += JBUI.scale(TRANSLATE)) {
        result.add(new StripeIcon(component, i));
      }
      result = ContainerUtil.reverse(result);
    }

    Icon passive = result.get(0);
    AsyncProcessIcon icon = new AsyncProcessIcon("ProgressWithStripes", result.toArray(new Icon[result.size()]), passive) {
      @Override
      public Dimension getPreferredSize() {
        return new Dimension(component.getWidth(), passive.getIconHeight());
      }
    };
    component.addComponentListener(new ComponentAdapter() {
      @Override
      public void componentResized(ComponentEvent e) {
        super.componentResized(e);
        icon.revalidate();
      }
    });
    return icon;
  }
}
