/*
 * Copyright 2000-2013 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.util;

import com.intellij.util.ui.ImageUtil;
import com.intellij.util.ui.JBUI;
import org.imgscalr.Scalr;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;

/**
* @author Konstantin Bulenkov
* @author tav
*/
public class JBHiDPIScaledImage extends BufferedImage {
  private final @Nullable Image myImage;
  private final int myUserWidth;
  private final int myUserHeight;
  private final float myScale;

  /**
   * Creates a scaled HiDPI-aware BufferedImage, targeting the system default scale.
   *
   * @param width the width in user coordinate space
   * @param height the height in user coordinate space
   * @param type the type
   */
  public JBHiDPIScaledImage(int width, int height, int type) {
    this((GraphicsConfiguration)null, width, height, type);
  }

  /**
   * Creates a scaled HiDPI-aware BufferedImage, targeting the graphics scale.
   *
   * @param g the graphics which provides the target scale
   * @param width the width in user coordinate space
   * @param height the height in user coordinate space
   * @param type the type
   */
  public JBHiDPIScaledImage(@Nullable Graphics2D g, int width, int height, int type) {
    super((int)(width * JBUI.sysScale(g)), (int)(height * JBUI.sysScale(g)), type);
    myImage = null;
    myUserWidth = width;
    myUserHeight = height;
    myScale = JBUI.sysScale(g);
  }

  /**
   * Creates a scaled HiDPI-aware BufferedImage, targeting the graphics config.
   *
   * @param gc the graphics config which provides the target scale
   * @param width the width in user coordinate space
   * @param height the height in user coordinate space
   * @param type the type
   */
  public JBHiDPIScaledImage(@Nullable GraphicsConfiguration gc, int width, int height, int type) {
    super((int)(width * JBUI.sysScale(gc)), (int)(height * JBUI.sysScale(gc)), type);
    myImage = null;
    myUserWidth = width;
    myUserHeight = height;
    myScale = JBUI.sysScale(gc);
  }

  /**
   * Creates a HiDPI-aware BufferedImage wrapper for the provided scaled raw image.
   * The wrapper image will represent the scaled raw image in user coordinate space.
   *
   * @param image the scaled raw image
   * @param width the width in user coordinate space
   * @param height the height in user coordinate space
   * @param type the type
   */
  public JBHiDPIScaledImage(@NotNull Image image, int width, int height, int type) {
    super(1, 1, type); // a dummy wrapper
    myImage = image;
    myUserWidth = width;
    myUserHeight = height;
    myScale = myUserWidth > 0 ? myImage.getWidth(null) / myUserWidth : 1f;
  }

  public float getScale() {
    return myScale;
  }

  /**
   * Returns JBHiDPIScaledImage of the same structure scaled by the provided factor.
   *
   * @param scaleFactor the scale factor
   * @return scaled instance
   */
  public JBHiDPIScaledImage scale(float scaleFactor) {
    Image img = myImage == null ? this: myImage;

    int w = (int)(scaleFactor * getRealWidth(null));
    int h = (int)(scaleFactor * getRealHeight(null));
    if (w <= 0 || h <= 0) return this;

    Image scaled = Scalr.resize(ImageUtil.toBufferedImage(img), Scalr.Method.QUALITY, w, h);

    int newUserWidth = (int)(w / this.myScale);
    int newUserHeight = (int)(h / this.myScale);

    if (myImage != null) {
      return new JBHiDPIScaledImage(scaled, newUserWidth, newUserHeight, getType());
    }
    JBHiDPIScaledImage newImg = new JBHiDPIScaledImage(newUserWidth, newUserHeight, getType());
    Graphics2D g = newImg.createGraphics();
    g.drawImage(scaled, 0, 0, newUserWidth, newUserHeight, 0, 0, scaled.getWidth(null), scaled.getHeight(null), null);
    g.dispose();
    return newImg;
  }

  public Image getDelegate() {
    return myImage;
  }

  /**
   * Returns the width in user coordinate space for the image created as a wrapper,
   * and the real width for the image created as a scaled one.
   *
   * @return the width
   */
  @Override
  public int getWidth() {
    return getWidth(null);
  }

  /**
   * Returns the height in user coordinate space for the image created as a wrapper,
   * and the real height for the image created as a scaled one.
   *
   * @return the height
   */
  @Override
  public int getHeight() {
    return getHeight(null);
  }

  /**
   * Returns the width in user coordinate space for the image created as a wrapper,
   * and the real width for the image created as a scaled one.
   *
   * @return the width
   */
  @Override
  public int getWidth(ImageObserver observer) {
    return myImage != null ? getUserWidth(observer) : getRealWidth(observer);
  }

  /**
   * Returns the height in user coordinate space for the image created as a wrapper,
   * and the real height for the image created as a scaled one.
   *
   * @return the height
   */
  @Override
  public int getHeight(ImageObserver observer) {
    return myImage != null ? getUserHeight(observer) : getRealHeight(observer);
  }

  /**
   * Returns the width in user coordinate space.
   *
   * @param observer the image observer
   * @return the width
   */
  public int getUserWidth(ImageObserver observer) {
    return myImage != null ? myUserWidth : (int)(super.getWidth(observer) / myScale);
  }

  /**
   * Returns the height in user coordinate space.
   *
   * @param observer the image observer
   * @return the height
   */
  public int getUserHeight(ImageObserver observer) {
    return myImage != null ? myUserHeight : (int)(super.getHeight(observer) / myScale);
  }

  /**
   * Returns the real width.
   *
   * @param observer the image observer
   * @return the width
   */
  public int getRealWidth(ImageObserver observer) {
    return myImage != null ? myImage.getWidth(observer) : super.getWidth(observer);
  }

  /**
   * Returns the real height.
   *
   * @param observer the image observer
   * @return the height
   */
  public int getRealHeight(ImageObserver observer) {
    return myImage != null ? myImage.getHeight(observer) : super.getHeight(observer);
  }

  @Override
  public Graphics2D createGraphics() {
    Graphics2D g = super.createGraphics();
    if (myImage == null) {
      return new HiDPIScaledGraphics(g);
    }
    return g;
  }
}
