/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2010 Oracle and/or its affiliates. All rights reserved.
 *
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
 * Other names may be trademarks of their respective owners.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common
 * Development and Distribution License("CDDL") (collectively, the
 * "License"). You may not use this file except in compliance with the
 * License. You can obtain a copy of the License at
 * http://www.netbeans.org/cddl-gplv2.html
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
 * specific language governing permissions and limitations under the
 * License.  When distributing the software, include this License Header
 * Notice in each file and include the License file at
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the
 * License Header, with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * If you wish your version of this file to be governed by only the CDDL
 * or only the GPL Version 2, indicate your decision by adding
 * "[Contributor] elects to include this software in this distribution
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 * single choice of license, a recipient has the option to distribute
 * your version of this file under either the CDDL, the GPL Version 2 or
 * to extend the choice of license to its licensees as provided above.
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 * Version 2 license, then the option applies only if the new code is
 * made subject to such option by the copyright holder.
 *
 * Contributor(s):
 *
 * Portions Copyrighted 2010 Sun Microsystems, Inc.
 */

package org.netbeans.modules.terminal;

import org.netbeans.modules.terminal.api.ui.IOVisibility;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import javax.swing.SwingUtilities;
import org.netbeans.modules.terminal.api.*;
import org.netbeans.modules.terminal.test.IOTest;
import org.openide.util.Exceptions;
import org.openide.windows.InputOutput;

/**
 * Test the three kinds of closing as described in terminal/README.close_semantics
 * @author ivan
 */
public class T1_Close_Test extends TestSupport {


    public T1_Close_Test(String testName) {
	super(testName);
    }


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

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

    public void testHello() {
	System.out.printf("testHello()\n");

	io.getOut().println("Hello to Out\r");
	io.getErr().println("Hello to Err\r");
	sleep(4);
    }


    private void testTitleHelp(InputOutput tio) {
	// This test doesn't work very well visually because when running
	// under the testsuite tabs' names always appear in bold irregardless.

	// title should not be in bold
	tio.getOut().println("testTitle\r");

	// previous getOut() should cause title to become bold
	// tio.getOut().println("title should be in bold\r");
	sleep(4);

	if (!IOVisibility.isSupported(tio))
	    return;

	// will remove tab and attempt to adjust title
	// should not have problems
	IOVisibility.setVisible(tio, false);
	sleep(2);

	// Next time we become visible title should not be bold
	tio.getOut().close();
	sleep(2);
	assertTrue("getOut() still connected after close()", ! IOConnect.isConnected(tio));

	IOVisibility.setVisible(tio, true);
	sleep(4);

	// Currently unfortunately title is still in bold, but can't
	// figure why.
	// When I try in TerminalExamples by hand it works.
    }

    public void testTitle() {

	testTitleHelp(io);
	InputOutput io1 = ioProvider.getIO("io1", null, ioContainer);
	io1.select();
	testTitleHelp(io1);
	io1.closeInputOutput();
    }

    private boolean visible = true;

    public void testVisibilityNotification() {
	if (!IONotifier.isSupported(io))
	    return;

	PropertyChangeListener pcl = new PropertyChangeListener() {
	    @Override
	    public void propertyChange(PropertyChangeEvent evt) {
		if (evt.getPropertyName().equals(IOVisibility.PROP_VISIBILITY)) {
		    assertTrue("property change not on EDT", SwingUtilities.isEventDispatchThread());
		    assertTrue("Got event '" + evt.getPropertyName() + "' instead of PROP_VISIBILITY",
			evt.getPropertyName().equals(IOVisibility.PROP_VISIBILITY));
		    visible = (Boolean) evt.getNewValue();
		} else if (evt.getPropertyName().equals(IOResizable.PROP_SIZE)) {
		} else {
		    System.out.printf("Unexpected event '%s'\n", evt.getPropertyName());
		}
	    }
	};
	IONotifier.addPropertyChangeListener(io, pcl);

	// setUp() calls select() so the terminal should be initially visible

	try {
	    // make it invisible
	    IOVisibility.setVisible(io, false);
	    sleep(1);
	    assertTrue("no visibility property change", visible == false);

	    // make it visible again
	    IOVisibility.setVisible(io, true);
	    sleep(1);
	    assertTrue("no visibility property change", visible == true);

	    // make it invisible again
	    IOVisibility.setVisible(io, false);
	    sleep(1);
	    assertTrue("no visibility property change", visible == false);

	    // make it visible again
	    IOVisibility.setVisible(io, true);
	    sleep(1);
	    assertTrue("no visibility property change", visible == true);
	} finally {
	    IONotifier.removePropertyChangeListener(io, pcl);
	}
    }

    public void testStreamClose() {
	// getIO(String, boolean newIO=false) reuses an IO
	// that is not stream-open (i.e. streams never started
	// or all closed.
	System.out.printf("testStreamClose()\n");

	InputOutput io1 = ioProvider.getIO("io1", null, ioContainer);

	InputOutput io2;
	InputOutput io3;

	// until we open any streams reusing getIO should find it
	io2 = ioProvider.getIO("io1", false, null , ioContainer);
	assertTrue("reusing getIO() didn't find unopened IO", io2 == io1);

	// after opening an io stream reusing getIO should create a new one.
	io1.select();		// so we can check the output
	io1.getOut().println("Hello to io1\r");
	sleep(4);
	io2 = ioProvider.getIO("io1", false, null, ioContainer);
	if (defaultProvider) {
	    // doesn't work as advertised
	    // the following will appear in "io1".
	    io2.getOut().println("Hello to io2\r");
	} else {
	    assertFalse("reusing getIO() found opened IO", io2 == io1);
	    // This used to appear in a separate window, IOContainer.default(),
	    // per BZ #182538, but it's fixed now.
	    io2.select();
	    io2.getOut().println("Hello to io2\r");
	}
	sleep(2);

	// after closing io stream reusing getIO should find it
	io1.getOut().close();
	io3 = ioProvider.getIO("io1", false);
	assertTrue("reusing getIO() didn't find stream closed IO", io3 == io1);

	// at this point io1 and io3 point to the same io.

	// but we can't write to it because we've closed it
	io1.select();		// so we can check the output
	io1.getOut().println("Should not appear\r");
	sleep(3);

	// until we reset it
	try {
	    io1.getOut().reset();
	} catch (IOException ex) {
	    Exceptions.printStackTrace(ex);
	    fail("reset() failed");
	}
	io1.select();		// so we can check the output
	io1.getOut().println("Hello to io1 after reset\r");
	sleep(4);
    }

    public void testMultiStreamClose() {
	// One of getOut() or getErr() or IOTerm.connect()
	// will mark the stream as open.
	//
	// Both getOut() and getErr() must be closed and
	// IOTerm must be disconnecetd for stream to be
	// considered closed.

	if (defaultProvider) {
	    System.out.printf("testMultiStreamClose() Skipped\n");
	    return;
	}

	// just with out
	assertTrue("IO not initially stream-closed",
		   ! IOConnect.isConnected(io));
	io.getOut().println("Hello to io1\r");
	assertTrue("IO still stream-closed after getOut()",
		   IOConnect.isConnected(io));
	io.getOut().close();
	sleep(1);
	assertTrue("IO not stream-closed after out close",
		   ! IOConnect.isConnected(io));

	// just with err

	// LATER
	// VV's fix for missing getErr() uses IOColorLines to
	// implement println() and IOColorLines uses getOut() !
	// io.getErr().println("Hello to io1\r");
	io.getErr();
	assertTrue("IO still stream-closed after getErr()",
		   IOConnect.isConnected(io));
	io.getErr().close();
	sleep(1);
	assertTrue("IO not stream-closed after err close",
		   ! IOConnect.isConnected(io));

	// just using connect
	if (IOTerm.isSupported(io)) {
	    // just with IOTerm.connect()
	    IOTerm.connect(io, null, null, null);
	    sleep(1);
	    assertTrue("IO still stream-closed after connect()",
		       IOConnect.isConnected(io));
	    IOTerm.disconnect(io, continuationInterlock());
	    sleep(1);
	    assertTrue("IO not stream-closed after disconnect",
		       ! IOConnect.isConnected(io));
	}


	// using all three close in one order
	assertTrue("IO should be stream-closed before \"all three\" test",
		   ! IOConnect.isConnected(io));
	io.getOut().println("Hello to io1\r");
	io.getErr();		// see above for why no print
	if (IOTerm.isSupported(io))
	    IOTerm.connect(io, null, null, null);
	assertTrue("IO should be stream-open after all 3 streams are open",
		   IOConnect.isConnected(io));

	if (IOTerm.isSupported(io))
	    IOTerm.disconnect(io, continuationInterlock());
	sleep(1);
	assertTrue("IO should still be stream-open after disconnect",
		   IOConnect.isConnected(io));
	io.getErr().close();
	sleep(1);
	assertTrue("IO should still be stream-open after closing err",
		   IOConnect.isConnected(io));
	io.getOut().close();
	sleep(1);
	assertTrue("IO should be stream-closed after closing out",
		   ! IOConnect.isConnected(io));
    }

    public void testWeakClose() {
	// weak closing removes IO from container
	// select() reinstalls it.

	System.out.printf("testWeakClose()\n");
	InputOutput ios[] = new InputOutput[4];
	ios[0] = io;
	sleep(1);
	// SHOULD not become visible unless we call select
	// BZ 181064
	ios[1] = ioProvider.getIO("test1", null, ioContainer);
	sleep(1);
	ios[2] = ioProvider.getIO("test2", null, ioContainer);
	sleep(1);
	ios[3] = ioProvider.getIO("test3", null, ioContainer);

	sleep(4);

	IOVisibility.setVisible(ios[3], false);
	sleep(1);
	IOVisibility.setVisible(ios[2], false);
	sleep(1);
	IOVisibility.setVisible(ios[1], false);
	// LATER ... who knows what wil happen:
	// IOTest.setVisible(ios[0], false);

	sleep(4);

	ios[3].select();
	sleep(1);

	ios[2].select();
	sleep(1);

	ios[1].select();
	sleep(1);

	sleep(3);
    }

    public void testUnconditionalClose() {
	IOVisibility.setClosable(io, false);

	// conditional close ... should not close
	IOTest.performCloseAction(io);
	sleep(1);
	assertTrue("CloseAction wasn't ignored", io.isClosed() == false);

	// unconditional close ... should close
	IOVisibility.setVisible(io, false);
	sleep(1);
	assertTrue("setVisibility(false) was ignored", io.isClosed() == true);
    }
}