package org.knopflerfish.bundle.soap.desktop;

import javax.wsdl.*;
import javax.wsdl.factory.*;
import javax.wsdl.xml.*;

import javax.wsdl.extensions.*;
import javax.xml.namespace.QName;
import javax.wsdl.Input;
import javax.wsdl.Output;

import java.net.*;
import java.io.*;
import java.util.*;
import org.w3c.dom.*;

import java.util.*;
import java.io.*;

import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.border.*;
import javax.swing.table.*;

import org.xml.sax.helpers.*;
import java.lang.reflect.*;

// Axis imports
import org.apache.axis.client.Call;
import org.apache.axis.client.Service;
import org.apache.axis.description.*;




/*
java -Ddebug=xtrue -cp "resources/xerces.jar;resources/wsdl4j.jar;resources/qname.jar;../../../jars/soap_desktop/soap_desktop-1.0.0.jar;resources/axis.jar;resources/jaxrpc.jar;resources/commons-logging.jar;resources/commons-discovery.jar;resources/saaj.jar" org.knopflerfish.bundle.soap.desktop.Main http://localhost:8080/axis/services/soapdemo1 "http://localhost:8080/axis/services/soapdemo1?wsdl"
*/

public class SwingRenderer {

  static boolean bDebug = "true".equals(System.getProperty("debug", "false"));

  Font stdFont = new Font("dialog", Font.PLAIN, 12);

  void test(WSDL wsdl) {
    JFrame frame = new JFrame("");
    
    JComponent comp = createComponent(wsdl);

    frame.getContentPane().add(comp);

    frame.pack();
    frame.setVisible(true);
    frame.show();
  }


  
  JComponent createComponent(WSDL wsdl) {
    JPanel panel = new JPanel(new BorderLayout());
    
    JTabbedPane tabPane = new JTabbedPane();


    //    BoxLayout box = new BoxLayout(panel, BoxLayout.Y_AXIS);

    //    panel.setLayout(box);

    Map ports = wsdl.def.getPortTypes();
    
    for(Iterator it = ports.keySet().iterator(); it.hasNext(); ) {
      QName name  = (QName)it.next();
      PortType pt = (PortType)ports.get(name);
      
      int ix = 0;
      for(Iterator it2 = pt.getOperations().iterator(); it2.hasNext();) {
	Operation op = (Operation)it2.next();
	
	JComponent opComp = createOperationComponent(wsdl, op);
	opComp.setBorder(null);

	JScrollPane scroll = new JScrollPane(opComp);
	scroll.setBorder(null);

	scroll.setPreferredSize(new Dimension(400, 300));

	tabPane.add(op.getName(), scroll);
	tabPane.setToolTipTextAt(ix, "SOAP operation " + op.getName());

	ix++;
      }
    }

    JLabel label = new JLabel(wsdl.endPoint);
    label.setToolTipText("SOAP endpoint");

    label.setHorizontalAlignment(SwingConstants.LEFT);
    label.setBackground(panel.getBackground().brighter().brighter());
    panel.add(tabPane, BorderLayout.CENTER);
    panel.add(label,   BorderLayout.SOUTH);

    
    return panel;
  }

  Color headerColor = new Color(210, 210, 210);

  JComponent createOperationComponent(final WSDL wsdl, 
				      final Operation op) {


    JPanel    panel = new JPanel(new BorderLayout());

    JScrollPane scroll = new JScrollPane(panel);

    JPanel    msgPanel = new JPanel();

    BoxLayout box   = new BoxLayout(msgPanel, BoxLayout.Y_AXIS);
    msgPanel.setLayout(box);

    Message  msg_in  = op.getInput().getMessage();
    Message  msg_out = op.getOutput().getMessage();
   
    final JComponent inComp  = createMessageComponent(wsdl, msg_in, "", false);
    //    final JComponent outComp = createMessageComponent(wsdl, msg_out, "result: ", true);
    
    final JPanel outComp = new JPanel(new BorderLayout());


    if(msg_in.getParts().size() > 0) {
      inComp.setBorder(BorderFactory.createTitledBorder("Arguments"));
    }

    msgPanel.add(inComp);
    JButton callB = new JButton("Call " + op.getName());
    callB.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent ev) {
	  doOperation(wsdl, op, inComp, outComp);
	}
      });
    callB.setToolTipText("Call operation " + op.getName());
    JPanel p2 = new JPanel(new BorderLayout());

    p2.add(callB, BorderLayout.WEST);
    msgPanel.add(p2);
    msgPanel.add(outComp);
    
    JPanel spacer = new JPanel();
    msgPanel.add(spacer);

    panel.add(msgPanel, BorderLayout.NORTH);

    return scroll;
  }

  void doOperation(WSDL wsdl, 
		   Operation op, 
		   JComponent inComp, 
		   final JComponent outComp) {

    try {

      Message  msg_in  = op.getInput().getMessage();
      Message  msg_out = op.getOutput().getMessage();
      
      Map map = wsdl.def.getServices();
      if(map.size() == 0) {
	throw new IllegalArgumentException("No services");
      }

      String endpoint = wsdl.endPoint;

      Service  service = new Service();
      Call     call    = (Call) service.createCall();

      call.setTargetEndpointAddress( new URL(endpoint) );
      call.setOperationName(new QName(op.getName()));
      
      java.util.List values = new ArrayList();

      Object         result  = null;
      java.util.List names = null;

      try {
	getParams(call, values, inComp, 0);	

	if(Activator.bVerbose) {
	  System.out.println("call with values=" + values);
	}

	Object[]       params = values.toArray();
	
      	result  = call.invoke(params);

	if(Activator.bVerbose) {
	  System.out.println("got result=" + result);
	}
	names   = Util.getPartNames(msg_out);
      } catch (IllegalArgumentException e) {
	result = e.getMessage();
	names  = new ArrayList();
	names.add("Exception");
      } catch (Exception e) {
	result = e;
	names  = new ArrayList();
	names.add("Exception");
      }

      final Object         result2 = result;
      final java.util.List names2  = names;

      SwingUtilities.invokeLater(new Runnable() {
	  public void run() {
	    JComponent comp = showOperationResult(names2, result2);
	    
	    comp.setBorder(BorderFactory.createTitledBorder("Result"));
	    
	    outComp.removeAll();
	    outComp.add(comp);
	    outComp.revalidate();
	    outComp.invalidate();
	    outComp.repaint();
	  }
	});
    } catch (Exception e) {
      e.printStackTrace();
    }
  }


  JComponent showOperationResult(java.util.List names,
				 Object result) {
    
    if(names == null || names.size() == 0) {
      if(result == null) {
	return new MyLabel("empty result");
      }
      throw new RuntimeException("no names found, object=" + result);
    }

    if(result == null) {
      throw new RuntimeException("null object result");
    }
    
    if(result.getClass().isArray() &&
       Array.getLength(result) == names.size()) {
      JPanel panel = new JPanel();

      BoxLayout box = new BoxLayout(panel, BoxLayout.Y_AXIS);
      panel.setLayout(box);

      for(int i = 0; i < names.size(); i++) {
	java.util.List l = new ArrayList();
	l.add(names.get(i));
	Object obj = Array.get(result, i);
	JComponent comp = showOperationResult(l, obj);
	panel.add(comp);
      }
      
      return panel;
    } else {
      if(names.size() > 1) {
	throw new RuntimeException("found " + names.size() + " names, " + 
				   "but just one result");
      }

      JPanel panel = new JPanel(new BorderLayout());
      JLabel name = new MyLabel(names.get(0).toString());
      name.setPreferredSize(new Dimension(120, 15));
      name.setVerticalAlignment(SwingConstants.TOP);

      name.setToolTipText(result.getClass().getName());

      JComponent rComp = makeResultComp(result);

      panel.add(name, BorderLayout.WEST);
      panel.add(rComp, BorderLayout.CENTER);

      return panel;
    }
  }

  JComponent makeResultComp(Object val) {
    if(val.getClass().isArray()) {
      JPanel panel = new JPanel();
      BoxLayout box = new BoxLayout(panel, BoxLayout.Y_AXIS);
      panel.setLayout(box);

      for(int i = 0; i < Array.getLength(val); i++) {
	Object obj = Array.get(val, i);
	JPanel p = new JPanel(new BorderLayout());
	
	panel.add(makeResultComp(obj));
      }

      return panel;
    } else if(val instanceof Map) {
      return makeResultMap((Map)val);
    } else if(val instanceof Collection) {
      return makeResultCollection((Collection)val);
    } else if(val instanceof Exception) {
      Exception e = (Exception)val;
      StringWriter sw = new StringWriter();
      e.printStackTrace(new PrintWriter(sw));
      JTextArea text = new JTextArea();
      text.setText(sw.toString());
      
      text.setEditable(false);
      return text;
    } else {
      return new MyLabel("" + val);
    }
  }

  JComponent makeResultCollection(final Collection c) {    
    
    TableModel model = new AbstractTableModel() {
	public int getColumnCount() { 
	  return 1; 
	}

	public int getRowCount() { 
	  return c.size();
	}

	public String getColumnName(int col) {
	  return "Values";
	}

	public Object getValueAt(int row, int col) { 
	  int i = 0;
	  for(Iterator it = c.iterator(); it.hasNext();) {
	    Object key = it.next();
	    if(i == row) {
	      return key;
	    }
	    i++;
	  }
	  return null;
	}
      };
    
    JTable table = new JTable(model);

    JScrollPane scroll = new JScrollPane(table);
    scroll.setPreferredSize(new Dimension(150, 100));
    return scroll;
  }

  JComponent makeResultMap(final Map map) {    
    TableModel model = new AbstractTableModel() {
	public int getColumnCount() { 
	  return 2; 
	}

	public int getRowCount() { 
	  return map.size();
	}

	public String getColumnName(int col) {
	  return (col == 0 ? "Key" : "Value");
	}

	public Object getValueAt(int row, int col) { 
	  int i = 0;
	  for(Iterator it = map.keySet().iterator(); it.hasNext();) {
	    Object key = it.next();
	    Object val = map.get(key);
	    if(i == row) {
	      return col == 0 ? key : Util.toDisplay(val);
	    }
	    i++;
	  }
	  return null;
	}
      };
	
    JTable table = new JTable(model);
	
    JScrollPane scroll = new JScrollPane(table);
    scroll.setPreferredSize(new Dimension(150, 100));
    return scroll;
  }



  void showObjectResult(Object result, JComponent comp) {
    Component[] children = comp.getComponents();
    
    if(comp instanceof JElement) {
      JElement je = (JElement)comp;
      if(comp instanceof JXSDPrimitiveComponent) {
	JXSDPrimitiveComponent p = (JXSDPrimitiveComponent)comp;
	p.setValue(result);
      } else {
	for(int i = 0; i < children.length; i++) {
	  if(children[i] instanceof JComponent) {
	    showObjectResult(result, (JComponent)children[i]);
	  }
	}
      }
    }
  }


  void getParams(Call call, 
		 java.util.List values, 
		 JComponent comp,
		 int dim) throws Exception {
    Component[] children = comp.getComponents();

    if(comp instanceof JElement) {
      JElement je = (JElement)comp;
      if(je.bArray) {
	if(Activator.bVerbose) {
	  System.out.println("array " + comp.getClass().getName());
	}
	java.util.List subValues = new ArrayList();

	for(int i = 0; i < children.length; i++) {
	  if(children[i] instanceof JComponent) {
	    getParams(call, subValues, (JComponent)children[i], 1);
	  }
	}

	if(Activator.bVerbose) {
	  System.out.println("adding subValues=" + subValues);
	}

	Object[] subParams = subValues.toArray();

	values.add(subParams);

	return;
      }
      if(comp instanceof JXSDPrimitiveComponent) {
	JXSDPrimitiveComponent p = (JXSDPrimitiveComponent)comp;

	values.add(p.getValue());

	if(false) {
	  QName type = new QName("http://www.w3.org/2001/XMLSchema", 
				 "xsd:" + p.xsd.getType());

	  call.addParameter(p.xsd.getName(),
			    type,
			    javax.xml.rpc.ParameterMode.IN);

	  System.out.println("addParam " + p.xsd.getName() + 
			     " = " + p.getValue() + 
			     ", type=" + type);
	}
      }
    }

    for(int i = 0; i < children.length; i++) {
      if(children[i] instanceof JComponent) {
	getParams(call, values, (JComponent)children[i], dim);
      }
    }
  }


  JComponent createMessageComponent(WSDL wsdl, 
				    Message msg,
				    String prefix,
				    boolean bReadonly) {

    GridBagLayout       gridBag = new GridBagLayout();
    GridBagConstraints  c       = new GridBagConstraints();

    c.fill = GridBagConstraints.BOTH;
    c.weightx = 1.0;
    c.weighty = 1.0;
    
    JPanel panel = new JPanel(gridBag);    
    
    Map partMap = msg.getParts();
    if(partMap.size() == 0) {
      // No parts
    } else {
      for(Iterator partIt = partMap.keySet().iterator(); partIt.hasNext();) {
	Object key  = partIt.next();
	Part   part = (Part)partMap.get(key);


	JLabel nameC = !bReadonly 
	  ? new MyLabel(part.getName())
	  : new MyLabel(part.getName());

	nameC.setVerticalTextPosition(SwingConstants.TOP);
	nameC.setPreferredSize(new Dimension(100, 10));
	nameC.setToolTipText(prefix + part.getName() + 
			     ": " + Util.getTypeString(part));
	
	c.gridwidth = GridBagConstraints.BOTH; //end row
	c.weightx   = 0.0;
	gridBag.setConstraints(nameC, c);
	panel.add(nameC);

	JComponent partC = createPartComponent(wsdl, part, bReadonly); 

	c.gridwidth = GridBagConstraints.REMAINDER;
	c.weightx   = 1.0;
	gridBag.setConstraints(partC, c);
	panel.add(partC);
      }
    }

    return panel;
  }

  JComponent createPartComponent(WSDL wsdl, 
				 Part part,
				 boolean bReadonly) {
    String type = Util.getTypeString(part);
    
    XSDElement el = (XSDElement)wsdl.schema.getTypeMap().get(type);
    
    
    if(el != null) {
      return createXSDElementComponent(wsdl, el, bReadonly, false);
    } else {
      return new JXSDPrimitiveComponent(wsdl, 
					new XSDElement(part.getName(), type),
					bReadonly);
    }
  }


  String pkgName = "org.knopflerfish.bundle.soap.desktop";

  JElement createXSDElementComponent(WSDL wsdl, 
				     XSDElement el,
				     boolean bReadonly,
				     boolean bArray) {
    String type = el.getType();
    
    JElement top = null;

    if(el instanceof XSDSoapArray) {
      bArray = true;
    }

    top = new JElement(new BorderLayout(), el);


    if(bDebug) {
      top.setBorder(BorderFactory.createTitledBorder(el.getClass().getName().substring(pkgName.length()+1)));
    }
    

    JElement comp;
		    
    if(type.startsWith("typens:")) {
      XSDElement ref =(XSDElement)wsdl.schema.getTypeMap().get(type.substring(7));
      if(ref != null) {
	comp = createXSDElementComponent(wsdl, ref, bReadonly, bArray);
      } else {
	comp = new JElement(new BorderLayout(), null);
	comp.add(new MyLabel("unresolved " + type), BorderLayout.CENTER);
      }
    } else {
      int n = 0; 
      for(Iterator it = el.getChildren(); it.hasNext();) {
	XSDElement subEl = (XSDElement)it.next();
	n++;
      }
      comp = createXSDElementComponentList(wsdl, el, bReadonly, bArray,
					   n == 0);
    }

    top.add(comp, BorderLayout.CENTER);

    return top;
  }

  class JXSDElementComponentSingle extends JElement {
    public JXSDElementComponentSingle(XSDElement xsd) {
      this(null, xsd);
    }

    public JXSDElementComponentSingle(LayoutManager layout, XSDElement xsd) {
      super(layout, xsd);
    }
  }

  class JXSDElementComponentList extends JElement {

    public JXSDElementComponentList(XSDElement xsd) {
      this(null, xsd);
    }

    public JXSDElementComponentList(LayoutManager layout, XSDElement xsd) {
      super(layout, xsd);
    }
  }

  class JXSDPrimitiveComponent extends JElement {
    public JComponent comp;
    public Class      clazz;

    public JXSDPrimitiveComponent(WSDL       wsdl, 
				  XSDElement xsd,
				  boolean    bReadonly) {
      super(new BorderLayout(), xsd);

      String type = xsd.getType();
      
      if(type.startsWith(wsdl.prefix)) {
	type = type.substring(wsdl.prefix.length());
      }
      
      if("int".equals(type)) {
	comp  = new JTextField("0");
	clazz = Integer.class;
      } else  if("string".equals(type)) {
	comp  = new JTextField("");
	clazz = String.class;
      } else  if("long".equals(type)) {
	comp  = new JTextField("");
	clazz = Long.class;
      } else if("boolean".equals(type)) {
	comp  = new JCheckBox();
	clazz = Boolean.class;
      } else if("double".equals(type)) {
	comp  = new JTextField("0.0");
	clazz = Double.class;
      } else if("float".equals(type)) {
	comp  = new JTextField("0.0");
	clazz = Float.class;
      } else {
	comp  = new MyLabel("unhandled type " + type);
	clazz = String.class;
      }
      
      comp.setEnabled(!bReadonly);

      add(comp, BorderLayout.CENTER);
    }


    public String getStringValue() {
      String s = null;
      if(comp instanceof JTextField) {
	s = ((JTextField)comp).getText();
      } else if(comp instanceof JCheckBox) {
	s = "" + ((JCheckBox)comp).isSelected();
      } else {
	throw new RuntimeException("NYI: output from " + comp);
      }

      return s;
    }

    public void setValue(Object val) {
      StringBuffer sb = new StringBuffer();
      if(val == null) {
	sb.append("<null>");
      } else if(val.getClass().isArray()) {
	for(int i = 0; i < Array.getLength(val); i++) {
	  Object obj = Array.get(val, i);
	  sb.append("" + obj);
	  if(i < Array.getLength(val) - 1) {
	    sb.append("\n");
	  }
	}
      } else {
	sb.append(val.toString());
      }
      System.out.println(xsd.getName() + ": setValue " + sb);
    }

    public Object getValue() {
      String s = getStringValue();

      if(clazz == String.class) {
	return s;
      } else if(clazz == Boolean.class) {
	return "true".equals(s.toLowerCase()) 
	  ? Boolean.TRUE
	  : Boolean.FALSE;
      } else {
	try {
	  Constructor cons = clazz.getConstructor(new Class[] { String.class });

	  Object val = cons.newInstance(new Object[] { s });
	  
	  return val;
	} catch (Exception e) {
	  throw new IllegalArgumentException("Failed to convert '" + s + "' to " + 
				     clazz.getName());
	}
      }
    }
  }

  JXSDElementComponentSingle 
    createXSDElementComponentSingle(WSDL wsdl, 
				    XSDElement el,
				    boolean bReadonly,
				    boolean bArray) {
    
    String type = el.getType();

    JXSDElementComponentSingle panel = new JXSDElementComponentSingle(el);
    
    String name = el.getName();

    if(bDebug) {
      if(name != null && !"".equals(name)) {
	panel.setBorder(BorderFactory.createTitledBorder(el.getName() + ":" + el.getType() + ":" + bArray));
      }
    }

    panel.setLayout(new BorderLayout());

    JLabel label = new MyLabel(el.getName());
    label.setToolTipText(el.getName() + ": " + type);
    label.setPreferredSize(new Dimension(100, 10));

    JXSDPrimitiveComponent 
      comp = new JXSDPrimitiveComponent(wsdl, el, bReadonly);
    
    panel.add(label, BorderLayout.WEST);
    panel.add(comp,  BorderLayout.CENTER);
    
    return panel;
  }
  
  JXSDElementComponentList 
    createXSDElementComponentList(final WSDL wsdl, 
				  final XSDElement el,
				  final boolean bReadonly,
				  final boolean bArray,
				  final boolean bSingle) {
    String type = el.getType();

    final JXSDElementComponentList panel 
      = new JXSDElementComponentList(new BorderLayout(), el);
    
    String name = el.getName();
    if(name != null && !"".equals(name)) {
      panel.setBorder(BorderFactory.createTitledBorder(name + (bDebug ? (":" + el.getType() + ":" + bArray) : "")));
    }

    boolean bAutoExpand = 
      (el instanceof XSDAll) ||
      (el instanceof XSDSoapArray);
    
    if(bArray && (!bAutoExpand || bSingle)) {
      final JPanel subPanel = new JPanel();
      BoxLayout box = new BoxLayout(subPanel, BoxLayout.Y_AXIS);
      subPanel.setLayout(box);

      panel.bArray = true;
      
      JButton newButton = new JButton("Add item");
      newButton.setToolTipText("Add item to array");
      newButton.addActionListener(new ActionListener() {
	  public void actionPerformed(ActionEvent ev) {
	    JComponent comp = makeBox(wsdl, el, bReadonly, true, bSingle);
	    subPanel.add(comp);
	    //	    System.out.println("created " + comp);
	    panel.invalidate();
	    subPanel.invalidate();

	    panel.revalidate();
	    subPanel.revalidate();

	    panel.repaint();
	    subPanel.repaint();
	  }
	});

      JPanel panelB = new JPanel(new BorderLayout());
      panelB.add(newButton, BorderLayout.WEST); 

      panel.add(panelB,    BorderLayout.NORTH); 
      panel.add(subPanel,  BorderLayout.CENTER); 
    } else {
      panel.add(makeBox(wsdl, el, bReadonly, false, bSingle), 
		BorderLayout.CENTER);
    }

    return panel;
  }

  JComponent makeBox(WSDL wsdl, 
		     XSDElement el,
		     boolean bReadonly,
		     boolean bArray,
		     boolean bSingle) {
    final JPanel panel = new JPanel(new BorderLayout());

    JPanel subPanel = new JPanel();
    BoxLayout box = new BoxLayout(subPanel, BoxLayout.Y_AXIS);
    subPanel.setLayout(box);
    
    JButton delButton = null;
    if(bArray) {
      String name = el.getName();

      if(name != null && !"".equals(name)) {
	subPanel.setBorder(BorderFactory.createTitledBorder(el.getName()));
      }
      JPanel panelB = new JPanel(new BorderLayout());

      delButton = new JButton("Delete");
      delButton.setToolTipText("Delete this item from array");
      delButton.addActionListener(new ActionListener() {
	  public void actionPerformed(ActionEvent ev) {
	    JComponent parent = (JComponent)panel.getParent();
	    parent.remove(panel);
	    parent.invalidate();
	    parent.revalidate();
	    parent.repaint();
	  }
	});

      if(!bSingle) {
	panelB.add(delButton, BorderLayout.WEST);
	subPanel.add(panelB, BorderLayout.NORTH);
      }
    }

    if(bSingle) { 
      JComponent 
	subComp = createXSDElementComponentSingle(wsdl, el, bReadonly, bArray);

      if(delButton != null) {
	subComp.add(delButton, BorderLayout.WEST);
      }

      subPanel.add(subComp, BorderLayout.CENTER);
    } else {
      for(Iterator it = el.getChildren(); it.hasNext();) {
	XSDElement subEl = (XSDElement)it.next();
	
	JComponent subComp = createXSDElementComponent(wsdl, 
						       subEl,
						       bReadonly,
						       bArray);
	subPanel.add(subComp);
      }
    }

    panel.add(subPanel, BorderLayout.CENTER);

    return panel;
  }
    

  class MyLabel extends JLabel {
    public MyLabel() {
      this("");
    }

    public MyLabel(String s) {
      super(s);
      setFont(stdFont);
      setVerticalAlignment(SwingConstants.TOP);
    }
  }
}

