package plugin;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;

import java.io.*;
import java.util.*;
import java.util.jar.*;
import java.util.zip.*;
import java.math.BigInteger;

import java.lang.reflect.*;

import lcm.spy.ChannelData;
import lcm.lcm.LCMDataInputStream;

import senlcm.raw_t;

public class RawPlugin implements lcm.spy.SpyPlugin
{
    public boolean canHandle(long fingerprint)
    {
        if (fingerprint == senlcm.raw_t.LCM_FINGERPRINT)
            return true;
        else
            return false;
    }

    class MyAction extends AbstractAction
    {
	ChannelData cd;
	JDesktopPane jdp;

	public MyAction(JDesktopPane jdp, ChannelData cd)
	{
	    super("Raw Data Viewer...");
	    this.jdp = jdp;
	    this.cd = cd;
	}

	public void actionPerformed(ActionEvent e) 
	{
            Viewer v = new Viewer(cd, "");
	    jdp.add(v);
	    v.toFront();
	}
    }

    public Action getAction(JDesktopPane jdp, ChannelData cd)
    {
        return new MyAction(jdp, cd);
    }


    class Viewer extends JInternalFrame implements lcm.lcm.LCMSubscriber
    {
        ChannelData cd;
        myObjectPanel op;

    	public Viewer(ChannelData cd, String menu_name)
    	{
            super(cd.name+": Raw Data Viewer", true, true);
            this.cd = cd;

            setLayout(new BorderLayout());
            op = new myObjectPanel(cd.name);
            add(new JScrollPane(op),BorderLayout.CENTER);
            setSize(550,400);
            setVisible(true);
            
            lcm.lcm.LCM.getSingleton().subscribe(cd.name, this);	    
        }

	public void messageReceived (lcm.lcm.LCM lc, String channel, LCMDataInputStream dis)
	{
            Object o = null;

            try {
                dis.reset();

                Class<?> cls = cd.cls;
                o = cls.getConstructor(DataInput.class).newInstance(dis);
                
                if (op != null) 
                    op.setObject(o);
			
            } catch (NullPointerException ex) {
                cd.nerrors++;
            } catch (NoSuchMethodException ex) {
                cd.nerrors++;
                System.out.println("Spy.messageReceived ex: "+ex);
            } catch (InstantiationException ex) {
                cd.nerrors++;
                System.out.println("Spy.messageReceived ex: "+ex);
            } catch (IllegalAccessException ex) {
                cd.nerrors++;
                System.out.println("Spy.messageReceived ex: "+ex);
            } catch (InvocationTargetException ex) {
                cd.nerrors++;
            }
	} // messageReceived

    } // class Viewer

    public class myObjectPanel extends JPanel
    {
        String name;
        Object o;
        int lastwidth = 400;
        int lastheight = 100;

        class Section
        {
            int x0, y0, x1, y1; // bounding coordinates for sensitive area
            boolean collapsed;
        }

        ArrayList<Section> sections = new ArrayList<Section>();

        public myObjectPanel(String name)
        {
            this.name = name;

            addMouseListener(new MyMouseAdapter());
        }

        class PaintState
        {
            Color indentColors[] = new Color[] {new Color(255,255,255), new Color(230,230,255), new Color(200,200,255)};
            Graphics g;
            FontMetrics fm;

            int indent_level;
            int color_level;
            int y;
            int textheight;

            int x[] = new int[3]; // tab stops
            int indentpx = 20; // pixels per indent level

            int maxwidth;

            int nextsection = 0;

            int collapse_depth = 0;

            public int beginSection(String type, String name, String value)
            {
                // allocate a new section number and make sure there's
                // an entry for us to use in the sections array.
                int section = nextsection++;
                Section cs;
                if (section == sections.size()) {
                    cs = new Section();
                    sections.add(cs);
                } 
	    
                cs = sections.get(section);
	    
                // Some enclosing section is collapsed, exit before drawing anything.

                if (collapse_depth == 0) 
                    {
                        // we're not currently collapsed. Draw the header (at least.)
                        beginColorBlock();
                        spacer();
		    
                        Font of = g.getFont();
                        g.setFont(of.deriveFont(Font.BOLD));
		    
                        String tok = cs.collapsed ? "+" : "-";
                        g.setColor(Color.white);
                        g.fillRect(x[0] + indent_level*indentpx, y, 1, 1);
                        g.setColor(Color.black);
		    
                        g.drawString(tok, x[0] + indent_level*indentpx, y);
                        g.drawString(type,  x[0] + indent_level*indentpx + 10, y);
                        g.drawString(name,  x[1], y);
                        g.drawString(value, x[2], y);
                        g.setFont(of);
		    
                        // set up the coordinates where clicking will toggle whether
                        // we are collapsed.
                        cs.x0 = x[0];
                        cs.x1 = getWidth();
                        cs.y0 = y - textheight;
                        cs.y1 = y;
		    
                        y += textheight;
		    
                        indent();
                    } 
                else 
                    {
                        // no clicking area.
                        cs.x0 = 0; cs.x1 = 0; cs.y0 = 0; cs.y1 = 0;
                    }


                // if this section is collapsed, stop drawing.
                if (sections.get(section).collapsed)
                    collapse_depth ++;
	    
                return section;
            }

            public void endSection(int section)
            {
                Section cs = sections.get(section);
                cs.y1 = y;

                // if this section is collapsed, resume drawing.
                if (sections.get(section).collapsed)
                    collapse_depth --;

                unindent();
                spacer();
                endColorBlock();
                spacer();
            }

            public void drawStrings(String type, String name, String value, boolean isstatic)
            {
                if (collapse_depth > 0)
                    return;

                Font of = g.getFont();
                if (isstatic)
                    g.setFont(of.deriveFont(Font.ITALIC));

                g.drawString(type,  x[0] + indent_level*indentpx, y);
                g.drawString(name,  x[1], y);
                g.drawString(value, x[2], y);

                y+= textheight;

                g.setFont(of);
            }

            public void spacer()
            {
                if (collapse_depth > 0)
                    return;

                y+= textheight/2;
            }

            public void beginColorBlock()
            {
                if (collapse_depth > 0)
                    return;

                color_level++;
                g.setColor(indentColors[color_level%indentColors.length]);
                g.fillRect(x[0] + indent_level*indentpx - indentpx/2, y - fm.getMaxAscent(), getWidth(), getHeight());
                g.setColor(Color.black);
            }

            public void endColorBlock()
            {
                if (collapse_depth > 0)
                    return;

                color_level--;
                g.setColor(indentColors[color_level%indentColors.length]);
                g.fillRect(x[0] + indent_level*indentpx -indentpx/2, y - fm.getMaxAscent(), getWidth(), getHeight());
                g.setColor(Color.black);
            }

            public void indent()
            {
                indent_level++;
            }
	
            public void unindent()
            {
                indent_level--;
            }

            public void finish()
            {
                g.setColor(Color.white);
                g.fillRect(0, y, getWidth(), getHeight());
            }
        }

        public void setObject(Object o)
        {
            this.o = o;
            repaint();
        }

        public Dimension getPreferredSize()
        {
            return new Dimension(lastwidth, lastheight);
        }

        public Dimension getMinimumSize()
        {
            return getPreferredSize();
        }

        public Dimension getMaximumSize()
        {
            return getPreferredSize();
        }

        public void paint(Graphics g)
        {
            int width = getWidth(), height = getHeight();
            g.setColor(Color.white);
            g.fillRect(0, 0, width, height);

            g.setColor(Color.black);
            FontMetrics fm = g.getFontMetrics();

            PaintState ps = new PaintState();

            ps.g = g;
            ps.fm = fm;
            ps.textheight = 15;
            ps.y = ps.textheight;
            ps.indent_level=1;
            ps.x[0] = 0;
            ps.x[1] = Math.min(200, width/3);
            ps.x[2] = Math.min(ps.x[1]+200, 2*width/3);

            if (o != null)
                paintRecurse(g, ps, "", o.getClass(), o, false); 

            ps.finish();
            if (ps.y != lastheight) {
                lastheight = ps.y;
                invalidate();
                getParent().validate();
            }
            if (ps.x[2] + ps.maxwidth > lastwidth) {
                lastwidth = ps.x[2] + ps.maxwidth + 10;
                lastheight = ps.y;
                invalidate();
                getParent().validate();
            }

        }


        void paintRecurse(Graphics g, PaintState ps, String name, Class cls, Object o, boolean isstatic)
        {
            if (o == null) {
                ps.drawStrings(cls==null ? "(null)" : cls.getName(), name, "(null)", isstatic);
                return;
            }

            if (cls.equals(Byte.TYPE)) {
                ps.drawStrings(cls.getName(), name,
                               String.format("0x%02X   %03d   %+04d   %c",
                                             ((Byte)o),((Byte)o).intValue()&0x00FF,((Byte)o),((Byte)o)&0x00FF),
                               isstatic);

            } else if (cls.isPrimitive()) {

                // This is our common case...
                ps.drawStrings(cls.getName(), name, o.toString(), isstatic);

            } else if (o instanceof Enum) {

                ps.drawStrings(cls.getName(), name, ((Enum) o).name(), isstatic);

            } else if (cls.equals(String.class)) {
	    
                ps.drawStrings("String", name, o.toString(), isstatic);
	    
            } else if (cls.isArray())  {

                int sz = Array.getLength(o);

                byte [] bytes = (byte []) o;
                String s = new String(bytes);
                ps.drawStrings("String", name, s, isstatic);
                ps.maxwidth = Math.max(ps.maxwidth, ps.fm.stringWidth(s));

                int sec = ps.beginSection(cls.getComponentType()+"[]", name+"["+sz+"]", "");
                for (int i = 0; i < sz; i++) 
                    paintRecurse(g, ps, name+"["+i+"]", cls.getComponentType(), Array.get(o, i), isstatic);
	    
                ps.endSection(sec);

            } else {
	    
                // it's a compound type. recurse.
                int sec = ps.beginSection(cls.getName(), name, "");
	    
                // it's a class
                Field fs[] = cls.getFields();
                for (Field f : fs) {
                    try {
                        paintRecurse(g, ps, f.getName(), f.getType(), f.get(o), isstatic || ((f.getModifiers()&Modifier.STATIC) != 0));
                    } catch (Exception ex) {
                    }
                }

                ps.endSection(sec);
            }
        }

        class MyMouseAdapter extends MouseAdapter
        {
            public void mouseClicked(MouseEvent e)
            {
                int x = e.getX(), y = e.getY();

                int bestsection = -1;
	    
                // find the bottom-most section that contains the mouse click.
                for (int i = 0; i < sections.size(); i++)
                    {
                        Section cs = sections.get(i);

                        if (x>=cs.x0 && x<=cs.x1 && y>=cs.y0 && y<=cs.y1) {
                            bestsection = i;
                        }
                    }

                if (bestsection >= 0)
                    sections.get(bestsection).collapsed ^= true;
            }
        }
    }


}
