/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.netbeans.api.options;

import java.awt.Frame;
import java.awt.Dialog;
import java.awt.GraphicsEnvironment;
import java.awt.event.ActionEvent;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.SwingUtilities;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.netbeans.junit.NbTestCase;
import org.netbeans.junit.RandomlyFails;
import org.netbeans.modules.options.advanced.Advanced;
import org.netbeans.modules.options.advanced.AdvancedPanel;
import org.netbeans.modules.options.advanced.AdvancedPanelController;
import org.netbeans.spi.options.OptionsCategory;
import org.openide.DialogDescriptor;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.util.Lookup;
import org.openide.util.lookup.Lookups;

/**
 *
 * @author Radek Matous
 */
public class OptionsDisplayerOpenTest extends NbTestCase {

    public static Test suite() {
        return GraphicsEnvironment.isHeadless() ? new TestSuite() : new TestSuite(OptionsDisplayerOpenTest.class);
    }

    private static TestDisplayer displayer = new TestDisplayer();
    private static final int REPEATER = 10; 
    private Collection<? extends RegisteredCategory> all;
    Logger log;
    static {
        String[] layers = new String[] {"org/netbeans/api/options/mf-layer.xml"};//NOI18N
        Object[] instances = new Object[] {displayer};
        IDEInitializer.setup(layers,instances);
    }
    
    public OptionsDisplayerOpenTest(String testName) {
        super(testName);
    }
    
    private Lookup lookup;
    
    @Override
    protected void setUp() throws Exception {
        log = Logger.getLogger("[Test - " + getName() + "]");
        lookup = Lookups.forPath("OptionsDialog"); // NOI18N
        Lookup.Result<RegisteredCategory> result = lookup.lookup(new Lookup.Template<RegisteredCategory>(RegisteredCategory.class));
        all = result.allInstances();
        assertTrue(all.size() > 0);
    }
    
    /**
     * Test of getDefault method, of class org.netbeans.api.options.OptionsDisplayer.
     */
    public void testGetDefault() {
        assertNotNull(OptionsDisplayer.getDefault());
    }

    @RandomlyFails // NB-Core-Build #2372
    public void testSimulatesAllNecessaryCallAndCheckThreading() throws Exception {
        RegisteredCategory rcTemp = null;
        for (RegisteredCategory rc : all) {
            rcTemp = rc;
            break;
        }
        final RegisteredCategory registeredCategory = rcTemp;        
        assertNotNull(registeredCategory);        
        //cals getComponent,update, getLookup
        open("Registered", true);
        try {
            SwingUtilities.invokeAndWait(new Runnable() {
                public void run() {
                    //calls isValid
                    registeredCategory.setInvalid();
                    //calls getHelpCtx
                    registeredCategory.helpChanged();
                }
            });
        } finally {
            //calls cancel
            close(false);
            
            try {
                open("Registered", true);                
            } finally {
                //calls applyChanges
                close();
            }
            registeredCategory.assertThreadingForAllCallsWereTested();
        }        
    }
    
    public void testOpenFromWorkerThread() {
        openOpen(null);
        openOpen("Registered");
        openOpen("Unknown");
        openCloseOpen(null);
        openCloseOpen("Registered");
        openCloseOpen("Unknown");        
    }
    
    public void testOpenFromAWT() throws Exception {
        SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
                testOpenFromWorkerThread();
            }
        });
    }
    
    public void testOpenFromMixedThreads() throws Exception {
        testOpenFromWorkerThread();                
        testOpenFromAWT();
        testOpenFromWorkerThread();
        testOpenFromAWT();
    }

    /** Tests method setCurrentSubcategory is called in implementing OptionsPanelController
     * when OptionsDisplayer.open(categoryId, subcategoryId) is called.
     */
    public void testSetCurrentSubcategoryCalled() {
        open("Registered/SubcategoryID", true);
        assertEquals("Subcategory not set.", "SubcategoryID", RegisteredCategory.subcategoryID);
        close();
        open("Registered", true);
        // check RegisteredCategory.create().setCurrentSubcategory not called
        assertEquals("Subcategory should not be set.", "SubcategoryID", RegisteredCategory.subcategoryID);
        close();
    }
    
    /**
     * Tests selection of subcategories in Advanced (aka Miscellaneous) category.
     */
    public void testAdvancedSubcategorySelection() throws Exception {
        open(OptionsDisplayer.ADVANCED, true);
        assertEquals("First subcategory should be selected by default.", Subcategory1.DISPLAY_NAME, getSelectedSubcategory(Advanced.class));
        close();
        open(OptionsDisplayer.ADVANCED+"/Subcategory2", true);
        assertEquals("Wrong subcategory selected.", Subcategory2.DISPLAY_NAME, getSelectedSubcategory(Advanced.class));
        close();
        open(OptionsDisplayer.ADVANCED+"/UnknownID", true);
        assertEquals("Wrong subcategory selected.", Subcategory2.DISPLAY_NAME, getSelectedSubcategory(Advanced.class));
        close();
        open(OptionsDisplayer.ADVANCED+"/Subcategory1", true);
        assertEquals("Wrong subcategory selected.", Subcategory1.DISPLAY_NAME, getSelectedSubcategory(Advanced.class));
        close();
    }
    
    /**
     * Tests selection of subcategories in user-created category.
     */
    @RandomlyFails // NB-Core-Build #3813
    public void testSubcategorySelection() throws Exception {
        open("MyAdvancedCategory", true);
        assertEquals("Subcategory2 should be first and selected by default.", Subcategory2.DISPLAY_NAME, getSelectedSubcategory(MyAdvancedCategory.class));
        close();
        open("MyAdvancedCategory/Subcategory1", true);
        assertEquals("Wrong subcategory selected.", Subcategory1.DISPLAY_NAME, getSelectedSubcategory(MyAdvancedCategory.class));
        close();
        open("MyAdvancedCategory/UnknownID", true);
        assertEquals("Wrong subcategory selected.", Subcategory1.DISPLAY_NAME, getSelectedSubcategory(MyAdvancedCategory.class));
        close();
        open("MyAdvancedCategory/Subcategory2", true);
        assertEquals("Wrong subcategory selected.", Subcategory2.DISPLAY_NAME, getSelectedSubcategory(MyAdvancedCategory.class));
        close();
        open("MyAdvancedCategory/Subcategory2/Subcategory22", true);
        assertEquals("setCurrentSubcategory not called in Subcategory2 controller.", "Subcategory22", Subcategory2.getSubpath());
        close();
    }

    /** Returns display name of subcategory selected in AdvancedPanel. */
    private String getSelectedSubcategory(Class<? extends OptionsCategory> categoryClass) throws Exception {
        OptionsCategory category = lookup.lookup(categoryClass);
        Method getAdvancedPanelMethod = AdvancedPanelController.class.getDeclaredMethod("getAdvancedPanel", (Class[])null);
        getAdvancedPanelMethod.setAccessible(true);
        AdvancedPanel advancedPanel = (AdvancedPanel)getAdvancedPanelMethod.invoke(category.create(), (Object[])null);
        Method getSelectedDisplayNameMethod = AdvancedPanel.class.getDeclaredMethod("getSelectedDisplayName", (Class[])null);
        getSelectedDisplayNameMethod.setAccessible(true);
        return (String) getSelectedDisplayNameMethod.invoke(advancedPanel, (Object[])null);
    }

    public void openOpen(String categoryId) {
        for (int i = 0; i < REPEATER; i++) {
            if (categoryId == null) {
                open(true);
                open(false);
                close();
                //don't call: OptionsDisplayer.getDefault().open(null) but OptionsDisplayer.getDefault().open()
                open(null, false);
                close();
            } else {
                if ("Registered".equals(categoryId)) {
                    open(categoryId, true);
                    open(categoryId, false);
                    close();
                } else {
                    open(categoryId, false);
                    open(categoryId, false);
                    close();
                }
            }
        }
    }
    
    public void openCloseOpen(String categoryId) {
        for (int i = 0; i < REPEATER; i++) {
            if (categoryId == null) {
                open(true);
                close();
                open(true);
                close();
            } else {
                if ("Registered".equals(categoryId)) {
                    open(categoryId, true);
                    close();
                    open(categoryId, true);
                    close();
                } else {
                    open(categoryId, false);
                    close();
                    open(categoryId, false);
                    close();
                }                
            }
        }
    }

    public void testModality() throws Exception {
	OptionsDisplayer.getDefault().open(false) ;
	modality(displayer.descriptor, false);
	close();

	TestDisplayer td = new TestDisplayer();
	td.createDialog(new DialogDescriptor(displayer, null, true, null));
	OptionsDisplayer.getDefault().open(true) ;
	modality(td.descriptor, true);
	close();
    }

    public void modality(DialogDescriptor desc, boolean expectedResult) {
        if (desc != null) {
            assertEquals(expectedResult, desc.isModal());
        }
    }
    
    public void open(boolean expectedResult) {
        modality(displayer.descriptor);
        boolean latestResult = OptionsDisplayer.getDefault().open() ;
        assertEquals(expectedResult, latestResult);
    }
    
    public void open(String path, boolean expectedResult) {
        modality(displayer.descriptor);
        boolean latestResult = OptionsDisplayer.getDefault().open(path);
        assertEquals(expectedResult, latestResult);
    }

    public void modality(DialogDescriptor desc) {
        if (desc != null) {
            assertFalse(desc.isModal());
        }
    }

    public void close() {
        close(true);
    }
    
    public void close(boolean okButtonForClose) {
        displayer.okButtonForClose = okButtonForClose;
        modality(displayer.descriptor);
        displayer.close();
    }
    
    @Override
    protected Level logLevel() {
        return Level.FINE;
    }
        
    public static class TestDisplayer extends DialogDisplayer implements Runnable {
        DialogDescriptor descriptor;
        private JDialog dialog;
        boolean okButtonForClose = true;
        public Object notify(NotifyDescriptor descriptor) {
            throw new UnsupportedOperationException("Not supported yet.");
        }
        
        public Dialog createDialog(DialogDescriptor descriptor) {
            this.descriptor = descriptor;
            return dialog = new TestDialog(descriptor);
        }
        
        public void close() {
            try {
                if (okButtonForClose || SwingUtilities.isEventDispatchThread()) {
                    run();
                } else {
                    SwingUtilities.invokeAndWait(this);
                }
            } catch (InterruptedException ex) {
            } catch (InvocationTargetException ex) {
            }
        }
        
        public void run() {
            if (descriptor != null) {
                Object[] oo = descriptor.getClosingOptions();
                for (int i = 0; i < oo.length; i++) {
                    if (okButtonForClose && oo[i] instanceof JButton && ((JButton)oo[i]).getActionCommand().equals("OK")) {
                        descriptor.getButtonListener().actionPerformed(new ActionEvent(oo[i], 0, ((JButton)oo[i]).getActionCommand()));
                        return;
                    } else if (!okButtonForClose && !(oo[i] instanceof JButton)) {
                        descriptor.getButtonListener().actionPerformed(new ActionEvent(DialogDescriptor.CANCEL_OPTION, 0, ""));
                        return;                        
                    }
                }
            }
        }
    }
    
    private static class TestDialog extends JDialog {
        TestDialog(DialogDescriptor descriptor) {
            super((Frame)null, descriptor.getTitle(), descriptor.isModal());
        }
        
        @Override
        public void setVisible(boolean b) {
            if (isModal()) {
                super.setVisible(b);
        }
    }
}
}
