import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyVetoException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JDesktopPane;
import javax.swing.JFileChooser;
import javax.swing.JInternalFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.filechooser.FileFilter;
import javax.swing.plaf.metal.DefaultMetalTheme;
import javax.swing.plaf.metal.MetalLookAndFeel;

import org.sunflow.Benchmark;
import org.sunflow.RealtimeBenchmark;
import org.sunflow.SunflowAPI;
import org.sunflow.core.Display;
import org.sunflow.core.Shader;
import org.sunflow.core.TextureCache;
import org.sunflow.core.accel.KDTree;
import org.sunflow.core.display.FileDisplay;
import org.sunflow.core.display.FrameDisplay;
import org.sunflow.core.display.ImgPipeDisplay;
import org.sunflow.core.display.OpenExrDisplay;
import org.sunflow.core.primitive.TriangleMesh;
import org.sunflow.core.shader.AmbientOcclusionShader;
import org.sunflow.core.shader.IDShader;
import org.sunflow.core.shader.NormalShader;
import org.sunflow.core.shader.PrimIDShader;
import org.sunflow.core.shader.QuickGrayShader;
import org.sunflow.core.shader.UVShader;
import org.sunflow.core.shader.WireframeShader;
import org.sunflow.image.Color;
import org.sunflow.system.ImagePanel;
import org.sunflow.system.Timer;
import org.sunflow.system.UI;
import org.sunflow.system.UserInterface;
import org.sunflow.system.UI.Module;
import org.sunflow.system.UI.PrintLevel;

@SuppressWarnings("serial")
public class SunflowGUI extends javax.swing.JFrame implements UserInterface {
    private static final int DEFAULT_WIDTH = 1024;
    private static final int DEFAULT_HEIGHT = 768;
    private JPanel jPanel3;
    private JScrollPane jScrollPane1;
    private JMenuItem exitMenuItem;
    private JSeparator jSeparator2;
    private JPanel jPanel1;
    private JButton renderButton;
    private JMenuItem jMenuItem4;
    private JSeparator jSeparator1;
    private JMenuItem fitWindowMenuItem;
    private JMenuItem tileWindowMenuItem;
    private JSeparator jSeparator5;
    private JMenuItem consoleWindowMenuItem;
    private JMenuItem editorWindowMenuItem;
    private JMenuItem imageWindowMenuItem;
    private JMenu windowMenu;
    private JInternalFrame consoleFrame;
    private JInternalFrame editorFrame;
    private JInternalFrame imagePanelFrame;
    private JDesktopPane desktop;
    private JCheckBoxMenuItem smallTrianglesMenuItem;
    private JMenuItem textureCacheClearMenuItem;
    private JSeparator jSeparator4;
    private JMenuItem resetZoomMenuItem;
    private JMenu imageMenu;
    private ImagePanel imagePanel;
    private JPanel jPanel6;
    private JCheckBoxMenuItem clearLogMenuItem;
    private JPanel jPanel5;
    private JButton taskCancelButton;
    private JProgressBar taskProgressBar;
    private JSeparator jSeparator3;
    private JCheckBoxMenuItem autoBuildMenuItem;
    private JMenuItem iprMenuItem;
    private JMenuItem renderMenuItem;
    private JMenuItem buildMenuItem;
    private JMenu sceneMenu;
    private JTextArea editorTextArea;
    private JTextArea consoleTextArea;
    private JButton clearConsoleButton;
    private JPanel jPanel4;
    private JScrollPane jScrollPane2;
    private JButton iprButton;
    private JButton buildButton;
    private JMenuItem saveAsMenuItem;
    private JMenuItem saveMenuItem;
    private JMenuItem openFileMenuItem;
    private JMenuItem newFileMenuItem;
    private JMenu fileMenu;
    private JMenuBar jMenuBar1;

    // non-swing items
    private String currentFile;
    private String currentTask;
    private int currentTaskLastP;
    private SunflowAPI api;

    public static void usage(boolean verbose) {
        System.out.println("Usage: SunflowGUI [options] scenefile");
        if (verbose) {
            System.out.println("Sunflow v" + SunflowAPI.VERSION + " textmode");
            System.out.println("Renders the specified scene file");
            System.out.println("Options:");
            System.out.println("  -o filename      Saves the output as the specified filename (png, hdr, tga)");
            System.out.println("                   #'s get expanded to the current frame number");
            System.out.println("  -nogui           Don't open the frame showing rendering progress");
            System.out.println("  -ipr             Render using progressive algorithm");
            System.out.println("  -sampler type    Render using the specified algorithm");
            System.out.println("  -threads n       Render using n threads");
            System.out.println("  -lopri           Set thread priority to low (default)");
            System.out.println("  -hipri           Set thread priority to high");
            System.out.println("  -smallmesh       Load triangle meshes using triangles optimized for memory use");
            System.out.println("  -dumpkd          Dump KDTree to an obj file for visualization");
            System.out.println("  -buildonly       Do not call render method after loading the scene");
            System.out.println("  -showaa          Display sampling levels per pixel for bucket renderer");
            System.out.println("  -nogi            Disable any global illumination engines in the scene");
            System.out.println("  -nocaustics      Disable any caustic engine in the scene");
            System.out.println("  -pathgi n        Use path tracing with n samples to render global illumination");
            System.out.println("  -quick_ambocc d  Applies ambient occlusion to the scene with specified maximum distance");
            System.out.println("  -quick_uvs       Applies a surface uv visualization shader to the scene");
            System.out.println("  -quick_normals   Applies a surface normal visualization shader to the scene");
            System.out.println("  -quick_id        Renders using a unique color for each instance");
            System.out.println("  -quick_prims     Renders using a unique color for each primitive");
            System.out.println("  -quick_gray      Renders using a plain gray diffuse shader");
            System.out.println("  -quick_wire      Renders using a wireframe shader");
            System.out.println("  -resolution w h  Changes the render resolution to the specified width and height (in pixels)");
            System.out.println("  -aa min max      Overrides the image anti-aliasing depths");
            System.out.println("  -bucket n order  Changes the default bucket size to n pixels and the default order");
            System.out.println("  -bake name       Bakes a lightmap for the specified instance");
            System.out.println("  -bakedir dir     Selects the type of lightmap baking: dir=view or ortho");
            System.out.println("  -filter type     Selects the image filter to use");
            System.out.println("  -bench           Run several built-in scenes for benchmark purposes");
            System.out.println("  -rtbench         Run realtime ray-tracing benchmark");
            System.out.println("  -frame n         Set frame number to the specified value");
            System.out.println("  -anim n1 n2      Render all frames between the two specified values (inclusive)");
            System.out.println("  -v verbosity     Set the verbosity level: 0=none,1=errors,2=warnings,3=info,4=detailed");
            System.out.println("  -h               Prints this message");
        }
        System.exit(1);
    }

    public static void main(String[] args) {
        if (args.length > 0) {
            boolean showFrame = true;
            String sampler = "bucket";
            boolean noRender = false;
            String filename = null;
            String input = null;
            int i = 0;
            int threads = 0;
            boolean lowPriority = true;
            boolean showAA = false;
            boolean noGI = false;
            boolean noCaustics = false;
            int pathGI = 0;
            Shader shaderOverride = null;
            int resolutionW = 0, resolutionH = 0;
            int aaMin = -5, aaMax = -5;
            int bucketSize = 0;
            String bucketOrder = null;
            String bakingName = null;
            boolean bakeViewdep = false;
            String filterType = null;
            boolean runBenchmark = false;
            boolean runRTBenchmark = false;
            int frameStart = 1, frameStop = 1;
            while (i < args.length) {
                if (args[i].equals("-o")) {
                    if (i > args.length - 2)
                        usage(false);
                    filename = args[i + 1];
                    i += 2;
                } else if (args[i].equals("-nogui")) {
                    showFrame = false;
                    i++;
                } else if (args[i].equals("-ipr")) {
                    sampler = "ipr";
                    i++;
                } else if (args[i].equals("-threads")) {
                    if (i > args.length - 2)
                        usage(false);
                    threads = Integer.parseInt(args[i + 1]);
                    i += 2;
                } else if (args[i].equals("-lopri")) {
                    lowPriority = true;
                    i++;
                } else if (args[i].equals("-hipri")) {
                    lowPriority = false;
                    i++;
                } else if (args[i].equals("-sampler")) {
                    if (i > args.length - 2)
                        usage(false);
                    sampler = args[i + 1];
                    i += 2;
                } else if (args[i].equals("-smallmesh")) {
                    TriangleMesh.setSmallTriangles(true);
                    i++;
                } else if (args[i].equals("-dumpkd")) {
                    KDTree.setDumpMode(true, "kdtree");
                    i++;
                } else if (args[i].equals("-buildonly")) {
                    noRender = true;
                    i++;
                } else if (args[i].equals("-showaa")) {
                    showAA = true;
                    i++;
                } else if (args[i].equals("-nogi")) {
                    noGI = true;
                    i++;
                } else if (args[i].equals("-nocaustics")) {
                    noCaustics = true;
                    i++;
                } else if (args[i].equals("-pathgi")) {
                    if (i > args.length - 2)
                        usage(false);
                    pathGI = Integer.parseInt(args[i + 1]);
                    i += 2;
                } else if (args[i].equals("-quick_ambocc")) {
                    if (i > args.length - 2)
                        usage(false);
                    float d = Float.parseFloat(args[i + 1]);
                    shaderOverride = new AmbientOcclusionShader(Color.WHITE, d);
                    i += 2;
                } else if (args[i].equals("-quick_uvs")) {
                    if (i > args.length - 1)
                        usage(false);
                    shaderOverride = new UVShader();
                    i++;
                } else if (args[i].equals("-quick_normals")) {
                    if (i > args.length - 1)
                        usage(false);
                    shaderOverride = new NormalShader();
                    i++;
                } else if (args[i].equals("-quick_id")) {
                    if (i > args.length - 1)
                        usage(false);
                    shaderOverride = new IDShader();
                    i++;
                } else if (args[i].equals("-quick_prims")) {
                    if (i > args.length - 1)
                        usage(false);
                    shaderOverride = new PrimIDShader();
                    i++;
                } else if (args[i].equals("-quick_gray")) {
                    if (i > args.length - 1)
                        usage(false);
                    shaderOverride = new QuickGrayShader();
                    i++;
                } else if (args[i].equals("-quick_wire")) {
                    if (i > args.length - 1)
                        usage(false);
                    shaderOverride = new WireframeShader();
                    i++;
                } else if (args[i].equals("-resolution")) {
                    if (i > args.length - 3)
                        usage(false);
                    resolutionW = Integer.parseInt(args[i + 1]);
                    resolutionH = Integer.parseInt(args[i + 2]);
                    i += 3;
                } else if (args[i].equals("-aa")) {
                    if (i > args.length - 3)
                        usage(false);
                    aaMin = Integer.parseInt(args[i + 1]);
                    aaMax = Integer.parseInt(args[i + 2]);
                    i += 3;
                } else if (args[i].equals("-bucket")) {
                    if (i > args.length - 3)
                        usage(false);
                    bucketSize = Integer.parseInt(args[i + 1]);
                    bucketOrder = args[i + 2];
                    i += 3;
                } else if (args[i].equals("-bake")) {
                    if (i > args.length - 2)
                        usage(false);
                    bakingName = args[i + 1];
                    i += 2;
                } else if (args[i].equals("-bakedir")) {
                    if (i > args.length - 2)
                        usage(false);
                    String baketype = args[i + 1];
                    if (baketype.equals("view"))
                        bakeViewdep = true;
                    else if (baketype.equals("ortho"))
                        bakeViewdep = false;
                    else
                        usage(false);
                    i += 2;
                } else if (args[i].equals("-filter")) {
                    if (i > args.length - 2)
                        usage(false);
                    filterType = args[i + 1];
                    i += 2;
                } else if (args[i].equals("-bench")) {
                    runBenchmark = true;
                    i++;
                } else if (args[i].equals("-rtbench")) {
                    runRTBenchmark = true;
                    i++;
                } else if (args[i].equals("-frame")) {
                    if (i > args.length - 2)
                        usage(false);
                    frameStart = frameStop = Integer.parseInt(args[i + 1]);
                    i += 2;
                } else if (args[i].equals("-anim")) {
                    if (i > args.length - 3)
                        usage(false);
                    frameStart = Integer.parseInt(args[i + 1]);
                    frameStop = Integer.parseInt(args[i + 2]);
                    i += 3;
                } else if (args[i].equals("-v")) {
                    if (i > args.length - 2)
                        usage(false);
                    UI.verbosity(Integer.parseInt(args[i + 1]));
                    i += 2;
                } else if (args[i].equals("-h") || args[i].equals("-help")) {
                    usage(true);
                } else {
                    if (input != null)
                        usage(false);
                    input = args[i];
                    i++;
                }
            }
            if (runBenchmark) {
                SunflowAPI.runSystemCheck();
                new Benchmark().execute();
                return;
            }
            if (runRTBenchmark) {
                SunflowAPI.runSystemCheck();
                new RealtimeBenchmark(showFrame, threads);
                return;
            }
            if (input == null)
                usage(false);
            SunflowAPI.runSystemCheck();
            if (frameStart < frameStop && showFrame) {
                UI.printWarning(Module.GUI, "Animations should not be rendered without -nogui - forcing GUI off anyway");
                showFrame = false;
            }
            if (frameStart < frameStop && filename == null) {
                filename = "output.#.png";
                UI.printWarning(Module.GUI, "Animation output was not specified - defaulting to: \"%s\"", filename);
            }
            for (int frameNumber = frameStart; frameNumber <= frameStop; frameNumber++) {
                SunflowAPI api = SunflowAPI.create(input, frameNumber);
                if (api == null)
                    continue;
                if (noRender)
                    continue;
                if (resolutionW > 0 && resolutionH > 0) {
                    api.parameter("resolutionX", resolutionW);
                    api.parameter("resolutionY", resolutionH);
                }
                if (aaMin != -5 || aaMax != -5) {
                    api.parameter("aa.min", aaMin);
                    api.parameter("aa.max", aaMax);
                }
                if (bucketSize > 0)
                    api.parameter("bucket.size", bucketSize);
                if (bucketOrder != null)
                    api.parameter("bucket.order", bucketOrder);
                api.parameter("aa.display", showAA);
                api.parameter("threads", threads);
                api.parameter("threads.lowPriority", lowPriority);
                if (bakingName != null) {
                    api.parameter("baking.instance", bakingName);
                    api.parameter("baking.viewdep", bakeViewdep);
                }
                if (filterType != null)
                    api.parameter("filter", filterType);
                if (noGI)
                    api.parameter("gi.engine", "none");
                else if (pathGI > 0) {
                    api.parameter("gi.engine", "path");
                    api.parameter("gi.path.samples", pathGI);
                }
                if (noCaustics)
                    api.parameter("caustics", "none");
                api.parameter("sampler", sampler);
                api.options(SunflowAPI.DEFAULT_OPTIONS);
                if (shaderOverride != null) {
                    api.shader("ambocc", shaderOverride);
                    api.shaderOverride("ambocc", true);
                }
                // create display
                Display display;
                String currentFilename = (filename != null) ? filename.replace("#", String.format("%04d", frameNumber)) : null;
                if (showFrame) {
                    display = new FrameDisplay(currentFilename);
                } else {
                    if (currentFilename != null && currentFilename.endsWith(".exr")) {
                        try {
                            display = new OpenExrDisplay(currentFilename, "zip", "float");
                        } catch (Exception e) {
                            e.printStackTrace();
                            return;
                        }
                    } else if (currentFilename != null && currentFilename.equals("imgpipe")) {
                        display = new ImgPipeDisplay();
                    } else
                        display = new FileDisplay(currentFilename);
                }
                api.render(SunflowAPI.DEFAULT_OPTIONS, display);
            }
        } else {
            MetalLookAndFeel.setCurrentTheme(new DefaultMetalTheme());
            SunflowGUI gui = new SunflowGUI();
            gui.setVisible(true);
            Dimension screenRes = Toolkit.getDefaultToolkit().getScreenSize();
            if (screenRes.getWidth() <= DEFAULT_WIDTH || screenRes.getHeight() <= DEFAULT_HEIGHT)
                gui.setExtendedState(MAXIMIZED_BOTH);
            gui.tileWindowMenuItem.doClick();
            SunflowAPI.runSystemCheck();
        }
    }

    public SunflowGUI() {
        super();
        currentFile = null;
        api = null;
        initGUI();
        pack();
        setLocationRelativeTo(null);
        newFileMenuItemActionPerformed(null);
        UI.set(this);
    }

    private void initGUI() {
        setTitle("Sunflow v" + SunflowAPI.VERSION);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        {
            desktop = new JDesktopPane();
            getContentPane().add(desktop, BorderLayout.CENTER);
            Dimension screenRes = Toolkit.getDefaultToolkit().getScreenSize();
            if (screenRes.getWidth() <= DEFAULT_WIDTH || screenRes.getHeight() <= DEFAULT_HEIGHT)
                desktop.setPreferredSize(new java.awt.Dimension(640, 480));
            else
                desktop.setPreferredSize(new java.awt.Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT));
            {
                imagePanelFrame = new JInternalFrame();
                desktop.add(imagePanelFrame);
                {
                    jPanel1 = new JPanel();
                    FlowLayout jPanel1Layout = new FlowLayout();
                    jPanel1Layout.setAlignment(FlowLayout.LEFT);
                    jPanel1.setLayout(jPanel1Layout);
                    imagePanelFrame.getContentPane().add(jPanel1, BorderLayout.NORTH);
                    {
                        renderButton = new JButton();
                        jPanel1.add(renderButton);
                        renderButton.setText("Render");
                        renderButton.addActionListener(new ActionListener() {
                            public void actionPerformed(ActionEvent evt) {
                                renderMenuItemActionPerformed(evt);
                            }
                        });
                    }
                    {
                        iprButton = new JButton();
                        jPanel1.add(iprButton);
                        iprButton.setText("IPR");
                        iprButton.addActionListener(new ActionListener() {
                            public void actionPerformed(ActionEvent evt) {
                                iprMenuItemActionPerformed(evt);
                            }
                        });
                    }
                }
                {
                    imagePanel = new ImagePanel();
                    imagePanelFrame.getContentPane().add(imagePanel, BorderLayout.CENTER);
                }
                imagePanelFrame.pack();
                imagePanelFrame.setResizable(true);
                imagePanelFrame.setMaximizable(true);
                imagePanelFrame.setVisible(true);
                imagePanelFrame.setTitle("Image");
                imagePanelFrame.setIconifiable(true);
            }
            {
                editorFrame = new JInternalFrame();
                desktop.add(editorFrame);
                editorFrame.setTitle("Script Editor");
                editorFrame.setMaximizable(true);
                editorFrame.setResizable(true);
                editorFrame.setIconifiable(true);
                {
                    jScrollPane1 = new JScrollPane();
                    editorFrame.getContentPane().add(jScrollPane1, BorderLayout.CENTER);
                    jScrollPane1.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
                    jScrollPane1.setPreferredSize(new java.awt.Dimension(360, 280));
                    {
                        editorTextArea = new JTextArea();
                        jScrollPane1.setViewportView(editorTextArea);
                        editorTextArea.setFont(new java.awt.Font("Monospaced", 0, 12));
                        // drag and drop
                        editorTextArea.setTransferHandler(new SceneTransferHandler());
                    }
                }
                {
                    jPanel3 = new JPanel();
                    editorFrame.getContentPane().add(jPanel3, BorderLayout.SOUTH);
                    FlowLayout jPanel3Layout = new FlowLayout();
                    jPanel3Layout.setAlignment(FlowLayout.RIGHT);
                    jPanel3.setLayout(jPanel3Layout);
                    {
                        buildButton = new JButton();
                        jPanel3.add(buildButton);
                        buildButton.setText("Build Scene");
                        buildButton.addActionListener(new ActionListener() {
                            public void actionPerformed(ActionEvent evt) {
                                buildMenuItemActionPerformed(evt);
                            }
                        });
                    }
                }
                editorFrame.pack();
                editorFrame.setVisible(true);
            }
            {
                consoleFrame = new JInternalFrame();
                desktop.add(consoleFrame);
                consoleFrame.setIconifiable(true);
                consoleFrame.setMaximizable(true);
                consoleFrame.setResizable(true);
                consoleFrame.setTitle("Console");
                {
                    jScrollPane2 = new JScrollPane();
                    consoleFrame.getContentPane().add(jScrollPane2, BorderLayout.CENTER);
                    jScrollPane2.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
                    jScrollPane2.setPreferredSize(new java.awt.Dimension(360, 100));
                    {
                        consoleTextArea = new JTextArea();
                        jScrollPane2.setViewportView(consoleTextArea);
                        consoleTextArea.setFont(new java.awt.Font("Monospaced", 0, 12));
                        consoleTextArea.setEditable(false);
                    }
                }
                {
                    jPanel4 = new JPanel();
                    consoleFrame.getContentPane().add(jPanel4, BorderLayout.SOUTH);
                    BorderLayout jPanel4Layout = new BorderLayout();
                    jPanel4.setLayout(jPanel4Layout);
                    {
                        jPanel6 = new JPanel();
                        BorderLayout jPanel6Layout = new BorderLayout();
                        jPanel6.setLayout(jPanel6Layout);
                        jPanel4.add(jPanel6, BorderLayout.CENTER);
                        jPanel6.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 0));
                        {
                            taskProgressBar = new JProgressBar();
                            jPanel6.add(taskProgressBar);
                            taskProgressBar.setEnabled(false);
                            taskProgressBar.setString("");
                            taskProgressBar.setStringPainted(true);
                            taskProgressBar.setOpaque(false);
                        }
                    }
                    {
                        jPanel5 = new JPanel();
                        FlowLayout jPanel5Layout = new FlowLayout();
                        jPanel5Layout.setAlignment(FlowLayout.RIGHT);
                        jPanel5.setLayout(jPanel5Layout);
                        jPanel4.add(jPanel5, BorderLayout.EAST);
                        {
                            taskCancelButton = new JButton();
                            jPanel5.add(taskCancelButton);
                            taskCancelButton.setText("Cancel");
                            taskCancelButton.setEnabled(false);
                            taskCancelButton.addActionListener(new ActionListener() {
                                public void actionPerformed(ActionEvent evt) {
                                    UI.taskCancel();
                                }
                            });
                        }
                        {
                            clearConsoleButton = new JButton();
                            jPanel5.add(clearConsoleButton);
                            clearConsoleButton.setText("Clear");
                            clearConsoleButton.addActionListener(new ActionListener() {
                                public void actionPerformed(ActionEvent evt) {
                                    clearConsole();
                                }
                            });
                        }
                    }
                }
                consoleFrame.pack();
                consoleFrame.setVisible(true);
            }
        }
        {
            jMenuBar1 = new JMenuBar();
            setJMenuBar(jMenuBar1);
            {
                fileMenu = new JMenu();
                jMenuBar1.add(fileMenu);
                fileMenu.setText("File");
                {
                    newFileMenuItem = new JMenuItem();
                    fileMenu.add(newFileMenuItem);
                    newFileMenuItem.setText("New");
                    newFileMenuItem.setAccelerator(KeyStroke.getKeyStroke("ctrl N"));
                    newFileMenuItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent evt) {
                            newFileMenuItemActionPerformed(evt);
                        }
                    });
                }
                {
                    openFileMenuItem = new JMenuItem();
                    fileMenu.add(openFileMenuItem);
                    openFileMenuItem.setText("Open ...");
                    openFileMenuItem.setAccelerator(KeyStroke.getKeyStroke("ctrl O"));
                    openFileMenuItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent evt) {
                            openFileMenuItemActionPerformed(evt);
                        }
                    });
                }
                {
                    saveMenuItem = new JMenuItem();
                    fileMenu.add(saveMenuItem);
                    saveMenuItem.setText("Save");
                    saveMenuItem.setAccelerator(KeyStroke.getKeyStroke("ctrl S"));
                    saveMenuItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent evt) {
                            saveCurrentFile(currentFile);
                        }
                    });
                }
                {
                    saveAsMenuItem = new JMenuItem();
                    fileMenu.add(saveAsMenuItem);
                    saveAsMenuItem.setText("Save As ...");
                    saveAsMenuItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent evt) {
                            saveAsMenuItemActionPerformed(evt);
                        }
                    });
                }
                {
                    jSeparator2 = new JSeparator();
                    fileMenu.add(jSeparator2);
                }
                {
                    exitMenuItem = new JMenuItem();
                    fileMenu.add(exitMenuItem);
                    exitMenuItem.setText("Exit");
                    exitMenuItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent evt) {
                            System.exit(0);
                        }
                    });
                }
            }
            {
                sceneMenu = new JMenu();
                jMenuBar1.add(sceneMenu);
                sceneMenu.setText("Scene");
                {
                    buildMenuItem = new JMenuItem();
                    sceneMenu.add(buildMenuItem);
                    buildMenuItem.setText("Build");
                    buildMenuItem.setAccelerator(KeyStroke.getKeyStroke("ctrl B"));
                    buildMenuItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent evt) {
                            if (sceneMenu.isEnabled())
                                buildMenuItemActionPerformed(evt);
                        }
                    });
                }
                {
                    autoBuildMenuItem = new JCheckBoxMenuItem();
                    sceneMenu.add(autoBuildMenuItem);
                    autoBuildMenuItem.setText("Build on open");
                    autoBuildMenuItem.setSelected(true);
                }
                {
                    jSeparator3 = new JSeparator();
                    sceneMenu.add(jSeparator3);
                }
                {
                    renderMenuItem = new JMenuItem();
                    sceneMenu.add(renderMenuItem);
                    renderMenuItem.setText("Render");
                    renderMenuItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent evt) {
                            renderMenuItemActionPerformed(evt);
                        }
                    });
                }
                {
                    iprMenuItem = new JMenuItem();
                    sceneMenu.add(iprMenuItem);
                    iprMenuItem.setText("IPR");
                    iprMenuItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent evt) {
                            iprMenuItemActionPerformed(evt);
                        }
                    });
                }
                {
                    clearLogMenuItem = new JCheckBoxMenuItem();
                    sceneMenu.add(clearLogMenuItem);
                    clearLogMenuItem.setText("Auto Clear Log");
                    clearLogMenuItem.setToolTipText("Clears the console before building or rendering");
                    clearLogMenuItem.setSelected(true);
                }
                {
                    jSeparator4 = new JSeparator();
                    sceneMenu.add(jSeparator4);
                }
                {
                    textureCacheClearMenuItem = new JMenuItem();
                    sceneMenu.add(textureCacheClearMenuItem);
                    textureCacheClearMenuItem.setText("Clear Texture Cache");
                    textureCacheClearMenuItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent evt) {
                            textureCacheClearMenuItemActionPerformed(evt);
                        }
                    });
                }
                {
                    smallTrianglesMenuItem = new JCheckBoxMenuItem();
                    sceneMenu.add(smallTrianglesMenuItem);
                    smallTrianglesMenuItem.setText("Low Mem Triangles");
                    smallTrianglesMenuItem.setToolTipText("Load future meshes using a low memory footprint triangle representation");
                    smallTrianglesMenuItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent evt) {
                            smallTrianglesMenuItemActionPerformed(evt);
                        }
                    });
                }
            }
            {
                imageMenu = new JMenu();
                jMenuBar1.add(imageMenu);
                imageMenu.setText("Image");
                {
                    resetZoomMenuItem = new JMenuItem();
                    imageMenu.add(resetZoomMenuItem);
                    resetZoomMenuItem.setText("Reset Zoom");
                    resetZoomMenuItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent evt) {
                            imagePanel.reset();
                        }
                    });
                }
                {
                    fitWindowMenuItem = new JMenuItem();
                    imageMenu.add(fitWindowMenuItem);
                    fitWindowMenuItem.setText("Fit to Window");
                    fitWindowMenuItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent evt) {
                            imagePanel.fit();
                        }
                    });
                }
                {
                    jSeparator1 = new JSeparator();
                    imageMenu.add(jSeparator1);
                }
                {
                    jMenuItem4 = new JMenuItem();
                    imageMenu.add(jMenuItem4);
                    jMenuItem4.setText("Save Image ...");
                    jMenuItem4.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent evt) {
                            // imagePanel.image;
                            JFileChooser fc = new JFileChooser(".");
                            fc.setFileFilter(new FileFilter() {
                                @Override
                                public String getDescription() {
                                    return "Image File";
                                }

                                @Override
                                public boolean accept(File f) {
                                    return (f.isDirectory() || f.getName().endsWith(".png") || f.getName().endsWith(".tga"));
                                }
                            });
                            if (fc.showSaveDialog(SunflowGUI.this) == JFileChooser.APPROVE_OPTION) {
                                String filename = fc.getSelectedFile().getAbsolutePath();
                                imagePanel.save(filename);
                            }
                        }
                    });
                }
            }
            {
                windowMenu = new JMenu();
                jMenuBar1.add(windowMenu);
                windowMenu.setText("Window");
            }
            {
                imageWindowMenuItem = new JMenuItem();
                windowMenu.add(imageWindowMenuItem);
                imageWindowMenuItem.setText("Image");
                imageWindowMenuItem.setAccelerator(KeyStroke.getKeyStroke("ctrl 1"));
                imageWindowMenuItem.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent evt) {
                        selectFrame(imagePanelFrame);
                    }
                });
            }
            {
                editorWindowMenuItem = new JMenuItem();
                windowMenu.add(editorWindowMenuItem);
                editorWindowMenuItem.setText("Script Editor");
                editorWindowMenuItem.setAccelerator(KeyStroke.getKeyStroke("ctrl 2"));
                editorWindowMenuItem.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent evt) {
                        selectFrame(editorFrame);
                    }
                });
            }
            {
                consoleWindowMenuItem = new JMenuItem();
                windowMenu.add(consoleWindowMenuItem);
                consoleWindowMenuItem.setText("Console");
                consoleWindowMenuItem.setAccelerator(KeyStroke.getKeyStroke("ctrl 3"));
                consoleWindowMenuItem.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent evt) {
                        selectFrame(consoleFrame);
                    }
                });
            }
            {
                jSeparator5 = new JSeparator();
                windowMenu.add(jSeparator5);
            }
            {
                tileWindowMenuItem = new JMenuItem();
                windowMenu.add(tileWindowMenuItem);
                tileWindowMenuItem.setText("Tile");
                tileWindowMenuItem.setAccelerator(KeyStroke.getKeyStroke("ctrl T"));
                tileWindowMenuItem.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent evt) {
                        tileWindowMenuItemActionPerformed(evt);
                    }
                });
            }
        }
    }

    private void newFileMenuItemActionPerformed(ActionEvent evt) {
        if (evt != null) {
            // check save?
        }
        // put some template code into the editor
        String template = "import org.sunflow.core.*;\nimport org.sunflow.core.accel.*;\nimport org.sunflow.core.camera.*;\nimport org.sunflow.core.primitive.*;\nimport org.sunflow.core.shader.*;\nimport org.sunflow.image.Color;\nimport org.sunflow.math.*;\n\npublic void build() {\n  // your code goes here\n\n}\n";
        editorTextArea.setText(template);
    }

    private void openFileMenuItemActionPerformed(ActionEvent evt) {
        JFileChooser fc = new JFileChooser(".");
        fc.setFileFilter(new FileFilter() {
            @Override
            public String getDescription() {
                return "Scene File";
            }

            @Override
            public boolean accept(File f) {
                return (f.isDirectory() || f.getName().endsWith(".sc") || f.getName().endsWith(".java"));
            }
        });

        if (fc.showOpenDialog(SunflowGUI.this) == JFileChooser.APPROVE_OPTION) {
            final String f = fc.getSelectedFile().getAbsolutePath();
            openFile(f);
        }
    }

    private void buildMenuItemActionPerformed(ActionEvent evt) {
        new Thread() {
            public void run() {
                setEnableInterface(false);
                if (clearLogMenuItem.isSelected())
                    clearConsole();
                Timer t = new Timer();
                t.start();
                try {
                    api = SunflowAPI.compile(editorTextArea.getText());
                } catch (NoClassDefFoundError e) {
                    UI.printError(Module.GUI, "Janino library not found. Please check command line.");
                    api = null;
                }
                if (api != null) {
                    try {
                        if (currentFile != null) {
                            String dir = new File(currentFile).getAbsoluteFile().getParent();
                            api.addIncludeSearchPath(dir);
                            api.addIncludeSearchPath(dir);
                        }
                        api.build();
                    } catch (Exception e) {
                        UI.printError(Module.GUI, "Build terminated abnormally: %s", e.getMessage());
                        for (StackTraceElement elt : e.getStackTrace()) {
                            UI.printInfo(Module.GUI, "       at %s", elt.toString());
                        }
                        e.printStackTrace();
                    }
                    t.end();
                    UI.printInfo(Module.GUI, "Build time: %s", t.toString());
                }
                setEnableInterface(true);
            }
        }.start();
    }

    private void clearConsole() {
        consoleTextArea.setText(null);
    }

    private void println(final String s) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                consoleTextArea.append(s + "\n");
            }
        });
    }

    private void setEnableInterface(boolean enabled) {
        // lock or unlock options which are unsafe during builds or renders
        newFileMenuItem.setEnabled(enabled);
        openFileMenuItem.setEnabled(enabled);
        saveMenuItem.setEnabled(enabled);
        saveAsMenuItem.setEnabled(enabled);
        sceneMenu.setEnabled(enabled);
        buildButton.setEnabled(enabled);
        renderButton.setEnabled(enabled);
        iprButton.setEnabled(enabled);
    }

    public void print(Module m, PrintLevel level, String s) {
        if (level == PrintLevel.ERROR)
            JOptionPane.showMessageDialog(SunflowGUI.this, s, String.format("Error - %s", m.name()), JOptionPane.ERROR_MESSAGE);
        println(UI.formatOutput(m, level, s));
    }

    public void taskStart(String s, int min, int max) {
        currentTask = s;
        currentTaskLastP = -1;
        final int taskMin = min;
        final int taskMax = max;
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                taskProgressBar.setEnabled(true);
                taskCancelButton.setEnabled(true);
                taskProgressBar.setMinimum(taskMin);
                taskProgressBar.setMaximum(taskMax);
                taskProgressBar.setValue(taskMin);
                taskProgressBar.setString(currentTask);
            }
        });
    }

    public void taskUpdate(int current) {
        final int taskCurrent = current;
        final String taskString = currentTask;
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                taskProgressBar.setValue(taskCurrent);
                int p = (int) (100.0 * taskProgressBar.getPercentComplete());
                if (p > currentTaskLastP) {
                    taskProgressBar.setString(taskString + " [" + p + "%]");
                    currentTaskLastP = p;
                }
            }
        });
    }

    public void taskStop() {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                taskProgressBar.setValue(taskProgressBar.getMinimum());
                taskProgressBar.setString("");
                taskProgressBar.setEnabled(false);
                taskCancelButton.setEnabled(false);
            }
        });
    }

    private void renderMenuItemActionPerformed(ActionEvent evt) {
        new Thread() {
            public void run() {
                setEnableInterface(false);
                if (clearLogMenuItem.isSelected())
                    clearConsole();
                if (api != null) {
                    api.parameter("sampler", "bucket");
                    api.options(SunflowAPI.DEFAULT_OPTIONS);
                    api.render(SunflowAPI.DEFAULT_OPTIONS, imagePanel);
                } else
                    UI.printError(Module.GUI, "Nothing to render!");
                setEnableInterface(true);
            }
        }.start();
    }

    private void iprMenuItemActionPerformed(ActionEvent evt) {
        new Thread() {
            public void run() {
                setEnableInterface(false);
                if (clearLogMenuItem.isSelected())
                    clearConsole();
                if (api != null) {
                    api.parameter("sampler", "ipr");
                    api.options(SunflowAPI.DEFAULT_OPTIONS);
                    api.render(SunflowAPI.DEFAULT_OPTIONS, imagePanel);
                } else
                    UI.printError(Module.GUI, "Nothing to IPR!");
                setEnableInterface(true);
            }
        }.start();
    }

    private void textureCacheClearMenuItemActionPerformed(ActionEvent evt) {
        TextureCache.flush();
    }

    private void smallTrianglesMenuItemActionPerformed(ActionEvent evt) {
        TriangleMesh.setSmallTriangles(smallTrianglesMenuItem.isSelected());
    }

    private void saveAsMenuItemActionPerformed(ActionEvent evt) {
        JFileChooser fc = new JFileChooser(".");
        fc.setFileFilter(new FileFilter() {
            @Override
            public String getDescription() {
                return "Scene File";
            }

            @Override
            public boolean accept(File f) {
                return (f.isDirectory() || f.getName().endsWith(".java"));
            }
        });

        if (fc.showSaveDialog(SunflowGUI.this) == JFileChooser.APPROVE_OPTION) {
            String f = fc.getSelectedFile().getAbsolutePath();
            if (!f.endsWith(".java"))
                f += ".java";
            File file = new File(f);
            if (!file.exists() || JOptionPane.showConfirmDialog(SunflowGUI.this, "This file already exists.\nOverwrite?", "Warning", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
                // save file
                saveCurrentFile(f);
            }
        }
    }

    private void saveCurrentFile(String filename) {
        if (filename == null) {
            // no filename was picked, go to save as dialog
            saveAsMenuItemActionPerformed(null);
            return;
        }
        FileWriter file;
        try {
            file = new FileWriter(filename);
            // get text from editor pane
            file.write(editorTextArea.getText());
            file.close();
            // update current filename
            currentFile = filename;
            UI.printInfo(Module.GUI, "Saved current script to \"%s\"", filename);
        } catch (IOException e) {
            UI.printError(Module.GUI, "Unable to save: \"%s\"", filename);
            e.printStackTrace();
        }
    }

    private void selectFrame(JInternalFrame frame) {
        try {
            frame.setSelected(true);
            frame.setIcon(false);
        } catch (PropertyVetoException e) {
            // this should never happen
            e.printStackTrace();
        }
    }

    private void tileWindowMenuItemActionPerformed(ActionEvent evt) {
        try {
            if (imagePanelFrame.isIcon())
                imagePanelFrame.setIcon(false);
            if (editorFrame.isIcon())
                editorFrame.setIcon(false);
            if (consoleFrame.isIcon())
                consoleFrame.setIcon(false);

            int width = desktop.getWidth();
            int height = desktop.getHeight();
            int widthLeft = width * 7 / 12;
            int widthRight = width - widthLeft;
            int pad = 2;
            int pad2 = pad + pad;

            imagePanelFrame.reshape(pad, pad, widthLeft - pad2, height - pad2);
            editorFrame.reshape(pad + widthLeft, pad, widthRight - pad2, height / 2 - pad2);
            consoleFrame.reshape(pad + widthLeft, pad + height / 2, widthRight - pad2, height / 2 - pad2);
        } catch (PropertyVetoException e) {
            e.printStackTrace();
        }
    }

    private void openFile(String filename) {
        if (filename.endsWith(".java")) {
            // read the file line by line
            String code = "";
            FileReader file;
            try {
                file = new FileReader(filename);
                BufferedReader bf = new BufferedReader(file);
                while (true) {
                    String line;
                    line = bf.readLine();
                    if (line == null)
                        break;
                    code += line;
                    code += "\n";
                }
                file.close();
                editorTextArea.setText(code);
            } catch (FileNotFoundException e) {
                UI.printError(Module.GUI, "Unable to load: \"%s\"", filename);
                return;
            } catch (IOException e) {
                UI.printError(Module.GUI, "Unable to load: \"%s\"", filename);
                return;
            }
            // loade went ok, use filename as current
            currentFile = filename;
            UI.printInfo(Module.GUI, "Loaded script: \"%s\"", filename);
        } else if (filename.endsWith(".sc")) {
            String template = "import org.sunflow.core.*;\nimport org.sunflow.core.accel.*;\nimport org.sunflow.core.camera.*;\nimport org.sunflow.core.primitive.*;\nimport org.sunflow.core.shader.*;\nimport org.sunflow.image.Color;\nimport org.sunflow.math.*;\n\npublic void build() {\n  parse(\"" + filename.replace("\\", "\\\\") + "\");\n}\n";
            editorTextArea.setText(template);
            // no java file associated
            currentFile = null;
            UI.printInfo(Module.GUI, "Created template for \"%s\"", filename);
        } else {
            UI.printError(Module.GUI, "Unknown file format selected");
            return;
        }
        editorTextArea.setCaretPosition(0);
        if (autoBuildMenuItem.isSelected()) {
            // try to compile the code we just loaded
            buildMenuItemActionPerformed(null);
        }

    }

    private class SceneTransferHandler extends TransferHandler {
        public boolean importData(JComponent c, Transferable t) {
            if (!sceneMenu.isEnabled())
                return false;
            // can I import it?
            if (!canImport(c, t.getTransferDataFlavors())) {
                return false;
            }
            try {
                // get a List of Files
                List files = (java.util.List) t.getTransferData(DataFlavor.javaFileListFlavor);
                for (int i = 0; i < files.size(); i++) {
                    final File file = (File) files.get(i);
                    String filename = file.getAbsolutePath();
                    // check extension
                    if (filename.endsWith(".sc") || filename.endsWith(".java")) {
                        openFile(filename);
                        // load only one file at a time, stop here
                        break;
                    }
                }
            } catch (Exception exp) {
                // debug
                exp.printStackTrace();
            }

            return false;
        }

        public boolean canImport(JComponent c, DataFlavor[] flavors) {
            // Just a quick check to see if a file can be accepted at this time
            // Are there any files around?
            for (int i = 0; i < flavors.length; i++) {
                if (flavors[i].isFlavorJavaFileListType())
                    return true;
            }

            // guess not
            return false;
        }
    }
}