package com.android.hierarchyviewer.ui;

import com.android.ddmlib.IDevice;
import com.android.ddmlib.RawImage;
import com.android.hierarchyviewer.util.WorkerThread;
import com.android.hierarchyviewer.scene.ViewNode;
import com.android.hierarchyviewer.ui.util.PngFileFilter;
import com.android.hierarchyviewer.ui.util.IconLoader;

import javax.swing.JComponent;
import javax.swing.JScrollPane;
import javax.swing.Timer;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JSlider;
import javax.swing.Box;
import javax.swing.JCheckBox;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;
import javax.imageio.ImageIO;

import org.jdesktop.swingworker.SwingWorker;

import java.io.IOException;
import java.io.File;
import java.awt.image.BufferedImage;
import java.awt.Graphics;
import java.awt.Dimension;
import java.awt.BorderLayout;
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.Rectangle;
import java.awt.Point;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.Insets;
import java.awt.FlowLayout;
import java.awt.AlphaComposite;
import java.awt.RenderingHints;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.concurrent.ExecutionException;

class ScreenViewer extends JPanel implements ActionListener {
    private final Workspace workspace;
    private final IDevice device;

    private GetScreenshotTask task;
    private BufferedImage image;
    private int[] scanline;
    private volatile boolean isLoading;

    private BufferedImage overlay;
    private AlphaComposite overlayAlpha = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f);

    private ScreenViewer.LoupeStatus status;
    private ScreenViewer.LoupeViewer loupe;
    private ScreenViewer.Crosshair crosshair;

    private int zoom = 8;
    private int y = 0;

    private Timer timer;
    private ViewNode node;

    private JSlider zoomSlider;

    ScreenViewer(Workspace workspace, IDevice device, int spacing) {
        setLayout(new GridBagLayout());
        setOpaque(false);

        this.workspace = workspace;
        this.device = device;

        timer = new Timer(5000, this);
        timer.setInitialDelay(0);
        timer.setRepeats(true);

        JPanel panel = buildViewerAndControls();
        add(panel, new GridBagConstraints(0, 0, 1, 1, 0.3f, 1.0f,
                GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH,
                new Insets(0, 0, 0, 0), 0, 0));

        JPanel loupePanel = buildLoupePanel(spacing);
        add(loupePanel, new GridBagConstraints(1, 0, 1, 1, 0.7f, 1.0f,
                GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH,
                new Insets(0, 0, 0, 0), 0, 0));

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                timer.start();
            }
        });
    }

    private JPanel buildLoupePanel(int spacing) {
        loupe = new LoupeViewer();
        loupe.addMouseWheelListener(new WheelZoomListener());
        CrosshairPanel crosshairPanel = new CrosshairPanel(loupe);

        JPanel loupePanel = new JPanel(new BorderLayout());
        loupePanel.add(crosshairPanel);
        status = new LoupeStatus();
        loupePanel.add(status, BorderLayout.SOUTH);

        loupePanel.setBorder(BorderFactory.createEmptyBorder(0, spacing, 0, 0));
        return loupePanel;
    }

    private class WheelZoomListener implements MouseWheelListener {
        public void mouseWheelMoved(MouseWheelEvent e) {
            if (zoomSlider != null) {
                int val = zoomSlider.getValue();
                val -= e.getWheelRotation() * 2;
                zoomSlider.setValue(val);
            }
        }
    }

    private JPanel buildViewerAndControls() {
        JPanel panel = new JPanel(new GridBagLayout());
        crosshair = new Crosshair(new ScreenshotViewer());
        crosshair.addMouseWheelListener(new WheelZoomListener());
        JScrollPane scroller = new JScrollPane(crosshair);
        scroller.setPreferredSize(new Dimension(320, 480));
        scroller.setBorder(null);
        panel.add(scroller,
                new GridBagConstraints(0, y++, 2, 1, 1.0f, 1.0f,
                    GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH,
                    new Insets(0, 0, 0, 0), 0, 0));
        buildSlider(panel, "Overlay:", "0%", "100%", 0, 100, 30, 1).addChangeListener(
                new ChangeListener() {
                    public void stateChanged(ChangeEvent event) {
                        float opacity = ((JSlider) event.getSource()).getValue() / 100.0f;
                        overlayAlpha = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity);
                        repaint();
                    }
        });
        buildOverlayExtraControls(panel);
        buildSlider(panel, "Refresh Rate:", "1s", "40s", 1, 40, 5, 1).addChangeListener(
                new ChangeListener() {
                    public void stateChanged(ChangeEvent event) {
                        int rate = ((JSlider) event.getSource()).getValue() * 1000;
                        timer.setDelay(rate);
                        timer.setInitialDelay(0);
                        timer.restart();
                    }
        });
        zoomSlider = buildSlider(panel, "Zoom:", "2x", "24x", 2, 24, 8, 2);
        zoomSlider.addChangeListener(
                new ChangeListener() {
                    public void stateChanged(ChangeEvent event) {
                        zoom = ((JSlider) event.getSource()).getValue();
                        loupe.clearGrid = true;
                        loupe.moveToPoint(crosshair.crosshair.x, crosshair.crosshair.y);
                        repaint();
                    }
        });
        panel.add(Box.createVerticalGlue(),
                new GridBagConstraints(0, y++, 2, 1, 1.0f, 1.0f,
                    GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE,
                    new Insets(0, 0, 0, 0), 0, 0));
        return panel;
    }

    private void buildOverlayExtraControls(JPanel panel) {
        JPanel extras = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));

        JButton loadOverlay = new JButton("Load...");
        loadOverlay.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                SwingWorker<?, ?> worker = openOverlay();
                if (worker != null) {
                    worker.execute();
                }
            }
        });
        extras.add(loadOverlay);

        JCheckBox showInLoupe = new JCheckBox("Show in Loupe");
        showInLoupe.setSelected(false);
        showInLoupe.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                loupe.showOverlay = ((JCheckBox) event.getSource()).isSelected();
                loupe.repaint();
            }
        });
        extras.add(showInLoupe);

        panel.add(extras, new GridBagConstraints(1, y++, 1, 1, 1.0f, 0.0f,
                    GridBagConstraints.LINE_START, GridBagConstraints.NONE,
                        new Insets(0, 0, 0, 0), 0, 0));
    }

    public SwingWorker<?, ?> openOverlay() {
        JFileChooser chooser = new JFileChooser();
        chooser.setFileFilter(new PngFileFilter());
        int choice = chooser.showOpenDialog(this);
        if (choice == JFileChooser.APPROVE_OPTION) {
            return new OpenOverlayTask(chooser.getSelectedFile());
        } else {
            return null;
        }
    }

    private JSlider buildSlider(JPanel panel, String title, String minName, String maxName,
            int min, int max, int value, int tick) {
        panel.add(new JLabel(title), new GridBagConstraints(0, y, 1, 1, 1.0f, 0.0f,
                    GridBagConstraints.LINE_END, GridBagConstraints.NONE,
                    new Insets(0, 0, 0, 6), 0, 0));
        JPanel sliderPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
        sliderPanel.add(new JLabel(minName));
        JSlider slider = new JSlider(min, max, value);
        slider.setMinorTickSpacing(tick);
        slider.setMajorTickSpacing(tick);
        slider.setSnapToTicks(true);
        sliderPanel.add(slider);
        sliderPanel.add(new JLabel(maxName));
        panel.add(sliderPanel, new GridBagConstraints(1, y++, 1, 1, 1.0f, 0.0f,
                    GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE,
                        new Insets(0, 0, 0, 0), 0, 0));
        return slider;
    }

    void stop() {
        timer.stop();
    }

    void start() {
        timer.start();
    }

    void select(ViewNode node) {
        this.node = node;
        repaint();
    }

    class LoupeViewer extends JComponent {
        private final Color lineColor = new Color(1.0f, 1.0f, 1.0f, 0.3f);

        private int width;
        private int height;
        private BufferedImage grid;
        private int left;
        private int top;
        public boolean clearGrid;

        private final Rectangle clip = new Rectangle();
        private boolean showOverlay = false;

        LoupeViewer() {
            addMouseListener(new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent event) {
                    moveToPoint(event);
                }
            });
            addMouseMotionListener(new MouseMotionAdapter() {
                @Override
                public void mouseDragged(MouseEvent event) {
                    moveToPoint(event);
                }
            });
        }

        @Override
        protected void paintComponent(Graphics g) {
            if (isLoading) {
                return;
            }

            g.translate(-left, -top);

            if (image != null) {
                Graphics2D g2 = (Graphics2D) g.create();
                g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                        RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
                g2.scale(zoom, zoom);
                g2.drawImage(image, 0, 0, null);
                if (overlay != null && showOverlay) {
                    g2.setComposite(overlayAlpha);
                    g2.drawImage(overlay, 0, image.getHeight() - overlay.getHeight(), null);
                }
                g2.dispose();
            }

            int width = getWidth();
            int height = getHeight();

            Graphics2D g2 = null;
            if (width != this.width || height != this.height) {
                this.width = width;
                this.height = height;

                grid = new BufferedImage(width + zoom + 1, height + zoom + 1,
                        BufferedImage.TYPE_INT_ARGB);
                clearGrid = true;
                g2 = grid.createGraphics();
            } else if (clearGrid) {
                g2 = grid.createGraphics();
                g2.setComposite(AlphaComposite.Clear);
                g2.fillRect(0, 0, grid.getWidth(), grid.getHeight());
                g2.setComposite(AlphaComposite.SrcOver);
            }

            if (clearGrid) {
                clearGrid = false;

                g2.setColor(lineColor);
                width += zoom;
                height += zoom;

                for (int x = zoom; x <= width; x += zoom) {
                    g2.drawLine(x, 0, x, height);
                }

                for (int y = 0; y <= height; y += zoom) {
                    g2.drawLine(0, y, width, y);
                }

                g2.dispose();
            }

            if (image != null) {
                g.getClipBounds(clip);
                g.clipRect(0, 0, image.getWidth() * zoom + 1, image.getHeight() * zoom + 1);
                g.drawImage(grid, clip.x - clip.x % zoom, clip.y - clip.y % zoom, null);
            }

            g.translate(left, top);
        }

        void moveToPoint(MouseEvent event) {
            int x = Math.max(0, Math.min((event.getX() + left) / zoom, image.getWidth() - 1));
            int y = Math.max(0, Math.min((event.getY() + top) / zoom, image.getHeight() - 1));
            moveToPoint(x, y);
            crosshair.moveToPoint(x, y);
        }

        void moveToPoint(int x, int y) {
            left = x * zoom - width / 2 + zoom / 2;
            top = y * zoom - height / 2 + zoom / 2;
            repaint();
        }
    }

    class LoupeStatus extends JPanel {
        private JLabel xLabel;
        private JLabel yLabel;
        private JLabel rLabel;
        private JLabel gLabel;
        private JLabel bLabel;
        private JLabel hLabel;
        private ScreenViewer.LoupeStatus.ColoredSquare square;
        private Color color;

        LoupeStatus() {
            setOpaque(true);
            setLayout(new GridBagLayout());
            setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));

            square = new ColoredSquare();
            add(square, new GridBagConstraints(0, 0, 1, 2, 0.0f, 0.0f,
                    GridBagConstraints.LINE_START, GridBagConstraints.NONE,
                    new Insets(0, 0, 0, 12), 0, 0 ));

            JLabel label;

            add(label = new JLabel("#ffffff"), new GridBagConstraints(0, 2, 1, 1, 0.0f, 0.0f,
                    GridBagConstraints.LINE_START, GridBagConstraints.NONE,
                    new Insets(0, 0, 0, 12), 0, 0 ));
            label.setForeground(Color.WHITE);
            hLabel = label;

            add(label = new JLabel("R:"), new GridBagConstraints(1, 0, 1, 1, 0.0f, 0.0f,
                    GridBagConstraints.LINE_START, GridBagConstraints.NONE,
                    new Insets(0, 6, 0, 6), 0, 0 ));
            label.setForeground(Color.WHITE);
            add(label = new JLabel("255"), new GridBagConstraints(2, 0, 1, 1, 0.0f, 0.0f,
                    GridBagConstraints.LINE_START, GridBagConstraints.NONE,
                    new Insets(0, 0, 0, 12), 0, 0 ));
            label.setForeground(Color.WHITE);
            rLabel = label;

            add(label = new JLabel("G:"), new GridBagConstraints(1, 1, 1, 1, 0.0f, 0.0f,
                    GridBagConstraints.LINE_START, GridBagConstraints.NONE,
                    new Insets(0, 6, 0, 6), 0, 0 ));
            label.setForeground(Color.WHITE);
            add(label = new JLabel("255"), new GridBagConstraints(2, 1, 1, 1, 0.0f, 0.0f,
                    GridBagConstraints.LINE_START, GridBagConstraints.NONE,
                    new Insets(0, 0, 0, 12), 0, 0 ));
            label.setForeground(Color.WHITE);
            gLabel = label;

            add(label = new JLabel("B:"), new GridBagConstraints(1, 2, 1, 1, 0.0f, 0.0f,
                    GridBagConstraints.LINE_START, GridBagConstraints.NONE,
                    new Insets(0, 6, 0, 6), 0, 0 ));
            label.setForeground(Color.WHITE);
            add(label = new JLabel("255"), new GridBagConstraints(2, 2, 1, 1, 0.0f, 0.0f,
                    GridBagConstraints.LINE_START, GridBagConstraints.NONE,
                    new Insets(0, 0, 0, 12), 0, 0 ));
            label.setForeground(Color.WHITE);
            bLabel = label;

            add(label = new JLabel("X:"), new GridBagConstraints(3, 0, 1, 1, 0.0f, 0.0f,
                    GridBagConstraints.LINE_START, GridBagConstraints.NONE,
                    new Insets(0, 6, 0, 6), 0, 0 ));
            label.setForeground(Color.WHITE);
            add(label = new JLabel("0 px"), new GridBagConstraints(4, 0, 1, 1, 0.0f, 0.0f,
                    GridBagConstraints.LINE_START, GridBagConstraints.NONE,
                    new Insets(0, 0, 0, 12), 0, 0 ));
            label.setForeground(Color.WHITE);
            xLabel = label;

            add(label = new JLabel("Y:"), new GridBagConstraints(3, 1, 1, 1, 0.0f, 0.0f,
                    GridBagConstraints.LINE_START, GridBagConstraints.NONE,
                    new Insets(0, 6, 0, 6), 0, 0 ));
            label.setForeground(Color.WHITE);
            add(label = new JLabel("0 px"), new GridBagConstraints(4, 1, 1, 1, 0.0f, 0.0f,
                    GridBagConstraints.LINE_START, GridBagConstraints.NONE,
                    new Insets(0, 0, 0, 12), 0, 0 ));
            label.setForeground(Color.WHITE);
            yLabel = label;

            add(Box.createHorizontalGlue(), new GridBagConstraints(5, 0, 1, 1, 1.0f, 0.0f,
                    GridBagConstraints.LINE_START, GridBagConstraints.BOTH,
                    new Insets(0, 0, 0, 0), 0, 0 ));
        }

        @Override
        protected void paintComponent(Graphics g) {
            g.setColor(Color.BLACK);
            g.fillRect(0, 0, getWidth(), getHeight());
        }

        void showPixel(int x, int y) {
            xLabel.setText(x + " px");
            yLabel.setText(y + " px");

            int pixel = image.getRGB(x, y);
            color = new Color(pixel);
            hLabel.setText("#" + Integer.toHexString(pixel));
            rLabel.setText(String.valueOf((pixel >> 16) & 0xff));
            gLabel.setText(String.valueOf((pixel >>  8) & 0xff));
            bLabel.setText(String.valueOf((pixel      ) & 0xff));

            square.repaint();
        }

        private class ColoredSquare extends JComponent {
            @Override
            public Dimension getPreferredSize() {
                Dimension d = super.getPreferredSize();
                d.width = 60;
                d.height = 30;
                return d;
            }

            @Override
            protected void paintComponent(Graphics g) {
                g.setColor(color);
                g.fillRect(0, 0, getWidth(), getHeight());

                g.setColor(Color.WHITE);
                g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
            }
        }
    }

    class Crosshair extends JPanel {
        // magenta = 0xff5efe
        private final Color crosshairColor = new Color(0x00ffff);
        Point crosshair = new Point();
        private int width;
        private int height;
        private final ScreenshotViewer screenshotViewer;

        Crosshair(ScreenshotViewer screenshotViewer) {
            this.screenshotViewer = screenshotViewer;
            setOpaque(true);
            setLayout(new BorderLayout());
            add(screenshotViewer);
            addMouseListener(new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent event) {
                    moveToPoint(event);
                }
            });
            addMouseMotionListener(new MouseMotionAdapter() {
                @Override
                public void mouseDragged(MouseEvent event) {
                    moveToPoint(event);
                }
            });
        }

        void moveToPoint(int x, int y) {
            crosshair.x = x;
            crosshair.y = y;
            status.showPixel(crosshair.x, crosshair.y);
            repaint();
        }

        private void moveToPoint(MouseEvent event) {
            crosshair.x = Math.max(0, Math.min(image.getWidth() - 1, event.getX()));
            crosshair.y = Math.max(0, Math.min(image.getHeight() - 1, event.getY()));
            loupe.moveToPoint(crosshair.x, crosshair.y);
            status.showPixel(crosshair.x, crosshair.y);

            repaint();
        }

        @Override
        public Dimension getPreferredSize() {
            return screenshotViewer.getPreferredSize();
        }

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

        @Override
        public void paint(Graphics g) {
            super.paint(g);

            if (crosshair == null || width != getWidth() || height != getHeight()) {
                width = getWidth();
                height = getHeight();
                crosshair = new Point(width / 2, height / 2);
            }

            g.setColor(crosshairColor);

            g.drawLine(crosshair.x, 0, crosshair.x, height);
            g.drawLine(0, crosshair.y, width, crosshair.y);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.setColor(Color.BLACK);
            g.fillRect(0, 0, getWidth(), getHeight());
        }
    }

    class ScreenshotViewer extends JComponent {
        private final Color boundsColor = new Color(0xff5efe);

        ScreenshotViewer() {
            setOpaque(true);
        }

        @Override
        protected void paintComponent(Graphics g) {
            g.setColor(Color.BLACK);
            g.fillRect(0, 0, getWidth(), getHeight());

            if (isLoading) {
                return;
            }

            if (image != null) {
                g.drawImage(image, 0, 0, null);
                if (overlay != null) {
                    Graphics2D g2 = (Graphics2D) g.create();
                    g2.setComposite(overlayAlpha);
                    g2.drawImage(overlay, 0, image.getHeight() - overlay.getHeight(), null);
                }
            }

            if (node != null) {
                Graphics s = g.create();
                s.setColor(boundsColor);
                ViewNode p = node.parent;
                while (p != null) {
                    s.translate(p.left - p.scrollX, p.top - p.scrollY);
                    p = p.parent;
                }
                s.drawRect(node.left, node.top, node.width - 1, node.height - 1);
                s.translate(node.left, node.top);

                s.setXORMode(Color.WHITE);
                if ((node.paddingBottom | node.paddingLeft |
                        node.paddingTop | node.paddingRight) != 0) {
                    s.setColor(Color.BLACK);
                    s.drawRect(node.paddingLeft, node.paddingTop,
                            node.width - node.paddingRight - node.paddingLeft - 1,
                            node.height - node.paddingBottom - node.paddingTop - 1);
                }
                if (node.hasMargins && (node.marginLeft | node.marginBottom |
                        node.marginRight | node.marginRight) != 0) {
                    s.setColor(Color.BLACK);
                    s.drawRect(-node.marginLeft, -node.marginTop,
                            node.marginLeft + node.width + node.marginRight - 1,
                            node.marginTop + node.height + node.marginBottom - 1);
                }

                s.dispose();
            }
        }

        @Override
        public Dimension getPreferredSize() {
            if (image == null) {
                return new Dimension(320, 480);
            }
            return new Dimension(image.getWidth(), image.getHeight());
        }
    }

    private class CrosshairPanel extends JPanel {
        private final Color crosshairColor = new Color(0xff5efe);
        private final Insets insets = new Insets(0, 0, 0, 0);

        CrosshairPanel(LoupeViewer loupe) {
            setLayout(new BorderLayout());
            add(loupe);
        }

        @Override
        public void paint(Graphics g) {
            super.paint(g);

            g.setColor(crosshairColor);

            int width = getWidth();
            int height = getHeight();

            getInsets(insets);

            int x = (width - insets.left - insets.right) / 2;
            int y = (height - insets.top - insets.bottom) / 2;

            g.drawLine(insets.left + x, insets.top, insets.left + x, height - insets.bottom);
            g.drawLine(insets.left, insets.top + y, width - insets.right, insets.top + y);
        }

        @Override
        protected void paintComponent(Graphics g) {
            g.setColor(Color.BLACK);
            Insets insets = getInsets();
            g.fillRect(insets.left, insets.top, getWidth() - insets.left - insets.right,
                    getHeight() - insets.top - insets.bottom);
        }
    }

    public void actionPerformed(ActionEvent event) {
        if (task != null && !task.isDone()) {
            return;
        }
        task = new GetScreenshotTask();
        task.execute();
    }

    private class GetScreenshotTask extends SwingWorker<Boolean, Void> {
        private GetScreenshotTask() {
            workspace.beginTask();
        }

        @Override
        @WorkerThread
        protected Boolean doInBackground() throws Exception {
            RawImage rawImage;
            try {
                rawImage = device.getScreenshot();
            } catch (IOException ioe) {
                return false;
            }

            boolean resize = false;
            isLoading = true;
            try {
                if (rawImage != null) {
                    if (image == null || rawImage.width != image.getWidth() ||
                            rawImage.height != image.getHeight()) {
                        image = new BufferedImage(rawImage.width, rawImage.height,
                                BufferedImage.TYPE_INT_ARGB);
                        scanline = new int[rawImage.width];
                        resize = true;
                    }

                    switch (rawImage.bpp) {
                        case 16:
                            rawImage16toARGB(rawImage);
                            break;
                        case 32:
                            rawImage32toARGB(rawImage);
                            break;
                    }
                }
            } finally {
                isLoading = false;
            }

            return resize;
        }
        
        private int getMask(int length) {
            int res = 0;
            for (int i = 0 ; i < length ; i++) {
                res = (res << 1) + 1;
            }
    
            return res;
        }

        private void rawImage32toARGB(RawImage rawImage) {
            byte[] buffer = rawImage.data;
            int index = 0;

            final int redOffset = rawImage.red_offset;
            final int redLength = rawImage.red_length;
            final int redMask = getMask(redLength);
            final int greenOffset = rawImage.green_offset;
            final int greenLength = rawImage.green_length;
            final int greenMask = getMask(greenLength);
            final int blueOffset = rawImage.blue_offset;
            final int blueLength = rawImage.blue_length;
            final int blueMask = getMask(blueLength);
            final int alphaLength = rawImage.alpha_length;
            final int alphaOffset = rawImage.alpha_offset;
            final int alphaMask = getMask(alphaLength);

            for (int y = 0 ; y < rawImage.height ; y++) {
                for (int x = 0 ; x < rawImage.width ; x++) {
                    int value = buffer[index++] & 0x00FF;
                    value |= (buffer[index++] & 0x00FF) << 8;
                    value |= (buffer[index++] & 0x00FF) << 16;
                    value |= (buffer[index++] & 0x00FF) << 24;

                    int r = ((value >>> redOffset) & redMask) << (8 - redLength);
                    int g = ((value >>> greenOffset) & greenMask) << (8 - greenLength);
                    int b = ((value >>> blueOffset) & blueMask) << (8 - blueLength);
                    int a = 0xFF;

                    if (alphaLength != 0) {
                        a = ((value >>> alphaOffset) & alphaMask) << (8 - alphaLength);
                    }

                    scanline[x] = a << 24 | r << 16 | g << 8 | b;
                }

                image.setRGB(0, y, rawImage.width, 1, scanline,
                        0, rawImage.width);
            }
        }

        private void rawImage16toARGB(RawImage rawImage) {
            byte[] buffer = rawImage.data;
            int index = 0;

            for (int y = 0 ; y < rawImage.height ; y++) {
                for (int x = 0 ; x < rawImage.width ; x++) {
                    int value = buffer[index++] & 0x00FF;
                    value |= (buffer[index++] << 8) & 0x0FF00;

                    int r = ((value >> 11) & 0x01F) << 3;
                    int g = ((value >> 5) & 0x03F) << 2;
                    int b = ((value     ) & 0x01F) << 3;

                    scanline[x] = 0xFF << 24 | r << 16 | g << 8 | b;
                }

                image.setRGB(0, y, rawImage.width, 1, scanline,
                        0, rawImage.width);
            }
        }

        @Override
        protected void done() {
            workspace.endTask();
            try {
                if (get()) {
                    validate();
                    crosshair.crosshair = new Point(image.getWidth() / 2,
                            image.getHeight() / 2);
                    status.showPixel(image.getWidth() / 2, image.getHeight() / 2);
                    loupe.moveToPoint(image.getWidth() / 2, image.getHeight() / 2);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            repaint();
        }
    }

    private class OpenOverlayTask extends SwingWorker<BufferedImage, Void> {
        private File file;

        private OpenOverlayTask(File file) {
            this.file = file;
            workspace.beginTask();
        }

        @Override
        @WorkerThread
        protected BufferedImage doInBackground() {
            try {
                return IconLoader.toCompatibleImage(ImageIO.read(file));
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            return null;
        }

        @Override
        protected void done() {
            try {
                overlay = get();
                repaint();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } finally {
                workspace.endTask();
            }
        }
    }
}
