/*
 * Copyright (c) 2009-2010 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package jme3tools.nvtex;

import com.jme3.asset.AssetManager;
import com.jme3.asset.plugins.FileLocator;
import com.jme3.export.binary.BinaryExporter;
import com.jme3.system.JmeSystem;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.plugins.HDRLoader;
import jme3tools.converters.ImageToAwt;
import jme3tools.converters.MipMapGenerator;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import javax.imageio.ImageIO;
import javax.swing.DefaultListModel;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.UIManager;

public class NVCompress extends javax.swing.JFrame {
    
    private static Preferences pref = Preferences.userNodeForPackage(NVCompress.class);
    private static File texToolsPath;
    private static final String appName = "NVCompress GUI 1.00";
    private Thread workThread = null;
    private AssetManager manager;

    public NVCompress() {
        initComponents();
        barProgress.setVisible(false);
        // puts the form in the center
        setLocationRelativeTo(null);
        System.out.println(appName);
        setTitle(appName);
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        pnlMapType = new javax.swing.JPanel();
        cmbMapType = new javax.swing.JComboBox();
        chkMips = new javax.swing.JCheckBox();
        lblMapType = new javax.swing.JLabel();
        chkRepeat = new javax.swing.JCheckBox();
        pnlCompressOpt = new javax.swing.JPanel();
        cmbCompressType = new javax.swing.JComboBox();
        chkLowQuality = new javax.swing.JCheckBox();
        lblCompressType = new javax.swing.JLabel();
        chkCuda = new javax.swing.JCheckBox();
        sclFileList = new javax.swing.JScrollPane();
        lstFileList = new javax.swing.JList();
        btnAddFiles = new javax.swing.JButton();
        btnRemoveFiles = new javax.swing.JButton();
        pnlExportOpt = new javax.swing.JPanel();
        lblTargetDir = new javax.swing.JLabel();
        txtTargetDir = new javax.swing.JTextField();
        btnBrowse = new javax.swing.JButton();
        chkAsSource = new javax.swing.JCheckBox();
        btnCompress = new javax.swing.JButton();
        btnDecompress = new javax.swing.JButton();
        barProgress = new javax.swing.JProgressBar();
        jMenuBar1 = new javax.swing.JMenuBar();
        menuFile = new javax.swing.JMenu();
        itemExit = new javax.swing.JMenuItem();
        menuHelp = new javax.swing.JMenu();
        menuAbout = new javax.swing.JMenuItem();

        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
        setTitle("NVCompress GUI");
        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                formWindowClosing(evt);
            }
        });

        pnlMapType.setBorder(javax.swing.BorderFactory.createTitledBorder("Input Options"));

        cmbMapType.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Color Map", "Normal Map", "Height/Convert to Normal Map" }));

        chkMips.setSelected(true);
        chkMips.setText("Generate Mipmaps");

        lblMapType.setText("Map Type: ");

        chkRepeat.setText("Repeating");

        javax.swing.GroupLayout pnlMapTypeLayout = new javax.swing.GroupLayout(pnlMapType);
        pnlMapType.setLayout(pnlMapTypeLayout);
        pnlMapTypeLayout.setHorizontalGroup(
            pnlMapTypeLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(pnlMapTypeLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(pnlMapTypeLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(chkRepeat)
                    .addComponent(chkMips)
                    .addGroup(pnlMapTypeLayout.createSequentialGroup()
                        .addComponent(lblMapType)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(cmbMapType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
                .addContainerGap(118, Short.MAX_VALUE))
        );
        pnlMapTypeLayout.setVerticalGroup(
            pnlMapTypeLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(pnlMapTypeLayout.createSequentialGroup()
                .addGroup(pnlMapTypeLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(lblMapType)
                    .addComponent(cmbMapType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(chkMips)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(chkRepeat)
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        pnlCompressOpt.setBorder(javax.swing.BorderFactory.createTitledBorder("Compression Options"));

        cmbCompressType.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "RGBA", "DXT1", "DXT1nm", "DXT1a", "DXT3", "DXT5", "DXT5nm", "ATI1", "ATI2/3Dc", "P4RGB565", "P8RGB565", "AWT", "PNG-RGBE" }));
        cmbCompressType.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cmbCompressTypeActionPerformed(evt);
            }
        });

        chkLowQuality.setText("Low Quality");

        lblCompressType.setText("Compression Type: ");

        chkCuda.setSelected(true);
        chkCuda.setText("Use GPU Compression (faster)");

        javax.swing.GroupLayout pnlCompressOptLayout = new javax.swing.GroupLayout(pnlCompressOpt);
        pnlCompressOpt.setLayout(pnlCompressOptLayout);
        pnlCompressOptLayout.setHorizontalGroup(
            pnlCompressOptLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(pnlCompressOptLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(pnlCompressOptLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(pnlCompressOptLayout.createSequentialGroup()
                        .addComponent(chkLowQuality)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(chkCuda))
                    .addGroup(pnlCompressOptLayout.createSequentialGroup()
                        .addComponent(lblCompressType)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(cmbCompressType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
                .addContainerGap(117, Short.MAX_VALUE))
        );
        pnlCompressOptLayout.setVerticalGroup(
            pnlCompressOptLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(pnlCompressOptLayout.createSequentialGroup()
                .addGroup(pnlCompressOptLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(chkLowQuality)
                    .addComponent(chkCuda))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addGroup(pnlCompressOptLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(lblCompressType)
                    .addComponent(cmbCompressType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        lstFileList.setModel(new DefaultListModel());
        lstFileList.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyTyped(java.awt.event.KeyEvent evt) {
                lstFileListKeyTyped(evt);
            }
        });
        sclFileList.setViewportView(lstFileList);

        btnAddFiles.setText("Add files..");
        btnAddFiles.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnAddFilesActionPerformed(evt);
            }
        });

        btnRemoveFiles.setText("Remove Selected");
        btnRemoveFiles.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnRemoveFilesActionPerformed(evt);
            }
        });

        pnlExportOpt.setBorder(javax.swing.BorderFactory.createTitledBorder("Export Options"));

        lblTargetDir.setText("Target Folder: ");

        btnBrowse.setText("Browse..");
        btnBrowse.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnBrowseActionPerformed(evt);
            }
        });

        chkAsSource.setText("Same as source");
        chkAsSource.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                chkAsSourceActionPerformed(evt);
            }
        });

        btnCompress.setText("Compress");
        btnCompress.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnCompressActionPerformed(evt);
            }
        });

        btnDecompress.setText("Decompress");
        btnDecompress.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnDecompressActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout pnlExportOptLayout = new javax.swing.GroupLayout(pnlExportOpt);
        pnlExportOpt.setLayout(pnlExportOptLayout);
        pnlExportOptLayout.setHorizontalGroup(
            pnlExportOptLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(pnlExportOptLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(pnlExportOptLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(pnlExportOptLayout.createSequentialGroup()
                        .addComponent(lblTargetDir)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(txtTargetDir, javax.swing.GroupLayout.DEFAULT_SIZE, 217, Short.MAX_VALUE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(btnBrowse))
                    .addComponent(chkAsSource)
                    .addGroup(pnlExportOptLayout.createSequentialGroup()
                        .addComponent(btnCompress)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(btnDecompress)))
                .addContainerGap())
        );
        pnlExportOptLayout.setVerticalGroup(
            pnlExportOptLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(pnlExportOptLayout.createSequentialGroup()
                .addGroup(pnlExportOptLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(lblTargetDir)
                    .addComponent(txtTargetDir, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(btnBrowse))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(chkAsSource)
                .addGap(7, 7, 7)
                .addGroup(pnlExportOptLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(btnCompress)
                    .addComponent(btnDecompress))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        barProgress.setStringPainted(true);

        menuFile.setText("File");

        itemExit.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_Q, java.awt.event.InputEvent.CTRL_MASK));
        itemExit.setText("Exit");
        itemExit.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                itemExitActionPerformed(evt);
            }
        });
        menuFile.add(itemExit);

        jMenuBar1.add(menuFile);

        menuHelp.setText("Help");

        menuAbout.setText("About");
        menuAbout.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                menuAboutActionPerformed(evt);
            }
        });
        menuHelp.add(menuAbout);

        jMenuBar1.add(menuHelp);

        setJMenuBar(jMenuBar1);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                    .addComponent(barProgress, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(pnlCompressOpt, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(pnlExportOpt, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(pnlMapType, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                        .addComponent(btnAddFiles)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(btnRemoveFiles))
                    .addComponent(sclFileList, javax.swing.GroupLayout.DEFAULT_SIZE, 263, Short.MAX_VALUE))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(pnlMapType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(pnlCompressOpt, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(pnlExportOpt, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addComponent(sclFileList, javax.swing.GroupLayout.DEFAULT_SIZE, 380, Short.MAX_VALUE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(barProgress, javax.swing.GroupLayout.DEFAULT_SIZE, 30, Short.MAX_VALUE)
                    .addComponent(btnRemoveFiles, javax.swing.GroupLayout.PREFERRED_SIZE, 24, Short.MAX_VALUE)
                    .addComponent(btnAddFiles, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addContainerGap())
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private String[] computeCompressParameters(){
        List<String> params = new ArrayList<String>();

        if (!chkCuda.isSelected())
            params.add("-nocuda");
        
        switch (cmbMapType.getSelectedIndex()){
            case 0: params.add("-color"); break;
            case 1: params.add("-normal"); break;
            case 2: params.add("-tonormal"); break;
        }

        if (!chkMips.isSelected())
            params.add("-nomips");
        if (chkRepeat.isSelected())
            params.add("-repeat");
        if (chkLowQuality.isSelected())
            params.add("-fast");

        switch (cmbCompressType.getSelectedIndex()){
            case 0: params.add("-rgb"); break;
            case 1: params.add("-bc1"); break;
            case 2: params.add("-bc1n"); break;
            case 3: params.add("-bc1a"); break;
            case 4: params.add("-bc2"); break;
            case 5: params.add("-bc3"); break;
            case 6: params.add("-bc3n"); break;
            case 7: params.add("-bc4"); break;
            case 8: params.add("-bc5"); break;
        }

        return params.toArray(new String[0]);
    }

    private void updateWork(String workStatus, int percent){
        barProgress.setString(workStatus + " - " + percent+"%");
        barProgress.setValue(percent);
    }

    private void setComponentEnabled(Container c, boolean enabled){
        c.setEnabled(enabled);
        for (Component child : c.getComponents()){
            if (child instanceof Container)
                setComponentEnabled((Container)child, enabled);
            else
                child.setEnabled(enabled);
        }
    }

    private void startWork(){
        setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
        barProgress.setVisible(true);
        barProgress.setValue(0);
        barProgress.setString("Status: Working");

        setComponentEnabled(pnlCompressOpt, false);
        setComponentEnabled(pnlExportOpt, false);
        setComponentEnabled(pnlMapType, false);
        lstFileList.setEnabled(false);
        btnAddFiles.setEnabled(false);
        btnRemoveFiles.setEnabled(false);
    }

    private void endWork(){
        workThread = null;

        setCursor(null);
        barProgress.setVisible(false);
        barProgress.setString("Status: Done");

        setComponentEnabled(pnlCompressOpt, true);
        setComponentEnabled(pnlExportOpt, true);
        setComponentEnabled(pnlMapType, true);
        lstFileList.setEnabled(true);
        btnAddFiles.setEnabled(true);
        btnRemoveFiles.setEnabled(true);

        // properly disables/enables certain components
        chkAsSourceActionPerformed(null);

        btnCompress.setText("Compress");
        btnDecompress.setText("Decompress");
    }

    private void runJ3(File input, File output, String statusStr) throws InterruptedException{
        updateWork(statusStr, 0);
        if (manager == null)
            manager = JmeSystem.newAssetManager();

        manager.registerLocator(input.getParent().toString(),
                                FileLocator.class.getName());

        String format = (String) cmbCompressType.getSelectedItem();
        if (format.equals("PNG-RGBE")){
            HDRLoader loader = new HDRLoader(true);
            try{
                FileInputStream in = new FileInputStream(input);
                Image image = loader.load(in, false);
                in.close();

                BufferedImage rgbeImage = ImageToAwt.convert(image, false, true, 0);
                if (output == null){
                    output = new File(input.getParent(), input.getName() + ".png");
                }
                ImageIO.write(rgbeImage, "png", output);
            } catch (IOException ex){
                ex.printStackTrace();
            }
        }else{
            Texture tex = manager.loadTexture(input.getName());
            Image image = tex.getImage();

            boolean mips = chkMips.isSelected();
            if (mips && !image.hasMipmaps()){
                MipMapGenerator.generateMipMaps(image);
            }
            if (output == null){
                output = new File(input.getParent(), input.getName() + ".j3i");
            }

            try{
                BinaryExporter.getInstance().save(image, output);
                BufferedImage preview = ImageToAwt.convert(image, false, true, 0);
                ImageIO.write(preview, "png", new File(output + ".png"));
            }catch (IOException ex){
                ex.printStackTrace();
            }
        }
    }

    private void runCommand(String[] args, String statusStr) throws InterruptedException{
        Process p = null;
        try{
            ProcessBuilder builder = new ProcessBuilder(args);
            updateWork(statusStr, 0);
            p = builder.start();
            BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()));
            String ln;
            while ((ln = r.readLine()) != null){
                if (Thread.interrupted())
                    throw new InterruptedException();
                
                if (ln.endsWith("%")){
                    // show status in bar
                    int percent = Integer.parseInt(ln.substring(0, ln.length()-1));
                    updateWork(statusStr, percent);
                }else if (ln.startsWith("time taken")){
                    ln = ln.substring(12, ln.length()-7).trim();
                    System.out.println("Time Taken: "+ln+" seconds");
                }
            }
            r.close();
            int error = p.waitFor();
            if (error != 0){
                System.out.println("Error Code: " + error);
            }
        } catch (IOException ex){
            ex.printStackTrace();
        } catch (InterruptedException ex){
            // may get interrupted if user canceled work
            if (p != null)
                p.destroy();

            // propegate exception incase caller is interested
            throw ex;
        }
    }

    private void runNVDecompress(final File inFile) throws InterruptedException{
        if (!inFile.getName().toLowerCase().endsWith(".dds"))
            return; // not a DDS file

        String[] args = new String[2];
        args[0] = new File(texToolsPath, "nvdecompress").toString();
        args[1] = inFile.toString();

        System.out.println("Decompressing file "+inFile);
        runCommand(args, "Decompressing file "+inFile.getName());
    }

    private void runNVCompress(final File inFile, File outFile) throws InterruptedException{
        String nvcompressCmd = new File(texToolsPath, "nvcompress").toString();
        int argCount = 2; // nvcompressCmd & inFile are always specified
        if (outFile != null)
            argCount ++;

        String[] params = computeCompressParameters();
        argCount += params.length;

        String[] args = new String[argCount];
        args[0] = nvcompressCmd;
        System.arraycopy(params, 0, args, 1, params.length);
        args[params.length + 1] = inFile.toString();
        if (outFile != null)
            args[params.length + 2] = outFile.toString();

        System.out.println("Converting file "+inFile);
//        System.out.println("Arguments: ");
//        for (String arg : args) System.out.println("   "+arg);

        runCommand(args, "Converting "+inFile.getName());
    }

    private void runJ3Compress(final File inFile, File outFile) throws InterruptedException{
        System.out.println("Converting file "+inFile);
        runJ3(inFile, outFile, "Converting "+inFile.getName());
    }

    private Object[] compileFileList(){
        Object[] values = lstFileList.getSelectedValues();
        if (values == null || values.length == 0){
            // no specific files selected, add all of them
            DefaultListModel listModel = (DefaultListModel) lstFileList.getModel();
            values = listModel.toArray();
        }
        return values;
    }

    private void runNVCompressAll(final File exportDir){
        final Object[] fileList = compileFileList();
        if (fileList != null && fileList.length > 0){
            startWork();
            workThread = new Thread(){
                @Override
                public void run(){
                    for (Object val : fileList){
                        File inFile = (File) val;
                        File outFile = null;
                        if (exportDir != null){
                            String name = inFile.getName();
                            int extPt = name.lastIndexOf(".");
                            if (extPt > 0)
                                name = name.substring(0, extPt);
                            
                            outFile = new File(exportDir, name+".dds");
                        }
                        try{
                            runNVCompress(inFile, outFile);
                        }catch (InterruptedException ex){
                            return; // user canceled
                        }
                    }
                    endWork();
                }
            };
            workThread.setDaemon(true);
            workThread.start();
        }
    }

    private void runJ3CompressAll(final File exportDir, final String ext){
        final Object[] fileList = compileFileList();
        if (fileList != null && fileList.length > 0){
            startWork();
            workThread = new Thread(){
                @Override
                public void run(){
                    for (Object val : fileList){
                        File inFile = (File) val;
                        File outFile = null;
                        if (exportDir != null){
                            String name = inFile.getName();
                            int extPt = name.lastIndexOf(".");
                            if (extPt > 0)
                                name = name.substring(0, extPt);

                            outFile = new File(exportDir, name+"."+ext);
                        }
                        try{
                            runJ3Compress(inFile, outFile);
                        }catch (InterruptedException ex){
                            return; // user canceled
                        }
                    }
                    endWork();
                }
            };
            workThread.setDaemon(true);
            workThread.start();
        }
    }

    private void runNVDecompressAll(){
        final Object[] fileList = compileFileList();
        if (fileList != null && fileList.length > 0){
            startWork();
            workThread = new Thread(){
                @Override
                public void run(){
                    for (Object val : fileList){
                        File inFile = (File) val;
                        try{
                            runNVDecompress(inFile);
                        }catch (InterruptedException ex){
                            return; // user canceled
                        }
                    }
                    endWork();
                }
            };
            workThread.setDaemon(true);
            workThread.start();
        }
    }

    private void cmbCompressTypeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmbCompressTypeActionPerformed

}//GEN-LAST:event_cmbCompressTypeActionPerformed

    private void btnCompressActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnCompressActionPerformed
        if (btnCompress.getText().equals("Cancel")){
            if (workThread != null){
                workThread.interrupt();
                System.out.println("User canceled decompression");
                endWork();
            }
        }else{
            // find out where to export
            File exportDir = null;
            if (!chkAsSource.isSelected()){
               String exportPath = txtTargetDir.getText();
               if (exportPath != null && !exportPath.equals(""))
                   exportDir = new File(exportPath);
            }

            String compression = (String) cmbCompressType.getSelectedItem();
            if (compression.equals("AWT") || compression.equals("PNG-RGBE")){
                runJ3CompressAll(exportDir, compression.equals("AWT") ? "j3i" : "pnge");
            }else{
                runNVCompressAll(exportDir);
            }
            
            btnCompress.setEnabled(true);
            btnCompress.setText("Cancel");
        }
}//GEN-LAST:event_btnCompressActionPerformed

    private void btnBrowseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnBrowseActionPerformed
        JFileChooser chooser = new JFileChooser();
        chooser.setDialogTitle("Select export directory");
        chooser.setMultiSelectionEnabled(false);
        chooser.setDialogType(JFileChooser.OPEN_DIALOG);
        chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        if (txtTargetDir.getText() != null && !txtTargetDir.getText().equals(""))
            chooser.setSelectedFile(new File(txtTargetDir.getText()));

        if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION){
            File f = chooser.getSelectedFile();
            if (f != null && f.exists() && f.isDirectory())
                txtTargetDir.setText(f.toString());
            else
                JOptionPane.showMessageDialog(this,
                        "Invalid export directory specified",
                        "Error", JOptionPane.ERROR_MESSAGE);
        }
    }//GEN-LAST:event_btnBrowseActionPerformed

    private void chkAsSourceActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkAsSourceActionPerformed
        lblTargetDir.setEnabled(!chkAsSource.isSelected());
        txtTargetDir.setEnabled(!chkAsSource.isSelected());
        btnBrowse.setEnabled(!chkAsSource.isSelected());
    }//GEN-LAST:event_chkAsSourceActionPerformed

    private void btnAddFilesActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnAddFilesActionPerformed
        JFileChooser chooser = new JFileChooser();
        chooser.setDialogTitle("Add input files");
        chooser.setMultiSelectionEnabled(true);
        chooser.setDialogType(JFileChooser.OPEN_DIALOG);
        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);

        if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION){
            File[] files = chooser.getSelectedFiles();
            for (File file : files){
                if (file.exists() && !file.isDirectory()){
                    // add to file list
                    DefaultListModel listModel = (DefaultListModel) lstFileList.getModel();
                    if (!listModel.contains(file))
                        listModel.addElement(file);
                }
            }
        }
    }//GEN-LAST:event_btnAddFilesActionPerformed

    private void btnRemoveFilesActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnRemoveFilesActionPerformed
        Object[] selected = lstFileList.getSelectedValues();
        DefaultListModel listModel = (DefaultListModel) lstFileList.getModel();
        for (Object val : selected){
            listModel.removeElement(val);
        }
    }//GEN-LAST:event_btnRemoveFilesActionPerformed

    private void itemExitActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_itemExitActionPerformed
        dispose();
}//GEN-LAST:event_itemExitActionPerformed

    private void menuAboutActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_menuAboutActionPerformed
        String aboutText = appName+"\n"+
                           "Created by Kirill Vainer.\n"+
                           "\n"+
                           "NVIDIA Texture Tools is Copyright© 2009 NVIDIA Corporation.\n"+
                           "\n"+
                           "Usage: \n"+
                           "   Press \"Add Files..\" to add files to convert\n"+
                           "   Select conversion options on the left, then\n"+
                           "   click \"Export\" to convert files to DDS\n";
        JOptionPane.showMessageDialog(this, aboutText, "About", JOptionPane.PLAIN_MESSAGE);
    }//GEN-LAST:event_menuAboutActionPerformed

    private void lstFileListKeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_lstFileListKeyTyped
        if (evt.getKeyCode() == KeyEvent.VK_DELETE){
            btnRemoveFilesActionPerformed(null);
        }
    }//GEN-LAST:event_lstFileListKeyTyped

    private void btnDecompressActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnDecompressActionPerformed
        if (btnDecompress.getText().equals("Cancel")){
            if (workThread != null){
                workThread.interrupt();
                System.out.println("User canceled decompression");
                endWork();
            }
        }else{
            runNVDecompressAll();
            btnDecompress.setEnabled(true);
            btnDecompress.setText("Cancel");
        }

}//GEN-LAST:event_btnDecompressActionPerformed

    private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
        if (workThread != null)
            workThread.interrupt();
    }//GEN-LAST:event_formWindowClosing

    private static boolean verifyTexToolsPath(File path){
        if (path.exists()){
            File[] files = path.listFiles();
            for (File f : files){
                if (f.getName().startsWith("nvcompress")){
                    return true;
                }
            }
        }
        return false;
    }

    private static File showToolsPathChooser(File prevPath){
        JFileChooser chooser = new JFileChooser();
        chooser.setDialogTitle("Select directory of NVTextureTools");
        chooser.setMultiSelectionEnabled(false);
        chooser.setDialogType(JFileChooser.OPEN_DIALOG);
        chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        if (prevPath != null)
            chooser.setSelectedFile(prevPath);
        
        if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION){
            return chooser.getSelectedFile();
        }else{
            return null;
        }
    }

    public static String attemptLocateToolsPath(){
        String path = pref.get("NVTextureToolsPath", null);
        if (path == null){
            // if texture tools has been properly installed
            // the path is located under TEXTURE_TOOLS_DIR env var
            path = System.getenv("TEXTURE_TOOLS_DIR");
        }
        return path;
    }

    public static void main(String args[]) {
        try{
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception ex){
            ex.printStackTrace();
        }

        System.out.println("Verifiying texture tools path..");
        String path = attemptLocateToolsPath();
        texToolsPath = path == null ? null : new File(path);

        if (texToolsPath != null){
            System.out.println("Found existing path: "+texToolsPath);
        }
        
        if (texToolsPath == null || !verifyTexToolsPath(texToolsPath)){
            while (true){
                File f = showToolsPathChooser(texToolsPath);
                if (f == null)
                    return;

                if (verifyTexToolsPath(f)){
                    texToolsPath = f;
                    pref.put("NVTextureToolsPath", texToolsPath.toString());
                    System.out.println("User specified valid path: "+texToolsPath);
                    try{
                        pref.flush();
                    } catch (BackingStoreException ex){}
                    break;
                }else{
                    JOptionPane.showMessageDialog(null, "Directory must"+
                                                  " contain nvcompress",
                                                  "Error",
                                                  JOptionPane.ERROR_MESSAGE);
                }
            }
        }

        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new NVCompress().setVisible(true);
            }
        });
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JProgressBar barProgress;
    private javax.swing.JButton btnAddFiles;
    private javax.swing.JButton btnBrowse;
    private javax.swing.JButton btnCompress;
    private javax.swing.JButton btnDecompress;
    private javax.swing.JButton btnRemoveFiles;
    private javax.swing.JCheckBox chkAsSource;
    private javax.swing.JCheckBox chkCuda;
    private javax.swing.JCheckBox chkLowQuality;
    private javax.swing.JCheckBox chkMips;
    private javax.swing.JCheckBox chkRepeat;
    private javax.swing.JComboBox cmbCompressType;
    private javax.swing.JComboBox cmbMapType;
    private javax.swing.JMenuItem itemExit;
    private javax.swing.JMenuBar jMenuBar1;
    private javax.swing.JLabel lblCompressType;
    private javax.swing.JLabel lblMapType;
    private javax.swing.JLabel lblTargetDir;
    private javax.swing.JList lstFileList;
    private javax.swing.JMenuItem menuAbout;
    private javax.swing.JMenu menuFile;
    private javax.swing.JMenu menuHelp;
    private javax.swing.JPanel pnlCompressOpt;
    private javax.swing.JPanel pnlExportOpt;
    private javax.swing.JPanel pnlMapType;
    private javax.swing.JScrollPane sclFileList;
    private javax.swing.JTextField txtTargetDir;
    // End of variables declaration//GEN-END:variables

}
