private static File promptForFile(String defaultFileName) {
        final SnapFileFilter fileFilter = new SnapFileFilter("TXT", "txt", "Text");
        return Dialogs.requestFileForSave(Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), false, fileFilter, ".txt", defaultFileName, null, "exportMaskPixels.lastDir");
    }

package org.esa.snap.rcp.actions.file.export;

import com.bc.ceres.core.ProgressMonitor;
import com.bc.ceres.swing.progress.ProgressMonitorSwingWorker;
import org.esa.snap.core.datamodel.*;
import org.esa.snap.core.util.SystemUtils;
import org.esa.snap.core.util.io.FileUtils;
import org.esa.snap.core.util.io.SnapFileFilter;
import org.esa.snap.core.util.math.MathUtils;
import org.esa.snap.rcp.SnapApp;
import org.esa.snap.rcp.util.Dialogs;
import org.esa.snap.rcp.util.MultiSizeIssue;
import org.esa.snap.ui.AbstractDialog;
import org.esa.snap.ui.ModalDialog;
import org.esa.snap.ui.SelectExportMethodDialog;
import org.esa.snap.ui.UIUtils;
import org.esa.snap.ui.product.ProductSceneView;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
import org.openide.awt.ActionRegistration;
import org.openide.util.ContextAwareAction;
import org.openide.util.HelpCtx;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.openide.util.WeakListeners;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BoxLayout;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.GregorianCalendar;

@ActionID(category = "File", id = "org.esa.snap.rcp.actions.file.export.ExportMaskPixelsAction")
@ActionRegistration(displayName = "#CTL_ExportMaskPixelsAction_MenuText", popupText = "#CTL_ExportMaskPixelsAction_PopupText", lazy = false)
@ActionReferences({ @ActionReference(path = "Menu/File/Export/Other", position = 50), @ActionReference(path = "Menu/Raster/Export", position = 100), @ActionReference(path = "Context/ProductSceneView", position = 50) })
@NbBundle.Messages({ "CTL_ExportMaskPixelsAction_MenuText=Mask Pixels", "CTL_ExportMaskPixelsAction_PopupText=Export Mask Pixels", "CTL_ExportMaskPixelsAction_DialogTitle=Export Mask Pixels", "CTL_ExportMaskPixelsAction_ShortDescription=Export Mask Pixels." })
public class ExportMaskPixelsAction extends AbstractAction implements ContextAwareAction, LookupListener, HelpCtx.Provider {

    private static final String HELP_ID = "exportMaskPixels";

    private static final String ERR_MSG_BASE = "Mask pixels cannot be exported:\n";

    private final Lookup.Result<ProductSceneView> result;

    public ExportMaskPixelsAction() {
        this(Utilities.actionsGlobalContext());
    }

    public ExportMaskPixelsAction(Lookup lkp) {
        super(Bundle.CTL_ExportMaskPixelsAction_MenuText());
        putValue("popupText", Bundle.CTL_ExportMaskPixelsAction_PopupText());
        result = lkp.lookupResult(ProductSceneView.class);
        result.addLookupListener(WeakListeners.create(LookupListener.class, this, result));
        setEnabled(false);
    }

    /**
     * Invoked when a command action is performed.
     *
     * @param event the command event
     */
    @Override
    public void actionPerformed(ActionEvent event) {
        ProductSceneView sceneView = SnapApp.getDefault().getSelectedProductSceneView();
        if (sceneView != null) {
            Product product = sceneView.getProduct();
            exportMaskPixels(product);
        }
    }

    @Override
    public Action createContextAwareInstance(Lookup lkp) {
        return new ExportMaskPixelsAction(lkp);
    }

    @Override
    public void resultChanged(LookupEvent le) {
        ProductSceneView sceneView = SnapApp.getDefault().getSelectedProductSceneView();
        boolean enabled = false;
        if (sceneView != null) {
            Product product = sceneView.getProduct();
            enabled = product.getMaskGroup().getNodeCount() > 0;
        }
        setEnabled(enabled);
    }

    // Private implementations for the "export Mask Pixels" command
    /**
     * Performs the actual "export Mask Pixels" command.
     * @param product
     */
    private void exportMaskPixels(Product product) {
        String[] maskNames = product.getMaskGroup().getNodeNames();
        final String maskName;
        if (maskNames.length == 1) {
            maskName = maskNames[0];
        } else {
            JPanel panel = new JPanel();
            BoxLayout boxLayout = new BoxLayout(panel, BoxLayout.X_AXIS);
            panel.setLayout(boxLayout);
            panel.add(new JLabel("Select Mask: "));
            JComboBox<String> maskCombo = new JComboBox<>(maskNames);
            panel.add(maskCombo);
            ModalDialog modalDialog = new ModalDialog(SnapApp.getDefault().getMainFrame(), Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), panel, ModalDialog.ID_OK_CANCEL | ModalDialog.ID_HELP, getHelpCtx().getHelpID());
            if (modalDialog.show() == AbstractDialog.ID_OK) {
                maskName = (String) maskCombo.getSelectedItem();
            } else {
                return;
            }
        }
        final RenderedImage maskImage = product.getMaskGroup().get(maskName).getSourceImage();
        if (maskImage == null) {
            Dialogs.showError(Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), ERR_MSG_BASE + "No Mask image available.");
            return;
        }
        // Compute total number of Mask pixels
        final long numMaskPixels = getNumMaskPixels(maskImage, product.getSceneRasterWidth(), product.getSceneRasterHeight());
        String numPixelsText;
        if (numMaskPixels == 1) {
            numPixelsText = "One Mask pixel will be exported.\n";
        } else {
            numPixelsText = numMaskPixels + " Mask pixels will be exported.\n";
        }
        // Get export method from user
        final String questionText = "How do you want to export the pixel values?\n";
        final JCheckBox createHeaderBox = new JCheckBox("Create header");
        final JCheckBox exportTiePointsBox = new JCheckBox("Export tie-points");
        final JCheckBox exportWavelengthsAndSFBox = new JCheckBox("Export wavelengths + solar fluxes");
        final int method = SelectExportMethodDialog.run(SnapApp.getDefault().getMainFrame(), getWindowTitle(), questionText + numPixelsText, new JCheckBox[] { createHeaderBox, exportTiePointsBox, exportWavelengthsAndSFBox }, getHelpCtx().getHelpID());
        final boolean mustCreateHeader = createHeaderBox.isSelected();
        final boolean mustExportTiePoints = exportTiePointsBox.isSelected();
        final boolean mustExportWavelengthsAndSF = exportWavelengthsAndSFBox.isSelected();
        final PrintWriter out;
        final StringBuffer clipboardText;
        final int initialBufferSize = 256000;
        if (method == SelectExportMethodDialog.EXPORT_TO_CLIPBOARD) {
            // Write into string buffer
            final StringWriter stringWriter = new StringWriter(initialBufferSize);
            out = new PrintWriter(stringWriter);
            clipboardText = stringWriter.getBuffer();
        } else if (method == SelectExportMethodDialog.EXPORT_TO_FILE) {
            // Write into file, get file from user
            final File file = promptForFile(createDefaultFileName(product, maskName));
            if (file == null) {
                // Cancel
                return;
            }
            final FileWriter fileWriter;
            try {
                fileWriter = new FileWriter(file);
            } catch (IOException e) {
                Dialogs.showError(Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), ERR_MSG_BASE + "Failed to create file '" + file + "':\n" + e.getMessage());
                // Error
                return;
            }
            out = new PrintWriter(new BufferedWriter(fileWriter, initialBufferSize));
            clipboardText = null;
        } else {
            // Cancel
            return;
        }
        final ProgressMonitorSwingWorker<Exception, Object> swingWorker = new ProgressMonitorSwingWorker<Exception, Object>(SnapApp.getDefault().getMainFrame(), Bundle.CTL_ExportMaskPixelsAction_DialogTitle()) {

            @Override
            protected Exception doInBackground(ProgressMonitor pm) throws Exception {
                Exception returnValue = null;
                try {
                    boolean success = exportMaskPixels(out, product, maskName, mustCreateHeader, mustExportTiePoints, mustExportWavelengthsAndSF, pm);
                    if (success && clipboardText != null) {
                        SystemUtils.copyToClipboard(clipboardText.toString());
                        clipboardText.setLength(0);
                    }
                } catch (Exception e) {
                    returnValue = e;
                } finally {
                    out.close();
                }
                return returnValue;
            }

            @Override
            public void done() {
                // clear status bar
                SnapApp.getDefault().setStatusBarMessage("");
                UIUtils.setRootFrameDefaultCursor(SnapApp.getDefault().getMainFrame());
                // On error, show error message
                Exception exception;
                try {
                    exception = get();
                } catch (Exception e) {
                    exception = e;
                }
                if (exception != null) {
                    Dialogs.showError(Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), ERR_MSG_BASE + exception.getMessage());
                }
            }
        };
        // show wait-cursor
        UIUtils.setRootFrameWaitCursor(SnapApp.getDefault().getMainFrame());
        // show message in status bar
        SnapApp.getDefault().setStatusBarMessage("Exporting Mask pixels...");
        // Start separate worker thread.
        swingWorker.execute();
    }

    private static String createDefaultFileName(final Product raster, String maskName) {
        String productName = FileUtils.getFilenameWithoutExtension(raster.getProduct().getName());
        return productName + "_" + maskName + "_Mask.txt";
    }

    private static String getWindowTitle() {
        return SnapApp.getDefault().getInstanceName() + " - " + Bundle.CTL_ExportMaskPixelsAction_DialogTitle();
    }

    /*
     * Opens a modal file chooser dialog that prompts the user to select the output file name.
     *
     * @param visatApp the VISAT application
     * @return the selected file, null means "Cancel"
     */

/**
     * Writes all pixel values of the given product within the given Mask to the specified out.
     *
     * @param out      the data output writer
     * @param product  the product providing the pixel values
     * @param maskName the mask name
     * @param mustCreateHeader
     * @param mustExportTiePoints
     * @param mustExportWavelengthsAndSF
     * @param pm       progress monitor
     * @return true for success, false if export has been terminated (by user)
     */
    private static boolean exportMaskPixels(final PrintWriter out, final Product product, String maskName, boolean mustCreateHeader, boolean mustExportTiePoints, boolean mustExportWavelengthsAndSF, ProgressMonitor pm) throws IOException {
        final RenderedImage maskImage = product.getMaskGroup().get(maskName).getSourceImage();
        final int minTileX = maskImage.getMinTileX();
        final int minTileY = maskImage.getMinTileY();
        final int numXTiles = maskImage.getNumXTiles();
        final int numYTiles = maskImage.getNumYTiles();
        final int w = product.getSceneRasterWidth();
        final int h = product.getSceneRasterHeight();
        final Rectangle imageRect = new Rectangle(0, 0, w, h);
        pm.beginTask("Writing pixel data...", numXTiles * numYTiles + 2);
        try {
            if (mustCreateHeader) {
                createHeader(out, product, maskName, mustExportWavelengthsAndSF);
            }
            pm.worked(1);
            writeColumnNames(out, product, maskName, mustExportTiePoints);
            pm.worked(1);
            for (int tileX = minTileX; tileX < minTileX + numXTiles; ++tileX) {
                for (int tileY = minTileY; tileY < minTileY + numYTiles; ++tileY) {
                    if (pm.isCanceled()) {
                        return false;
                    }
                    final Rectangle tileRectangle = new Rectangle(maskImage.getTileGridXOffset() + tileX * maskImage.getTileWidth(), maskImage.getTileGridYOffset() + tileY * maskImage.getTileHeight(), maskImage.getTileWidth(), maskImage.getTileHeight());
                    final Rectangle r = imageRect.intersection(tileRectangle);
                    if (!r.isEmpty()) {
                        Raster maskTile = maskImage.getTile(tileX, tileY);
                        for (int y = r.y; y < r.y + r.height; y++) {
                            for (int x = r.x; x < r.x + r.width; x++) {
                                if (maskTile.getSample(x, y, 0) != 0) {
                                    writeDataLine(out, product, maskName, x, y, mustExportTiePoints);
                                }
                            }
                        }
                    }
                    pm.worked(1);
                }
            }
        } finally {
            pm.done();
        }
        return true;
    }

    private static void createHeader(PrintWriter out, Product product, String maskName, boolean mustExportWavelengthsAndSF) {
        out.write("# Exported mask '" + maskName + "' on " + new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss.mmmmmm").format(new GregorianCalendar().getTime()) + "\n");
        out.write("# Product name: " + product.getName() + "\n");
        if (product.getFileLocation() != null) {
            out.write("# Product file location: " + product.getFileLocation() + "\n");
        }
        out.write("\n");
        if (mustExportWavelengthsAndSF) {
            out.write("# Wavelength:");
            // account for pixel-x, pixel-y, lon, lat columns
            out.write("\t\t\t");
            for (final Band band : product.getBands()) {
                out.print("\t");
                out.print("" + band.getSpectralWavelength());
            }
            out.print("\n");
            out.write("# Solar flux:");
            // account for pixel-x, pixel-y, lon, lat columns
            out.write("\t\t\t");
            for (final Band band : product.getBands()) {
                out.print("\t");
                out.print("" + band.getSolarFlux());
            }
            out.print("\n");
        }
    }

    /*
     * Writes the header line of the dataset to be exported.
     *
     * @param out                     the data output writer
     * @param geoCoding               the product's geo-coding
     * @param bands                   the array of bands to be considered
     * @param mustExportTiePointGrids if tie-point grids shall be considered
     * @param tiePointGrids           the array of tie-point grids to be considered
     */
    private static void writeColumnNames(final PrintWriter out, final Product product, final String maskName, final boolean mustExportTiePointGrids) {
        if (!product.isMultiSize()) {
            out.print("Pixel-X");
            out.print("\t");
            out.print("Pixel-Y");
        } else {
            // Add the mask name to the identification of the pixel
            out.print("Pixel-X." + maskName);
            out.print("\t");
            out.print("Pixel-Y." + maskName);
        }
        if (product.getSceneGeoCoding() != null) {
            out.print("\t");
            out.print("Longitude");
            out.print("\t");
            out.print("Latitude");
        }
        for (final Band band : product.getBands()) {
            out.print("\t");
            out.print(band.getName());
        }
        if (mustExportTiePointGrids) {
            for (final TiePointGrid grid : product.getTiePointGrids()) {
                out.print("\t");
                out.print(grid.getName());
            }
        }
        out.print("\n");
    }

    /**
     * Writes a data line of the dataset to be exported for the given pixel position.
     *
     * @param out                 the data output writer
     * @param product             the product
     * @param maskName            the mask name (where the pixel X and Y where read)
     * @param x                   the current pixel's X coordinate
     * @param y                   the current pixel's Y coordinate
     * @param mustExportTiePoints if tie-point grids shall be exported
     * @throws IOException
     */
    private static void writeDataLine(final PrintWriter out, final Product product, final String maskName, final int x, final int y, final boolean mustExportTiePoints) throws IOException {
        // All the positions computations will be done at the centre of pixel cell
        final PixelPos pixelPosRef = new PixelPos(x + 0.5f, y + 0.5f);
        out.print(String.valueOf(pixelPosRef.x));
        out.print("\t");
        out.print(String.valueOf(pixelPosRef.y));
        final Mask mask = product.getMaskGroup().get(maskName);
        final GeoCoding maskGeocoding = mask.getGeoCoding();
        if (maskGeocoding != null) {
            final GeoPos geoPos = maskGeocoding.getGeoPos(pixelPosRef, null);
            out.print("\t");
            out.print(String.valueOf(geoPos.lon));
            out.print("\t");
            out.print(String.valueOf(geoPos.lat));
        }
        final int[] intPixel = new int[1];
        final float[] floatPixel = new float[1];
        for (final Band band : product.getBands()) {
            out.print("\t");
            PixelPos pixelForBand = product.getPixelForBand(pixelPosRef, mask, band);
            int xForBand = MathUtils.floorInt(pixelForBand.x);
            int yForBand = MathUtils.floorInt(pixelForBand.y);
            if (band.isPixelValid(xForBand, yForBand)) {
                if (band.isFloatingPointType()) {
                    band.readPixels(xForBand, yForBand, 1, 1, floatPixel, ProgressMonitor.NULL);
                    out.print(floatPixel[0]);
                } else {
                    band.readPixels(xForBand, yForBand, 1, 1, intPixel, ProgressMonitor.NULL);
                    out.print(intPixel[0]);
                }
            } else {
                out.print(RasterDataNode.NO_DATA_TEXT);
            }
        }
        // end loop on bands
        if (mustExportTiePoints) {
            for (final TiePointGrid grid : product.getTiePointGrids()) {
                PixelPos pixelForGrid = product.getPixelForBand(pixelPosRef, mask, grid);
                grid.readPixels(MathUtils.floorInt(pixelForGrid.x), MathUtils.floorInt(pixelForGrid.y), 1, 1, floatPixel, ProgressMonitor.NULL);
                out.print("\t");
                out.print(floatPixel[0]);
                if (Float.isNaN(floatPixel[0])) {
                    System.out.println("NaN for " + grid.getName());
                }
            }
        }
        // end loop on Tie Point grids
        out.print("\n");
    }

    /*
     * Computes the total number of pixels within the specified Mask.
     *
     * @param raster   the raster data node
     * @param maskImage the rendered image masking out the Mask
     * @return the total number of pixels in the Mask
     */
    private static long getNumMaskPixels(final RenderedImage maskImage, int sceneRasterWidth, int sceneRasterHeight) {
        final int minTileX = maskImage.getMinTileX();
        final int minTileY = maskImage.getMinTileY();
        final int numXTiles = maskImage.getNumXTiles();
        final int numYTiles = maskImage.getNumYTiles();
        final Rectangle imageRect = new Rectangle(0, 0, sceneRasterWidth, sceneRasterHeight);
        long numMaskPixels = 0;
        for (int tileX = minTileX; tileX < minTileX + numXTiles; ++tileX) {
            for (int tileY = minTileY; tileY < minTileY + numYTiles; ++tileY) {
                final Rectangle tileRectangle = new Rectangle(maskImage.getTileGridXOffset() + tileX * maskImage.getTileWidth(), maskImage.getTileGridYOffset() + tileY * maskImage.getTileHeight(), maskImage.getTileWidth(), maskImage.getTileHeight());
                final Rectangle r = imageRect.intersection(tileRectangle);
                if (!r.isEmpty()) {
                    Raster maskTile = maskImage.getTile(tileX, tileY);
                    for (int y = r.y; y < r.y + r.height; y++) {
                        for (int x = r.x; x < r.x + r.width; x++) {
                            if (maskTile.getSample(x, y, 0) != 0) {
                                numMaskPixels++;
                            }
                        }
                    }
                }
            }
        }
        return numMaskPixels;
    }

    @Override
    public HelpCtx getHelpCtx() {
        return new HelpCtx(HELP_ID);
    }
}


for (int y = r.y; y < r.y + r.height; y++) {
                        for (int x = r.x; x < r.x + r.width; x++) {
                            if (maskTile.getSample(x, y, 0) != 0) {
                                numMaskPixels++;
                            }
                        }
                    }

package org.esa.snap.rcp.actions.file.export;

import com.bc.ceres.core.ProgressMonitor;
import com.bc.ceres.swing.progress.ProgressMonitorSwingWorker;
import org.esa.snap.core.datamodel.*;
import org.esa.snap.core.util.SystemUtils;
import org.esa.snap.core.util.io.FileUtils;
import org.esa.snap.core.util.io.SnapFileFilter;
import org.esa.snap.core.util.math.MathUtils;
import org.esa.snap.rcp.SnapApp;
import org.esa.snap.rcp.util.Dialogs;
import org.esa.snap.rcp.util.MultiSizeIssue;
import org.esa.snap.ui.AbstractDialog;
import org.esa.snap.ui.ModalDialog;
import org.esa.snap.ui.SelectExportMethodDialog;
import org.esa.snap.ui.UIUtils;
import org.esa.snap.ui.product.ProductSceneView;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
import org.openide.awt.ActionRegistration;
import org.openide.util.ContextAwareAction;
import org.openide.util.HelpCtx;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.openide.util.WeakListeners;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BoxLayout;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.GregorianCalendar;

@ActionID(category = "File", id = "org.esa.snap.rcp.actions.file.export.ExportMaskPixelsAction")
@ActionRegistration(displayName = "#CTL_ExportMaskPixelsAction_MenuText", popupText = "#CTL_ExportMaskPixelsAction_PopupText", lazy = false)
@ActionReferences({ @ActionReference(path = "Menu/File/Export/Other", position = 50), @ActionReference(path = "Menu/Raster/Export", position = 100), @ActionReference(path = "Context/ProductSceneView", position = 50) })
@NbBundle.Messages({ "CTL_ExportMaskPixelsAction_MenuText=Mask Pixels", "CTL_ExportMaskPixelsAction_PopupText=Export Mask Pixels", "CTL_ExportMaskPixelsAction_DialogTitle=Export Mask Pixels", "CTL_ExportMaskPixelsAction_ShortDescription=Export Mask Pixels." })
public class ExportMaskPixelsAction extends AbstractAction implements ContextAwareAction, LookupListener, HelpCtx.Provider {

    private static final String HELP_ID = "exportMaskPixels";

    private static final String ERR_MSG_BASE = "Mask pixels cannot be exported:\n";

    private final Lookup.Result<ProductSceneView> result;

    public ExportMaskPixelsAction() {
        this(Utilities.actionsGlobalContext());
    }

    public ExportMaskPixelsAction(Lookup lkp) {
        super(Bundle.CTL_ExportMaskPixelsAction_MenuText());
        putValue("popupText", Bundle.CTL_ExportMaskPixelsAction_PopupText());
        result = lkp.lookupResult(ProductSceneView.class);
        result.addLookupListener(WeakListeners.create(LookupListener.class, this, result));
        setEnabled(false);
    }

    /**
     * Invoked when a command action is performed.
     *
     * @param event the command event
     */
    @Override
    public void actionPerformed(ActionEvent event) {
        ProductSceneView sceneView = SnapApp.getDefault().getSelectedProductSceneView();
        if (sceneView != null) {
            Product product = sceneView.getProduct();
            exportMaskPixels(product);
        }
    }

    @Override
    public Action createContextAwareInstance(Lookup lkp) {
        return new ExportMaskPixelsAction(lkp);
    }

    @Override
    public void resultChanged(LookupEvent le) {
        ProductSceneView sceneView = SnapApp.getDefault().getSelectedProductSceneView();
        boolean enabled = false;
        if (sceneView != null) {
            Product product = sceneView.getProduct();
            enabled = product.getMaskGroup().getNodeCount() > 0;
        }
        setEnabled(enabled);
    }

    // Private implementations for the "export Mask Pixels" command
    /**
     * Performs the actual "export Mask Pixels" command.
     * @param product
     */
    private void exportMaskPixels(Product product) {
        String[] maskNames = product.getMaskGroup().getNodeNames();
        final String maskName;
        if (maskNames.length == 1) {
            maskName = maskNames[0];
        } else {
            JPanel panel = new JPanel();
            BoxLayout boxLayout = new BoxLayout(panel, BoxLayout.X_AXIS);
            panel.setLayout(boxLayout);
            panel.add(new JLabel("Select Mask: "));
            JComboBox<String> maskCombo = new JComboBox<>(maskNames);
            panel.add(maskCombo);
            ModalDialog modalDialog = new ModalDialog(SnapApp.getDefault().getMainFrame(), Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), panel, ModalDialog.ID_OK_CANCEL | ModalDialog.ID_HELP, getHelpCtx().getHelpID());
            if (modalDialog.show() == AbstractDialog.ID_OK) {
                maskName = (String) maskCombo.getSelectedItem();
            } else {
                return;
            }
        }
        final RenderedImage maskImage = product.getMaskGroup().get(maskName).getSourceImage();
        if (maskImage == null) {
            Dialogs.showError(Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), ERR_MSG_BASE + "No Mask image available.");
            return;
        }
        // Compute total number of Mask pixels
        final long numMaskPixels = getNumMaskPixels(maskImage, product.getSceneRasterWidth(), product.getSceneRasterHeight());
        String numPixelsText;
        if (numMaskPixels == 1) {
            numPixelsText = "One Mask pixel will be exported.\n";
        } else {
            numPixelsText = numMaskPixels + " Mask pixels will be exported.\n";
        }
        // Get export method from user
        final String questionText = "How do you want to export the pixel values?\n";
        final JCheckBox createHeaderBox = new JCheckBox("Create header");
        final JCheckBox exportTiePointsBox = new JCheckBox("Export tie-points");
        final JCheckBox exportWavelengthsAndSFBox = new JCheckBox("Export wavelengths + solar fluxes");
        final int method = SelectExportMethodDialog.run(SnapApp.getDefault().getMainFrame(), getWindowTitle(), questionText + numPixelsText, new JCheckBox[] { createHeaderBox, exportTiePointsBox, exportWavelengthsAndSFBox }, getHelpCtx().getHelpID());
        final boolean mustCreateHeader = createHeaderBox.isSelected();
        final boolean mustExportTiePoints = exportTiePointsBox.isSelected();
        final boolean mustExportWavelengthsAndSF = exportWavelengthsAndSFBox.isSelected();
        final PrintWriter out;
        final StringBuffer clipboardText;
        final int initialBufferSize = 256000;
        if (method == SelectExportMethodDialog.EXPORT_TO_CLIPBOARD) {
            // Write into string buffer
            final StringWriter stringWriter = new StringWriter(initialBufferSize);
            out = new PrintWriter(stringWriter);
            clipboardText = stringWriter.getBuffer();
        } else if (method == SelectExportMethodDialog.EXPORT_TO_FILE) {
            // Write into file, get file from user
            final File file = promptForFile(createDefaultFileName(product, maskName));
            if (file == null) {
                // Cancel
                return;
            }
            final FileWriter fileWriter;
            try {
                fileWriter = new FileWriter(file);
            } catch (IOException e) {
                Dialogs.showError(Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), ERR_MSG_BASE + "Failed to create file '" + file + "':\n" + e.getMessage());
                // Error
                return;
            }
            out = new PrintWriter(new BufferedWriter(fileWriter, initialBufferSize));
            clipboardText = null;
        } else {
            // Cancel
            return;
        }
        final ProgressMonitorSwingWorker<Exception, Object> swingWorker = new ProgressMonitorSwingWorker<Exception, Object>(SnapApp.getDefault().getMainFrame(), Bundle.CTL_ExportMaskPixelsAction_DialogTitle()) {

            @Override
            protected Exception doInBackground(ProgressMonitor pm) throws Exception {
                Exception returnValue = null;
                try {
                    boolean success = exportMaskPixels(out, product, maskName, mustCreateHeader, mustExportTiePoints, mustExportWavelengthsAndSF, pm);
                    if (success && clipboardText != null) {
                        SystemUtils.copyToClipboard(clipboardText.toString());
                        clipboardText.setLength(0);
                    }
                } catch (Exception e) {
                    returnValue = e;
                } finally {
                    out.close();
                }
                return returnValue;
            }

            @Override
            public void done() {
                // clear status bar
                SnapApp.getDefault().setStatusBarMessage("");
                UIUtils.setRootFrameDefaultCursor(SnapApp.getDefault().getMainFrame());
                // On error, show error message
                Exception exception;
                try {
                    exception = get();
                } catch (Exception e) {
                    exception = e;
                }
                if (exception != null) {
                    Dialogs.showError(Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), ERR_MSG_BASE + exception.getMessage());
                }
            }
        };
        // show wait-cursor
        UIUtils.setRootFrameWaitCursor(SnapApp.getDefault().getMainFrame());
        // show message in status bar
        SnapApp.getDefault().setStatusBarMessage("Exporting Mask pixels...");
        // Start separate worker thread.
        swingWorker.execute();
    }

    private static String createDefaultFileName(final Product raster, String maskName) {
        String productName = FileUtils.getFilenameWithoutExtension(raster.getProduct().getName());
        return productName + "_" + maskName + "_Mask.txt";
    }

    private static String getWindowTitle() {
        return SnapApp.getDefault().getInstanceName() + " - " + Bundle.CTL_ExportMaskPixelsAction_DialogTitle();
    }

    /*
     * Opens a modal file chooser dialog that prompts the user to select the output file name.
     *
     * @param visatApp the VISAT application
     * @return the selected file, null means "Cancel"
     */
    private static File promptForFile(String defaultFileName) {
        final SnapFileFilter fileFilter = new SnapFileFilter("TXT", "txt", "Text");
        return Dialogs.requestFileForSave(Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), false, fileFilter, ".txt", defaultFileName, null, "exportMaskPixels.lastDir");
    }

    /**
     * Writes all pixel values of the given product within the given Mask to the specified out.
     *
     * @param out      the data output writer
     * @param product  the product providing the pixel values
     * @param maskName the mask name
     * @param mustCreateHeader
     * @param mustExportTiePoints
     * @param mustExportWavelengthsAndSF
     * @param pm       progress monitor
     * @return true for success, false if export has been terminated (by user)
     */
    private static boolean exportMaskPixels(final PrintWriter out, final Product product, String maskName, boolean mustCreateHeader, boolean mustExportTiePoints, boolean mustExportWavelengthsAndSF, ProgressMonitor pm) throws IOException {
        final RenderedImage maskImage = product.getMaskGroup().get(maskName).getSourceImage();
        final int minTileX = maskImage.getMinTileX();
        final int minTileY = maskImage.getMinTileY();
        final int numXTiles = maskImage.getNumXTiles();
        final int numYTiles = maskImage.getNumYTiles();
        final int w = product.getSceneRasterWidth();
        final int h = product.getSceneRasterHeight();
        final Rectangle imageRect = new Rectangle(0, 0, w, h);
        pm.beginTask("Writing pixel data...", numXTiles * numYTiles + 2);
        try {
            if (mustCreateHeader) {
                createHeader(out, product, maskName, mustExportWavelengthsAndSF);
            }
            pm.worked(1);
            writeColumnNames(out, product, maskName, mustExportTiePoints);
            pm.worked(1);
            for (int tileX = minTileX; tileX < minTileX + numXTiles; ++tileX) {
                for (int tileY = minTileY; tileY < minTileY + numYTiles; ++tileY) {
                    if (pm.isCanceled()) {
                        return false;
                    }
                    final Rectangle tileRectangle = new Rectangle(maskImage.getTileGridXOffset() + tileX * maskImage.getTileWidth(), maskImage.getTileGridYOffset() + tileY * maskImage.getTileHeight(), maskImage.getTileWidth(), maskImage.getTileHeight());
                    final Rectangle r = imageRect.intersection(tileRectangle);
                    if (!r.isEmpty()) {
                        Raster maskTile = maskImage.getTile(tileX, tileY);
                        for (int y = r.y; y < r.y + r.height; y++) {
                            for (int x = r.x; x < r.x + r.width; x++) {
                                if (maskTile.getSample(x, y, 0) != 0) {
                                    writeDataLine(out, product, maskName, x, y, mustExportTiePoints);
                                }
                            }
                        }
                    }
                    pm.worked(1);
                }
            }
        } finally {
            pm.done();
        }
        return true;
    }

    private static void createHeader(PrintWriter out, Product product, String maskName, boolean mustExportWavelengthsAndSF) {
        out.write("# Exported mask '" + maskName + "' on " + new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss.mmmmmm").format(new GregorianCalendar().getTime()) + "\n");
        out.write("# Product name: " + product.getName() + "\n");
        if (product.getFileLocation() != null) {
            out.write("# Product file location: " + product.getFileLocation() + "\n");
        }
        out.write("\n");
        if (mustExportWavelengthsAndSF) {
            out.write("# Wavelength:");
            // account for pixel-x, pixel-y, lon, lat columns
            out.write("\t\t\t");
            for (final Band band : product.getBands()) {
                out.print("\t");
                out.print("" + band.getSpectralWavelength());
            }
            out.print("\n");
            out.write("# Solar flux:");
            // account for pixel-x, pixel-y, lon, lat columns
            out.write("\t\t\t");
            for (final Band band : product.getBands()) {
                out.print("\t");
                out.print("" + band.getSolarFlux());
            }
            out.print("\n");
        }
    }

    /*
     * Writes the header line of the dataset to be exported.
     *
     * @param out                     the data output writer
     * @param geoCoding               the product's geo-coding
     * @param bands                   the array of bands to be considered
     * @param mustExportTiePointGrids if tie-point grids shall be considered
     * @param tiePointGrids           the array of tie-point grids to be considered
     */
    private static void writeColumnNames(final PrintWriter out, final Product product, final String maskName, final boolean mustExportTiePointGrids) {
        if (!product.isMultiSize()) {
            out.print("Pixel-X");
            out.print("\t");
            out.print("Pixel-Y");
        } else {
            // Add the mask name to the identification of the pixel
            out.print("Pixel-X." + maskName);
            out.print("\t");
            out.print("Pixel-Y." + maskName);
        }
        if (product.getSceneGeoCoding() != null) {
            out.print("\t");
            out.print("Longitude");
            out.print("\t");
            out.print("Latitude");
        }
        for (final Band band : product.getBands()) {
            out.print("\t");
            out.print(band.getName());
        }
        if (mustExportTiePointGrids) {
            for (final TiePointGrid grid : product.getTiePointGrids()) {
                out.print("\t");
                out.print(grid.getName());
            }
        }
        out.print("\n");
    }

    /**
     * Writes a data line of the dataset to be exported for the given pixel position.
     *
     * @param out                 the data output writer
     * @param product             the product
     * @param maskName            the mask name (where the pixel X and Y where read)
     * @param x                   the current pixel's X coordinate
     * @param y                   the current pixel's Y coordinate
     * @param mustExportTiePoints if tie-point grids shall be exported
     * @throws IOException
     */
    private static void writeDataLine(final PrintWriter out, final Product product, final String maskName, final int x, final int y, final boolean mustExportTiePoints) throws IOException {
        // All the positions computations will be done at the centre of pixel cell
        final PixelPos pixelPosRef = new PixelPos(x + 0.5f, y + 0.5f);
        out.print(String.valueOf(pixelPosRef.x));
        out.print("\t");
        out.print(String.valueOf(pixelPosRef.y));
        final Mask mask = product.getMaskGroup().get(maskName);
        final GeoCoding maskGeocoding = mask.getGeoCoding();
        if (maskGeocoding != null) {
            final GeoPos geoPos = maskGeocoding.getGeoPos(pixelPosRef, null);
            out.print("\t");
            out.print(String.valueOf(geoPos.lon));
            out.print("\t");
            out.print(String.valueOf(geoPos.lat));
        }
        final int[] intPixel = new int[1];
        final float[] floatPixel = new float[1];
        for (final Band band : product.getBands()) {
            out.print("\t");
            PixelPos pixelForBand = product.getPixelForBand(pixelPosRef, mask, band);
            int xForBand = MathUtils.floorInt(pixelForBand.x);
            int yForBand = MathUtils.floorInt(pixelForBand.y);
            if (band.isPixelValid(xForBand, yForBand)) {
                if (band.isFloatingPointType()) {
                    band.readPixels(xForBand, yForBand, 1, 1, floatPixel, ProgressMonitor.NULL);
                    out.print(floatPixel[0]);
                } else {
                    band.readPixels(xForBand, yForBand, 1, 1, intPixel, ProgressMonitor.NULL);
                    out.print(intPixel[0]);
                }
            } else {
                out.print(RasterDataNode.NO_DATA_TEXT);
            }
        }
        // end loop on bands
        if (mustExportTiePoints) {
            for (final TiePointGrid grid : product.getTiePointGrids()) {
                PixelPos pixelForGrid = product.getPixelForBand(pixelPosRef, mask, grid);
                grid.readPixels(MathUtils.floorInt(pixelForGrid.x), MathUtils.floorInt(pixelForGrid.y), 1, 1, floatPixel, ProgressMonitor.NULL);
                out.print("\t");
                out.print(floatPixel[0]);
                if (Float.isNaN(floatPixel[0])) {
                    System.out.println("NaN for " + grid.getName());
                }
            }
        }
        // end loop on Tie Point grids
        out.print("\n");
    }

    /*
     * Computes the total number of pixels within the specified Mask.
     *
     * @param raster   the raster data node
     * @param maskImage the rendered image masking out the Mask
     * @return the total number of pixels in the Mask
     */
    private static long getNumMaskPixels(final RenderedImage maskImage, int sceneRasterWidth, int sceneRasterHeight) {
        final int minTileX = maskImage.getMinTileX();
        final int minTileY = maskImage.getMinTileY();
        final int numXTiles = maskImage.getNumXTiles();
        final int numYTiles = maskImage.getNumYTiles();
        final Rectangle imageRect = new Rectangle(0, 0, sceneRasterWidth, sceneRasterHeight);
        long numMaskPixels = 0;
        for (int tileX = minTileX; tileX < minTileX + numXTiles; ++tileX) {
            for (int tileY = minTileY; tileY < minTileY + numYTiles; ++tileY) {
                final Rectangle tileRectangle = new Rectangle(maskImage.getTileGridXOffset() + tileX * maskImage.getTileWidth(), maskImage.getTileGridYOffset() + tileY * maskImage.getTileHeight(), maskImage.getTileWidth(), maskImage.getTileHeight());
                final Rectangle r = imageRect.intersection(tileRectangle);
                if (!r.isEmpty()) {
                    Raster maskTile = maskImage.getTile(tileX, tileY);

                }
            }
        }
        return numMaskPixels;
    }

    @Override
    public HelpCtx getHelpCtx() {
        return new HelpCtx(HELP_ID);
    }
}


try {
                fileWriter = new FileWriter(file);
            } catch (IOException e) {
                Dialogs.showError(Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), ERR_MSG_BASE + "Failed to create file '" + file + "':\n" + e.getMessage());
                // Error
                return;
            }

package org.esa.snap.rcp.actions.file.export;

import com.bc.ceres.core.ProgressMonitor;
import com.bc.ceres.swing.progress.ProgressMonitorSwingWorker;
import org.esa.snap.core.datamodel.*;
import org.esa.snap.core.util.SystemUtils;
import org.esa.snap.core.util.io.FileUtils;
import org.esa.snap.core.util.io.SnapFileFilter;
import org.esa.snap.core.util.math.MathUtils;
import org.esa.snap.rcp.SnapApp;
import org.esa.snap.rcp.util.Dialogs;
import org.esa.snap.rcp.util.MultiSizeIssue;
import org.esa.snap.ui.AbstractDialog;
import org.esa.snap.ui.ModalDialog;
import org.esa.snap.ui.SelectExportMethodDialog;
import org.esa.snap.ui.UIUtils;
import org.esa.snap.ui.product.ProductSceneView;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
import org.openide.awt.ActionRegistration;
import org.openide.util.ContextAwareAction;
import org.openide.util.HelpCtx;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.openide.util.WeakListeners;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BoxLayout;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.GregorianCalendar;

@ActionID(category = "File", id = "org.esa.snap.rcp.actions.file.export.ExportMaskPixelsAction")
@ActionRegistration(displayName = "#CTL_ExportMaskPixelsAction_MenuText", popupText = "#CTL_ExportMaskPixelsAction_PopupText", lazy = false)
@ActionReferences({ @ActionReference(path = "Menu/File/Export/Other", position = 50), @ActionReference(path = "Menu/Raster/Export", position = 100), @ActionReference(path = "Context/ProductSceneView", position = 50) })
@NbBundle.Messages({ "CTL_ExportMaskPixelsAction_MenuText=Mask Pixels", "CTL_ExportMaskPixelsAction_PopupText=Export Mask Pixels", "CTL_ExportMaskPixelsAction_DialogTitle=Export Mask Pixels", "CTL_ExportMaskPixelsAction_ShortDescription=Export Mask Pixels." })
public class ExportMaskPixelsAction extends AbstractAction implements ContextAwareAction, LookupListener, HelpCtx.Provider {

    private static final String HELP_ID = "exportMaskPixels";

    private static final String ERR_MSG_BASE = "Mask pixels cannot be exported:\n";

    private final Lookup.Result<ProductSceneView> result;

    public ExportMaskPixelsAction() {
        this(Utilities.actionsGlobalContext());
    }

    public ExportMaskPixelsAction(Lookup lkp) {
        super(Bundle.CTL_ExportMaskPixelsAction_MenuText());
        putValue("popupText", Bundle.CTL_ExportMaskPixelsAction_PopupText());
        result = lkp.lookupResult(ProductSceneView.class);
        result.addLookupListener(WeakListeners.create(LookupListener.class, this, result));
        setEnabled(false);
    }

    /**
     * Invoked when a command action is performed.
     *
     * @param event the command event
     */
    @Override
    public void actionPerformed(ActionEvent event) {
        ProductSceneView sceneView = SnapApp.getDefault().getSelectedProductSceneView();
        if (sceneView != null) {
            Product product = sceneView.getProduct();
            exportMaskPixels(product);
        }
    }

    @Override
    public Action createContextAwareInstance(Lookup lkp) {
        return new ExportMaskPixelsAction(lkp);
    }

    @Override
    public void resultChanged(LookupEvent le) {
        ProductSceneView sceneView = SnapApp.getDefault().getSelectedProductSceneView();
        boolean enabled = false;
        if (sceneView != null) {
            Product product = sceneView.getProduct();
            enabled = product.getMaskGroup().getNodeCount() > 0;
        }
        setEnabled(enabled);
    }

    // Private implementations for the "export Mask Pixels" command
    /**
     * Performs the actual "export Mask Pixels" command.
     * @param product
     */
    private void exportMaskPixels(Product product) {
        String[] maskNames = product.getMaskGroup().getNodeNames();
        final String maskName;
        if (maskNames.length == 1) {
            maskName = maskNames[0];
        } else {
            JPanel panel = new JPanel();
            BoxLayout boxLayout = new BoxLayout(panel, BoxLayout.X_AXIS);
            panel.setLayout(boxLayout);
            panel.add(new JLabel("Select Mask: "));
            JComboBox<String> maskCombo = new JComboBox<>(maskNames);
            panel.add(maskCombo);
            ModalDialog modalDialog = new ModalDialog(SnapApp.getDefault().getMainFrame(), Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), panel, ModalDialog.ID_OK_CANCEL | ModalDialog.ID_HELP, getHelpCtx().getHelpID());
            if (modalDialog.show() == AbstractDialog.ID_OK) {
                maskName = (String) maskCombo.getSelectedItem();
            } else {
                return;
            }
        }
        final RenderedImage maskImage = product.getMaskGroup().get(maskName).getSourceImage();
        if (maskImage == null) {
            Dialogs.showError(Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), ERR_MSG_BASE + "No Mask image available.");
            return;
        }
        // Compute total number of Mask pixels
        final long numMaskPixels = getNumMaskPixels(maskImage, product.getSceneRasterWidth(), product.getSceneRasterHeight());
        String numPixelsText;
        if (numMaskPixels == 1) {
            numPixelsText = "One Mask pixel will be exported.\n";
        } else {
            numPixelsText = numMaskPixels + " Mask pixels will be exported.\n";
        }
        // Get export method from user
        final String questionText = "How do you want to export the pixel values?\n";
        final JCheckBox createHeaderBox = new JCheckBox("Create header");
        final JCheckBox exportTiePointsBox = new JCheckBox("Export tie-points");
        final JCheckBox exportWavelengthsAndSFBox = new JCheckBox("Export wavelengths + solar fluxes");
        final int method = SelectExportMethodDialog.run(SnapApp.getDefault().getMainFrame(), getWindowTitle(), questionText + numPixelsText, new JCheckBox[] { createHeaderBox, exportTiePointsBox, exportWavelengthsAndSFBox }, getHelpCtx().getHelpID());
        final boolean mustCreateHeader = createHeaderBox.isSelected();
        final boolean mustExportTiePoints = exportTiePointsBox.isSelected();
        final boolean mustExportWavelengthsAndSF = exportWavelengthsAndSFBox.isSelected();
        final PrintWriter out;
        final StringBuffer clipboardText;
        final int initialBufferSize = 256000;
        if (method == SelectExportMethodDialog.EXPORT_TO_CLIPBOARD) {
            // Write into string buffer
            final StringWriter stringWriter = new StringWriter(initialBufferSize);
            out = new PrintWriter(stringWriter);
            clipboardText = stringWriter.getBuffer();
        } else if (method == SelectExportMethodDialog.EXPORT_TO_FILE) {
            // Write into file, get file from user
            final File file = promptForFile(createDefaultFileName(product, maskName));
            if (file == null) {
                // Cancel
                return;
            }
            final FileWriter fileWriter;

out = new PrintWriter(new BufferedWriter(fileWriter, initialBufferSize));
            clipboardText = null;
        } else {
            // Cancel
            return;
        }
        final ProgressMonitorSwingWorker<Exception, Object> swingWorker = new ProgressMonitorSwingWorker<Exception, Object>(SnapApp.getDefault().getMainFrame(), Bundle.CTL_ExportMaskPixelsAction_DialogTitle()) {

            @Override
            protected Exception doInBackground(ProgressMonitor pm) throws Exception {
                Exception returnValue = null;
                try {
                    boolean success = exportMaskPixels(out, product, maskName, mustCreateHeader, mustExportTiePoints, mustExportWavelengthsAndSF, pm);
                    if (success && clipboardText != null) {
                        SystemUtils.copyToClipboard(clipboardText.toString());
                        clipboardText.setLength(0);
                    }
                } catch (Exception e) {
                    returnValue = e;
                } finally {
                    out.close();
                }
                return returnValue;
            }

            @Override
            public void done() {
                // clear status bar
                SnapApp.getDefault().setStatusBarMessage("");
                UIUtils.setRootFrameDefaultCursor(SnapApp.getDefault().getMainFrame());
                // On error, show error message
                Exception exception;
                try {
                    exception = get();
                } catch (Exception e) {
                    exception = e;
                }
                if (exception != null) {
                    Dialogs.showError(Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), ERR_MSG_BASE + exception.getMessage());
                }
            }
        };
        // show wait-cursor
        UIUtils.setRootFrameWaitCursor(SnapApp.getDefault().getMainFrame());
        // show message in status bar
        SnapApp.getDefault().setStatusBarMessage("Exporting Mask pixels...");
        // Start separate worker thread.
        swingWorker.execute();
    }

    private static String createDefaultFileName(final Product raster, String maskName) {
        String productName = FileUtils.getFilenameWithoutExtension(raster.getProduct().getName());
        return productName + "_" + maskName + "_Mask.txt";
    }

    private static String getWindowTitle() {
        return SnapApp.getDefault().getInstanceName() + " - " + Bundle.CTL_ExportMaskPixelsAction_DialogTitle();
    }

    /*
     * Opens a modal file chooser dialog that prompts the user to select the output file name.
     *
     * @param visatApp the VISAT application
     * @return the selected file, null means "Cancel"
     */
    private static File promptForFile(String defaultFileName) {
        final SnapFileFilter fileFilter = new SnapFileFilter("TXT", "txt", "Text");
        return Dialogs.requestFileForSave(Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), false, fileFilter, ".txt", defaultFileName, null, "exportMaskPixels.lastDir");
    }

    /**
     * Writes all pixel values of the given product within the given Mask to the specified out.
     *
     * @param out      the data output writer
     * @param product  the product providing the pixel values
     * @param maskName the mask name
     * @param mustCreateHeader
     * @param mustExportTiePoints
     * @param mustExportWavelengthsAndSF
     * @param pm       progress monitor
     * @return true for success, false if export has been terminated (by user)
     */
    private static boolean exportMaskPixels(final PrintWriter out, final Product product, String maskName, boolean mustCreateHeader, boolean mustExportTiePoints, boolean mustExportWavelengthsAndSF, ProgressMonitor pm) throws IOException {
        final RenderedImage maskImage = product.getMaskGroup().get(maskName).getSourceImage();
        final int minTileX = maskImage.getMinTileX();
        final int minTileY = maskImage.getMinTileY();
        final int numXTiles = maskImage.getNumXTiles();
        final int numYTiles = maskImage.getNumYTiles();
        final int w = product.getSceneRasterWidth();
        final int h = product.getSceneRasterHeight();
        final Rectangle imageRect = new Rectangle(0, 0, w, h);
        pm.beginTask("Writing pixel data...", numXTiles * numYTiles + 2);
        try {
            if (mustCreateHeader) {
                createHeader(out, product, maskName, mustExportWavelengthsAndSF);
            }
            pm.worked(1);
            writeColumnNames(out, product, maskName, mustExportTiePoints);
            pm.worked(1);
            for (int tileX = minTileX; tileX < minTileX + numXTiles; ++tileX) {
                for (int tileY = minTileY; tileY < minTileY + numYTiles; ++tileY) {
                    if (pm.isCanceled()) {
                        return false;
                    }
                    final Rectangle tileRectangle = new Rectangle(maskImage.getTileGridXOffset() + tileX * maskImage.getTileWidth(), maskImage.getTileGridYOffset() + tileY * maskImage.getTileHeight(), maskImage.getTileWidth(), maskImage.getTileHeight());
                    final Rectangle r = imageRect.intersection(tileRectangle);
                    if (!r.isEmpty()) {
                        Raster maskTile = maskImage.getTile(tileX, tileY);
                        for (int y = r.y; y < r.y + r.height; y++) {
                            for (int x = r.x; x < r.x + r.width; x++) {
                                if (maskTile.getSample(x, y, 0) != 0) {
                                    writeDataLine(out, product, maskName, x, y, mustExportTiePoints);
                                }
                            }
                        }
                    }
                    pm.worked(1);
                }
            }
        } finally {
            pm.done();
        }
        return true;
    }

    private static void createHeader(PrintWriter out, Product product, String maskName, boolean mustExportWavelengthsAndSF) {
        out.write("# Exported mask '" + maskName + "' on " + new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss.mmmmmm").format(new GregorianCalendar().getTime()) + "\n");
        out.write("# Product name: " + product.getName() + "\n");
        if (product.getFileLocation() != null) {
            out.write("# Product file location: " + product.getFileLocation() + "\n");
        }
        out.write("\n");
        if (mustExportWavelengthsAndSF) {
            out.write("# Wavelength:");
            // account for pixel-x, pixel-y, lon, lat columns
            out.write("\t\t\t");
            for (final Band band : product.getBands()) {
                out.print("\t");
                out.print("" + band.getSpectralWavelength());
            }
            out.print("\n");
            out.write("# Solar flux:");
            // account for pixel-x, pixel-y, lon, lat columns
            out.write("\t\t\t");
            for (final Band band : product.getBands()) {
                out.print("\t");
                out.print("" + band.getSolarFlux());
            }
            out.print("\n");
        }
    }

    /*
     * Writes the header line of the dataset to be exported.
     *
     * @param out                     the data output writer
     * @param geoCoding               the product's geo-coding
     * @param bands                   the array of bands to be considered
     * @param mustExportTiePointGrids if tie-point grids shall be considered
     * @param tiePointGrids           the array of tie-point grids to be considered
     */
    private static void writeColumnNames(final PrintWriter out, final Product product, final String maskName, final boolean mustExportTiePointGrids) {
        if (!product.isMultiSize()) {
            out.print("Pixel-X");
            out.print("\t");
            out.print("Pixel-Y");
        } else {
            // Add the mask name to the identification of the pixel
            out.print("Pixel-X." + maskName);
            out.print("\t");
            out.print("Pixel-Y." + maskName);
        }
        if (product.getSceneGeoCoding() != null) {
            out.print("\t");
            out.print("Longitude");
            out.print("\t");
            out.print("Latitude");
        }
        for (final Band band : product.getBands()) {
            out.print("\t");
            out.print(band.getName());
        }
        if (mustExportTiePointGrids) {
            for (final TiePointGrid grid : product.getTiePointGrids()) {
                out.print("\t");
                out.print(grid.getName());
            }
        }
        out.print("\n");
    }

    /**
     * Writes a data line of the dataset to be exported for the given pixel position.
     *
     * @param out                 the data output writer
     * @param product             the product
     * @param maskName            the mask name (where the pixel X and Y where read)
     * @param x                   the current pixel's X coordinate
     * @param y                   the current pixel's Y coordinate
     * @param mustExportTiePoints if tie-point grids shall be exported
     * @throws IOException
     */
    private static void writeDataLine(final PrintWriter out, final Product product, final String maskName, final int x, final int y, final boolean mustExportTiePoints) throws IOException {
        // All the positions computations will be done at the centre of pixel cell
        final PixelPos pixelPosRef = new PixelPos(x + 0.5f, y + 0.5f);
        out.print(String.valueOf(pixelPosRef.x));
        out.print("\t");
        out.print(String.valueOf(pixelPosRef.y));
        final Mask mask = product.getMaskGroup().get(maskName);
        final GeoCoding maskGeocoding = mask.getGeoCoding();
        if (maskGeocoding != null) {
            final GeoPos geoPos = maskGeocoding.getGeoPos(pixelPosRef, null);
            out.print("\t");
            out.print(String.valueOf(geoPos.lon));
            out.print("\t");
            out.print(String.valueOf(geoPos.lat));
        }
        final int[] intPixel = new int[1];
        final float[] floatPixel = new float[1];
        for (final Band band : product.getBands()) {
            out.print("\t");
            PixelPos pixelForBand = product.getPixelForBand(pixelPosRef, mask, band);
            int xForBand = MathUtils.floorInt(pixelForBand.x);
            int yForBand = MathUtils.floorInt(pixelForBand.y);
            if (band.isPixelValid(xForBand, yForBand)) {
                if (band.isFloatingPointType()) {
                    band.readPixels(xForBand, yForBand, 1, 1, floatPixel, ProgressMonitor.NULL);
                    out.print(floatPixel[0]);
                } else {
                    band.readPixels(xForBand, yForBand, 1, 1, intPixel, ProgressMonitor.NULL);
                    out.print(intPixel[0]);
                }
            } else {
                out.print(RasterDataNode.NO_DATA_TEXT);
            }
        }
        // end loop on bands
        if (mustExportTiePoints) {
            for (final TiePointGrid grid : product.getTiePointGrids()) {
                PixelPos pixelForGrid = product.getPixelForBand(pixelPosRef, mask, grid);
                grid.readPixels(MathUtils.floorInt(pixelForGrid.x), MathUtils.floorInt(pixelForGrid.y), 1, 1, floatPixel, ProgressMonitor.NULL);
                out.print("\t");
                out.print(floatPixel[0]);
                if (Float.isNaN(floatPixel[0])) {
                    System.out.println("NaN for " + grid.getName());
                }
            }
        }
        // end loop on Tie Point grids
        out.print("\n");
    }

    /*
     * Computes the total number of pixels within the specified Mask.
     *
     * @param raster   the raster data node
     * @param maskImage the rendered image masking out the Mask
     * @return the total number of pixels in the Mask
     */
    private static long getNumMaskPixels(final RenderedImage maskImage, int sceneRasterWidth, int sceneRasterHeight) {
        final int minTileX = maskImage.getMinTileX();
        final int minTileY = maskImage.getMinTileY();
        final int numXTiles = maskImage.getNumXTiles();
        final int numYTiles = maskImage.getNumYTiles();
        final Rectangle imageRect = new Rectangle(0, 0, sceneRasterWidth, sceneRasterHeight);
        long numMaskPixels = 0;
        for (int tileX = minTileX; tileX < minTileX + numXTiles; ++tileX) {
            for (int tileY = minTileY; tileY < minTileY + numYTiles; ++tileY) {
                final Rectangle tileRectangle = new Rectangle(maskImage.getTileGridXOffset() + tileX * maskImage.getTileWidth(), maskImage.getTileGridYOffset() + tileY * maskImage.getTileHeight(), maskImage.getTileWidth(), maskImage.getTileHeight());
                final Rectangle r = imageRect.intersection(tileRectangle);
                if (!r.isEmpty()) {
                    Raster maskTile = maskImage.getTile(tileX, tileY);
                    for (int y = r.y; y < r.y + r.height; y++) {
                        for (int x = r.x; x < r.x + r.width; x++) {
                            if (maskTile.getSample(x, y, 0) != 0) {
                                numMaskPixels++;
                            }
                        }
                    }
                }
            }
        }
        return numMaskPixels;
    }

    @Override
    public HelpCtx getHelpCtx() {
        return new HelpCtx(HELP_ID);
    }
}


{
        for (Model model : Model.values()) {
            if (model.id == id)
                return model;
        }
        return null;
    }

package tsuteto.tofu.tileentity;

import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.world.World;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.fluids.*;
import tsuteto.tofu.api.TfMaterialRegistry;
import tsuteto.tofu.api.recipe.TfReformerRecipe;
import tsuteto.tofu.api.recipe.TfReformerRecipeRegistry;
import tsuteto.tofu.api.tileentity.ITfConsumer;
import tsuteto.tofu.api.tileentity.TileEntityTfMachineSidedInventoryBase;
import tsuteto.tofu.block.BlockTfReformer;
import tsuteto.tofu.init.TcFluids;
import tsuteto.tofu.recipe.Ingredient;

public class TileEntityTfReformer extends TileEntityTfMachineSidedInventoryBase implements IFluidHandler, ITfConsumer {

    public enum Model {

        simple(0, 2, new int[0]), mix(1, 5, new int[] { SLOT_INGREDIENT_ITEM1, SLOT_INGREDIENT_ITEM2, SLOT_INGREDIENT_ITEM3 });

        public final int id;

        public final int stackSize;

        public final int[] ingredientSlots;

        Model(int id, int stackSize, int[] ingredientSlots) {
            this.id = id;
            this.stackSize = stackSize;
            this.ingredientSlots = ingredientSlots;
        }
    }

    public static final int SLOT_INPUT_ITEM = 0;

    public static final int SLOT_OUTPUT_ITEM = 1;

    public static final int SLOT_INGREDIENT_ITEM1 = 2;

    public static final int SLOT_INGREDIENT_ITEM2 = 3;

    public static final int SLOT_INGREDIENT_ITEM3 = 4;

    public static final double TF_CAPACITY = 20;

    public static final double COST_TF_PER_TICK = 1;

    private static final int[] slotsTop = new int[] { 0 };

    private static final int[] slotsSides1 = new int[] { 0, 1 };

    private static final int[] slotsSides2 = new int[] { 1, 2, 3, 4 };

    private static final int[] slotsBottom = new int[] { 1 };

    public Model model;

    public double tfCapacity = TF_CAPACITY;

    public double tfAmount = 0;

    public TfReformerRecipe currentRecipe;

    public double tfOutput = 0;

    public double wholeTfOutput = 0;

    public int externalProcessTime = 0;

    public boolean isExternalProcessed = false;

    public FluidTank fluidTank = new FluidTank(0);

    private double tfConsumed = 0.0D;

    private TfReformerRecipe lastRecipe = null;

    private boolean shouldUpdateRecipe = false;

    public boolean isWorking;

    private boolean prevWorking;

    public TileEntityTfReformer() {
        this.model = Model.simple;
        this.fluidTank.setFluid(new FluidStack(TcFluids.SOYMILK, 0));
        this.fluidTank.setCapacity(TfMaterialRegistry.calcFluidAmountFrom(this.tfCapacity, TcFluids.SOYMILK));
    }

    @Override
    protected String getInventoryNameTranslate() {
        return "container.tofucraft.TfReformer." + model.name();
    }

    public static Model getModelById(int id)

    @Override
    public void onTileEntityCreated(World world, int x, int y, int z, EntityLivingBase entityCreatedBy, ItemStack itemstack) {
        this.model = TileEntityTfReformer.getModelById(itemstack.getItemDamage());
        this.itemStacks = new ItemStack[model.stackSize];
    }

    /**
     * Reads a tile entity from NBT.
     */
    @Override
    public void readFromNBT(NBTTagCompound nbtTagCompound) {
        int rev = nbtTagCompound.getByte("Rev");
        if (rev == 1) {
            this.model = Model.simple;
            this.itemStacks = new ItemStack[model.stackSize];
            this.tfOutput = nbtTagCompound.getShort("ProcO");
            this.tfAmount = nbtTagCompound.getFloat("TfAmount");
        } else {
            this.model = getModelById(nbtTagCompound.getByte("Model"));
            if (this.model == null)
                this.invalidate();
            this.itemStacks = new ItemStack[model.stackSize];
            this.tfOutput = nbtTagCompound.getDouble("ProcO");
            this.tfAmount = nbtTagCompound.getDouble("TfAmount");
        }
        super.readFromNBT(nbtTagCompound);
        this.fluidTank.setCapacity(TfMaterialRegistry.calcFluidAmountFrom(this.tfCapacity, TcFluids.SOYMILK));
        this.updateFluidTank();
        this.updateCurrentRecipe();
    }

    /**
     * Writes a tile entity to NBT.
     */
    @Override
    public void writeToNBT(NBTTagCompound nbtTagCompound) {
        super.writeToNBT(nbtTagCompound);
        nbtTagCompound.setByte("Model", (byte) this.model.id);
        nbtTagCompound.setDouble("ProcO", this.tfOutput);
        nbtTagCompound.setDouble("TfAmount", this.tfAmount);
    }

    @Override
    protected int getNBTRevision() {
        return 2;
    }

    @SideOnly(Side.CLIENT)
    public double getProgressScaledOutput() {
        if (wholeTfOutput > 0) {
            return this.tfOutput / this.wholeTfOutput;
        } else {
            return 0;
        }
    }

    /**
     * Returns true if the machine is currently processing
     */
    public boolean isProcessing() {
        return this.isWorking;
    }

    @Override
    public void updateEntity() {
        boolean isInventoryChanged = false;
        this.isWorking = false;
        if (!this.worldObj.isRemote) {
            tfConsumed = 0;
            if (shouldUpdateRecipe) {
                this.updateCurrentRecipe();
                shouldUpdateRecipe = false;
            }
            if (this.tfOutput == 0 && this.canProcessOutput()) {
                this.updateFluidTank();
            }
            if (this.wholeTfOutput > 0 && this.canProcessOutput()) {
                // Processing
                this.tfConsumed = Math.min(this.tfAmount, COST_TF_PER_TICK);
                this.tfOutput += tfConsumed;
                this.tfAmount -= tfConsumed;
                this.isWorking = true;
                if (this.tfOutput >= wholeTfOutput) {
                    // Finish
                    this.tfOutput = 0;
                    this.onOutputCompleted();
                    isInventoryChanged = true;
                }
            } else if (this.lastRecipe != this.currentRecipe) {
                // Stop processing
                this.tfOutput = 0;
                this.wholeTfOutput = 0;
            }
            if (this.isExternalProcessed) {
                this.externalProcessTime = 20;
                this.isExternalProcessed = false;
            } else if (this.externalProcessTime > 0) {
                --this.externalProcessTime;
            }
            if (this.isWorking != this.prevWorking) {
                isInventoryChanged = true;
                BlockTfReformer.updateMachineState(this.isProcessing(), this.worldObj, this.xCoord, this.yCoord, this.zCoord);
            }
        }
        if (isInventoryChanged) {
            this.markDirty();
        }
        this.lastRecipe = this.currentRecipe;
        this.prevWorking = this.isWorking;
    }

    /**
     * Checks if the machine accepts the item to output soymilk
     */
    private boolean canProcessOutput() {
        if (this.itemStacks[SLOT_INPUT_ITEM] == null)
            return false;
        if (this.tfAmount <= 0)
            return false;
        if (this.currentRecipe == null)
            return false;
        Ingredient containerItem = this.currentRecipe.containerItem;
        if (!containerItem.matches(this.itemStacks[SLOT_INPUT_ITEM]))
            return false;
        ItemStack var1 = this.currentRecipe.result;
        // Check if the filled item can be stacked to the output slot
        if (this.itemStacks[SLOT_OUTPUT_ITEM] == null)
            return true;
        if (!this.itemStacks[SLOT_OUTPUT_ITEM].isItemEqual(var1))
            return false;
        // Check if the stack will overflow
        int result = itemStacks[SLOT_OUTPUT_ITEM].stackSize + var1.stackSize;
        return result <= getInventoryStackLimit() && result <= var1.getMaxStackSize();
    }

    /**
     * When the output process completed. Take one from the output container stack and fill soymilk converted from TF
     */
    public void onOutputCompleted() {
        ItemStack var1 = this.currentRecipe.result;
        if (this.itemStacks[SLOT_OUTPUT_ITEM] == null) {
            this.itemStacks[SLOT_OUTPUT_ITEM] = var1.copy();
        } else if (this.itemStacks[SLOT_OUTPUT_ITEM].isItemEqual(var1)) {
            itemStacks[SLOT_OUTPUT_ITEM].stackSize += var1.stackSize;
        }
        if (--this.itemStacks[SLOT_INPUT_ITEM].stackSize == 0) {
            this.itemStacks[SLOT_INPUT_ITEM] = null;
        }
        // Ingredient slots
        for (int id : model.ingredientSlots) {
            ItemStack input = this.itemStacks[id];
            if (input != null) {
                if (!this.currentRecipe.isCatalystItem(input)) {
                    ItemStack container = input.getItem().getContainerItem(input);
                    if (container != null) {
                        this.itemStacks[id] = container;
                    } else {
                        if (--this.itemStacks[id].stackSize == 0) {
                            this.itemStacks[id] = null;
                        }
                    }
                }
            }
        }
    }

    /**
     * Updates the fluid tank to synchronize with the tf amount of the machine
     */
    private void updateFluidTank() {
        if (fluidTank.getFluid() == null) {
            this.fluidTank.setFluid(new FluidStack(TcFluids.SOYMILK, 0));
        }
        this.fluidTank.getFluid().amount = TfMaterialRegistry.calcFluidAmountFrom(this.tfAmount, TcFluids.SOYMILK);
    }

    public void updateCurrentRecipe() {
        TfReformerRecipe recipe = null;
        if (model == Model.mix) {
            recipe = TfReformerRecipeRegistry.getRecipe(this.itemStacks[SLOT_INPUT_ITEM], new ItemStack[] { this.itemStacks[SLOT_INGREDIENT_ITEM1], this.itemStacks[SLOT_INGREDIENT_ITEM2], this.itemStacks[SLOT_INGREDIENT_ITEM3] });
        } else if (model == Model.simple) {
            recipe = TfReformerRecipeRegistry.getRecipe(this.itemStacks[SLOT_INPUT_ITEM], new ItemStack[0]);
        }
        if (recipe != null) {
            this.wholeTfOutput = recipe.tfAmountNeeded;
        }
        this.currentRecipe = recipe;
    }

    @Override
    public void markDirty() {
        super.markDirty();
        this.shouldUpdateRecipe = true;
    }

    /**
     * Fill a container item by Forge fluid system (IFluidHandler)
     * Applicable to some industrial mods
     */
    @Override
    public int fill(ForgeDirection from, FluidStack resource, boolean doFill) {
        return 0;
    }

    /**
     * Drain soymilk from the machine by Forge fluid system (IFluidHandler)
     * Applicable to industrial mods or some
     */
    @Override
    public FluidStack drain(ForgeDirection from, FluidStack resource, boolean doDrain) {
        this.updateFluidTank();
        if ((resource == null) || (!resource.isFluidEqual(fluidTank.getFluid()))) {
            return null;
        }
        if (!canDrain(from, resource.getFluid()))
            return null;
        FluidStack fluid = fluidTank.drain(resource.amount, doDrain);
        if (fluid != null && doDrain && fluid.amount > 0) {
            this.tfAmount -= TfMaterialRegistry.calcTfAmountFrom(fluid);
            this.updateFluidTank();
            this.isExternalProcessed = true;
        }
        return fluid;
    }

    /**
     * Drain soymilk from the machine by Forge fluid system (IFluidHandler) for pipes
     * Applicable to industrial mods or some
     */
    @Override
    public FluidStack drain(ForgeDirection from, int maxDrain, boolean doDrain) {
        this.updateFluidTank();
        FluidStack fluid = fluidTank.drain(maxDrain, doDrain);
        if (fluid != null && doDrain && fluid.amount > 0) {
            this.tfAmount -= TfMaterialRegistry.calcTfAmountFrom(fluid);
            this.updateFluidTank();
            this.isExternalProcessed = true;
        }
        return fluid;
    }

    @Override
    public boolean canFill(ForgeDirection from, Fluid fluid) {
        // This machine cannot be filled
        return false;
    }

    @Override
    public boolean canDrain(ForgeDirection from, Fluid fluid) {
        if (this.tfAmount <= 0)
            return false;
        return fluidTank.getFluid().getFluid().getID() == fluid.getID();
    }

    @Override
    public FluidTankInfo[] getTankInfo(ForgeDirection from) {
        this.updateFluidTank();
        return new FluidTankInfo[] { this.fluidTank.getInfo() };
    }

    @Override
    public double getCurrentTfConsumed() {
        return tfConsumed;
    }

    @Override
    public void chargeTf(double amount) {
        this.tfAmount += amount;
    }

    @Override
    public double getMaxTfCapacity() {
        return Math.min(tfCapacity - this.tfAmount, 5);
    }

    @Override
    public int[] getAccessibleSlotsFromSide(int var1) {
        return var1 == 0 ? slotsBottom : (var1 == 1 ? slotsTop : model == Model.simple ? slotsSides1 : slotsSides2);
    }

    @Override
    public boolean isItemValidForSlot(int par1, ItemStack par2ItemStack) {
        return par1 != 1;
    }

    public boolean canExtractItem(int slot, ItemStack item, int side) {
        return slot == 1;
    }
}


package tsuteto.tofu.tileentity;

import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.world.World;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.fluids.*;
import tsuteto.tofu.api.TfMaterialRegistry;
import tsuteto.tofu.api.recipe.TfReformerRecipe;
import tsuteto.tofu.api.recipe.TfReformerRecipeRegistry;
import tsuteto.tofu.api.tileentity.ITfConsumer;
import tsuteto.tofu.api.tileentity.TileEntityTfMachineSidedInventoryBase;
import tsuteto.tofu.block.BlockTfReformer;
import tsuteto.tofu.init.TcFluids;
import tsuteto.tofu.recipe.Ingredient;

public class TileEntityTfReformer extends TileEntityTfMachineSidedInventoryBase implements IFluidHandler, ITfConsumer {

    public enum Model {

        simple(0, 2, new int[0]), mix(1, 5, new int[] { SLOT_INGREDIENT_ITEM1, SLOT_INGREDIENT_ITEM2, SLOT_INGREDIENT_ITEM3 });

        public final int id;

        public final int stackSize;

        public final int[] ingredientSlots;

        Model(int id, int stackSize, int[] ingredientSlots) {
            this.id = id;
            this.stackSize = stackSize;
            this.ingredientSlots = ingredientSlots;
        }
    }

    public static final int SLOT_INPUT_ITEM = 0;

    public static final int SLOT_OUTPUT_ITEM = 1;

    public static final int SLOT_INGREDIENT_ITEM1 = 2;

    public static final int SLOT_INGREDIENT_ITEM2 = 3;

    public static final int SLOT_INGREDIENT_ITEM3 = 4;

    public static final double TF_CAPACITY = 20;

    public static final double COST_TF_PER_TICK = 1;

    private static final int[] slotsTop = new int[] { 0 };

    private static final int[] slotsSides1 = new int[] { 0, 1 };

    private static final int[] slotsSides2 = new int[] { 1, 2, 3, 4 };

    private static final int[] slotsBottom = new int[] { 1 };

    public Model model;

    public double tfCapacity = TF_CAPACITY;

    public double tfAmount = 0;

    public TfReformerRecipe currentRecipe;

    public double tfOutput = 0;

    public double wholeTfOutput = 0;

    public int externalProcessTime = 0;

    public boolean isExternalProcessed = false;

    public FluidTank fluidTank = new FluidTank(0);

    private double tfConsumed = 0.0D;

    private TfReformerRecipe lastRecipe = null;

    private boolean shouldUpdateRecipe = false;

    public boolean isWorking;

    private boolean prevWorking;

    public TileEntityTfReformer() {
        this.model = Model.simple;
        this.fluidTank.setFluid(new FluidStack(TcFluids.SOYMILK, 0));
        this.fluidTank.setCapacity(TfMaterialRegistry.calcFluidAmountFrom(this.tfCapacity, TcFluids.SOYMILK));
    }

    @Override
    protected String getInventoryNameTranslate() {
        return "container.tofucraft.TfReformer." + model.name();
    }

    public static Model getModelById(int id) {
        for (Model model : Model.values()) {
            if (model.id == id)
                return model;
        }
        return null;
    }

    @Override
    public void onTileEntityCreated(World world, int x, int y, int z, EntityLivingBase entityCreatedBy, ItemStack itemstack) {
        this.model = TileEntityTfReformer.getModelById(itemstack.getItemDamage());
        this.itemStacks = new ItemStack[model.stackSize];
    }

    /**
     * Reads a tile entity from NBT.
     */
    @Override
    public void readFromNBT(NBTTagCompound nbtTagCompound) {
        int rev = nbtTagCompound.getByte("Rev");
        if (rev == 1) {
            this.model = Model.simple;
            this.itemStacks = new ItemStack[model.stackSize];
            this.tfOutput = nbtTagCompound.getShort("ProcO");
            this.tfAmount = nbtTagCompound.getFloat("TfAmount");
        } else {
            this.model = getModelById(nbtTagCompound.getByte("Model"));
            if (this.model == null)
                this.invalidate();
            this.itemStacks = new ItemStack[model.stackSize];
            this.tfOutput = nbtTagCompound.getDouble("ProcO");
            this.tfAmount = nbtTagCompound.getDouble("TfAmount");
        }
        super.readFromNBT(nbtTagCompound);
        this.fluidTank.setCapacity(TfMaterialRegistry.calcFluidAmountFrom(this.tfCapacity, TcFluids.SOYMILK));
        this.updateFluidTank();
        this.updateCurrentRecipe();
    }

    /**
     * Writes a tile entity to NBT.
     */
    @Override
    public void writeToNBT(NBTTagCompound nbtTagCompound) {
        super.writeToNBT(nbtTagCompound);
        nbtTagCompound.setByte("Model", (byte) this.model.id);
        nbtTagCompound.setDouble("ProcO", this.tfOutput);
        nbtTagCompound.setDouble("TfAmount", this.tfAmount);
    }

    @Override
    protected int getNBTRevision() {
        return 2;
    }

    @SideOnly(Side.CLIENT)
    public double getProgressScaledOutput() {
        if (wholeTfOutput > 0) {
            return this.tfOutput / this.wholeTfOutput;
        } else {
            return 0;
        }
    }

    /**
     * Returns true if the machine is currently processing
     */
    public boolean isProcessing() {
        return this.isWorking;
    }

    @Override
    public void updateEntity() {
        boolean isInventoryChanged = false;
        this.isWorking = false;
        if (!this.worldObj.isRemote) {
            tfConsumed = 0;
            if (shouldUpdateRecipe) {
                this.updateCurrentRecipe();
                shouldUpdateRecipe = false;
            }
            if (this.tfOutput == 0 && this.canProcessOutput()) {
                this.updateFluidTank();
            }
            if (this.wholeTfOutput > 0 && this.canProcessOutput()) {
                // Processing
                this.tfConsumed = Math.min(this.tfAmount, COST_TF_PER_TICK);
                this.tfOutput += tfConsumed;
                this.tfAmount -= tfConsumed;
                this.isWorking = true;
                if (this.tfOutput >= wholeTfOutput) {
                    // Finish
                    this.tfOutput = 0;
                    this.onOutputCompleted();
                    isInventoryChanged = true;
                }
            } else if (this.lastRecipe != this.currentRecipe) {
                // Stop processing
                this.tfOutput = 0;
                this.wholeTfOutput = 0;
            }
            if (this.isExternalProcessed) {
                this.externalProcessTime = 20;
                this.isExternalProcessed = false;
            } else if (this.externalProcessTime > 0) {
                --this.externalProcessTime;
            }
            if (this.isWorking != this.prevWorking) {
                isInventoryChanged = true;
                BlockTfReformer.updateMachineState(this.isProcessing(), this.worldObj, this.xCoord, this.yCoord, this.zCoord);
            }
        }
        if (isInventoryChanged) {
            this.markDirty();
        }
        this.lastRecipe = this.currentRecipe;
        this.prevWorking = this.isWorking;
    }

    /**
     * Checks if the machine accepts the item to output soymilk
     */
    private boolean canProcessOutput() {
        if (this.itemStacks[SLOT_INPUT_ITEM] == null)
            return false;
        if (this.tfAmount <= 0)
            return false;
        if (this.currentRecipe == null)
            return false;
        Ingredient containerItem = this.currentRecipe.containerItem;
        if (!containerItem.matches(this.itemStacks[SLOT_INPUT_ITEM]))
            return false;
        ItemStack var1 = this.currentRecipe.result;
        // Check if the filled item can be stacked to the output slot
        if (this.itemStacks[SLOT_OUTPUT_ITEM] == null)
            return true;
        if (!this.itemStacks[SLOT_OUTPUT_ITEM].isItemEqual(var1))
            return false;
        // Check if the stack will overflow
        int result = itemStacks[SLOT_OUTPUT_ITEM].stackSize + var1.stackSize;
        return result <= getInventoryStackLimit() && result <= var1.getMaxStackSize();
    }

    /**
     * When the output process completed. Take one from the output container stack and fill soymilk converted from TF
     */
    public void onOutputCompleted() {
        ItemStack var1 = this.currentRecipe.result;
        if (this.itemStacks[SLOT_OUTPUT_ITEM] == null) {
            this.itemStacks[SLOT_OUTPUT_ITEM] = var1.copy();
        } else if (this.itemStacks[SLOT_OUTPUT_ITEM].isItemEqual(var1)) {
            itemStacks[SLOT_OUTPUT_ITEM].stackSize += var1.stackSize;
        }
        if (--this.itemStacks[SLOT_INPUT_ITEM].stackSize == 0) {
            this.itemStacks[SLOT_INPUT_ITEM] = null;
        }
        // Ingredient slots
        for (int id : model.ingredientSlots) {
            ItemStack input = this.itemStacks[id];
            if (input != null) {
                if (!this.currentRecipe.isCatalystItem(input)) {
                    ItemStack container = input.getItem().getContainerItem(input);
                    if (container != null) {
                        this.itemStacks[id] = container;
                    } else {
                        if (--this.itemStacks[id].stackSize == 0) {
                            this.itemStacks[id] = null;
                        }
                    }
                }
            }
        }
    }

    /**
     * Updates the fluid tank to synchronize with the tf amount of the machine
     */
    private void updateFluidTank() {
        if (fluidTank.getFluid() == null) {
            this.fluidTank.setFluid(new FluidStack(TcFluids.SOYMILK, 0));
        }
        this.fluidTank.getFluid().amount = TfMaterialRegistry.calcFluidAmountFrom(this.tfAmount, TcFluids.SOYMILK);
    }

    public void updateCurrentRecipe() {
        TfReformerRecipe recipe = null;
        if (model == Model.mix) {
            recipe = TfReformerRecipeRegistry.getRecipe(this.itemStacks[SLOT_INPUT_ITEM], new ItemStack[] { this.itemStacks[SLOT_INGREDIENT_ITEM1], this.itemStacks[SLOT_INGREDIENT_ITEM2], this.itemStacks[SLOT_INGREDIENT_ITEM3] });
        } else if (model == Model.simple) {
            recipe = TfReformerRecipeRegistry.getRecipe(this.itemStacks[SLOT_INPUT_ITEM], new ItemStack[0]);
        }
        if (recipe != null) {
            this.wholeTfOutput = recipe.tfAmountNeeded;
        }
        this.currentRecipe = recipe;
    }

    @Override
    public void markDirty() {
        super.markDirty();
        this.shouldUpdateRecipe = true;
    }

    /**
     * Fill a container item by Forge fluid system (IFluidHandler)
     * Applicable to some industrial mods
     */
    @Override
    public int fill(ForgeDirection from, FluidStack resource, boolean doFill) {
        return 0;
    }

    /**
     * Drain soymilk from the machine by Forge fluid system (IFluidHandler)
     * Applicable to industrial mods or some
     */
    @Override
    public FluidStack drain(ForgeDirection from, FluidStack resource, boolean doDrain) {
        this.updateFluidTank();
        if ((resource == null) || (!resource.isFluidEqual(fluidTank.getFluid()))) {
            return null;
        }
        if (!canDrain(from, resource.getFluid()))
            return null;
        FluidStack fluid = fluidTank.drain(resource.amount, doDrain);
        if (fluid != null && doDrain && fluid.amount > 0) {
            this.tfAmount -= TfMaterialRegistry.calcTfAmountFrom(fluid);
            this.updateFluidTank();
            this.isExternalProcessed = true;
        }
        return fluid;
    }

    /**
     * Drain soymilk from the machine by Forge fluid system (IFluidHandler) for pipes
     * Applicable to industrial mods or some
     */
    @Override
    public FluidStack drain(ForgeDirection from, int maxDrain, boolean doDrain) {
        this.updateFluidTank();
        FluidStack fluid = fluidTank.drain(maxDrain, doDrain);
        if (fluid != null && doDrain && fluid.amount > 0) {
            this.tfAmount -= TfMaterialRegistry.calcTfAmountFrom(fluid);
            this.updateFluidTank();
            this.isExternalProcessed = true;
        }
        return fluid;
    }

    @Override
    public boolean canFill(ForgeDirection from, Fluid fluid) {
        // This machine cannot be filled
        return false;
    }

    @Override
    public boolean canDrain(ForgeDirection from, Fluid fluid) {
        if (this.tfAmount <= 0)
            return false;
        return fluidTank.getFluid().getFluid().getID() == fluid.getID();
    }

    @Override
    public FluidTankInfo[] getTankInfo(ForgeDirection from) {
        this.updateFluidTank();
        return new FluidTankInfo[] { this.fluidTank.getInfo() };
    }

    @Override
    public double getCurrentTfConsumed() {
        return tfConsumed;
    }

    @Override
    public void chargeTf(double amount) {
        this.tfAmount += amount;
    }

    @Override
    public double getMaxTfCapacity() {
        return Math.min(tfCapacity - this.tfAmount, 5);
    }

    @Override
    public int[] getAccessibleSlotsFromSide(int var1) {
        return var1 == 0 ? slotsBottom : (var1 == 1 ? slotsTop : model == Model.simple ? slotsSides1 : slotsSides2);
    }

    @Override
    public boolean isItemValidForSlot(int par1, ItemStack par2ItemStack) {
        r

    }

    public boolean canExtractItem(int slot, ItemStack item, int side) {
        return slot == 1;
    }
}