/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.sun.jini.example.browser;

import net.jini.core.entry.Entry;
import java.lang.reflect.Field;
import java.lang.reflect.Array;
import java.awt.Component;
import java.awt.BorderLayout;
import java.util.logging.Level;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.JLabel;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.plaf.metal.MetalLookAndFeel;

/**
 * @version 0.2 06/04/98
 */
abstract class EntryTreePanel extends JPanel {
  /**
   * running mode. 
   *
   * @serial
   */
  private boolean isControllable;


  /**
   * @serial
   */

  private JScrollPane scrollPane;

  /**
   * @serial
   */
  protected JTree tree;

  /**
   * @serial
   */
  protected ObjectNode root;

  /**
   * @serial
   */
  protected DefaultTreeModel model;

  /**
   * @serial
   */
  private boolean showModifier = false;

  /**
   * @serial
   */
  private boolean showPackage = false;


  public EntryTreePanel(boolean isControllable) {
    this.isControllable = isControllable;

    // Init this panel
    setLayout(new BorderLayout());

    // Init tree node and model (attribute tree nodes)
    root = new ObjectNode(isControllable);
    //initTree();
    model = new DefaultTreeModel(root);

    // Init tree view
    tree = new JTree(model);
    //tree.addMouseListener(new DoubleClicker(this));
    tree.setRootVisible(false);
    ObjectNodeRenderer renderer = new ObjectNodeRenderer();
    tree.setCellRenderer(renderer);
    tree.setRowHeight(0);	// let the renderer handle it
    scrollPane = new JScrollPane(tree);
    add(scrollPane, "Center");

    tree.validate();
    scrollPane.validate();
  }

  protected abstract Entry[] getEntryArray();


  protected void initTree() {
    Entry[] entries = getEntryArray();
    if(entries == null)
      entries = new Entry[0];

    for(int i = 0; i < entries.length; i++){
      // check controllability
      boolean nodeControllable = false;
      if(isControllable && ! (entries[i] instanceof net.jini.lookup.entry.ServiceControlled)) {
	nodeControllable = true;
      }

      ObjectNode node = new ObjectNode(entries[i], nodeControllable);
      root.add(node);
      try {
	recursiveObjectTree(node);
      } catch(IllegalAccessException e){
	Browser.logger.log(Level.INFO, "entry access failed", e);
      }
    }
  }

  public void refreshPanel() {
    // reconstruct nodes
    root.removeAllChildren();
    initTree();
    
    model.nodeStructureChanged(root);

    tree.validate();
    scrollPane.validate();
  }

  protected void recursiveObjectTree(ObjectNode node)
    throws IllegalArgumentException, IllegalAccessException {

    Object obj = node.getObject();
    if(obj == null)
      return;
    //Field[] fields = obj.getClass().getDeclaredFields();
    Field[] fields = obj.getClass().getFields();

    for(int i = 0; i < fields.length; i++){
      Field f = fields[i];

      if(Introspector.isHidden(f))
	continue;

      Class clazz = f.getType();
      ObjectNode child = null;
      String fname = f.getName();
      if(clazz.isPrimitive()){
	String clazzName = clazz.toString();
	Object fobj = null;
	if("int".equals(clazzName)){
	  fobj = Integer.valueOf(f.getInt(obj));
	} else if("boolean".equals(clazzName)){
	  fobj = new Boolean(f.getBoolean(obj));
	} else if("byte".equals(clazzName)){
	  fobj = Byte.valueOf(f.getByte(obj));
	} else if("char".equals(clazzName)){
	  fobj = Character.valueOf(f.getChar(obj));
	} else if("double".equals(clazzName)){
	  fobj = new Double(f.getDouble(obj));
	} else if("float".equals(clazzName)){
	  fobj = new Float(f.getFloat(obj));
	} else if("long".equals(clazzName)){
	  fobj = Long.valueOf(f.getLong(obj));
	}

	child = new ObjectNode(fobj, clazz, fname, true);
      } else if(Introspector.isWrapper(clazz) || Introspector.isString(clazz)) {
	child = new ObjectNode(f.get(obj), clazz, fname, true);
      } else if(clazz.isArray()){
	child = new ObjectNode(f.get(obj), clazz, fname, false);
	child.setAdministrable(node.isAdministrable());
	child.setControllable(node.isControllable());
	recursiveArrayTree(child, f);
      } else {
	// unknown type
	Object subobj = f.get(obj);

	// check if sub object has a viewable members.
	if(countViewableFields(clazz) > 0){
	  child = new ObjectNode(subobj, clazz, fname, false);
	  child.setAdministrable(node.isAdministrable());
	  child.setControllable(node.isControllable());
	  recursiveObjectTree(child);
	} else {
	  child = new ObjectNode(subobj, clazz, fname, true);
	}
      }
      node.add(child);
    }
  }

  private int countViewableFields(Class clazz) {

    int count = 0;
    //Field[] fields = obj.getClass().getDeclaredFields();
    //Field[] fields = obj.getClass().getFields();
    Field[] fields = clazz.getFields();
    for(int i = 0; i < fields.length; i++){
      Field f = fields[i];

      if(Introspector.isHidden(f))
	continue;

      count++;
    }

    return count;
  }

  private void recursiveArrayTree(ObjectNode node, Field f)
    throws IllegalArgumentException, IllegalAccessException {

    String name = node.getFieldName();
    Object aobj = node.getObject();

    int length = Array.getLength(aobj);

    Class clazz = f.getType().getComponentType();

    if(clazz.isPrimitive() || Introspector.isWrapper(clazz) || Introspector.isString(clazz)){
      // primitive, wrapper objects, string array
      for(int i = 0; i < length; i++){
	Object elem = Array.get(aobj, i);
	//String fname = name + "[" + i + "]"; 
	ObjectNode child = new ObjectNode(elem, clazz, name, i, true);
	node.add(child);
      }
    } else {
      // Object or Array (*sigh*)
      for(int i = 0; i < length; i++){
	Object elem = Array.get(aobj, i);
	//String fname = name + "[" + i + "]"; 
	ObjectNode child = new ObjectNode(elem, clazz, name, i, false);
	recursiveObjectTree(child);
	child.setAdministrable(node.isAdministrable());
	child.setControllable(node.isControllable());

	node.add(child);
      }
    }
  }


  class ObjectNodeRenderer implements TreeCellRenderer {
    private JLabel label;

    public ObjectNodeRenderer() {
      label = new JLabel();
      label.setOpaque(true);
    }
    
    public Component getTreeCellRendererComponent(JTree tree,
						  Object value,
						  boolean isSelected,
						  boolean isExpanded,
						  boolean isLeaf,
						  int row,
						  boolean cellHasFocus){

      //label.setFont(tree.getFont());
      label.setForeground(tree.getForeground());
      if(isSelected){
	//label.setBackground(UIManager.getColor("Tree.backgroundSelectionColor"));
	//label.setForeground(UIManager.getColor("Tree.textSelectionColor"));
	label.setBackground(MetalLookAndFeel.getPrimaryControl());
      } else {
	//label.setBackground(UIManager.getColor("Tree.backgroundNonSelectionColor"));
	//label.setForeground(UIManager.getColor("Tree.textNonSelectionColor"));
	label.setBackground(tree.getBackground());
      }

      ObjectNode node = (ObjectNode) value;
      label.setText(node.getTitle());
      label.setIcon(node.getIcon());
      return label;
    }
  }
}
