/*
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * JackAsser wrote this file. As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a beer in return, JackAsser.
 * ----------------------------------------------------------------------------
 */

import java.util.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
import java.awt.image.*;
import java.awt.color.*;
import java.io.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.filechooser.FileFilter;
import javax.swing.event.*;
import Acme.JPM.Encoders.*;

class LevelFileFilter extends FileFilter
{
    public String getDescription()
    {
        return "(.pd64) Pinball Dreams 64 - Level files";
    }

    public boolean accept(File f)
    {
        if (!f.isFile())
            return true;

        return f.getName().endsWith(".pd64");
    }
};

class AngleFileFilter extends FileFilter
{
    public String getDescription()
    {
        return "(.gif) GIF Images - Angular raw data";
    }

    public boolean accept(File f)
    {
        if (!f.isFile())
            return true;

        return f.getName().endsWith(".gif");
    }
};

class RawFileFilter extends FileFilter
{
    public String getDescription()
    {
        return "(.pd64raw) Pinball Dreams 64 - Raw files";
    }

    public boolean accept(File f)
    {
        if (!f.isFile())
            return true;

        return f.getName().endsWith(".pd64raw");
    }
};

class Editor extends JPanel
{
    private static final int m_version=1;
    private Image m_offscreen;
    private Image m_backdrop;
    private ArrayList m_polygons;
	private Object m_selectedPolygon;
	private int m_selectedVertex;
	private int m_selectedEdge;
	private boolean m_dragging=false;
	private int m_nx;
	private int m_ny;
    private int m_dragReferenceX;
    private int m_dragReferenceY;
    private int m_zoom;
    private ArrayList m_copyBuffer;
    private File m_file;

    private ArrayList duplicatePolygon(ArrayList polygon)
    {
        ArrayList newPolygon=new ArrayList();
        Iterator i=polygon.iterator();
        while (i.hasNext())
        {
            Point p=(Point)i.next();
            newPolygon.add(new Point((int)p.getX(), ((int)p.getY())));
        }
        return newPolygon;
    }

    public File getFile()
    {
        return m_file;
    }

    public boolean load(File f)
    {
        try
        {
            ObjectInputStream ois=new ObjectInputStream(new FileInputStream(f));
            try
            {
                Integer i=(Integer)ois.readObject();
                if (i.intValue()==1)                // Version 1
                {
                    Object o=ois.readObject();
                    ArrayList level=(ArrayList)o;
                    m_polygons=level;
                }

                m_selectedPolygon=null;
                m_selectedEdge=-1;
                m_selectedVertex=-1;
                m_dragging=false;
                m_file=f;
                repaint();
            }
            catch (Exception e)
            {
                e.printStackTrace();
                return false;
            }
            finally
            {
                ois.close();
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    public boolean save(File f)
    {
        try
        {
            ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(f));
            try
            {
                oos.writeObject(new Integer(m_version));
                oos.writeObject(m_polygons);
                m_file=f;
            }
            catch (Exception e)
            {
                e.printStackTrace();
                return false;
            }
            finally
            {
                oos.close();
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    public ArrayList getPolygons()
    {
        return m_polygons;
    }

    public int getZoomLevel()
    {
        return m_zoom;
    }

    public void setZoomLevel(int zoomLevel)
    {
        if (m_zoom<1)
            return;
        m_zoom=zoomLevel;
        m_selectedVertex=-1;
        m_selectedEdge=-1;
        repaint();
    }

	public Editor(String backdrop)
	{
		super();

        m_file=null;

        m_zoom=3;

        m_polygons=new ArrayList();
                  /*
        ArrayList polygon=new ArrayList();
            polygon.add(new Point(0,0));
            polygon.add(new Point(50,0));
            polygon.add(new Point(50,100));
            polygon.add(new Point(0,100));
        m_polygons.add(polygon);

        polygon=new ArrayList();
            polygon.add(new Point(50,101));
            polygon.add(new Point(100,101));
            polygon.add(new Point(150,101));
            polygon.add(new Point(100,200));
            polygon.add(new Point(50,200));
        m_polygons.add(polygon);
        */

        m_selectedPolygon=null;
		m_selectedVertex=-1;
		m_selectedEdge=-1;
		m_dragging=false;

		m_backdrop=Toolkit.getDefaultToolkit().getImage(backdrop);

		MediaTracker mt=new MediaTracker(this);
		mt.addImage(m_backdrop,0);

        try
		{
			mt.waitForAll();
		}
		catch (InterruptedException e)
		{
			;
		}

        getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete");
        getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK), "copy");
        getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK), "paste");

        getActionMap().put("copy", new AbstractAction("copy")
        {
            public void actionPerformed(ActionEvent evt)
            {
                if (m_selectedPolygon!=null)
                {
                    m_copyBuffer=duplicatePolygon((ArrayList)m_selectedPolygon);
                }
            }
        });

        getActionMap().put("paste", new AbstractAction("paste")
        {
            public void actionPerformed(ActionEvent evt)
            {
                if (m_copyBuffer!=null)
                {
                    Iterator i=m_copyBuffer.iterator();
                    while(i.hasNext())
                    {
                        Point p=(Point)i.next();
                        p.translate(10,5);
                    }
                    m_polygons.add(m_copyBuffer);
                    m_selectedPolygon=m_copyBuffer;
                    m_selectedVertex=-1;
                    m_selectedEdge=-1;
                    m_dragging=false;
                    m_copyBuffer=duplicatePolygon((ArrayList)m_selectedPolygon);
                    repaint();
                }
            }
        });

		getActionMap().put("delete", new AbstractAction("delete")
		{
			public void actionPerformed(ActionEvent evt)
			{
				if (m_selectedPolygon!=null)
				{
					ArrayList polygon=(ArrayList)m_selectedPolygon;

					if (m_selectedVertex!=-1)
					{
						polygon.remove(m_selectedVertex);
						m_selectedVertex=-1;
						m_selectedEdge=-1;
						if (polygon.size()<3)
						{
							m_polygons.remove(m_polygons.indexOf(m_selectedPolygon));
							m_selectedPolygon=null;
						}
						repaint();
                        return;
					}

                    if (JOptionPane.showConfirmDialog(Editor.this.getParent(), "Are you sure?", "Delete polygon", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION)
                    {
                        m_polygons.remove(m_polygons.indexOf(m_selectedPolygon));
					    m_selectedPolygon=null;
                        m_selectedVertex=-1;
                        m_selectedEdge=-1;
						repaint();
                        return;
                    }
				}
			}
		});

		setRequestFocusEnabled(true);
		requestFocus();

		addMouseListener(new MouseAdapter()
		{
            public void mouseEntered(MouseEvent e)
            {
                requestFocus();
            }

			public void mousePressed(MouseEvent e)
			{
                int mx=e.getX()/2/m_zoom;
                int my=e.getY()/m_zoom;
                requestFocus();

				if (m_selectedVertex!=-1)
				{
					m_dragging=true;
                    repaint();
				}
                else
                {
                    if (m_selectedPolygon!=null)
                    {
                        Polygon poly=new Polygon();
                        Iterator j=((ArrayList)m_selectedPolygon).iterator();
                        while (j.hasNext())
                        {
                            Point p=(Point)j.next();
                            poly.addPoint((int)p.getX(), (int)p.getY());
                        }

                        if (poly.contains(mx,my))
                        {
                            m_dragging=true;
                            m_dragReferenceX=mx;
                            m_dragReferenceY=my;
                            repaint();
                        }
                    }
                }
			}

			public void mouseReleased(MouseEvent e)
			{
				if (m_selectedVertex!=-1)
				{
					m_selectedEdge=-1;
					m_dragging=false;
					repaint();
				}
			}

			public void mouseClicked(MouseEvent e)
			{
                int mx=e.getX()/2/m_zoom;
				int my=e.getY()/m_zoom;

                if (e.getClickCount()==1)
                {
                    if ((m_selectedEdge!=-1) && (m_selectedPolygon!=null))
                    {
                        ArrayList al=(ArrayList)m_selectedPolygon;
                        m_selectedEdge=(m_selectedEdge+1)%al.size();
                        al.add(m_selectedEdge, new Point(m_nx, m_ny));

                        m_selectedVertex=m_selectedEdge;
                        m_selectedEdge=-1;
                        m_dragging=false;
                        repaint();
                        return;
                    }

                    Iterator i=m_polygons.iterator();
                    while (i.hasNext())
                    {
                        ArrayList polygon=(ArrayList)i.next();
                        Polygon poly=new Polygon();
                        Iterator j=polygon.iterator();
                        while (j.hasNext())
                        {
                            Point p=(Point)j.next();
                            poly.addPoint((int)p.getX(), (int)p.getY());
                        }

                        if (poly.contains(mx,my))
                        {
                            m_selectedPolygon=polygon;
                            m_selectedVertex=-1;
                            m_selectedEdge=-1;
                            m_dragging=false;
                            repaint();
                            return;
                        }
                    }

                    m_selectedPolygon=null;
                    m_selectedVertex=-1;
                    m_selectedEdge=-1;
                    m_dragging=false;
                    repaint();
                }
                else if (e.getClickCount()==2)
                {
                    ArrayList newPoly=new ArrayList();
                        newPoly.add(new Point(mx,my-20));
                        newPoly.add(new Point(mx+20,my+20));
                        newPoly.add(new Point(mx-20,my+20));
                    m_polygons.add(newPoly);
                    m_selectedPolygon=newPoly;
                    m_selectedVertex=-1;
                    m_selectedEdge=-1;
                    m_dragging=false;
                    repaint();
                    return;
                }
			}
		});

        addMouseMotionListener(new MouseMotionAdapter()
        {
            public void mouseDragged(MouseEvent e)
            {
                if (!m_dragging)
                    return;

				if (m_selectedVertex!=-1)
				{
					int mx=e.getX()/2/m_zoom;
					int my=e.getY()/m_zoom;

					ArrayList polygon=(ArrayList)m_selectedPolygon;
					Point point=(Point)polygon.get(m_selectedVertex);
					point.move(mx,my);

					repaint();
				}
                else
                {
                    if (m_selectedPolygon!=null)
                    {
                        int mx=e.getX()/2/m_zoom;
					    int my=e.getY()/m_zoom;
                        int dx=mx-m_dragReferenceX;
                        int dy=my-m_dragReferenceY;

                        ArrayList polygon=(ArrayList)m_selectedPolygon;
                        Iterator i=polygon.iterator();
                        while(i.hasNext())
                        {
                            Point p=(Point)i.next();
                            p.translate(dx,dy);
                        }

                        m_dragReferenceX=mx;
                        m_dragReferenceY=my;

					    repaint();
                    }
                }
            }

			public void mouseMoved(MouseEvent e)
			{
				double mx=e.getX()/2/m_zoom;
				double my=e.getY()/m_zoom;
				mx*=2;
				boolean redraw=false;

				m_dragging=false;

				if (m_selectedPolygon!=null)
				{
					// Find closest vertex
                    int selectedIndex=-1;
                    double minRadius=Double.MAX_VALUE;
                    int index=0;

                    if (!e.isControlDown())
                    {
                        Iterator j=((ArrayList)m_selectedPolygon).iterator();
                        while (j.hasNext())
                        {
                            Point p=(Point)j.next();
                            double x=p.getX()*2;
                            double y=p.getY();
                            double r=(x-mx)*(x-mx)+(y-my)*(y-my);
                            if (r<minRadius)
                            {
                                minRadius=r;
                                selectedIndex=index;
                            }
                            index++;
                        }

                        if (minRadius>10*10)
                            selectedIndex=-1;

                        if (selectedIndex!=m_selectedVertex)
                        {
                            m_selectedVertex=selectedIndex;
                            redraw=true;
                        }
                    }
                    else
                        m_selectedVertex=-1;


					// Find closest edge
					selectedIndex=-1;
					minRadius=Double.MAX_VALUE;
					ArrayList al=(ArrayList)m_selectedPolygon;
					for (index=0;index<al.size();index++)
					{
						Point p1=(Point)al.get(index);
						Point p2=(Point)al.get((index+1)%al.size());
						double x1=p1.getX()*2;
						double y1=p1.getY();
						double x2=p2.getX()*2;
						double y2=p2.getY();
						double ux=mx-x1;
						double uy=my-y1;
						double vx=x2-x1;
						double vy=y2-y1;
						double vl=Math.sqrt(vx*vx+vy*vy);
						if (vl!=0)
						{
							ux/=vl;
							uy/=vl;
							vx/=vl;
							vy/=vl;
							double s=ux*vx+uy*vy;
							if ((s>=0) && (s<=1))
							{
								double va=-vy;
								double vb=vx;
								double vc=-(va*x1+vb*y1);
								double d=Math.abs(va*mx+vb*my+vc);

								if ((d<minRadius) && (d<10))
								{
									m_nx=(int)((x1+s*(x2-x1))/2+0.5);
									m_ny=(int)(y1+s*(y2-y1)+0.5);
									minRadius=d;
									selectedIndex=index;
								}
							}
						}
					}

					if (minRadius>10)
						selectedIndex=-1;

//					if (selectedIndex!=m_selectedEdge)
//					{
						m_selectedEdge=selectedIndex;
						redraw=true;
//					}

					if (redraw)
					{
						repaint();
					}
				}
			}
        });
	}

    public Image getBackDrop()
    {
        return m_backdrop;
    }

	public Dimension getPreferredSize()
	{
		return new Dimension(m_backdrop.getWidth(null)*m_zoom, m_backdrop.getHeight(null)*m_zoom);
	}

	public void update(Graphics g)
	{
		paint(g);
	}

    public static Polygon getMCOutline(int[] ix, int[] iy, double r)
    {
        ArrayList output=new ArrayList();

        for (int i=0;i<ix.length;i++)
        {
            double x1=ix[i%ix.length]*2;
            double y1=iy[i%ix.length];
            double x2=ix[(i+1)%ix.length]*2;
            double y2=iy[(i+1)%ix.length];
            double x3=ix[(i+2)%ix.length]*2;
            double y3=iy[(i+2)%ix.length];

            double ux=x2-x1;
            double uy=y2-y1;
            double vx=x3-x2;
            double vy=y3-y2;
            double ul=Math.sqrt(ux*ux+uy*uy);
            double vl=Math.sqrt(vx*vx+vy*vy);
            ux/=ul;
            uy/=ul;
            vx/=vl;
            vy/=vl;
            double cosa=ux*vx+uy*vy;

            double ua=-uy;
            double ub=ux;
            double uc=-(ua*x1+ub*y1);
            double va=-vy;
            double vb=vx;
            double vc;

            double d=ua*x3+ub*y3+uc;	// Orthogonal distance to line

            // Stroke lines
            double ox1=x1-ua*r;
            double oy1=y1-ub*r;
            double ox2=x2-ua*r;
            double oy2=y2-ub*r;
            double ox3=x2-va*r;
            double oy3=y2-vb*r;
            double ox4=x3-va*r;
            double oy4=y3-vb*r;

            //output.addPoint((int)(ox1)/2,(int)oy1);

            if (d>0)			// Convex corner, add circle
            {
                output.add(new Point2D.Double(ox2/2,oy2));

                ux=ox2-x2;
                uy=oy2-y2;
                vx=ox3-x2;
                vy=oy3-y2;
                ux/=r;
                uy/=r;
                vx/=r;
                vy/=r;

                // Calculate begin angle
                double bega,enda;
                if (ux>0)
                    bega=-Math.asin(uy);
                else
                    bega=Math.asin(uy)-Math.PI;

                // Calculate end angle
                enda=-Math.asin(vy);
                if (vx>0)
                    enda=-Math.asin(vy);
                else
                    enda=Math.asin(vy)-Math.PI;

                // Draw arc
                double lx=ox2/2;
                double ly=oy2;

                if (bega<enda)
                    enda-=Math.PI*2;

                double step=(enda-bega)/13.0;
                for (int ii=0;ii<13;ii++)
                {
                    bega+=step;
                    double x=(x2+Math.cos(bega)*r)/2;
                    double y=y2-Math.sin(bega)*r;
                    output.add(new Point2D.Double(x,y));
                    lx=x;
                    ly=y;
                }
            }
            else				// Concave corner, clip lines
            {
                uc=-(ua*ox1+ub*oy1);
                vc=-(va*ox3+vb*oy3);

                double tmp=(ub*va-ua*vb);
                if (tmp!=0)
                {
                    double y=(ua*vc-uc*va)/tmp;
                    double x;
                    if (ua!=0)
                        x=-(ub*y+uc)/ua;
                    else
                        x=-(vb*y+vc)/va;
                    x/=2;
                    output.add(new Point2D.Double(x,y));
                }
            }
        }

        // Remove interal self-intersecting loops
        for (int i=0; i<output.size(); i++)
        {
            Point2D.Double a_beg=(Point2D.Double)output.get(i);
            Point2D.Double a_end=(Point2D.Double)output.get((i+1)%output.size());

            double x=0,y=0;
            boolean intersect=false;
            int j;
				// Scan forward from i+2 and the whole revolution
            for (j=i+2; j<i+1+output.size(); j++)
            {
                Point2D.Double b_beg=(Point2D.Double)output.get(j%output.size());
                Point2D.Double b_end=(Point2D.Double)output.get((j+1)%output.size());

                double x1=a_beg.getX();
                double y1=a_beg.getY();
                double x2=a_end.getX();
                double y2=a_end.getY();
                double x3=b_beg.getX();
                double y3=b_beg.getY();
                double x4=b_end.getX();
                double y4=b_end.getY();
                double numerator1   = (x4-x3)*(y1-y3) - (y4-y3)*(x1-x3);
                double numerator2   = (x2-x1)*(y1-y3) - (y2-y1)*(x1-x3);
                double denominator = (y4-y3)*(x2-x1) - (x4-x3)*(y2-y1);
                if ((denominator!=0) && (numerator1!=0) && (numerator2!=0))
                {
                    double t1=numerator1/denominator;
                    double t2=numerator2/denominator;
                    if ((t1>=0) && (t1<=1) && (t2>=0) && (t2<=1))
                    {
                        x=x1+t1*(x2-x1);
                        y=y1+t1*(y2-y1);
                        intersect=true;
                        break;
                    }
                }
            }

            if (intersect)
            {
                int verticesToRemoveForward=j-i;
				int verticesToRemoveBackward=(i+output.size())-j;				
				int verticesToRemove=verticesToRemoveForward;
//                System.out.println("removing "+verticesToRemoveForward+"/"+verticesToRemoveBackward+" i="+(i%output.size())+" j="+(j%output.size()));

				if (verticesToRemoveForward>verticesToRemoveBackward)
				{
					i=j;
					verticesToRemove=verticesToRemoveBackward;
				}

                for (int l=0;l<verticesToRemove;l++)
                    output.remove((i+1)%output.size());
                output.add((i+1)%output.size(), new Point2D.Double(x,y));

				// Restart i-loop
				i=-1;
            }
        }

        // Output polygon and remove duplicate vertices.
        Polygon polygon=new Polygon();
        int lastx=-100,lasty=-100;
        for (int i=0; i<output.size(); i++)
        {
            Point2D.Double point=(Point2D.Double)output.get(i);
            int x=(int)(point.getX()+1);
            int y=(int)(point.getY()+0.5);
            if ((x!=lastx) ||(y!=lasty))
            {
                double d=Math.sqrt((x-lastx)*(x-lastx) + (y-lasty)*(y-lasty));
                if (d>=3.0)
                {
                    polygon.addPoint(x, y);
                    lastx=x;
                    lasty=y;
                }
            }
        }

        return polygon;
    }

	public void paint(Graphics g)
	{
		int w=m_backdrop.getWidth(null)/2;
		int h=m_backdrop.getHeight(null);

        if (m_offscreen==null)
            m_offscreen=createImage(w,h);

        Graphics os=m_offscreen.getGraphics();

        os.drawImage(m_backdrop,0,0,w,h,null);

        int x[],y[];

        Iterator i;
        i=m_polygons.iterator();
		os.setColor(new Color(255,255,255,255));
        while (i.hasNext())
        {
            ArrayList polygon=(ArrayList)i.next();
			if (polygon==m_selectedPolygon)
			{
	            x=new int[polygon.size()];
    	        y=new int[polygon.size()];

        	    Iterator j=polygon.iterator();
	            int index=0;
	            while (j.hasNext())
	            {
	                Point p=(Point)j.next();
	                x[index]=(int)p.getX();
	                y[index]=(int)p.getY();
	                index++;
	            }

				Polygon outline=getMCOutline(x,y,7.5);
    	        //os.fillPolygon(outline);
				os.drawPolygon(outline);
			}
        }

        i=m_polygons.iterator();
        while (i.hasNext())
        {
            ArrayList polygon=(ArrayList)i.next();
            x=new int[polygon.size()];
            y=new int[polygon.size()];

            Iterator j=polygon.iterator();
            int index=0;
            while (j.hasNext())
            {
                Point p=(Point)j.next();
                x[index]=(int)p.getX();
                y[index]=(int)p.getY();
                index++;
            }

			if (polygon==m_selectedPolygon)
			    os.setColor(new Color(255,255,96,128));
			else
			    os.setColor(new Color(255,255,96,96));
            os.fillPolygon(x,y,x.length);

			if (polygon==m_selectedPolygon)
			{
    	        //os.setColor(new Color(255,255,255));
	            //os.drawPolygon(x,y,x.length);
        	    os.setColor(new Color(0,0,255));
	            for (int k=0;k<x.length;k++)
				{
					if ((k==m_selectedEdge) && (!m_dragging))
					{
						os.setColor(new Color(255,255,0));
						os.drawLine(x[k],y[k],x[(k+1)%x.length], y[(k+1)%x.length]);
		        	    os.setColor(new Color(0,0,255));
					}

					if ((k==m_selectedVertex) && (!m_dragging))
	    	            os.fillRect(x[k]-1,y[k]-1,3,3);
					else
	    	            os.fillRect(x[k],y[k],1,1);
				}

				if ((m_selectedEdge!=-1) && (!m_dragging))
				{
					os.setColor(Color.BLACK);
					os.fillRect(m_nx,m_ny,1,1);
				}
			}
			//else
			//	os.drawPolygon(x,y,x.length);
        }

        g.drawImage(m_offscreen, 0,0, w*m_zoom*2, h*m_zoom, null);
	}
}

class Exporter extends JDialog
{
    private Editor m_editor;
    private JTextArea m_codePreview;
    private ArrayList m_polygons;
    private Preview m_preview;
	private JRadioButton humanReadable;
	private JRadioButton rleData;
	private TreeMap m_holes;
	private String m_holeFile;

    class Preview extends JPanel
    {
        public static final int VIEWMODE_HUMAN_READABLE = 0;
        public static final int VIEWMODE_RLE_DATA       = 1;

        private Image m_offScreen;
        private Editor m_editor;
        private Image m_backDrop;
        private Image m_humanReadable;
        private Image m_rle;
        private int m_raw[];
        private int m_viewMode;

        public Preview(Editor editor)
        {
            m_editor=editor;
            m_viewMode=VIEWMODE_HUMAN_READABLE;

            int w=m_editor.getBackDrop().getWidth(null)/2;
            int h=m_editor.getBackDrop().getHeight(null);

            m_backDrop=GrayFilter.createDisabledImage(m_editor.getBackDrop());
            m_humanReadable=m_editor.createImage(w,h);

            m_raw=new int[w*h];
            m_rle=m_editor.createImage(w,h);

            MediaTracker mt=new MediaTracker(this);
            mt.addImage(m_backDrop,0);
            mt.addImage(m_humanReadable,1);
            mt.addImage(m_rle,2);
            try
            {
                mt.waitForAll();
            }
            catch (InterruptedException e)
            {
                ;
            }

            Graphics os=m_humanReadable.getGraphics();
            Graphics rle=m_rle.getGraphics();
            os.drawImage(m_backDrop,0,0,w,h,null);

            rle.setColor(new Color(255,255,0));
            rle.fillRect(0,0,w,h);

            Iterator i=m_polygons.iterator();
            while (i.hasNext())
            {
                Polygon polygon=(Polygon)i.next();
                os.setColor(new Color(255,255,96,96));
                os.fillPolygon(polygon);

                rle.setColor(new Color(0,255,0));
                rle.fillPolygon(polygon);

                // Draw edges
                for (int ei=0;ei<polygon.npoints;ei++)
                {
                    int x1=polygon.xpoints[ei];
                    int y1=polygon.ypoints[ei];
                    int x2=polygon.xpoints[(ei+1)%polygon.npoints];
                    int y2=polygon.ypoints[(ei+1)%polygon.npoints];
                    int dx=-(x2-x1)*2;
                    int dy=y2-y1;
                    float _h=(float)Math.atan2((double)dy,(double)dx);
                    if (_h<0)
                        _h+=Math.PI*2.0f;

                    float _s=1.0f;
                    float _v=1.0f;
                    float rgb[] = hsv2rgb(_h, _s, _v);
                    os.setColor(new Color(rgb[0],rgb[1],rgb[2]));
                    os.drawLine(x2,y2,x1,y1);

                    int c=(int)((_h/(Math.PI*2.0f))*256.0f+0.5f);
					c+=1024;
					c&=255;

                    // Flip, rotate and fix... so it gets right for the engine... =)
                    c+=128;
                    c&=254;

                    rle.setColor(new Color(c,c,c));
                    rle.drawLine(x2,y2,x1,y1);
                }

                // Draw averaged vertices
                for (int ei=0;ei<polygon.npoints;ei++)
                {
                   int x1=polygon.xpoints[ei];
                   int y1=polygon.ypoints[ei];
                   int x2=polygon.xpoints[(ei+1)%polygon.npoints];
                   int y2=polygon.ypoints[(ei+1)%polygon.npoints];
                   int x3=polygon.xpoints[(ei+2)%polygon.npoints];
                   int y3=polygon.ypoints[(ei+2)%polygon.npoints];
                   double dx1=-(x3-x2)*2;
                   double dy1=y3-y2;
                   double dx2=-(x2-x1)*2;
                   double dy2=y2-y1;
                   if ((Math.abs(dx1)+Math.abs(dy1)>0) && (Math.abs(dx2)+Math.abs(dy2)>0))
                   {
                        float _h1=(float)Math.atan2((double)dy1,(double)dx1);
                        if (_h1<0)
                           _h1+=Math.PI*2.0f;
                        float _h2=(float)Math.atan2((double)dy2,(double)dx2);
                        if (_h2<0)
                           _h2+=Math.PI*2.0f;

                        float _s=1.0f;
                        float _v=1.0f;
                        float rgb1[] = hsv2rgb(_h1, _s, _v);
                        float rgb2[] = hsv2rgb(_h2, _s, _v);
                        float rgb[]=new float[3];
                        rgb[0]=(rgb1[0]+rgb2[0])/2.0f;
                        rgb[1]=(rgb1[1]+rgb2[1])/2.0f;
                        rgb[2]=(rgb1[2]+rgb2[2])/2.0f;
                        os.setColor(new Color(rgb[0],rgb[1],rgb[2]));
                        os.fillRect(x2,y2,1,1);

                        int c1=(int)((_h1/(Math.PI*2.0f))*256.0f+0.5f);
                        int c2=(int)((_h2/(Math.PI*2.0f))*256.0f+0.5f);

						c1+=1024;
						c2+=1024;
						c1&=255;
						c2&=255;
						
                        int c=(c1+c2)/2 + (Math.abs(c1-c2)>128?128:0);

                        // Flip, rotate and fix... so it gets right for the engine... =)
                        c+=128;
                        c&=254;

                        rle.setColor(new Color(c,c,c));
                        rle.fillRect(x2,y2,1,1);
                   }
                }
            }

            PixelGrabber pg=new PixelGrabber(m_rle, 0, 0, w, h, m_raw, 0, w);
            try
            {
                pg.grabPixels();
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }

            // AARRGGBB (AA=0xFF=Opaque AA=0x00=Transparent)
            int angles[]=new int[1000]; // x,a,x,a,x,a...
            int nrAngles=0;
            for (int sy=0;sy<h;sy++)
            {
                // Analyse line
                nrAngles=0;
                for (int sx=0;sx<w;sx++)
                {
                    int v=m_raw[sx+sy*w];
                    if (v==0xff00ff00)  // Solid
                    {

                    }
                    else if (v==0xffffff00)  // Air
                    {

                    }
                    else                // Edge
                    {
                        angles[nrAngles*2+0]=sx;
                        angles[nrAngles*2+1]=v&0xfe;
                        nrAngles++;
                    }
                }

                // Angle extend fill lines
                if (nrAngles>0)
                {
                    for (int sx=0;sx<w;sx++)
                    {
                        int v=m_raw[sx+sy*w];
                        if (v==0xff00ff00)  // Solid
                        {
                            int nearestX=Integer.MAX_VALUE;
                            int angle=0;
                            for (int l=0;l<nrAngles;l++)
                            {
                                int x=angles[l*2+0];
                                if (Math.abs(x-sx)<nearestX)
                                {
                                    nearestX=Math.abs(x-sx);
                                    angle=angles[l*2+1];
                                }
                            }
									 angle&=0xfe;
                            m_raw[sx+sy*w]=0xff000000 | (angle<<16) | (angle<<8) | angle;
                        }
                    }
                }
            }
            m_rle=Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(w,h,m_raw,0,w));
        }

        public void setViewMode(int viewMode)
        {
            m_viewMode=viewMode;
            repaint();
        }

        public int getViewMode()
        {
            return m_viewMode;
        }

        public void update(Graphics g)
        {
            paint(g);
        }

        // 0<=h<2*PI. 0<=r,g,b,s,v<=1
        private float[] hsv2rgb(float h, float s, float v)
        {
            float rgb[]=new float[3];
            float r=0, g=0, b=0, p, q, f, t;
            int i;

            if (s==0)
            {
                rgb[0] = rgb[1] = rgb[2] = 1.0f;
                return rgb;
            }

            h /= Math.PI/3;
            i = (int)h;
            f = h - i;
            p = v * (1.0f-s);
            q = v * (1.0f-(s*f));
            t = v * (1.0f-(s*(1.0f-f)));

            switch ( i )
            {
                case 0:  r = v;  g = t;  b = p; break;
                case 1:  r = q;  g = v;  b = p; break;
                case 2:  r = p;  g = v;  b = t; break;
                case 3:  r = p;  g = q;  b = v; break;
                case 4:  r = t;  g = p;  b = v; break;
                case 5:  r = v;  g = p;  b = q; break;
            }

            rgb[0] = r;
            rgb[1] = g;
            rgb[2] = b;

            return rgb;
        }

        public void paint(Graphics g)
        {
            int w=m_backDrop.getWidth(null)/2;
            int h=m_backDrop.getHeight(null);

            if (m_viewMode==VIEWMODE_HUMAN_READABLE)
                g.drawImage(m_humanReadable, 0,0, w*2*2, h*2, null);
            else if (m_viewMode==VIEWMODE_RLE_DATA)
                g.drawImage(m_rle, 0,0, w*2*2, h*2, null);
        }

        public Dimension getPreferredSize()
        {
            return new Dimension(m_backDrop.getWidth(null)*2, m_backDrop.getHeight(null)*2);
        }

        public int getLevelWidth()
        {
            return m_editor.getBackDrop().getWidth(null)/2;
        }

        public int getLevelHeight()
        {
            return m_editor.getBackDrop().getHeight(null);
        }

        public int[] getRaw()
        {
            return m_raw;
        }

		public void setRaw(int[] raw, Image i)
		{
			m_raw=raw;
			m_rle=i;
			setViewMode(VIEWMODE_RLE_DATA);
		}
    }

    private void generateCode(String name, String tableName)
    {
        StringBuffer sb=new StringBuffer();

        int w=m_preview.getLevelWidth();
        int h=m_preview.getLevelHeight();
        int[] raw=m_preview.getRaw();

		int duplicateTable[]=new int[h];
		int rleData[][]=new int[h][256];
		int rleDataSize[]=new int[h];
        int pointertable[]=new int[h];

        int size=0;
        int duplicates=0;

		// Locate duplicates
		for (int row=0; row<h; row++)
		{
			for (int x=0;x<256;x++)
				rleData[row][x]=-1;
			rleDataSize[row]=0;
			duplicateTable[row]=row;
		}

		for (int row=1; row<h; row++)
		{
			boolean same=false;
 			int crow=0;
            for (crow=0; crow<row; crow++)
            {
                same=true;
                for (int x=0;x<w;x++)
                {
                    if (raw[x+row*w]!=raw[x+crow*w])
                    {
                        same=false;
                        break;
                    }
                }
                if (same)
                    break;
            }
            if (same)
            {
                duplicates++;
				duplicateTable[row]=crow;				
            }
		}

		// RLE-encode
        for (int row=0; row<h; row++)
        {
			if (duplicateTable[row]!=row)
			{
				pointertable[row]=duplicateTable[row];
				continue;				
			}

            pointertable[row]=row;

            boolean first=true;
            boolean changeOnLastX=false;

            int lastValue=raw[row*w];
            if (lastValue==0xffffff00)  // Air
                lastValue=0x55;
            else
                lastValue=lastValue&0xff;

            for (int x=0;x<w;x++)
            {
                int v=raw[x+row*w];
                if (v==0xffffff00)  // Air
                    v=0x55;
                else if ((v&0x00ff0000) == 0x00ff0000)	// Free value
					v=v&0xff;
				else
                    v=v&0xfe;

                if (v!=lastValue)
                {
					rleData[row][rleDataSize[row]++] = lastValue; 
                    if (x<w-1)
						rleData[row][rleDataSize[row]++] = x; 
					else
						rleData[row][rleDataSize[row]++] = 0xa1; 

                    if (x==w-1)
                        changeOnLastX=true;
                    lastValue=v;
                }
            }
            if (!changeOnLastX)
            {
				rleData[row][rleDataSize[row]++] = lastValue; 				
				rleData[row][rleDataSize[row]++] = 0xa1; 
            }
        }

		// Make the values relative
		for (int row=0; row<h; row++)
		{
			if (duplicateTable[row]==row)
			{
				for (int i=rleDataSize[row]-1; i>3; i-=2)
					rleData[row][i]=rleData[row][i]-rleData[row][i-2];
			}
		}

		// Hole stuffing -------------------------------------------------------------------------------------------------

		readHoleFile();

		// hole2rows holds the resulting rows that where stuffed into the hole denoted by key (key is infact a string of [hole size:labelindex])
		TreeMap hole2rows=new TreeMap(new Comparator()
		{
			public int compare(Object o1, Object o2)
			{
				String s1=(String)o1;
				String s2=(String)o2;
				String split1[];
				String split2[];
				split1=s1.split(":");
				split2=s2.split(":");
				int i1=Integer.parseInt(split1[0]);
				int i2=Integer.parseInt(split2[0]);
				int l1=Integer.parseInt(split1[1]);
				int l2=Integer.parseInt(split2[1]);
				return ((i2-i1)*10000)+(l1-l2);
			}
		});

		// Simble data structor that marks if a row is stuff into a hole or not
		HashSet stuffedRows=new HashSet();

		int totalHoleBytes=0;
		int totalUsedBytes=0;
		{
			TreeMap sortedRows=new TreeMap(new Comparator()
			{
				public int compare(Object o1, Object o2)
				{
					int i1=((Integer)o1).intValue();
					int i2=((Integer)o2).intValue();
					return i2-i1;
				}
			});

			// Sort all rows according to size. Map sizes and array of rows.
			for (int row=0; row<h; row++)
			{
				if (duplicateTable[row]==row)
				{
					Integer rowSize=new Integer(rleDataSize[row]);
					Integer rowIndex=new Integer(row);
	
					ArrayList rows=(ArrayList)sortedRows.get(rowSize);
					if (rows==null)
					{
						rows=new ArrayList();
						sortedRows.put(rowSize, rows);
					}
					rows.add(rowIndex);
				}
			}

/*
			// Print row size distribution
			System.out.println("Row size distribution:");
			Iterator it=sortedRows.keySet().iterator();
			while (it.hasNext())
			{
				Integer rowSize=(Integer)it.next();
				ArrayList rows=(ArrayList)sortedRows.get(rowSize);
				System.out.print("\t"+rowSize+"b: ");
				for (int j=0;j<rows.size();j++)
					System.out.print(rows.get(j)+" ");
				System.out.println();
			}
*/

			// Fill all holes as much as possible
			Iterator holeI=m_holes.keySet().iterator();
			while (holeI.hasNext())
			{
				Object holeKey=holeI.next();
				int amount=((Integer)m_holes.get(holeKey)).intValue();
				int holeSize=((Integer)holeKey).intValue();

				int used=0;
				for (int subHole=0;subHole<amount;subHole++)
				{
					String subHoleKey=holeKey+":"+(amount-subHole-1);
					totalHoleBytes+=holeSize;
					int holeSizeLeft=holeSize;
//					System.out.println("Filling hole "+(subHole+1)+"/"+amount+" of size "+holeSize);

					Iterator rowI=sortedRows.keySet().iterator();
					while((rowI.hasNext()) && (holeSizeLeft>0))
					{
						Object rowKey=rowI.next();
						int rowSize=((Integer)rowKey).intValue();
						if (rowSize>holeSizeLeft)
							continue;
						ArrayList rows=(ArrayList)sortedRows.get(rowKey);
						if (rows.size()==0)
							continue;
	
						while((rows.size()>0) && (rowSize<=holeSizeLeft) && (holeSizeLeft>0))
						{
							holeSizeLeft-=rowSize;
							Object row=rows.remove(0);

							ArrayList rowsInHole=(ArrayList)hole2rows.get(subHoleKey);
							if (rowsInHole==null)
							{
								rowsInHole=new ArrayList();
								hole2rows.put(subHoleKey, rowsInHole);
							}
							rowsInHole.add(row);
							stuffedRows.add(row);

//							System.out.println("\tFitted row "+row+". size="+rowSize+" leftInHole="+holeSizeLeft);
							totalUsedBytes+=rowSize;
						}
					}

					if (holeSizeLeft!=holeSize)
						used++;
				}

				amount-=used;
				m_holes.put(holeKey, new Integer(amount));
			}

//			System.out.println("Managed to fill "+totalUsedBytes+"/"+totalHoleBytes+" bytes into the holes (holes utilization "+(totalUsedBytes*100/totalHoleBytes)+"%)");
		}

		// Trim last byte if possible of remaining rows
		int trimmedLastBytes=0;
		for (int row=0; row<h-1; row++)
		{
			if ((duplicateTable[row]==row) && (!stuffedRows.contains(new Integer(row))))
			{
				int nextrow=row+1;
				for (; nextrow<h; nextrow++)
				{
					if ((duplicateTable[nextrow]==nextrow) && (!stuffedRows.contains(new Integer(nextrow))))
						break;
				}

				if (nextrow<h)
				{
					if (rleData[nextrow][0] >= rleData[row][rleDataSize[row]-1])
					{
						rleDataSize[row]--;
						trimmedLastBytes++;
					}
				}
			}
		}

		// output source --------------------------------------------------------------------------------------------------

		// Output holes that are left
		try
		{
			PrintWriter out=new PrintWriter(new BufferedWriter(new FileWriter(m_holeFile+".left")));

			Iterator holeI=m_holes.keySet().iterator();
			while (holeI.hasNext())
			{
				Object holeKey=holeI.next();
				int amount=((Integer)m_holes.get(holeKey)).intValue();
				if (amount==0)
					continue;
				int holeSize=((Integer)holeKey).intValue();
				out.println((holeSize/8)+","+amount);
			}
			out.close();
			System.out.println("Generated "+m_holeFile+".left");
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		
		// Output hole stuffed rows
		Iterator holeI=hole2rows.keySet().iterator();
		while(holeI.hasNext())
		{
			String subHoleKey=(String)holeI.next();
			String split[]=subHoleKey.split(":");
			int holeSize=Integer.parseInt(split[0]);
			int labelIndex=Integer.parseInt(split[1]);
			sb.append("*=empty"+holeSize+"bytes"+labelIndex+"\n");

			Iterator rowI=((ArrayList)hole2rows.get(subHoleKey)).iterator();
			while(rowI.hasNext())
			{
				int row=((Integer)rowI.next()).intValue();
				sb.append(name+row+" .byte ");

				for (int i=0; i<rleDataSize[row]; i++)
	            {
	                sb.append("$"+Integer.toHexString(rleData[row][i]));
	                if (i<rleDataSize[row]-1)
	                    sb.append(",");
	                if ((i&1)==1)
	                    sb.append(" ");
	            }
    	        sb.append("\n");

				// Locate duplicate rows to row for conformance in output
				for (int x=row+1;x<h;x++)
				{
					if (duplicateTable[x]==row)
						sb.append(";row"+x+" is a duplicate of row"+row+"\n");
				}
			}	

			sb.append("\n");
		}

		// Output non hole stuffed rows
		sb.append("*=nonstuffedrows\n");
		for (int row=0; row<h; row++)
		{
			if (stuffedRows.contains(new Integer(row)))
				continue;

			if (duplicateTable[row]!=row)
			{
				if (!stuffedRows.contains(new Integer(duplicateTable[row])))
					sb.append(";row"+row+" is a duplicate of row"+duplicateTable[row]+"\n");
				continue;
			}

			sb.append(name+row+" .byte ");
			for (int i=0; i<rleDataSize[row]; i++)
			{
				sb.append("$"+Integer.toHexString(rleData[row][i]));
				size++;
				if (i<rleDataSize[row]-1)
					sb.append(",");
				if ((i&1)==1)
					sb.append(" ");
			}
			sb.append("\n");
			sb.append(";$"+Integer.toHexString(size)+"\n");
		}
        sb.append("\n");

		// Output pointer table
        sb.append(tableName+"low\n");
        for (int row=0; row<h; row++)
        {
            if ((row%8)==0)
                sb.append("   .byte ");

            sb.append("<"+name);
            sb.append(pointertable[row]);
            size+=1;

            if ((row%8)==7)
                sb.append("\n");
            else
                sb.append(", ");
        }

        sb.append("\n");
        sb.append(tableName+"high\n");
        for (int row=0; row<h; row++)
        {
            if ((row%8)==0)
                sb.append("   .byte ");
            sb.append(">"+name);
            sb.append(pointertable[row]);
            size+=1;

            if ((row%8)==7)
                sb.append("\n");
            else
                sb.append(", ");
        }

        sb.append("\n");
        sb.append(";Duplicate lines = "+duplicates+"\n");
        sb.append(";Trimmed last bytes = "+trimmedLastBytes+"\n");
		sb.append(";Stuffed hole bytes = "+totalUsedBytes+"/"+totalHoleBytes+"\n");
        sb.append(";Total size = "+size+" ($"+Integer.toHexString(size)+")"+"\n");

        m_codePreview.setText(sb.toString());
    }

	private void exportRaw()
	{
		try
		{
			int w=m_preview.getLevelWidth();
			int h=m_preview.getLevelHeight();
			int[] raw=m_preview.getRaw();
			byte out[]=new byte[w*h];
			for (int yp=0;yp<h;yp++)
				for (int xp=0;xp<w;xp++)
				{
					byte b;
					if (raw[xp+yp*w]==0xffffff00)
						b=0x55;
					else if ((raw[xp+yp*w]&0x00ff0000)==0x00ff0000)
						b=(byte)(raw[xp+yp*w]&0xff);
					else
						b=(byte)(raw[xp+yp*w]&0xfe);
					out[xp+yp*w]=b;
				}

			FileOutputStream fos=new FileOutputStream("angles.raw");
			fos.write(out);
			fos.close();

			JOptionPane.showConfirmDialog(this, "Into file: angles.raw", "Raw data exported...", JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE);
		}
		catch (IOException e)
		{
			e.printStackTrace();
			JOptionPane.showConfirmDialog(this, "An exception occured. Check console for information.", "Raw data exported...", JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE);
		}
	}

	private void importAngles()
	{
		JFileChooser chooser = new JFileChooser();
		chooser.setFileFilter(new AngleFileFilter());
		int returnVal = chooser.showOpenDialog(this);
		if (returnVal==JFileChooser.APPROVE_OPTION)
		{
			File file=chooser.getSelectedFile();
			Image i=Toolkit.getDefaultToolkit().getImage(file.getAbsolutePath());
			MediaTracker mt=new MediaTracker(this);
			mt.addImage(i,0);
	        try
			{
				mt.waitForAll();
			}
			catch (InterruptedException e)
			{
				;
			}

			int w=m_preview.getLevelWidth();
			int h=m_preview.getLevelHeight();
			int[] raw=new int[w*h];
			PixelGrabber pg=new PixelGrabber(i,0,0,w,h,raw,0,w);
			try
			{
				pg.grabPixels();
			}
			catch (InterruptedException e)
			{
				;
			}
			m_preview.setRaw(raw,i);
			humanReadable.setEnabled(false);
			rleData.doClick();
			m_preview.repaint();
		}
	}

	private void exportAngles()
	{
		try
		{
			int w=m_preview.getLevelWidth();
			int h=m_preview.getLevelHeight();
			int[] raw=m_preview.getRaw();
			byte out[]=new byte[w*h];
			for (int yp=0;yp<h;yp++)
				for (int xp=0;xp<w;xp++)
				{
					byte b;
					if (raw[xp+yp*w]==0xffffff00)
						b=0x55;
					else
						b=(byte)(raw[xp+yp*w]&0xfe);
					out[xp+yp*w]=b;
				}

			byte r[],g[],b[];
			r=new byte[256];
			g=new byte[256];
			b=new byte[256];
			for (int i=0;i<256;i++)
			{
				r[i]=b[i]=(byte)255;
				g[i]=(byte)0;
			}
			for (int i=0;i<256;i+=2)
				r[i]=g[i]=b[i]=(byte)i;
			r[0x55]=(byte)255;
			g[0x55]=(byte)255;
			b[0x55]=(byte)0;

			ColorModel cm=new IndexColorModel(8,256,r,g,b);
			MemoryImageSource mis=new MemoryImageSource(w,h,cm,out,0,w);

			JFileChooser chooser = new JFileChooser();
			chooser.setFileFilter(new AngleFileFilter());
			int returnVal = chooser.showSaveDialog(this);
			if (returnVal==JFileChooser.APPROVE_OPTION)
			{
				File file=chooser.getSelectedFile();
				FileOutputStream fos=new FileOutputStream(file);
				ImageEncoder ie=new GifEncoder(mis,fos,false);
				ie.encode();
				fos.close();
			}
		}
		catch (IOException e)
		{
			e.printStackTrace();
			JOptionPane.showConfirmDialog(this, "An exception occured. Check console for information.", "Exporting data...", JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE);
		}
	}

	private void readHoleFile()
	{
		m_holes=new TreeMap(new Comparator()
		{
			public int compare(Object o1, Object o2)
			{
				int i1=((Integer)o1).intValue();
				int i2=((Integer)o2).intValue();
				return i2-i1;
			}
		});

		try
		{
			BufferedReader br=new BufferedReader(new FileReader(m_holeFile));

			while(true)
			{
				String line=br.readLine();
				if (line==null)
					break;

				String explode[]=line.split(",");
				int bytes=Integer.parseInt(explode[0])*8;
				int number=Integer.parseInt(explode[1]);
				m_holes.put(new Integer(bytes), new Integer(number));
			}
		
			br.close();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}

    public Exporter(Editor editor, String holeFile)
    {
        super();
        setTitle("Export...");
        setBackground(Color.lightGray);
        setModal(true);
        setResizable(false);

		m_holeFile=holeFile;

        m_editor=editor;

        m_polygons=new ArrayList();

        // Grab the source polygons, outline them and store.
        ArrayList polygons=m_editor.getPolygons();
        int x[],y[];
        Iterator i=polygons.iterator();
		while (i.hasNext())
        {
            ArrayList polygon=(ArrayList)i.next();
            x=new int[polygon.size()];
            y=new int[polygon.size()];

            Iterator j=polygon.iterator();
            int index=0;
            while (j.hasNext())
            {
                Point p=(Point)j.next();
                x[index]=(int)p.getX();
                y[index]=(int)p.getY();
                index++;
            }
            m_polygons.add(Editor.getMCOutline(x,y,7.5));
        }

        GridBagLayout gbl=new GridBagLayout();
        GridBagConstraints gbc=new GridBagConstraints();

        getContentPane().setLayout(gbl);

        gbc.weightx=0;
        gbc.weighty=0;
        gbc.fill=GridBagConstraints.BOTH;

        m_preview=new Preview(m_editor);

        JScrollPane jsp1=new JScrollPane(m_preview,JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
            jsp1.setBorder(BorderFactory.createTitledBorder("Graphical Output"));
            jsp1.setWheelScrollingEnabled(true);
		    jsp1.getViewport().setPreferredSize(new Dimension(640,400));
            jsp1.getViewport().setMaximumSize(new Dimension(640,10000));
            //jsp1.getViewport().setMinimumSize(new Dimension(640,0));
        //getContentPane().add(jsp1, gbc);

        gbc.gridwidth=GridBagConstraints.REMAINDER;

        m_codePreview=new JTextArea();
            m_codePreview.setFont(new Font("Courier New", Font.PLAIN, 12));
            m_codePreview.setAutoscrolls(true);
        JScrollPane jsp2=new JScrollPane(m_codePreview);
            jsp2.setBorder(BorderFactory.createTitledBorder("Code Preview"));
            jsp2.setWheelScrollingEnabled(true);
            jsp2.setPreferredSize(new Dimension(300,400));
        //getContentPane().add(jsp2, gbc);

        getContentPane().add(new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, jsp1, jsp2), gbc);

        gbc.gridwidth=3;//GridBagConstraints.RELATIVE;

        gbc.fill=GridBagConstraints.NONE;
        gbc.anchor=GridBagConstraints.LINE_START;


        JPanel jp=new JPanel();
            jp.setLayout(new FlowLayout(FlowLayout.LEFT));
            jp.setPreferredSize(new Dimension(200,130));
            jp.setBorder(BorderFactory.createTitledBorder("View Mode"));

            humanReadable=new JRadioButton("Human Readable");
                humanReadable.setSelected(true);
                humanReadable.addActionListener(new ActionListener()
                {
                    public void actionPerformed(ActionEvent e)
                    {
                        m_preview.setViewMode(Preview.VIEWMODE_HUMAN_READABLE);
                    }
                });
            jp.add(humanReadable);
            rleData=new JRadioButton("RLE Data");
                rleData.addActionListener(new ActionListener()
                {
                    public void actionPerformed(ActionEvent e)
                    {
                        m_preview.setViewMode(Preview.VIEWMODE_RLE_DATA);
                    }
                });
            jp.add(rleData);			

            ButtonGroup bg=new ButtonGroup();
                bg.add(humanReadable);
                bg.add(rleData);

			JButton exportRaw=new JButton("Save as raw");
				exportRaw.addActionListener(new ActionListener()
				{
					public void actionPerformed(ActionEvent e)
					{
						exportRaw();
					}
				});
			jp.add(exportRaw);
        getContentPane().add(jp, gbc);


        JPanel jp2=new JPanel();
            jp2.setLayout(new FlowLayout(FlowLayout.LEFT));
            jp2.setPreferredSize(new Dimension(300,130));
            jp2.setBorder(BorderFactory.createTitledBorder("Code generation"));

            JLabel l;

            l=new JLabel("Row names:");
            jp2.add(l);
            final JTextField rownames=new JTextField("row");
            rownames.setPreferredSize(new Dimension(200,25));
            jp2.add(rownames);

            l=new JLabel("Table name:");
            jp2.add(l);
            final JTextField tablename=new JTextField("rowtable");
            tablename.setPreferredSize(new Dimension(200,25));
            jp2.add(tablename);

            JButton generate=new JButton("Generate code");
            jp2.add(generate);

            generate.addActionListener(new ActionListener()
            {
                public void actionPerformed(ActionEvent e)
                {
                    generateCode(rownames.getText(), tablename.getText());
                }
            });
        getContentPane().add(jp2, gbc);

        JPanel jp3=new JPanel();
            jp3.setLayout(new FlowLayout(FlowLayout.LEFT));
            jp3.setPreferredSize(new Dimension(180,130));
            jp3.setBorder(BorderFactory.createTitledBorder("Export/Import angles"));

			JButton exportAngles=new JButton("Export");
				exportAngles.addActionListener(new ActionListener()
				{
					public void actionPerformed(ActionEvent e)
					{
						exportAngles();
					}
				});
			jp3.add(exportAngles);

			JButton importAngles=new JButton("Import");
				importAngles.addActionListener(new ActionListener()
				{
					public void actionPerformed(ActionEvent e)
					{
						importAngles();
					}
				});
			jp3.add(importAngles);
        getContentPane().add(jp3, gbc);

        generateCode("row", "rowtable");

        pack();
        setVisible(true);
    }
}

public class PD extends JFrame
{
    private Action m_actionLoad;
    private Action m_actionSave;
    private Action m_actionSaveAs;
    private Action m_actionExport;
    private Action m_actionExit;
	private Editor m_editor;
    private static final String m_baseTitle = "Pinball Dreams (c64) Wall Editor";
	private String m_holeFile;

    private void exit()
    {
        if (JOptionPane.showConfirmDialog(this, "Are you sure?", "Exit", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION)
        {
            setVisible(false);
            dispose();
            System.exit(0);
        }
    }

	public PD(String holeFile)
	{
		super(m_baseTitle);

		m_holeFile=holeFile;

		setResizable(true);
		getContentPane().setLayout(new BorderLayout());
		addWindowListener(new WindowAdapter()
		{
			public void windowClosing(WindowEvent e)
			{
                exit();
			}
		});

        setTitle(m_baseTitle+" - <new level>");

        m_actionLoad=new AbstractAction("Load...")
        {
            public void actionPerformed(ActionEvent evt)
            {
                JFileChooser chooser = new JFileChooser();
                chooser.setFileFilter(new LevelFileFilter());
                int returnVal = chooser.showOpenDialog(PD.this);
                if (returnVal==JFileChooser.APPROVE_OPTION)
                {
                    if (m_editor.load(chooser.getSelectedFile()))
                    {
                        setTitle(m_baseTitle+" - "+m_editor.getFile().getAbsolutePath());
                    }
                }
            }
        };

        m_actionSave=new AbstractAction("Save...")
        {
            public void actionPerformed(ActionEvent evt)
            {
                if (m_editor.getFile()!=null)
                {
                    if (m_editor.save(m_editor.getFile()))
                    {
                        setTitle(m_baseTitle+" - "+m_editor.getFile().getAbsolutePath());
                    }
                }
                else
                    m_actionSaveAs.actionPerformed(evt);
            }
        };

        m_actionSaveAs=new AbstractAction("Save As...")
        {
            public void actionPerformed(ActionEvent evt)
            {
                JFileChooser chooser = new JFileChooser();
                chooser.setFileFilter(new LevelFileFilter());
                int returnVal = chooser.showSaveDialog(PD.this);
                if (returnVal==JFileChooser.APPROVE_OPTION)
                {
                    if (m_editor.save(chooser.getSelectedFile()))
                    {
                        setTitle(m_baseTitle+" - "+m_editor.getFile().getAbsolutePath());
                    }
                }
            }
        };

        m_actionExport=new AbstractAction("Export...")
        {
            public void actionPerformed(ActionEvent evt)
            {
                Exporter exporter=new Exporter(m_editor, m_holeFile);
            }
        };

        m_actionExit=new AbstractAction("Exit")
        {
            public void actionPerformed(ActionEvent evt)
            {
                exit();
            }
        };

        m_editor=new Editor("bitmap.gif");
		    JScrollPane jsp=new JScrollPane(m_editor);
            jsp.setWheelScrollingEnabled(true);
		    jsp.getViewport().setPreferredSize(new Dimension(960,800));
        getContentPane().add(jsp, BorderLayout.CENTER);

        m_editor.load(new File("level.pd64"));

        m_editor.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_MASK), "load");
        m_editor.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_MASK), "save");
        m_editor.getActionMap().put("load", m_actionLoad);
        m_editor.getActionMap().put("save", m_actionSave);

        JPanel topPanel=new JPanel();
            topPanel.setLayout(new BorderLayout());
            JMenuBar mb=new JMenuBar();
                JMenu file=new JMenu("File");
                    file.add(new JMenuItem(m_actionLoad));
                    file.add(new JMenuItem(m_actionSave));
                    file.add(new JMenuItem(m_actionSaveAs));
                    file.add(new JMenuItem(m_actionExport));
                    file.add(new JSeparator());
                    file.add(new JMenuItem(m_actionExit));
                mb.add(file);
                //JMenu edit=new JMenu("Edit");
                //    edit.add(new JMenuItem("Bitmap..."));
                //mb.add(edit);
            topPanel.add(mb, BorderLayout.NORTH);

            JToolBar tb=new JToolBar();
                tb.add(m_actionLoad);
                tb.add(m_actionSave);
                tb.add(new JToolBar.Separator());

                JComboBox editMode=new JComboBox();
                    editMode.addItem("Polygons");
                    editMode.addItem("Lines");
                    editMode.setMaximumSize(new Dimension(80,1000));
                tb.add(editMode);

                final JComboBox zoomLevel=new JComboBox();
                    zoomLevel.addItem("100%");
                    zoomLevel.addItem("200%");
                    zoomLevel.addItem("300%");
                    zoomLevel.addItem("400%");
                    zoomLevel.addItem("500%");
                    zoomLevel.setMaximumSize(new Dimension(80,1000));
                    zoomLevel.setSelectedIndex(m_editor.getZoomLevel()-1);
                tb.add(zoomLevel);

                zoomLevel.addActionListener(new ActionListener()
                {
                    public void actionPerformed(ActionEvent e)
                    {
                        m_editor.setZoomLevel(zoomLevel.getSelectedIndex()+1);
                    }
                });

                tb.setFloatable(false);
            topPanel.add(tb, BorderLayout.CENTER);
        getContentPane().add(topPanel, BorderLayout.NORTH);

		pack();
		setVisible(true);
		toFront();
	}

	public static void main(String[] args)
    {
		if (args.length!=1)
		{
			System.err.println("Usage: java PD holefile");
			System.exit(-1);
		}

        Thread.currentThread().setPriority(Thread.NORM_PRIORITY-1);
		new PD(args[0]);
	}
}
