/*
 * 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.openide.loaders;

import java.awt.Dialog;
import java.awt.EventQueue;
import java.awt.GraphicsEnvironment;
import java.io.PrintStream;
import javax.swing.JEditorPane;
import javax.swing.SwingUtilities;
import javax.swing.text.Document;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.netbeans.junit.NbTestCase;
import org.netbeans.junit.RandomlyFails;
import org.openide.DialogDescriptor;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.cookies.CloseCookie;
import org.openide.cookies.EditCookie;
import org.openide.cookies.EditorCookie;
import org.openide.cookies.OpenCookie;
import org.openide.cookies.PrintCookie;
import org.openide.cookies.SaveCookie;
import org.openide.filesystems.FileLock;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileSystem;
import org.openide.util.Mutex;
import org.openide.util.io.NbMarshalledObject;
import org.openide.util.test.MockLookup;
import org.openide.windows.TopComponent;

/** DefaultDataObject is supposed to have open operation that shows the text
 * editor or invokes a dialog with questions.
 *
 * @author  Jaroslav Tulach
 */
public final class DefaultDataObjectHasOpenTest extends NbTestCase {

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

    static {
        System.setProperty("org.openide.windows.DummyWindowManager.VISIBLE", "false");
    }

    private FileSystem lfs;
    private DataObject obj;

    public DefaultDataObjectHasOpenTest(String name) {
        super(name);
    }

    @Override
    protected int timeOut() {
        return 60000;
    }

    @Override
    protected void setUp() throws Exception {
        super.setUp();

        MockLookup.setInstances(new DD());

        String[] fsstruct = {
            "AA/a.test"
        };

        TestUtilHid.destroyLocalFileSystem(getName());
        lfs = TestUtilHid.createLocalFileSystem(getWorkDir(), fsstruct);

        FileObject fo = lfs.findResource("AA/a.test");
        assertNotNull("file not found", fo);
        obj = DataObject.find(fo);

        assertEquals("The right class", obj.getClass(), DefaultDataObject.class);

        assertFalse("Designed to run outside of AWT", SwingUtilities.isEventDispatchThread());
        DD.options = null;
        DD.disableTest = false;
    }

    private void waitForAWT() throws Exception {
        // just wait till all the stuff from AWT is finished
        Mutex.EVENT.readAccess(new Mutex.Action<Void>() {
            public Void run() {
                return null;
            }
        });
    }

    @Override
    protected void tearDown() throws Exception {
        waitForAWT();
        DD.disableTest = true;

        super.tearDown();
        if (obj != null) {
            CloseCookie cc;
            cc = obj.getCookie(CloseCookie.class);
            if (cc != null) {
                DD.toReturn = NotifyDescriptor.NO_OPTION;
                cc.close();
            }
        }

        waitForAWT();
    }

    public void testHasOpenCookie() {
        assertNotNull(obj.getCookie(OpenCookie.class));
    }

    public void testHasEditCookie() {
        assertNotNull(obj.getCookie(EditCookie.class));
    }

    @RandomlyFails // NB-Core-Build #1766
    public void testHasEditorCookieForResonableContentOfFiles() throws Exception {
        EditorCookie c = tryToOpen(
                "Ahoj Jardo," +
                "how are you" +
                "\t\n\rBye.\u00a9\u00e1\u00e9\u00ed\u00f3\u00fa"
                );
        assertNotNull(c);

        doRegularCheck(c);
    }

    private void doRegularCheck(final EditorCookie c) throws Exception {
        assertEquals(
                "Next questions results in the same cookie",
                c,
                obj.getCookie(EditorCookie.class)
                );
        assertEquals(
                "Next questions results in the same cookie",
                c,
                obj.getLookup().lookup(EditorCookie.class)
                );
        assertEquals(
                "Print cookie is provided",
                c,
                obj.getCookie(PrintCookie.class)
                );
        assertEquals(
                "CloseCookie as well",
                c,
                obj.getCookie(CloseCookie.class)
                );

        OpenCookie open = obj.getCookie(OpenCookie.class);
        open.open();


        Document d = null;
        for (int i = 0; i < 10; i++) {
            d = c.getDocument();
            if (d != null) {
                break;
            }
            Thread.sleep(1000);
        }
        assertNotNull(d);

        d.insertString(0, "Kuk", null);

        assertNotNull(
                "Now there is a save cookie",
                obj.getCookie(SaveCookie.class)
                );
    }

    public void testNoEditorCookieForBinaryFiles() throws Exception {
        EditorCookie c = tryToOpen("\u0003\u0001\u0000");
        assertNull(c);
    }

    public void testOpenAsksAQuestionForBinaryFiles() throws Exception {
        EditorCookie c = tryToOpen("\u0003\u0001\u0000");
        assertNull(c);

        DD.toReturn = DialogDescriptor.CANCEL_OPTION;
        OpenCookie open = obj.getCookie(OpenCookie.class);
        open.open();

        assertNotNull("There was a query", DD.options);
        assertEquals("Yes no options", 2, DD.options.length);
        assertEquals(DialogDescriptor.OK_OPTION, DD.options[0]);
        assertEquals(DialogDescriptor.CANCEL_OPTION, DD.options[1]);
    }

    public void testBinaryFilesQuestionContainsOpenActions() throws Exception {
        EditorCookie c = tryToOpen("\u0003\u0001\u0000");
        assertNull(c);

        DD.toReturn = DialogDescriptor.CLOSED_OPTION;
        OpenCookie open = obj.getCookie(OpenCookie.class);
        open.open();

        assertNotNull("There was a query", DD.options);
        assertEquals("Two options", 2, DD.options.length);
        assertEquals(DialogDescriptor.OK_OPTION, DD.options[0]);
        assertEquals(DialogDescriptor.CANCEL_OPTION, DD.options[1]);
        DD.options = null;

        DD.toReturn = DialogDescriptor.OK_OPTION;
        open.open();

        assertNotNull("There was a query", DD.options);
        assertEquals("Still 2 options", 2, DD.options.length);
        assertEquals(DialogDescriptor.OK_OPTION, DD.options[0]);
        assertEquals(DialogDescriptor.CANCEL_OPTION, DD.options[1]);

        c = obj.getCookie(EditorCookie.class);
        assertNotNull("Editor cookie created", c);

        doRegularCheck(c);
    }

    private EditorCookie tryToOpen(String content) throws Exception {
        FileObject fo = obj.getPrimaryFile();
        FileLock lock = fo.lock();
        PrintStream os = new PrintStream(fo.getOutputStream(lock));
        os.print(content);
        os.close();
        lock.releaseLock();

        return obj.getCookie(EditorCookie.class);
    }

    public void testThatTheNameOfDataObjectsNodeContainsTheExtensionIssue18780() throws Exception {
        String name = obj.getPrimaryFile().getNameExt();
        assertEquals("Name contains extension", name, obj.getNodeDelegate().getName());
    }

    public void testComponentCanBeSerialized() throws Exception {
        doComponentCanBeSerialized("Sample\nTest\nTo\nSerialized");
    }

    public void testComponentCanBeSerializedEvenItIsBinary() throws Exception {
        // make sure it has the cookie
        OpenCookie open = obj.getCookie(OpenCookie.class);
        DD.toReturn = DialogDescriptor.OK_OPTION;
        open.open();
        open = null;

        doComponentCanBeSerialized("\u0003\u0001\u0000");
    }

    public void testComponentCanBeSerializedEvenItIsBig() throws Exception {
        // make sure it has the cookie
        OpenCookie open = obj.getCookie(OpenCookie.class);
        DD.toReturn = DialogDescriptor.OK_OPTION;
        open.open();
        open = null;

        FileObject fo = obj.getPrimaryFile();
        FileLock lock = fo.lock();
        PrintStream os = new PrintStream(fo.getOutputStream(lock));
        for (int i = 0; i < 1024 * 1024 / 10; i++) {
            os.print("Ahoj " + i + "\n");
        }
        os.close();
        lock.releaseLock();
        assertTrue("Larger than 1Mb", 1024 * 1024 + 1000 < fo.getSize());

        doComponentCanBeSerialized(null);
    }

    private void doComponentCanBeSerialized(final String txt) throws Exception {
        EventQueue.invokeAndWait(new Runnable() {
            public void run() {
                try {
                    final EditorCookie c = (txt != null) ? tryToOpen(txt) : obj.getCookie(EditorCookie.class);
                    assertNotNull(c);
                    c.open();

                    JEditorPane[] arr = c.getOpenedPanes();

                    assertNotNull("Something opened", arr);
                    assertEquals("One opened", 1, arr.length);

                    Object o = SwingUtilities.getAncestorOfClass(TopComponent.class, arr[0]);
                    assertNotNull("Top component found", o);
                    NbMarshalledObject mar = new NbMarshalledObject(o);

                    ((TopComponent) o).close();

                    obj.setValid(false);

                    TopComponent tc = (TopComponent) mar.get();

                    assertNotNull("Successfully deserialized", tc);

                    if (obj == DataObject.find(obj.getPrimaryFile())) {
                        fail("Strange, obj should be garbage collected" + obj);
                    }
                } catch (Exception x) {
                    throw new RuntimeException(x);
                }
            }
        });
    }

    /** Our own dialog displayer.
     */
    private static final class DD extends DialogDisplayer {
        public static Object[] options;
        public static Object toReturn;
        public static boolean disableTest;

        public Dialog createDialog(DialogDescriptor descriptor) {
            throw new IllegalStateException("Not implemented");
        }

        public Object notify(NotifyDescriptor descriptor) {
            if (disableTest) {
                return toReturn;
            } else {
                assertNull(options);
                assertNotNull(toReturn);
                options = descriptor.getOptions();
                Object r = toReturn;
                toReturn = null;
                return r;
            }
        }

    } // end of DD

}
