/*
 * 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 consulo.ide.impl.idea.codeInsight.preview;

import consulo.util.dataholder.Key;
import consulo.ide.impl.idea.openapi.util.text.StringUtil;
import consulo.virtualFileSystem.VirtualFile;
import consulo.language.psi.PsiElement;
import consulo.language.psi.PsiFileSystemItem;
import consulo.language.psi.PsiReference;
import consulo.ide.impl.idea.reference.SoftReference;
import consulo.ui.ex.awt.UIUtil;
import jakarta.annotation.Nonnull;

import org.jetbrains.annotations.TestOnly;

import jakarta.annotation.Nullable;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/**
 * @author spleaner
 */
public class ImagePreviewComponent extends JPanel implements PreviewHintComponent {
  private static final Key<Long> TIMESTAMP_KEY = Key.create("Image.timeStamp");
  private static final Key<SoftReference<BufferedImage>> BUFFERED_IMAGE_REF_KEY = Key.create("Image.bufferedImage");
  private static final Key<String> FORMAT_KEY = Key.create("Image.format");

  private static final List<String> supportedExtensions = Arrays.asList(ImageIO.getReaderFormatNames());
  @Nonnull
  private final BufferedImage myImage;

  /**
   * @param image buffered image
   * @param imageFileSize File length in bytes.
   */
  private ImagePreviewComponent(@Nonnull final BufferedImage image, final long imageFileSize) {
    setLayout(new BorderLayout());

    myImage = image;
    add(new ImageComp(), BorderLayout.CENTER);
    add(createLabel(image, imageFileSize), BorderLayout.SOUTH);

    setBackground(UIUtil.getToolTipBackground());
    setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.black), BorderFactory.createEmptyBorder(5, 5, 5, 5)));
  }

  @Override
  @TestOnly
  public boolean isEqualTo(@Nullable PreviewHintComponent other) {
    if (!(other instanceof ImagePreviewComponent)) {
      return false;
    }
    ImagePreviewComponent otherPreview = (ImagePreviewComponent)other;
    if (myImage.getWidth() == otherPreview.myImage.getWidth() && myImage.getHeight() == otherPreview.myImage.getHeight()) {
      for (int x = 0; x < myImage.getWidth(); x++) {
        for (int y = 0; y < myImage.getHeight(); y++) {
          if (myImage.getRGB(x, y) != otherPreview.myImage.getRGB(x, y)) {
            return false;
          }
        }
      }
      return true;
    }
    return false;
  }

  @Nonnull
  private static JLabel createLabel(@Nonnull final BufferedImage image, long imageFileSize) {
    final int width = image.getWidth();
    final int height = image.getHeight();
    final ColorModel colorModel = image.getColorModel();
    final int i = colorModel.getPixelSize();
    return new JLabel(String.format("%dx%d, %dbpp, %s", width, height, i, StringUtil.formatFileSize(imageFileSize)));
  }

  @SuppressWarnings({"AutoUnboxing"})
  private static boolean refresh(@Nonnull VirtualFile file) throws IOException {
    Long loadedTimeStamp = file.getUserData(TIMESTAMP_KEY);
    SoftReference<BufferedImage> imageRef = file.getUserData(BUFFERED_IMAGE_REF_KEY);
    if (loadedTimeStamp == null || loadedTimeStamp < file.getTimeStamp() || SoftReference.dereference(imageRef) == null) {
      try {
        final byte[] content = file.contentsToByteArray();
        InputStream inputStream = new ByteArrayInputStream(content, 0, content.length);
        ImageInputStream imageInputStream = ImageIO.createImageInputStream(inputStream);
        try {
          Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(imageInputStream);
          if (imageReaders.hasNext()) {
            ImageReader imageReader = imageReaders.next();
            try {
              file.putUserData(FORMAT_KEY, imageReader.getFormatName());
              ImageReadParam param = imageReader.getDefaultReadParam();
              imageReader.setInput(imageInputStream, true, true);
              int minIndex = imageReader.getMinIndex();
              BufferedImage image = imageReader.read(minIndex, param);
              file.putUserData(BUFFERED_IMAGE_REF_KEY, new SoftReference<BufferedImage>(image));
              return true;
            }
            finally {
              imageReader.dispose();
            }
          }
        }
        finally {
          imageInputStream.close();
        }
      }
      finally {
        // We perform loading no more needed
        file.putUserData(TIMESTAMP_KEY, System.currentTimeMillis());
      }
    }
    return false;
  }

  public static JComponent getPreviewComponent(@Nullable final PsiElement parent) {
    if (parent == null) {
      return null;
    }
    final PsiReference[] references = parent.getReferences();
    for (final PsiReference reference : references) {
      final PsiElement fileItem = reference.resolve();
      if (fileItem instanceof PsiFileSystemItem) {
        final PsiFileSystemItem item = (PsiFileSystemItem)fileItem;
        if (!item.isDirectory()) {
          final VirtualFile file = item.getVirtualFile();
          if (file != null && supportedExtensions.contains(file.getExtension())) {
            try {
              refresh(file);
              SoftReference<BufferedImage> imageRef = file.getUserData(BUFFERED_IMAGE_REF_KEY);
              final BufferedImage image = SoftReference.dereference(imageRef);
              if (image != null) {
                return new ImagePreviewComponent(image, file.getLength());
              }
            }
            catch (IOException ignored) {
              // nothing
            }
          }
        }
      }
    }

    return null;
  }

  /**
   * This method doesn't use caching, so if you want to use it then you should consider implementing external cache.
   */
  public static ImagePreviewComponent getPreviewComponent(@Nonnull final BufferedImage image, final long imageFileSize) {
    return new ImagePreviewComponent(image, imageFileSize);
  }

  private class ImageComp extends JComponent {
    private final Dimension myPreferredSize;

    private ImageComp() {
      if (myImage.getWidth() > 300 || myImage.getHeight() > 300) {
        // will make image smaller
        final float factor = 300.0f / Math.max(myImage.getWidth(), myImage.getHeight());
        myPreferredSize = new Dimension((int)(myImage.getWidth() * factor), (int)(myImage.getHeight() * factor));
      }
      else {
        myPreferredSize = new Dimension(myImage.getWidth(), myImage.getHeight());
      }
    }

    @Override
    public void paint(final Graphics g) {
      super.paint(g);
      Rectangle r = getBounds();
      final int width = myImage.getWidth();
      final int height = myImage.getHeight();

      g.drawImage(myImage, 0, 0, r.width > width ? width : r.width, r.height > height ? height : r.height, this);
    }

    @Override
    public Dimension getPreferredSize() {
      return myPreferredSize;
    }

    @Override
    public Dimension getMinimumSize() {
      return getPreferredSize();
    }

    @Override
    public Dimension getMaximumSize() {
      return getPreferredSize();
    }
  }
}
