/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: ChainG.java
 * Written by Eric Kim, Sun Microsystems.
 *
 * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.simulation.test;

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

/**
 * ChainG, chain utility in GUI. This is the default main class for test.jar.
 */
public class ChainG extends JFrame implements TreeModelListener, ChainNode.ShiftListener {

    static final int WIDTH = 1800;

    static final int HEIGHT = 600;

    ChainControl chainControl = null;

    String fileName = null;

    JTree treeLeft = null;

    DefaultTreeModel treeModel = null;

    JTextArea bitsTextArea, logTextArea;

    JTextField pathText, systemComment, setInBitsText;

    JLabel labelPath, labelComment;

    JRadioButton outBitsButton, inBitsButton, outBitsExpButton;

    JButton setInBitsButton, shiftButton;

    JCheckBox readEnable, writeEnable;

    /** Creates a new instance of ChainG */
    public ChainG(String xml) {
        super("ChainG");
        createGUI();
        openFile(xml);
    }

    public ChainG(ChainControl cc) {
        super("ChainG");
        createGUI();
        setChainControl(cc);
    }

    /*
     * I don't know why this doesn't display the new hierarchy, because
     * chainControl is getting filled correctly (see the println below). It would
     * be too much trouble to figure out how this GUI stuff works!
     */
    private void openFile(String name) {
        if (name != null) {
            this.fileName = name;
            try {
                chainControl = new ChainControl(name);
            } catch (OutOfMemoryError e) {
                System.out.println("Out of memory, rerun with larger heap space using -Xmx1000m");
                System.exit(1);
            }
            if (chainControl != null)
                chainControl.resetInBits();
        }
        //        System.out.println("First chip in XML file: "
        //                + chainControl.system.getChild(0));
        setChainControl(chainControl);
    }

    private void setChainControl(ChainControl control) {
        if (control != null) {
            chainControl = control;
            treeModel = new DefaultTreeModel(chainControl.getSystem());
            treeLeft.setModel(treeModel);
            treeModel.addTreeModelListener(this);
            MyTreeNode system = chainControl.getSystem();
            for (int i=0; i<system.getChildCount(); i++) {
                MyTreeNode anode = system.getChildAt(i);
                if (!(anode instanceof ChipNode)) continue;
                ChipNode chip = (ChipNode)anode;
                for (int j=0; j<chip.getChildCount(); j++) {
                    ChainNode chain = (ChainNode)chip.getChildAt(j);
                    chain.addListener(this);
                }
            }
        } else {
            treeLeft.setModel(null);
        }
    }

    private void createGUI() {
        this.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        JComponent contentPane = (JComponent) this.getContentPane();

        treeLeft = new JTree();

        MyRenderer myRenderer = new MyRenderer();
        treeLeft.setCellRenderer(myRenderer);

        /** left panel */
        GridBagLayout gridbag = new GridBagLayout();
        GridBagConstraints c = new GridBagConstraints();
        JPanel paneLeft = new JPanel(gridbag);
        c.fill = GridBagConstraints.BOTH;

        /** top panel of left panel */
        GridBagLayout gridbagTop = new GridBagLayout();
        JPanel displayPanel = new JPanel(gridbagTop);
        c.fill = GridBagConstraints.HORIZONTAL;
        c.gridx = 0;
        c.gridy = 0;
        c.gridwidth = 2;
        c.weightx = 1;
        c.weighty = 0;
        gridbag.setConstraints(displayPanel, c);
        paneLeft.add(displayPanel);

        c.fill = GridBagConstraints.BOTH;
        JLabel showLabel = new JLabel("Show:", SwingConstants.LEFT);
        c.gridx = 0;
        c.gridy = 0;
        c.gridwidth = 1;
        c.weightx = 1;
        c.weighty = 0;
        gridbagTop.setConstraints(showLabel, c);
        displayPanel.add(showLabel);

        outBitsButton = new JRadioButton("OutBits", true);
        c.gridx = 1;
        c.gridy = 0;
        c.gridwidth = 1;
        c.weightx = 1;
        c.weighty = 0;
        gridbagTop.setConstraints(outBitsButton, c);
        displayPanel.add(outBitsButton);
        outBitsButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                displayButtonChanged();
            }
        });

        inBitsButton = new JRadioButton("InBits", true);
        c.gridx = 2;
        c.gridy = 0;
        c.gridwidth = 1;
        c.weightx = 1;
        c.weighty = 0;
        gridbagTop.setConstraints(inBitsButton, c);
        displayPanel.add(inBitsButton);
        inBitsButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                displayButtonChanged();
            }
        });

        outBitsExpButton = new JRadioButton("OutBitsExp", true);
        c.gridx = 3;
        c.gridy = 0;
        c.gridwidth = 1;
        c.weightx = 1;
        c.weighty = 0;
        gridbagTop.setConstraints(outBitsExpButton, c);
        displayPanel.add(outBitsExpButton);
        outBitsExpButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                displayButtonChanged();
            }
        });

        ButtonGroup bitsToShowGroup = new ButtonGroup();
        bitsToShowGroup.add(outBitsButton);
        bitsToShowGroup.add(inBitsButton);
        bitsToShowGroup.add(outBitsExpButton);
        outBitsButton.setSelected(true);


        JScrollPane scrollPaneLeft = new JScrollPane(treeLeft);
        scrollPaneLeft
                .setPreferredSize(new Dimension(WIDTH / 2, HEIGHT * 4 / 5));
        c.gridx = 0;
        c.gridy = 1;
        c.gridwidth = 2;
        c.weightx = 1;
        c.weighty = 1;
        gridbag.setConstraints(scrollPaneLeft, c);
        paneLeft.add(scrollPaneLeft);

        labelPath = new JLabel("Path", SwingConstants.CENTER);
        c.fill = GridBagConstraints.HORIZONTAL;
        c.gridx = 0;
        c.gridy = 2;
        c.gridwidth = 1;
        c.weightx = 0;
        c.weighty = 0;
        gridbag.setConstraints(labelPath, c);
        paneLeft.add(labelPath);

        labelComment = new JLabel(" Comment ", SwingConstants.CENTER);
        c.gridx = 0;
        c.gridy = 3;
        gridbag.setConstraints(labelComment, c);
        paneLeft.add(labelComment);

        pathText = new JTextField();
        pathText.setEditable(false);
        c.gridx = 1;
        c.gridy = 2;
        c.weightx = 1;
        gridbag.setConstraints(pathText, c);
        paneLeft.add(pathText);

        systemComment = new JTextField();
        systemComment.setEditable(false);
        c.gridx = 1;
        c.gridy = 3;
        gridbag.setConstraints(systemComment, c);
        paneLeft.add(systemComment);

        setInBitsButton = new JButton("Set inBits to: ");
        setInBitsButton.setEnabled(false);
        c.gridx = 0;
        c.gridy = 4;
        c.gridwidth = 1;
        c.weightx = 0;
        c.weighty = 0;
        gridbag.setConstraints(setInBitsButton, c);
        paneLeft.add(setInBitsButton);
        setInBitsButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                setSelectedInBits();
            }
        });

        setInBitsText = new JTextField();
        setInBitsText.setEditable(false);
        c.gridx = 1;
        c.gridy = 4;
        c.gridwidth = 1;
        c.weightx = 0;
        c.weighty = 0;
        gridbag.setConstraints(setInBitsText, c);
        paneLeft.add(setInBitsText);

        shiftButton = new JButton("Shift Selected Chain");
        shiftButton.setEnabled(false);
        c.gridx = 0;
        c.gridy = 5;
        c.gridwidth = 1;
        c.weightx = 0;
        c.weighty = 0;
        gridbag.setConstraints(shiftButton, c);
        paneLeft.add(shiftButton);
        shiftButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                doShift();
            }
        });

        GridBagLayout gridbagSOP = new GridBagLayout();
        JPanel shiftOptionsPanel = new JPanel(gridbagSOP);
        c.gridx = 1;
        c.gridy = 5;
        c.gridwidth = 1;
        c.weightx = 0;
        c.weighty = 0;
        gridbag.setConstraints(shiftOptionsPanel, c);
        paneLeft.add(shiftOptionsPanel);

        readEnable = new JCheckBox("read enable", false);
        c.gridx = 0;
        c.gridy = 0;
        c.gridwidth = 1;
        c.weightx = 1;
        c.weighty = 0;
        gridbagSOP.setConstraints(readEnable, c);
        shiftOptionsPanel.add(readEnable);

        writeEnable = new JCheckBox("write enable", false);
        c.gridx = 1;
        c.gridy = 0;
        c.gridwidth = 1;
        c.weightx = 1;
        c.weighty = 0;
        gridbagSOP.setConstraints(writeEnable, c);
        shiftOptionsPanel.add(writeEnable);

        /** bottom panel */
        gridbag = new GridBagLayout();
        JPanel paneBottom = new JPanel(gridbag);
        c.fill = GridBagConstraints.BOTH;

        bitsTextArea = new JTextArea(3, 10);
        bitsTextArea.setEditable(false);
        bitsTextArea.setLineWrap(true);
        JScrollPane scrollPaneBottom = new JScrollPane(bitsTextArea,
                JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPaneBottom.setBorder(new TitledBorder("BitVector"));
        scrollPaneBottom.setBackground(Color.WHITE);
        c.gridx = 0;
        c.gridy = 0;
        c.weightx = 1;
        c.weighty = 0.5;
        gridbag.setConstraints(scrollPaneBottom, c);
        paneBottom.add(scrollPaneBottom);

        logTextArea = new JTextArea(5, 10);
        logTextArea.setEditable(false);
        JScrollPane scrollPaneBottom2 = new JScrollPane(logTextArea,
                JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        scrollPaneBottom2.setBorder(new TitledBorder("Log Window"));
        scrollPaneBottom2.setBackground(Color.WHITE);
        c.gridx = 0;
        c.gridy = 1;
        c.weightx = 1;
        c.weighty = 0.5;
        gridbag.setConstraints(scrollPaneBottom2, c);
        paneBottom.add(scrollPaneBottom2);

        /** split panels */
        JSplitPane splitPaneBottom = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
                paneLeft, paneBottom);
        contentPane.add(splitPaneBottom);

        /** menu bar */
        JMenuBar menuBar = new JMenuBar();
        setJMenuBar(menuBar);

        JMenu menu = new JMenu("File");
        menu.setMnemonic('f');
        menuBar.add(menu);

        JMenuItem menuItem = new JMenuItem("Open File");
        menuItem.setMnemonic('o');
        menuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                openPressed();
            }
        });
        menu.add(menuItem);

        menuItem = new JMenuItem("Reopen");
        menuItem.setMnemonic('r');
        menuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                reopenPressed();
            }
        });
        menu.add(menuItem);

        menu = new JMenu("Edit");
        menu.setMnemonic('e');
        menuBar.add(menu);

        menuItem = new JMenuItem("Copy");
        menuItem.setMnemonic('c');
        menuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                copyPressed();
            }
        });
        menu.add(menuItem);

        /** tree select listener */
        treeLeft.addTreeSelectionListener(new TreeSelectionListener() {
            public void valueChanged(TreeSelectionEvent e) {
                treeLeftSelChanged(e);
            }
        });

    }

    /** open xml file */
    private void openPressed() {
        String file = getFileName(true);
        if (file == null) return;
        logOut("Opening: " + file);
        openFile(file);
    }

    /** reopen file */
    private void reopenPressed() {
        logOut("Reopening: " + fileName);
        openFile(fileName);
    }

    protected String getFileName(boolean forOpen) {
        JFileChooser fc = new JFileChooser();
        fc.setCurrentDirectory(new File("."));
        int result = (forOpen ? (fc.showOpenDialog(this)) : fc
                .showSaveDialog(this));
        java.io.File chosenFile = fc.getSelectedFile();
        if (result == JFileChooser.APPROVE_OPTION && chosenFile != null)
            return chosenFile.getPath();
        return null; // return null if no file chosen or dialog cancelled
    }

    private void copyPressed() {
        logOut("copy not implemented");
    }

    private MyTreeNode getSelectedNode() {
        TreePath p = treeLeft.getSelectionPath();
        if (p != null) {
            return (MyTreeNode) p.getLastPathComponent();
        }
        return null;
    }

    /**
     * when system tree is clicked, display comment, bits and path
     * @param e event 
     */
    private void treeLeftSelChanged(TreeSelectionEvent e) {
        MyTreeNode node = getSelectedNode();
        if (node != null) {
            if (node.getComment() != null) {
                systemComment.setText(node.getComment());
            } else {
                systemComment.setText("");
            }
            if (node.getClass() == ChainNode.class
                    || node.getClass() == SubchainNode.class) {
                SubchainNode chainNode = (SubchainNode) node;
                bitsTextArea.setText("InBits:\t"+chainNode.getInBitsIndiscriminate().getState()+"\n"+
                                     "OutBits:\t"+chainNode.getOutBitsIndiscriminate().getState()+"\n"+
                                     "OutBitsExp:\t"+chainNode.getOldOutBitsExpected().getState());
                pathText.setText(chainNode.getPathString());
                setInBitsText.setText(chainNode.getInBitsIndiscriminate().getState());
            } else {
                bitsTextArea.setText("None Selected");
                pathText.setText("None Selected");
            }
        }
        updateButtonEnables();
    }

    private void updateButtonEnables() {
        if (inBitsButton.isSelected()) {
            MyTreeNode node = getSelectedNode();
            if ((node instanceof SubchainNode)) {
                setInBitsButton.setEnabled(true);
                setInBitsText.setEditable(true);
                shiftButton.setEnabled(true);
                readEnable.setEnabled(true);
                writeEnable.setEnabled(true);
                return;
            }
        }
        setInBitsButton.setEnabled(false);
        setInBitsText.setEditable(false);
        shiftButton.setEnabled(false);
        readEnable.setEnabled(false);
        writeEnable.setEnabled(false);
    }

    private void displayButtonChanged() {
        if (treeModel != null) {
            treeModel.nodeChanged(chainControl.getSystem());
        }
        updateButtonEnables();
    }

    private void doShift() {
        if (chainControl.getJtag() == null) {
            JOptionPane.showMessageDialog(this, "No Jtag Tester specified in ChainControl",
                    "Error", JOptionPane.ERROR_MESSAGE);
            return;
        }
        String path = pathText.getText();
        path = chainControl.getParentChain(path);
        shiftButton.setEnabled(false);
        shiftButton.setText("Shifting...");
        chainControl.shift(path, readEnable.isSelected(), writeEnable.isSelected(), Infrastructure.SEVERITY_WARNING,
                Infrastructure.SEVERITY_WARNING, Infrastructure.SEVERITY_WARNING);
    }

    public void shiftCompleted(ChainNode node) {
        treeModel.nodeChanged(node);
        shiftButton.setText("Shift Selected Chain");
        updateButtonEnables();
    }

    private void setSelectedInBits() {
        MyTreeNode node = getSelectedNode();
        if (node instanceof SubchainNode) {
            SubchainNode subchainNode = (SubchainNode)node;
            String newInBits = setInBitsText.getText().trim();
            int newBitsLength = newInBits.length();
            int nodeLength = subchainNode.getInBitsIndiscriminate().getNumBits();
            if (newBitsLength != nodeLength) {
                JOptionPane.showMessageDialog(this, "Cannot set node of "+nodeLength+" bits to string of "+newBitsLength+" bits",
                        "Error", JOptionPane.ERROR_MESSAGE);
                return;
            }
            subchainNode.setInBits(newInBits);
            treeLeftSelChanged(null);
            treeModel.nodeChanged(chainControl.getSystem());
        }
    }

    private ChainNode getParentChainNode(MyTreeNode node) {
        if (node instanceof ChainNode) return (ChainNode)node;
        while (node != null) {
            node = node.getParent();
            if (node instanceof ChainNode) return (ChainNode)node;
        }
        return null;
    }

    /** main program: instantiate ChainModel and JFrame */
    public static void main(String[] argv) {
        final String xml;
        if (argv.length >= 1) {
            xml = argv[0];
        } else {
            xml = null;
        }
        SwingUtilities.invokeLater(new Runnable() {
            public void run() { createAndShowGUI(xml); }
        });
    }

    public static void createAndShowGUI(ChainControl cc, String windowTitle) {
        JFrame mainFrame = new ChainG(cc);
        mainFrame.setTitle(windowTitle);
        mainFrame.pack();
        mainFrame.setVisible(true);
    }

    public static void createAndShowGUI(ChainControl cc) {
        createAndShowGUI(cc, "Chip");
    }

    private static void createAndShowGUI(String xml) {
        JFrame mainFrame = new ChainG(xml);
        mainFrame.pack();
        mainFrame.setVisible(true);
    }

    public void treeNodesChanged(TreeModelEvent e) {
        treeLeftSelChanged(null);
    }
    public void treeNodesInserted(TreeModelEvent e) {}
    public void treeNodesRemoved(TreeModelEvent e) {}
    public void treeStructureChanged(TreeModelEvent e) {}

    /** display a line in log area */
    public void logOut(String line) {
        if (logTextArea != null) {
            logTextArea.append(line + "\n");
        }
    }

    /** customize icons */
    private class MyRenderer extends DefaultTreeCellRenderer {
        ImageIcon icon1, icon2;

        public MyRenderer() {
            icon1 = new ImageIcon("icon/chip.gif");
            icon2 = new ImageIcon("icon/chain_root.gif");
        }

        public Component getTreeCellRendererComponent(JTree tree, Object value,
                boolean sel, boolean expanded, boolean leaf, int row,
                boolean hasFocus) {
            super.getTreeCellRendererComponent(tree, value, sel, expanded,
                    leaf, row, hasFocus);
            if (value.getClass() == TestNode.class) {
                setIcon(null);
            } else if (value.getClass() == ChipNode.class) {
                setIcon(icon1);
            } else if (value.getClass() == ChainNode.class) {
                setIcon(icon2);
            }

            String state = "";
            if (value instanceof SubchainNode) {
                SubchainNode node = (SubchainNode)value;
                if (inBitsButton.isSelected()) {
                    state = node.getInBitsIndiscriminate().getState();
                } else if (outBitsButton.isSelected()) {
                    state = node.getOutBitsIndiscriminate().getState();
                } else if (outBitsExpButton.isSelected()) {
                    state = node.getOldOutBitsExpected().getState();
                }
                setText(value.toString() + " " + state);
            }

            return this;
        }
    }
}
