import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.PanelUI;
import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.*;
import java.util.*;
import java.util.List;

/**
 * This tests how transparent windows and repaint resolution using transparent windows when Swing's
 * volatile image buffering is disabled.
 * <p>
 * Instructions are visible inside the app.
 * </p>
 */
public class NonVolatileRepaintManagerTest extends JDialog {
    public static void main(String[] args) {
        System.setProperty("swing.volatileImageBufferEnabled", "false");
        SwingUtilities.invokeLater(() -> {
            NonVolatileRepaintManagerTest t = new NonVolatileRepaintManagerTest();
            t.pack();
            t.setLocationRelativeTo(null);
            t.setVisible(true);
        });
    }

    JTextPane instructions = new JTextPane();
    JCheckBox customRepaintManagerCheckbox = new JCheckBox("Apply Custom RepaintManager");

    public NonVolatileRepaintManagerTest() {
        instructions.setText("Instructions\n\nLook at this window. This test passes if both of these conditions are met:\n\n1. The window does NOT have a black border.\n2. Toggling the checkbox does NOT affect text antialiasing on a high resolution monitor.");
        instructions.setBorder(new EmptyBorder(10,10,10,10));
        instructions.setOpaque(false);
        instructions.setEditable(false);

        setUndecorated(true);
        JPanel p = new JPanel();
        p.setOpaque(false);
        p.setBorder(new EmptyBorder(10,10,10,10));
        p.setUI(new PanelUI() {
            @Override
            public void paint(Graphics g, JComponent c) {
                Graphics2D g2 = (Graphics2D) g;
                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                g2.setColor(new Color(220, 180, 0, 200));
                g2.fill(new RoundRectangle2D.Double(5, 5,c.getWidth()-10,c.getHeight()-10,20,20));
            }
        });
        p.setLayout(new BorderLayout());
        p.add(instructions, BorderLayout.NORTH);
        p.add(customRepaintManagerCheckbox, BorderLayout.SOUTH);
        getContentPane().add(p);
        setBackground(new Color(0,0,0,0));

        customRepaintManagerCheckbox.addActionListener(e -> {
            if (customRepaintManagerCheckbox.isSelected()) {
                RepaintManager.setCurrentManager(new DisabledVolatileImageBufferRepaintManager_workaround());
            } else {
                RepaintManager.setCurrentManager(new RepaintManager());
            }
            SwingUtilities.invokeLater(() -> repaint());
        });
    }
}

class DisabledVolatileImageBufferRepaintManager_workaround extends RepaintManager {
    BufferedImage offscreenBuffer;

    @Override
    public Image getOffscreenBuffer(Component c, int proposedWidth, int proposedHeight) {
        GraphicsConfiguration gc = c.getGraphicsConfiguration();
        if (gc == null) {
            // can we ever reach this condition?
            return super.getOffscreenBuffer(c, proposedWidth, proposedHeight);
        }
	
        Window w = c instanceof Window window ? window : SwingUtilities.getWindowAncestor(c);
        if (w == null || w.isOpaque()) {
            // opaque windows are what the super class already handles correctly:
            return super.getOffscreenBuffer(c, proposedWidth, proposedHeight);
        }

        float scalingFactor = (float)Math.sqrt(gc.getDefaultTransform().getDeterminant());
        int pixelWidth = Math.max(1, Math.round(proposedWidth * scalingFactor));
        int pixelHeight = Math.max(1, Math.round(proposedHeight * scalingFactor));

        if (offscreenBuffer == null || offscreenBuffer.getWidth() < pixelWidth || offscreenBuffer.getHeight() < pixelHeight) {

            // This is copied and pasted from MTLGraphicsConfig#createAcceleratedImage , except we use TRANSLUCENT:

            ColorModel model = gc.getColorModel(Transparency.TRANSLUCENT);
            WritableRaster wr = model.createCompatibleWritableRaster(pixelWidth, pixelHeight);
            offscreenBuffer = new BufferedImage(model, wr,
                    model.isAlphaPremultiplied(), new Hashtable<>());
        }

        BufferedImage subimage = offscreenBuffer.getSubimage(0,0,pixelWidth,pixelHeight);

        if (scalingFactor == 1)
            return subimage;

        // step 2: it will be pixelated on high-res monitors unless we return a MultiResolutionImage:

        return new ScaledResolutionImage(subimage, proposedWidth, proposedHeight, scalingFactor);
    }
}

/**
 * This can be a high-resolution image that presents itself as having a smaller virtual width/height.
 * For example on a 200% resolution monitor this image may act like it's 400x300, but it's actually backed
 * by a 800x600 BufferedImage.
 */
class ScaledResolutionImage extends Image implements MultiResolutionImage {
    final int width, height;
    final BufferedImage img;
    final float scalingFactor;

    public ScaledResolutionImage(BufferedImage img, int width, int height, float scalingFactor) {
        this.img = Objects.requireNonNull(img);
        this.width = width;
        this.height = height;
        this.scalingFactor = scalingFactor;
    }

    @Override
    public int getWidth(ImageObserver observer) {
        return width;
    }

    @Override
    public int getHeight(ImageObserver observer) {
        return height;
    }

    @Override
    public ImageProducer getSource() {
        return img.getSource();
    }

    @Override
    public Graphics2D getGraphics() {
        Graphics2D g = img.createGraphics();
        g.scale(scalingFactor, scalingFactor);
        return g;
    }

    @Override
    public Object getProperty(String name, ImageObserver observer) {
        return img.getProperty(name, observer);
    }

    @Override
    public Image getResolutionVariant(double destImageWidth, double destImageHeight) {
        return img;
    }

    @Override
    public List<Image> getResolutionVariants() {
        return Arrays.asList(this, img);
    }
} 
