/*
 * $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.tool;

import java.awt.Component;
import java.io.File;
import java.io.FileNotFoundException;
import javax.swing.Icon;
import javax.swing.JFileChooser;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileView;
import com.sun.javatest.TestSuite;
import com.sun.javatest.util.FileInfoCache;

/**
 * A custom JFileChooser, for a user to choose a test suite.
 */
public class TestSuiteChooser extends JFileChooser
{
    static {
        // the following is a workaround to force the TestSuite class to
        // be safely loaded, before the JFileChooser starts running its background
        // thread, exposing a JVM bug in class loading.
        Class testSuiteClass = TestSuite.class;
    }

    /**
     * Create a TestSuiteChooser, initially showing the user's current directory.
     */
    public TestSuiteChooser() {
        this(new File(System.getProperty("user.dir")));
    }

    /**
     * Create a TestSuiteChooser, initially showing a given directory.
     * @param initialDir the initial directory to be shown
     */
    public TestSuiteChooser(File initialDir) {
        super(normalize(initialDir));
        setName("tsc");

        uif = new UIFactory(this);  // no helBroker required
        icon = IconFactory.getSelectableFolderIcon();

        setDialogTitle(uif.getI18NString("tsc.title"));
        setApproveButtonText(uif.getI18NString("tsc.open.btn"));
    setApproveButtonMnemonic(uif.getI18NMnemonic("tsc.open.mne"));
    setApproveButtonToolTipText(uif.getI18NString("tsc.open.tip"));

        // only want to accept test suites
        setAcceptAllFileFilterUsed(false);
        setFileFilter(new TSC_FileFilter());

        // we want a file view that displays special icons for
        // test suites and sets test suite directories non-traversable,
        // so that double clicking in the list view will select
        // (ie choose) them
        setFileView(new TSC_FileView());

        // we need FILES in the mode so that non-traversable
        // items appear in the list
        // -- see BasicDirectoryModel.LoadFilesThread
        // we need DIRECTORIES in the mode so that directories
        // (eg test suite directories) can be selected (ie chosen)
        // -- see BasicFileChooserUI.ApproveSelectionAction
        setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);

        // these parameters are still not ideal ...
        // approveSelection(File) below gets called for all directories:
        // we still have to redispatch according to whether it is a work
        // directory or not.
    }

    /**
     * Get the test suite that was most recently selected by the user.
     * @return the test suite that was most recently selected by the user
     * @see #showDialog
     * @see #setSelectedTestSuite
     */
    public TestSuite getSelectedTestSuite() {
        return selectedTestSuite;
    }

    /**
     * Set the selected test suite.
     * @param ts the test suite to select
     * @see #getSelectedTestSuite
     */
    public void setSelectedTestSuite(TestSuite ts) {
        if (ts != null)
            setSelectedFile(ts.getRoot());
        selectedTestSuite = ts;
    }

    /**
     * Show a dialog to allow the user to select a test suite.
     * If a test suite is selected, it can be accessed via getSelectedTestSuite.
     * @param parent the component to be used at the parent of this dialog
     * @return an integer signifying how the dialog was dismissed
     * (APPROVE_OPTION or CANCEL_OPTION).
     * @see #APPROVE_OPTION
     * @see #CANCEL_OPTION
     * @see #getSelectedTestSuite
     */
    public int showDialog(Component parent) {
        return showDialog(parent, getApproveButtonText());
    }

    // override JFileChooser method
    public void approveSelection() {
        File file = getSelectedFile();

        if (isTraversable(file)) {
            // This is a to work around a bug in JFileChooser.
            // When double-clicking on the list, the "traversable"
            // attribute is used to determine whether to approve the
            // selection or to set it as the current "directory".
            // That is good.  However, the Open (Approve) button
            // simply looks at whether it is a directory or not,
            // which requires that the selectionMode must include
            // DIRECTORIES.  It must separately include FILES otherwise
            // they don't show up in the list.  Bottom line is that
            // this method may be called with directories we don't want.
            // So we check if we want (to open) this directory,
            // and if not, we simply set it as the current directory.
            setCurrentDirectory(file);
            // Unfortunately, if we are here, the selectedFile has been
            // trampled prior to approveSelection being called.
            // The best that we can do is clear it
            setSelectedFile(null);
            setSelectedFiles(null);
            return;
        }

        // mainline code to approve a selection
        try {
            selectedTestSuite = TestSuite.open(file);
            super.approveSelection();
        }
        catch (FileNotFoundException e) {
            uif.showError("tsc.cantFindTestSuite", e.getMessage());
        }
        catch (TestSuite.Fault e) {
            uif.showError("tsc.notATestSuite", e.getMessage());
        }
    }

    private boolean isDirectory(File f) {
        return f.isDirectory();
    }

    private boolean isTestSuite(File f) {
        // Take care not touch the floppy disk drive on Windows
        // because if there is no disk in it, the user will get a dialog.
        // Root directories (such as A:) have an empty name,
        // so use that to avoid touching the file itself.
        // This means we can't put a test suite in the root of
        // the file system, but that is a lesser inconvenience
        // than floppy dialogs!
        if (isIgnoreable(f))
            return false;

        Boolean b = (Boolean) (cache.get(f));
        if (b == null) {
            boolean v = TestSuite.isTestSuite(f);
            cache.put(f, (v ? Boolean.TRUE : Boolean.FALSE));
            return v;
        }
        else
            return b.booleanValue();
    }

    private static boolean isIgnoreable(File f) {
        // Take care not touch the floppy disk drive on Windows
        // because if there is no disk in it, the user will get a dialog.
        // Root directories (such as A:) have an empty name,
        // so use that to avoid touching the file itself.
        // This means we can't put a test suite in the root of
        // the file system, but that is a lesser inconvenience
        // than floppy dialogs!
        return (f.getName().equals(""));
    }

    private static File normalize(File dir) {
        // check this and all parent directories, in case any one is a test suite
        for (File d = dir; d != null && !isIgnoreable(d); d = d.getParentFile()) {
            if (TestSuite.isTestSuite(d)) {
                // found a parent directory that is a test suite,
                // so normalize to this directory's parent
                File p = d.getParentFile();
                return (p != null ? p : dir);
            }
        }

        // no test suite found, so nothing wrong with this dir
        return dir;
    }

    private FileInfoCache cache = new FileInfoCache(60*1000);
    private TestSuite selectedTestSuite;
    private UIFactory uif;
    private Icon icon;

    private class TSC_FileView extends FileView {
        public String getDescription(File f) {
            return null;
        }

        public Icon getIcon(File f) {
            return (isTestSuite(f) ? icon : null);
        }

        public String getName(File f) {
            // Take care to get names of file system roots correct
            String name = f.getName();
            return (name.length() == 0 ? f.getPath() : name);
        }

        public String getTypeDescription(File f) {
            return null;
        }

        public Boolean isTraversable(File f) {
            return (isDirectory(f) && !isTestSuite(f) ? Boolean.TRUE : Boolean.FALSE);
        }
    }

    private class TSC_FileFilter extends FileFilter {
        public boolean accept(File f) {
            return (isDirectory(f) ? true : isTestSuite(f));
        }

        public String getDescription() {
            return uif.getI18NString("tsc.ft");
        }
    }
}
