/*
 * $Id$
 *
 * Copyright 1996-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
package com.sun.javatest.exec;

import java.awt.Component;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.lang.reflect.InvocationTargetException;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.Timer;
import javax.swing.border.Border;

import com.sun.javatest.Status;
import com.sun.javatest.TestResult;
import com.sun.javatest.tool.I18NUtils;
import com.sun.javatest.tool.ToolDialog;
import com.sun.javatest.tool.UIFactory;

class ProgressMonitor extends ToolDialog {

    ProgressMonitor(Component parent, UIFactory uif, MonitorState state) {
        super(parent, uif, "pm");

        /*ToolDialog
        super(parent, uif.getI18NString("pm.title"), false);
        */

        // ToolDialog this.uif = uif;
        this.state = state;
    }

    protected void initGUI() {
        setI18NTitle("pm.title");

        subpanels = new StatusSubpanel[4];

        /*ToolDialog
        Container content = getContentPane();
        content.setName("progressD");
        content.setLayout(new BorderLayout());
        */

        JPanel body = new JPanel(new GridBagLayout());
        GridBagConstraints c = new GridBagConstraints();
        c.fill = GridBagConstraints.BOTH;

        // TOP
        c.gridwidth = 2;
        c.gridx = 0;
        c.gridy = 0;
        c.insets.left = 8;
        c.insets.right = 8;
        c.insets.top = 5;
        c.weightx = 1;
        c.weighty = 0;
        progressSubpanel = new ProgressSubpanel();
        subpanels[0] = progressSubpanel;
        body.add(progressSubpanel, c);

        // LEFT SIDE
        c.gridheight = 1;
        c.gridwidth = 1;
        c.gridx = 0;
        c.gridy = 1;
        c.insets.right = 0;
        c.weightx = 1;
        c.weighty = 2;

        timeSubpanel = new TimeSubpanel();
        subpanels[1] = timeSubpanel;
        body.add(timeSubpanel, c);

        c.gridy = 2;
        memorySubpanel = new MemorySubpanel();
        subpanels[2] = memorySubpanel;
        body.add(memorySubpanel, c);

        // RIGHT SIDE
        // list of tests running
        c.fill = GridBagConstraints.BOTH;
        c.gridheight = 2;
        c.gridwidth = 1;
        c.gridx = 1;
        c.gridy = 1;
        c.insets.right = 8;
        c.weightx = 3;
        c.weighty = 1;
        activitySubpanel = new ActivitySubpanel();
        subpanels[3] = activitySubpanel;
        body.add(activitySubpanel, c);

        setBody(body);
        /*ToolDialog
        content.add(body, BorderLayout.CENTER);
        */

        /*ToolDialog
        JPanel btnPanel = new JPanel(new GridBagLayout());
        GridBagConstraints bc = new GridBagConstraints();
        bc.anchor = GridBagConstraints.EAST;
        bc.insets.top = 5;
        bc.insets.bottom = 11;  // value from JL&F Guidelines
        bc.insets.right = 5;   // value from JL&F Guidelines
        bc.weightx = 1;

        JButton helpBtn = uif.createHelpButton("pm.help", "browse.testMonitor.csh");
        btnPanel.add(helpBtn, bc);

        JButton closeBtn = uif.createCloseButton("pm.close");
        bc.insets.right = 11;   // value from JL&F Guidelines
        bc.weightx = 0;
        btnPanel.add(closeBtn, bc);

        content.add(btnPanel, BorderLayout.SOUTH);
        */
        JButton closeBtn = uif.createCloseButton("pm.close");
        setButtons(new JButton[] { closeBtn }, closeBtn);

        // other stuff
        state.addObserver(listener);

        if (state.isRunning())
            listener.starting();

        /*ToolDialog
        HelpBroker b = uif.getHelpBroker();
        //Desktop.addHelpDebugListener(progDialog);
        b.enableHelpKey(getRootPane(), "browse.testMonitor.csh", null);
        */

        //ToolDialog pack();

        for (int i = 0; i < subpanels.length; i++) {
            // make sure the panels are in any initial state
            // important for getting the "last" run info, less important if
            // there's a run in progress or a future runs
            subpanels[i].update();
        }

        /*ToolDialog
        getAccessibleContext().setAccessibleDescription(
                            uif.getI18NString("pm.desc"));
        */
    }

    void setTreePanelModel(TreePanelModel tpm) {
        this.tpm = tpm;
    }

    //----------member variables-----------------------------------------------------

    private MonitorState state;
    private TreePanelModel tpm;

    private ProgressSubpanel progressSubpanel;
    private ActivitySubpanel activitySubpanel;
    private TimeSubpanel timeSubpanel;
    private MemorySubpanel memorySubpanel;

    private StatusSubpanel[] subpanels;

    private volatile boolean running;
    private Listener listener = new Listener();

    private static final int UPDATE_FREQUENCY = 1000;
    private static int componentCount; // for generating names


    //----------nested classes-------------------------------------------------------

    class Listener implements MonitorState.Observer, ActionListener {
        Listener() {
            timer = new Timer(UPDATE_FREQUENCY, this);
        }

        public void actionPerformed(ActionEvent e) {
            if (e.getSource() == timer) {
                for (int i = 0; i < subpanels.length; i++)
                    subpanels[i].update();
            }
        }

        // these are probably on whichever thread the harness is running on
        // be sure to switch them onto the event thread
        public void starting() {
            running = true;

            for (int i = 0; i < subpanels.length; i++)
                subpanels[i].starting();

            timer.start();
        }

        public void postProcessing() {
            for (int i = 0; i < subpanels.length; i++) {
                    // make sure the panels update one last time
                subpanels[i].update();
                subpanels[i].postProcessing();
            }   // for
        }

        public void stopping() {
            // it is assumed that finished() will be called right away
        }

        public void finished(final boolean allOk) {
            running = false;
            for (int i = 0; i < subpanels.length; i++) {
                // make sure the panels update one last time
                subpanels[i].update();
                subpanels[i].stopping();
            }   // for

            timer.stop();
        }

        private Timer timer;
    }

    private abstract class StatusSubpanel extends JPanel {
        /**
         * Refresh any data in this subpanel.
         * This method is always called on the event dispatch thread.
         */
        abstract void update();

        /**
         * Test run is starting.  This method can be ignored if it does not apply.
         */
        void starting() {
        }

        void postProcessing() {
        }

        /**
         * Test run is finishing.  This method can be ignored if it does not apply.
         */
        void stopping() {
        }
    }

    private class ProgressSubpanel extends StatusSubpanel
    {
        ProgressSubpanel() {
            setBorder(uif.createTitledBorder("pm.prog"));
            setLayout(new GridBagLayout());

            // Line label
            GridBagConstraints lnc = new GridBagConstraints();
            lnc.gridx = 0;
            lnc.anchor = GridBagConstraints.EAST;
            lnc.insets.right = 10;

            // Field label
            GridBagConstraints lc = new GridBagConstraints();
            lc.insets.right = 5;

            // Field
            GridBagConstraints fc = new GridBagConstraints();
            fc.weightx = 1;
            fc.fill = GridBagConstraints.HORIZONTAL;
            fc.insets.right = 15;

            // Remaining field
            GridBagConstraints rc = new GridBagConstraints();
            rc.weightx = 1;
            rc.fill = GridBagConstraints.BOTH;
            rc.gridwidth = GridBagConstraints.REMAINDER;

            JTextField tf = uif.createHeading("pm.tests");
            uif.setAccessibleInfo(tf, "pm.tests");
            add(tf, lnc);
            JLabel lab = uif.createLabel("pm.tests.pass");
            add(lab, lc);
            passTf = uif.createOutputField("pm.tests.pass", 6);
            passTf.setHorizontalAlignment(JTextField.RIGHT);
            lab.setLabelFor(passTf);
            lab.setDisplayedMnemonic(uif.getI18NString("pm.tests.pass.mne").charAt(0));
            add(passTf, fc);

            lab = uif.createLabel("pm.tests.fail");
            lab.setDisplayedMnemonic(uif.getI18NString("pm.tests.fail.mne").charAt(0));
            add(lab, lc);
            failTf = uif.createOutputField("pm.tests.fail", 6);
            failTf.setHorizontalAlignment(JTextField.RIGHT);
            lab.setLabelFor(failTf);
            add(failTf, fc);

            lab = uif.createLabel("pm.tests.err");
            add(lab, lc);
            errorTf = uif.createOutputField("pm.tests.err", 6);
            lab.setDisplayedMnemonic(uif.getI18NString("pm.tests.err.mne").charAt(0));
            errorTf.setHorizontalAlignment(JTextField.RIGHT);
            lab.setLabelFor(errorTf);
            add(errorTf, fc);

            lab = uif.createLabel("pm.tests.nr");
            lab.setDisplayedMnemonic(uif.getI18NString("pm.tests.nr.mne").charAt(0));
            add(lab, lc);
            notRunTf = uif.createOutputField("pm.tests.nr", 6);
            notRunTf.setHorizontalAlignment(JTextField.RIGHT);
            lab.setLabelFor(notRunTf);
            add(notRunTf, rc);
            add(Box.createVerticalStrut(10), rc);

            Color[] colors = new Color[Status.NUM_STATES];
            colors[Status.PASSED] = I18NUtils.getStatusBarColor(Status.PASSED);
            colors[Status.FAILED] = I18NUtils.getStatusBarColor(Status.FAILED);
            colors[Status.ERROR] = I18NUtils.getStatusBarColor(Status.ERROR);
            colors[Status.NOT_RUN] = I18NUtils.getStatusBarColor(Status.NOT_RUN);

            /*
            String[] actions = new String[5 + 1];
            actions[Status.PASSED] = Integer.toString(Status.PASSED);
            actions[Status.FAILED] = Integer.toString(Status.FAILED);
            actions[Status.ERROR] = Integer.toString(Status.ERROR);
            actions[Status.NOT_RUN] = Integer.toString(Status.NOT_RUN);
            actions[actions.length - 1] = "-1";
            */

            tf = uif.createHeading("pm.tests.mtr");
            uif.setAccessibleInfo(tf, "pm.tests.mtr");
            add(tf, lnc);
            add(meter = new ProgressMeter(colors, state), rc);
            uif.setAccessibleInfo(meter, "pm.tests.bar");
            /*
            meter.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));

            ActionListener al = new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    model.showSummary(Integer.parseInt(e.getActionCommand()));
                }
            };

            meter.addActionListener(al);
            */
            uif.setToolTip(this, "pm.tests");
        }

        public void update() {
            updateAll();
        }

        //--------------------------------------------------------------------------------

        private void updateAll() {
            meter.update();

            int[] stats = state.getStats();

            setCount(passTf, stats[Status.PASSED]);
            setCount(failTf, stats[Status.FAILED]);
            setCount(errorTf, stats[Status.ERROR]);
            setCount(notRunTf,state.getTestsRemainingCount());
        }

        private final void setCount(JTextField tf, int value) {
            if (EventQueue.isDispatchThread())
                tf.setText(Integer.toString(value));
            else
                try {
                    EventQueue.invokeAndWait(new BranchPanel.TextUpdater(tf,
                                             Integer.toString(value), uif));
                }
                catch (InterruptedException e) {
                }
                catch (InvocationTargetException e) {
                }
        }

        private JTextField passTf;
        private JTextField failTf;
        private JTextField errorTf;
        private JTextField notRunTf;

        private ProgressMeter meter;
        private int[] meterStats;
    }

    private class ActivitySubpanel extends StatusSubpanel {
        ActivitySubpanel() {
            setBorder(uif.createTitledBorder("pm.activity"));
            setLayout(new CardLayout());

            idleCard = createSimpleCard("pm.idle");
            addCard(idleCard);
            initRunningCard();
            showCard(idleCard);
        }

        void starting() {
            showCard(runningCard);
            //StatusDialog.this.validate();
        }

        void postProcessing() {
            showCard(idleCard);
        }

        void stopping() {
        }

        void update() {
            testListData.removeAllElements();
            TestResult[] rt = state.getRunningTests();
            for (int i = 0; i < rt.length; i++)
                testListData.addElement(rt[i]);
        }

        //------------------------------------------------------------------

        private JComponent createSimpleCard(String uiKey) {
            JPanel card = new JPanel();
            card.setLayout(new GridBagLayout());
            GridBagConstraints c = new GridBagConstraints();
            c.anchor = GridBagConstraints.CENTER;
            c.gridwidth = GridBagConstraints.REMAINDER;
            c.weightx = 1;
            c.weighty = 1;
            JTextField tf = uif.createHeading(uiKey);
            uif.setAccessibleInfo(tf, uiKey);
            card.add(tf, c);

            JLabel icon = uif.createIconLabel(uiKey);
            c.anchor = GridBagConstraints.WEST;
            c.weighty = 0;
            card.add(icon, c);

            return card;
        }

        private void initRunningCard() {
            testListData = new DefaultListModel();
            final JList list = uif.createList("pm.runlist", testListData);
            list.setBorder(BorderFactory.createEtchedBorder());
            list.setCellRenderer(RenderingUtilities.createTestListRenderer());
            list.addMouseListener(new MouseAdapter() {
                    public void mouseClicked(MouseEvent e) {
                        int index = list.locationToIndex(e.getPoint());
                        Object target = list.getModel().getElementAt(index);
                        if (target instanceof TestResult && tpm != null) {
                            tpm.showTest(((TestResult)target).getTestName());
                        }
                    }
                }
            );
            runningCard =
                uif.createScrollPane(list, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
                                JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
            addCard(runningCard);
        }

        private void addCard(JComponent comp) {
            String s = comp.getName();
            if (s == null)
                comp.setName("StatusDialog.component" + componentCount++);
            add(comp, comp.getName());
        }

        private void showCard(JComponent comp) {
            //setVisible(false);
            ((CardLayout)(getLayout())).show(this, comp.getName());
            //setVisible(true);
        }

        private JComponent idleCard;
        private JComponent runningCard;

        private JTextField fileField;
        private DefaultListModel testListData;
        private JList testList;
        private String rootDir;
    }

    private class TimeSubpanel extends StatusSubpanel {
        TimeSubpanel() {
            setLayout(new GridBagLayout());
            Border b1 = uif.createTitledBorder("pm.time");
            Border b2 = BorderFactory.createEmptyBorder(10, 5, 10, 5);
            setBorder(BorderFactory.createCompoundBorder(b1, b2));

            // Line label
            GridBagConstraints lnc = new GridBagConstraints();
            lnc.gridx = 0;
            lnc.anchor = GridBagConstraints.EAST;
            lnc.insets.right = 10;

            // Remaining field
            GridBagConstraints rc = new GridBagConstraints();
            rc.fill = GridBagConstraints.BOTH;
            rc.gridwidth = GridBagConstraints.REMAINDER;
            rc.weightx = 1;

            JLabel lab = uif.createLabel("pm.time.sofar");
            lab.setDisplayedMnemonic(uif.getI18NString("pm.time.sofar.mne").charAt(0));
            add(lab, lnc);
            add(elapsedField = uif.createOutputField("pm.time.sofar", 8), rc);
            elapsedField.setText("00:00:00");
            lab.setLabelFor(elapsedField);

            lab = uif.createLabel("pm.time.remain");
            lab.setDisplayedMnemonic(uif.getI18NString("pm.time.remain.mne").charAt(0));
            add(lab, lnc);
            add(estimatedRemainingField = uif.createOutputField("pm.time.remain", 8), rc);
            estimatedRemainingField.setText("00:00:00");
            lab.setLabelFor(estimatedRemainingField);

            // workaround unknown GridBagLayout problem
            add(Box.createVerticalStrut(10), rc);

            uif.setToolTip(this, "pm.time");
        }

        void update() {
            long elapsed = state.getElapsedTime();
            elapsedField.setText(ElapsedTimeMonitor.millisToString(elapsed));

            long remain = state.getEstimatedTime();
            estimatedRemainingField.setText(ElapsedTimeMonitor.millisToString(remain));
        }

        private JTextField elapsedField;
        private JTextField estimatedRemainingField;
        //private ProgressMeter meter;
        private int[] meterStats = new int[2];
    }

    private class MemorySubpanel extends StatusSubpanel {
        MemorySubpanel() {
            setLayout(new GridBagLayout());
            Border b1 = uif.createTitledBorder("pm.memory");
            Border b2 = BorderFactory.createEmptyBorder(10, 5, 10, 5);
            setBorder(BorderFactory.createCompoundBorder(b1, b2));

            // Line label
            GridBagConstraints lnc = new GridBagConstraints();
            lnc.gridx = 0;
            lnc.anchor = GridBagConstraints.EAST;
            lnc.insets.right = 10;
            lnc.weightx = 0;
            lnc.fill = GridBagConstraints.NONE;

            // Remaining field
            GridBagConstraints rc = new GridBagConstraints();
            rc.fill = GridBagConstraints.BOTH;
            rc.gridwidth = GridBagConstraints.REMAINDER;
            rc.weightx = 1;

            JLabel lab = uif.createLabel("pm.memory.used");
            lab.setDisplayedMnemonic(uif.getI18NString("pm.memory.used.mne").charAt(0));
            add(lab, lnc);
            add(usedField = uif.createOutputField("pm.memory.used", 10), rc);
            lab.setLabelFor(usedField);

            lab = uif.createLabel("pm.memory.ttl");
            lab.setDisplayedMnemonic(uif.getI18NString("pm.memory.ttl.mne").charAt(0));
            add(lab, lnc);
            add(totalField = uif.createOutputField("pm.memory.ttl", 10), rc);
            lab.setLabelFor(totalField);

            // workaround unknown GridBagLayout problem
            add(Box.createVerticalStrut(10), rc);

            /*
            rc.fill = GridBagConstraints.VERTICAL;
            Color[] mc = {
                new Color(128, 0, 0),
                new Color(0, 128, 0)
            };
            add(meter = new ProgressMeter(mc, state), rc);
            */
            uif.setToolTip(this, "pm.memory");
        }

        void update() {
            int freeMem = (int)(runtime.freeMemory() / 1024);
            int totalMem = (int)(runtime.totalMemory() / 1024);
            int usedMem = totalMem - freeMem;
            usedField.setText(usedMem + "K");
            totalField.setText(totalMem + "K");

        }

        private JTextField usedField;
        private JTextField totalField;
        private ProgressMeter meter;
        private int[] meterStats = new int[2];
        private Runtime runtime = Runtime.getRuntime();
    }
}
