/*
 * GlobalSettingView.java
 * 
 * Copyright (c) 2003-2004 Digi International
 * This program and the information contained in it is confidential and
 * proprietary to Digi International and may not be used, copied, or re-
 * produced without the prior written permission of Digi International.
 * 
 */

package com.digi.config.ui;

import com.digi.config.core.*;
import com.digi.config.util.*;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
import java.net.*;




/** 
 * This class is a view panel that will show all fields in the current settings cluster of the device. 
 * It also lets the user select one of the fields and then edit its value. The selected field and its
 * editable value will be shown at the bottom of the panel.
 * 
 * This view can be embedded in other panels as desired.
 */


public class GlobalSettingView extends ConfigViewImpl {
    /** Label identifying this view */
    JLabel heading;
    /** Panel that holds the tree and the edit panels */
    JPanel encompassingPanel;
    /** The JTree that shows the setting Cluster hierarchy */
    JTree globalKvpFieldTree;
    /** The data model that maps the setting Cluster data into a JTree model */
    KvpNodeTreeModel kvpFieldTreeModel;
    /** The subpanel that holds the editable Field components */
    JPanel editKvpFieldPanel;
    /** The currently selected Field */
    KvpField selectedKvpField;
    /** The JLabel component that holds the name of the Field being edited */
    JLabel editKvpFieldName;
    /** The JTextField that holds the editable value of the Field being edited */
    JTextField editKvpFieldValue;

    /** View's current setting cluster values */
    KvpNode viewSettingTree;
    /** Listens to changes in the device fields */
    DeviceChangeListener deviceChangedListener;



    /**
     * Basic constructor. Sets up the tree view and the edit panel.
     */
    public GlobalSettingView() throws Exception {
        super(true);
        // Set up basic view constructs. The data will be plugged in on the activate call
        kvpFieldTreeModel = new KvpNodeTreeModel();
        globalKvpFieldTree = new JTree(kvpFieldTreeModel);
        globalKvpFieldTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
        globalKvpFieldTree.setCellRenderer(new KvpFieldRenderer());
        globalKvpFieldTree.addTreeSelectionListener(new TreeSelectionListener() {
             /**
               * Called whenever the value of the selection changes.
               * @param e the event that characterizes the change.
               */
             public void valueChanged(TreeSelectionEvent e) {
                 Object node = e.getPath().getLastPathComponent();
                 if (node==null) {
                     return;
                 }
                 if (node instanceof KvpNode) {
                     KvpNode kvpNode = (KvpNode)node;
                     if ((kvpNode.getName().length()>0) && (kvpNode.getChildCount()==0)) {
                         doEditKvpField(kvpNode);
                     }
                 }
             }
         }
        );
        encompassingPanel = new CustomPanel(getName());
        encompassingPanel.setLayout(new BorderLayout());
        encompassingPanel.add(globalKvpFieldTree, BorderLayout.CENTER);
        encompassingPanel.add(getEditKvpFieldPanel(), BorderLayout.SOUTH);
    }


    /**
     * Returns a one word name identifying this view. 
     */
    public String getName() {
        return "GlobalSettingView";
    }

    /** 
     * Return the Component that displays the primary content for this view
     */
    public Component getViewContent() {
        return encompassingPanel;
    }



    /**
     * This method is called to instruct the view that it is about to be made
     * active. When a view is active it needs to make sure its content is correct.
     * An example of why a panel may be inactive is if it were on a tabbed pane 
     * and was not currently visible or if the user selected some other view.
     * 
     * Views should use this method to create their content on first touch or when 
     * the device kvpGroup have been refreshed.
     */
    public void activate() {
        SystemLog.debug("Activating GlobalSettingView");
        // Get a copy of the device kvpGroup for this view to use
        this.viewSettingTree = device.getSettingTree();
        this.viewSettingTree.resetChanged();
        // build a tree model over the kvpGroup data
        kvpFieldTreeModel.setKvpNode(this.viewSettingTree);
        // Start listening to the device for changes to its kvpGroup
        deviceChangedListener = new DeviceChangeListener() {
            public void deviceChanged(DeviceChangeEvent e) {
                // get the latest kvpGroup data from the device
                viewSettingTree = device.getSettingTree();
                viewSettingTree.resetChanged();
                // point the tree model to the new kvpGroup data
                kvpFieldTreeModel.setKvpNode(viewSettingTree);
            }
        };
        device.addSettingChangeListener(deviceChangedListener);
        // do normal activate processing
        super.activate();
    }

    /**
     * This method is called to instruct the view that it is about to be made
     * inactive. When a view is inactive, it does not need to worry
     * about maintaining its content in a correct state. An example of why a panel
     * may be inactive is if it were on a tabbed pane and was not currently visible
     * or if the user selected some other view.
     */
    public void deactivate() {
        // do normal deactivate processing
        super.deactivate();
        // Stop listening to the device for changes to its kvpGroup
        device.removeSettingChangeListener(deviceChangedListener);
    }


    /**
     * Indicates if any changes have been made by the user that have not yet been
     * saved to the device(ie committed).
     */
    public boolean isChanged() {
        return this.viewSettingTree.hasChanged();
    }

    /**
     * This method instructs the view to place any changes the user has made within 
     * the view into the provided clusters. Once the changes have been saved to
     * to the device, a subsequent call to commitChanges() will be made.
     */
    public void getChanges(KvpNode settingCluster, KvpNode stateCluster) {
        settingCluster.merge(viewSettingTree);
    }

    /**
     * Instructs the view to consider any user changes within the view as 
     * saved to the device (ie committed).
     */
    public void commitChanges() {
        this.viewSettingTree.resetChanged();
    }

    /** 
     * Instructs the view to discard any changes the user has made within the
     * view and revert those fields to the original state. The view is also 
     * free to refresh all its fields to the present cached state of the device at 
     * this time.
     */
    public void cancelChanges() {
        // Get a copy of the device kvpGroup for this view to use
        this.viewSettingTree = device.getSettingTree();
        this.viewSettingTree.resetChanged();
        // point the tree model to the new view kvpGroup data
        kvpFieldTreeModel.setKvpNode(this.viewSettingTree);
    }


    /**
     * Sets up a kvpField for editting. This method is called when a kvpField is selected
     * from the tree.
     */
    public void doEditKvpField(KvpField aKvpField) {
        selectedKvpField = aKvpField;
        editKvpFieldName.setText(aKvpField.getName()+" = ");
        editKvpFieldValue.setText(aKvpField.getStringValue());
        editKvpFieldValue.requestFocus();
    }

    /**
     * Finishes up an edit of a kvpField and pushes any changes back into the kvpField object.
     */
    public void doEditKvpFieldComplete() {
        TreePath path = this.globalKvpFieldTree.getSelectionPath();
        if (path!=null) {
            kvpFieldTreeModel.valueForPathChanged(path, editKvpFieldValue.getText());
        }
    }

    /**
     * Returns the edit kvpGroup panel - constructing it if necessary.
     */
    public JPanel getEditKvpFieldPanel() {
        if (editKvpFieldPanel==null) {
            editKvpFieldPanel = new JPanel(new BorderLayout());
            editKvpFieldName = new JLabel();
            editKvpFieldValue = new JTextField();
            editKvpFieldPanel.add(editKvpFieldName, BorderLayout.WEST);
            editKvpFieldPanel.add(editKvpFieldValue, BorderLayout.CENTER);
            editKvpFieldValue.addActionListener(new ActionListener() {
                 /**
                  * Invoked when an action occurs.
                  */
                 public void actionPerformed(ActionEvent e) {
                     doEditKvpFieldComplete();
                 }
             }
            );
            editKvpFieldPanel.setMinimumSize(new Dimension(400, 100));
        }
        return editKvpFieldPanel;
    }




    /** 
     * A basic Renderer for the KvpNode tree. It shows nodes in the tree as follows:
     *   KvpFieldMap - always labeled "Device KvpNode"
     *   KvpNode   - node labeled with name of the kvpGroup
     *   KvpField    - node labelled with kvpField_name = kvpField_value
     * 
     *  Also, if a kvpField is selected, it will be displayed in reverse highlighting
     */
    public class KvpFieldRenderer extends JLabel implements TreeCellRenderer {
        /**
         * Sets the value of the current tree cell to <code>value</code>.
         * If <code>selected</code> is true, the cell will be drawn as if
         * selected. If <code>expanded</code> is true the node is currently
         * expanded and if <code>leaf</code> is true the node represets a
         * leaf and if <code>hasFocus</code> is true the node currently has
         * focus. <code>tree</code> is the <code>JTree</code> the receiver is being
         * configured for.  Returns the <code>Component</code> that the renderer
         * uses to draw the value.
         *
         * @return	the <code>Component</code> that the renderer uses to draw the value
         */
        public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
            this.setBackground(Color.WHITE);
            this.setForeground(Color.BLACK);
            if (value instanceof KvpNode) {
                KvpNode node = (KvpNode)value;
                String text = "Device";    // default root value
                if (node.getName().length()>0) {
                    text = node.getName();
                    String index = node.getAttribute(RciProtocol.INDEX_TAG);
                    if (index!=null) {
                        text += "["+index+"]";
                    }
                    if (node.getStringValue().length()>0) {
                        text += " = "+node.getStringValue();
                    }
                }
                this.setText(text);
                if (selected) {
                    this.setOpaque(true);
                    this.setForeground(Color.WHITE);
                    this.setBackground(Color.BLACK);
                }
            } else {
                this.setText("Error. Invalid Node type of "+value.getClass().getName());
            }
//             if (value instanceof KvpNode) {
//                 this.setText("Device KvpNode");
//             } else if (value instanceof KvpNode) {
//                 this.setText(((KvpNode)value).getName());
//             } else if (value instanceof KvpField) {
//                 if (selected) {
//                     this.setOpaque(true);
//                     this.setForeground(Color.WHITE);
//                     this.setBackground(Color.BLACK);
//                 } else {
//                 }
//                 this.setText(((KvpField)value).getName()+" = "+((KvpField)value).getStringValue());
//             } else {
//                 this.setText("Error!");
//             }
            return this;
        }
    }



    public class KvpNodeTreeModel implements TreeModel {
        private KvpNode rootNode;
        private Vector treeModelListeners = new Vector();
        /**
         * Define which kvpGroup Map should be surfaced by this model
         */
        public void setKvpNode(KvpNode rootNode) {
            System.out.println("GlobalSettingView.setKvpNode("+rootNode+")");
            this.rootNode = rootNode;
            if (rootNode!=null) {
                this.fireTreeStructureChanged(rootNode);
            }
        }

        /**
         * The only event raised by this model is TreeStructureChanged with the old root
         * as the path. This event essentially means the entire kvpGroup tree has changed.
         */
        protected void fireTreeStructureChanged(KvpNode rootNode) {
            int len = treeModelListeners.size();
            TreeModelEvent e = new TreeModelEvent(this, new Object[] {rootNode});
            for (int i=0; i<len; i++) {
                ((TreeModelListener)treeModelListeners.elementAt(i)).treeStructureChanged(e);
            }
        }

        /**
         * The only event raised by this model is TreeStructureChanged with the old root
         * as the path. This event essentially means the entire kvpGroup tree has changed.
         */
        protected void fireTreeNodesChanged(TreePath path) {
            int len = treeModelListeners.size();
            TreeModelEvent e = new TreeModelEvent(this, path);
            for (int i=0; i<len; i++) {
                ((TreeModelListener)treeModelListeners.elementAt(i)).treeNodesChanged(e);
            }
        }


        /**
         * Returns the root of the tree.  Returns <code>null</code>
         * only if the tree has no nodes.
         *
         * @return  the root of the tree
         */
        public Object getRoot() {
            return rootNode;
        }

        /**
         * Returns the index of child in parent.  If <code>parent</code>
         * is <code>null</code> or <code>child</code> is <code>null</code>,
         * returns -1.
         *
         * @param parent a note in the tree, obtained from this data source
         * @param child the node we are interested in
         * @return the index of the child in the parent, or -1 if either
         *    <code>child</code> or <code>parent</code> are <code>null</code>
         */
        public int getIndexOfChild(Object parent, Object child) {
            // finish this... very poor performance implementation... add indexing support to
            // kvpGroup classes to improve.
            if ((parent==null) || (child==null)) {
                return -1;
            }
            if (parent instanceof KvpNode) {
                ArrayList temp = new ArrayList(((KvpNode)parent).getAllFields());
                return temp.indexOf(child);
            }
            return -1;
        }

        /**
         * Returns the child of <code>parent</code> at index <code>index</code>
         * in the parent's
         * child array.  <code>parent</code> must be a node previously obtained
         * from this data source. This should not return <code>null</code>
         * if <code>index</code>
         * is a valid index for <code>parent</code> (that is <code>index >= 0 &&
         * index < getChildCount(parent</code>)).
         *
         * @param   parent  a node in the tree, obtained from this data source
         * @return  the child of <code>parent</code> at index <code>index</code>
         */
        public Object getChild(Object parent, int index) {
            // finish this... very poor performance implementation... add indexing support to
            // kvpGroup classes to improve.
            if (parent==null) {
                return null;
            }
            if (parent instanceof KvpNode) {
                ArrayList temp = new ArrayList(((KvpNode)parent).getAllFields());
                return temp.get(index);
            }
            return null;
        }

        /**
         * Adds a listener for the <code>TreeModelEvent</code>
         * posted after the tree changes.
         *
         * @param   l       the listener to add
         * @see     #removeTreeModelListener
         */
        public void addTreeModelListener(TreeModelListener l) {
            treeModelListeners.addElement(l);
        }
        /**
         * Returns the number of children of <code>parent</code>.
         * Returns 0 if the node
         * is a leaf or if it has no children.  <code>parent</code> must be a node
         * previously obtained from this data source.
         *
         * @param   parent  a node in the tree, obtained from this data source
         * @return  the number of children of the node <code>parent</code>
         */
        public int getChildCount(Object parent) {
            int count = 0;
            if (parent instanceof KvpNode) {
                count = ((KvpNode)parent).getAllFields().size();
            }
            return count;
        }
        /**
         * Returns <code>true</code> if <code>node</code> is a leaf.
         * It is possible for this method to return <code>false</code>
         * even if <code>node</code> has no children.
         * A directory in a filesystem, for example,
         * may contain no files; the node representing
         * the directory is not a leaf, but it also has no children.
         *
         * @param   node  a node in the tree, obtained from this data source
         * @return  true if <code>node</code> is a leaf
         */
        public boolean isLeaf(Object node) {
            boolean result = true;
            if (node instanceof KvpNode) {
                result = ((KvpNode)node).getChildCount()==0;
            }
            return result;
        }
        /**
          * Messaged when the user has altered the value for the item identified
          * by <code>path</code> to <code>newValue</code>.
          * If <code>newValue</code> signifies a truly new value
          * the model should post a <code>treeNodesChanged</code> event.
          *
          * @param path path to the node that the user has altered
          * @param newValue the new value from the TreeCellEditor
          */
        public void valueForPathChanged(TreePath path, Object newValue) {
            Object node = path.getLastPathComponent();
            if (node instanceof KvpField) {
                KvpField kvpField = (KvpField)node;
                // The TreeCellEditor for a kvpField node returns a string
                kvpField.setStringValue((String)newValue);
                // Notify listeners of change
                this.fireTreeNodesChanged(path);
            }
        }
        /**
         * Removes a listener previously added with
         * <code>addTreeModelListener</code>.
         *
         * @see     #addTreeModelListener
         * @param   l       the listener to remove
         */
        public void removeTreeModelListener(TreeModelListener l) {
            this.treeModelListeners.remove(l);
        }
    }


}

