/*
 * ConfigViewImpl.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.border.*;
import javax.swing.text.*;
import java.awt.*;
import java.util.*;
import java.net.*;
import java.io.Serializable;
import java.text.*;


/** 
 * This interface defines the basic operations required for all configuration views
 * that are used in the configuration application.
 */


public abstract class ConfigViewImpl implements ConfigView { 
    /** The device we are configuring */
    Device device;

    protected boolean active;
    protected boolean editView;
    protected boolean includeSettingsFields;
    protected boolean includeStateFields;

    /** Generic heading label based on view name */
    JComponent heading;
    /** Generic button panel with Save, Cancel, Refresh, & Help */
    private JPanel buttonPanel;
    /** current view index */
    int viewIndex = 1;


    /**
     * Basic constructor of a readonly config view
     */
    public ConfigViewImpl() throws Exception {
        this(false);
    }

    /**
     * Constructor that specifies if this view allows editting of the kvpGroup. If it
     * does, then the view will show the Save & Cancel buttons in the button panel.
     * @param editView identifies if this view can edit the device kvpGroup
     */
    public ConfigViewImpl(boolean editView) throws Exception {
        this.initializeActions();
        active = false;
        this.editView = editView;
        includeSettingsFields = true;
        includeStateFields = false;
    }

    /**
     * Directs the view to Save and Refresh Setting data.
     * 
     * @param flag is true if State data is to be included in view processing.
     */
    public void setIncludeSettingsFields(boolean flag) {
        includeSettingsFields = flag;
    }

    /**
     * Directs the view to Save and Refresh State data.
     * 
     * @param flag is true if State data is to be included in view processing.
     */
    public void setIncludeStateFields(boolean flag) {
        includeStateFields = flag;
    }

    /** 
     * Returns the Component that displays the heading of the view.
     * If this view has no heading to display this method returns null.
     */
    public Component getViewHeading() {
        if (heading==null) {
            heading = new JLabel(ConfigResource.getUiRbString(getName()+"Title"));
            heading.setBackground(new Color(0, 0, 139));
            heading.setForeground(Color.WHITE);
            heading.setOpaque(true);
        }
        return heading;
    }


    /** 
     * Returns the Component that displays the buttons below the view.
     * If this view has no buttons to display this method returns null.
     * 
     * Default buttons provide ability to Save changes, Cancel changes, Refresh kvpGroup, and get Help
     */
    public Component getViewButtons(){
        if (buttonPanel==null) {
            buttonPanel = new CustomPanel();
            buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
            // Add standard Config View Action Buttons
            if (editView) {
                buttonPanel.add(Box.createHorizontalGlue());
                buttonPanel.add(new JButton(saveChangesAction));
                buttonPanel.add(Box.createHorizontalGlue());
                buttonPanel.add(new JButton(cancelChangesAction));
            }
            buttonPanel.add(Box.createHorizontalGlue());
            buttonPanel.add(new JButton(refreshAction));
            //buttonPanel.add(Box.createHorizontalGlue());    Panel help disabled for initial release
            //buttonPanel.add(new JButton(viewHelpAction));
            buttonPanel.add(Box.createHorizontalGlue());
        }
        return buttonPanel;
    }


    /**
     * Informs view what device to work with. Subclass Views should flush any
     * cached device data and mark current view content as invalid
     * so that it is refreshed appropriatly.
     */
    public void setDevice(Device device) {
        // update device reference
        this.device = device;
    }

    /**
     * Informs view to use the specified primary index in selecting the
     * data to display in the view. Not all views will support indexed
     * viewing and it is up to each view to define what the index is based 
     * upon. 
     * <p>
     * An example of an indexed view would be the SerialBasicView. This view
     * is indexed off the port # whose data is to be displayed in the view. An
     * external control in the SerialView lets the user select the port to view
     * and then that view instructs the SerialBasicView to use the selected 
     * port number as it's ViewIndex. Other views could use the index to base
     * their view off other integer keys such as Alarm#, Destination#,etc...
     * <p>
     * Views that do not support indexing can simply ignore this call.
     */
    public void setViewIndex(int primaryIndex) {
        // default behavior is to simply record the index. Subviews can override as needed.
        viewIndex = primaryIndex;
    }



    /**
     * 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 can use this method to create their content on first touch or to refresh their
     * content when the device kvpGroup have been reset.
     */
    public void activate() {
        // Default implementation is to simply mark the panel active.
        active = true;
    }

    /**
     * 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() {
        // Default implementation is to simply mark the panel inactive
        active = false;
    }


    /**
     * Returns a resource key to the help information for this view. The actual
     * URL will be looked up in the Help Resource bundle. The default behavior is
     * to base the resource key off the view name
     */
    public String getHelpResource(){
        return this.getName()+"Help";
    }

    /**
     * This method instructs the view to validate any of the changes the user has made
     * within the view. Any errors are to be added to the provided errorList.
     * 
     * @param errorList is a collection of ViewValidationError objects
     */
    public void validateChanges(Collection errorList) {
        // Default implementation is to not check for errors
    }





    //////////////////////////////////////////////////////////////////////////////////////
    // Internal Support Methods
    //////////////////////////////////////////////////////////////////////////////////////


    ConfigAction saveChangesAction;
    ConfigAction cancelChangesAction;
    ConfigAction refreshAction;
    ConfigAction viewHelpAction;

    /**
    * Defines the main actions that can be invoked from the menus or
    * toolbar buttons. These actions will be used to construct the menus &
    * toolbar buttons.
    */
    protected void initializeActions() throws Exception {
        saveChangesAction = new ConfigAction("SaveChanges", this, "doSaveChangesAction");
        cancelChangesAction = new ConfigAction("CancelChanges", this, "doCancelChangesAction");
        refreshAction = new ConfigAction("Refresh", this, "doRefreshAction");
        viewHelpAction = new ConfigAction("ViewHelp", this, "doViewHelpAction");
    }

    //
    // Action Methods offered by this view to its subclasses.
    //

    /**
     * This action saves any user modified Fields in the view to the device
     */
    public void doSaveChangesAction() {
        Busy.begin(ConfigResource.getUiRbString("SaveChangesStatus"));

        SystemLog.debug("ConfigViewImpl.doSaveChangesAction invoked");
        // If the view has changes
        if (this.isChanged()) {
            // If view has changes, have it validate them
            Vector errorList = new Vector();
            this.validateChanges(errorList);
            if (errorList.size()>0) {
                // If there are validation errors, display them in a dialog
                StringBuffer msgBuff = new StringBuffer();
                for (Iterator i=errorList.iterator(); i.hasNext();) {
                    ValidationError error = (ValidationError)i.next();
                    if (error.getDetailedMsg()!=null) {
                        msgBuff.append(error.getDetailedMsg()+"\n");
                    } else {
                        msgBuff.append(error.getMsg()+"\n");
                    }
                }
                // popup a dialog describing the error
                StringBuffer popupMsg = new StringBuffer();
                popupMsg.append(ConfigResource.getUiRbString("ValidationErrorDesc")+"\n");
                popupMsg.append(msgBuff);
                JOptionPane.showMessageDialog(buttonPanel,
                                              popupMsg.toString(),
                                              ConfigResource.getUiRbString("ValidationErrorTitle"),
                                              JOptionPane.ERROR_MESSAGE);
                // Also add a log entry
                SystemLog.log("ValidationError", new Serializable[] {msgBuff.toString()});
                Busy.end();
                return; // don't do the save until all validation errors have been fixed
            }
            // If the view has changes, get them 
            KvpNode tempSettingTree = new KvpNode();
            KvpNode tempStateTree = new KvpNode();
            this.getChanges(tempSettingTree, tempStateTree);
            // now try saving them on the device
            try {
                if ((tempSettingTree.getAllGroups().size() > 0) && includeSettingsFields) {
                    device.setSettingTree(tempSettingTree);
                }
                if ((tempStateTree.getAllGroups().size() > 0) && includeStateFields) {
                    device.setStateTree(tempStateTree);
                }
                this.commitChanges();
                SystemLog.log("Op1Succ",new Serializable[]{ConfigResource.getUiRbString("SaveChangesMenu")});
            } catch (DeviceCommandException dce) {
                SystemLog.log("Op1Fail",new Serializable[]{ConfigResource.getUiRbString("SaveChangesMenu")},dce);
            } catch (DeviceValidationException dse) {
                // Find out what device fields are invalid
                SystemLog.debug("Invalid fields detected while saving to device", dse);
                KvpNode errorCluster = dse.getCluster();
                Collection errors = errorCluster.getErrors();
                StringBuffer msgBuff = new StringBuffer();
                for (Iterator i=errors.iterator(); i.hasNext();) {
                    RciCommandError error = (RciCommandError)i.next();
                    msgBuff.append(error.toString()+"\n");
                }
                // popup a dialog showing the errors 
                StringBuffer popupMsg = new StringBuffer();
                popupMsg.append(ConfigResource.getUiRbString("ValidationErrorDesc")+"\n");
                popupMsg.append(msgBuff);
                JOptionPane.showMessageDialog(buttonPanel,
                                              popupMsg.toString(),
                                              ConfigResource.getUiRbString("ValidationErrorTitle"),
                                              JOptionPane.ERROR_MESSAGE);
                // Also add a log entry
                SystemLog.log("ValidationError", new Serializable[] {msgBuff.toString()});
                Busy.end();
                return; // don't do the save until all validation errors have been fixed
            } 
        } else {
            SystemLog.log("SaveNotRequired");  
        }

        Busy.end();
    }

    /**
     * This action cancells any user modified field changes in the view and 
     * reverts the view to its prior state or to a newly refreshed state.
     */
    public void doCancelChangesAction() {
        // Tell view to cancel its changes and revert to current kvpGroup
        this.cancelChanges();
        SystemLog.log("Op1Succ",new Serializable[]{ConfigResource.getUiRbString("CancelChangesMenu")});
    }

    /**
     * Instructs the device proxy to refresh its cached settings which in turn 
     * notifies device listeners of any change in setting state.
     */
    public void doRefreshAction() {
        Busy.begin(ConfigResource.getUiRbString("RefreshStatus"));

        // Tell the device to refresh its settings. Note, that views who are
        // listening to device setting changes will receive notification if 
        // field values change as a result of the refresh operation.
        try {
            if (includeSettingsFields) {
                device.refreshSettingTree(); // refresh the devices cached setting cluster
            }
            if (includeStateFields) {
                device.refreshStateTree(); // refresh the devices cached state cluster
            }
            this.cancelChanges();     // cancel any outstanding edits to the view
            SystemLog.log("Op1Succ",new Serializable[]{ConfigResource.getUiRbString("RefreshMenu")});
        } catch (DeviceCommandException dce) {
            SystemLog.log("Op1Fail",new Serializable[]{ConfigResource.getUiRbString("RefreshMenu")}, dce);
        }

        Busy.end();
    }

    /**
     * Displays help information on the current view
     */
    public void doViewHelpAction() {
        SystemLog.debug("ConfigViewImpl.doHelpAction invoked");
        String key = getHelpResource();
        if (key==null) {
            SystemLog.log("NoHelpAvailable");
        } else {
            SystemLog.debug("Launching view help . Help key = "+key);
            HelpDisplay.showHelp(key);
        }
    }

    /**
     * Creates a Text Field Document for Text Validation for Text Fields
     * requiring Integer input
     */
    protected Document createIntegerDocument() {
        return new PlainDocument() {
            public void sendBeep() {
                java.awt.Toolkit.getDefaultToolkit().beep();
            }

            public void insertString(int offs, String str, AttributeSet a)
                throws BadLocationException {
                if (str == null) return;

                // Create Actual String
                String newString = null;
                if (getLength() == 0) newString = str;
                else if (offs == 0) newString = str + getText(0, getLength());
                else if (offs == (getLength() - 1)) newString = getText(0, getLength()) + str;
                else newString = getText(0, offs) + str + getText(offs, getLength() - offs);

                // Verify Length
                if (newString.length() > 5) {
                    sendBeep();
                    return;
                }

                // Verify only Numeric
                if (!newString.matches("^[0-9]+")) {
                    sendBeep();
                    return;
                }

                // All is Good
                super.insertString(offs, new String(str), a);
            }
        };
    }

    /**
     * Creates a Text Field Document for Text Validation for Text Fields
     * requiring Network Address input such as IP Addresses
     */
    protected Document createNetworkAddressDocument() {
        return new PlainDocument() {
            public void sendBeep() {
                java.awt.Toolkit.getDefaultToolkit().beep();
            }

            public void insertString(int offs, String str, AttributeSet a)
                throws BadLocationException {
                if (str == null) return;                
                
                // Create Actual String
                String newString = null;
                if (getLength() == 0) newString = str;
                else if (offs == 0) newString = str + getText(0, getLength());
                else if (offs == (getLength() - 1)) newString = getText(0, getLength()) + str;
                else newString = getText(0, offs) + str + getText(offs, getLength() - offs);

                // Verify String only numbers and decimals
                if (!newString.matches("^[0-9.]*$")) {
                    sendBeep();
                    return;
                }

                // Split String into each address and verify max of 4
                String[] addressList = newString.split("[.]", -1);
                if (addressList.length > 4) {
                    sendBeep(); 
                    return;
                }

                // Verify Each Address for Length and Value
                for (int i = 0; i < addressList.length; i++) {
                    if (addressList[i].length() == 0) continue;

                    if ((addressList[i].length() > 3) || 
                        (Integer.parseInt(addressList[i]) > 255)) {
                        sendBeep();
                        return;
                    }
                }

                // All is Good                
                super.insertString(offs, new String(str), a);
            }
        };
    }

    /**
     * Creates a Text Field Document for Text Validation for Text Fields
     * requiring any input - verifies no non-XML chars exist
     */
    protected Document createGeneralDocument() {
        return new PlainDocument() {
            public void sendBeep() {
                java.awt.Toolkit.getDefaultToolkit().beep();
            }

            public void insertString(int offs, String str, AttributeSet a)
                throws BadLocationException {
                if (str == null) return;

                // Verify no invalid XML characters
                if (str.matches("[<>&]")) {
                    sendBeep();
                    return;
                }

                // All is Good                
                super.insertString(offs, new String(str), a);
            }
        };
    }

    /**
     * Creates a Text Field Document for Text Validation for Text Fields
     * requiring Email addresses
     */
    protected Document createEmailDocument() {
        return new PlainDocument() {
            public void sendBeep() {
                java.awt.Toolkit.getDefaultToolkit().beep();
            }

            public void insertString(int offs, String str, AttributeSet a)
                throws BadLocationException {
                if (str == null) return;                
                
                // Create Actual String
                String newString = null;
                if (getLength() == 0) newString = str;
                else if (offs == 0) newString = str + getText(0, getLength());
                else if (offs == (getLength() - 1)) newString = getText(0, getLength()) + str;
                else newString = getText(0, offs) + str + getText(offs, getLength() - offs);

                // Verify String valid email address characters
                if (!newString.matches("^[a-zA-Z0-9_.@-]*$")) {
                    sendBeep();
                    return;
                }

                // Verify only 1 '@'
                int count = 0;
                for (int i = 0; i < newString.length(); i++) {
                    if (newString.charAt(i) == '@') count++;
                    if (count > 1) {
                        sendBeep();
                        return;
                    }
                }

                // All is Good                
                super.insertString(offs, new String(str), a);
            }
        };
    }

}

