/**
 * Copyright (c) 2012 EBM WebSourcing, 2012-2013 Linagora
 * 
 * This program/library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2.1 of the License, or (at your
 * option) any later version.
 * 
 * This program/library 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 Lesser General Public License
 * for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program/library; If not, see <http://www.gnu.org/licenses/>
 * for the GNU Lesser General Public License version 2.1.
 */
package org.ow2.petals.cli.shell;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.ow2.petals.cli.api.exception.DuplicatedCommandException;
import org.ow2.petals.cli.api.exception.ShellException;
import org.ow2.petals.cli.shell.command.AbstractCommand;
import org.ow2.petals.cli.shell.command.CommandExecutedWithError;
import org.ow2.petals.cli.shell.command.CommandNoArg;
import org.ow2.petals.cli.shell.command.CommandResult;

/**
 * Unit tests of {@link PetalsInteractiveCli}
 *
 * @author Christophe DENEUX - EBM Websourcing
 */
public class PetalsInteractiveCliTest {

    @BeforeClass
    public static void beforeClass() {
        System.setProperty("jline.shutdownhook", "true");
        PetalsInteractiveCliTest.hackForWindows( true );
    }

    @AfterClass
    public static void afterClass() {
        System.clearProperty("jline.shutdownhook");
        PetalsInteractiveCliTest.hackForWindows( false );
    }

    /**
     * Checks the run method against an unregistered entered command.
     */
    @Test
    public final void testRun_WithUnregisteredCommand() throws IOException {

        final PrintStream standardConsoleError = System.err;
        final InputStream standardConsoleInput = System.in;

        try {
            final String unknownCommand = "unknowncommand";
            final ByteArrayOutputStream errBAIS = new ByteArrayOutputStream();
            System.setErr(new PrintStream(errBAIS));
            System.setIn(new ByteArrayInputStream((unknownCommand + "\n").getBytes()));
            final AbstractShell shell = new PetalsInteractiveCli(false, false);

            shell.run();

            final String error = errBAIS.toString();
            assertFalse(error.isEmpty());
            assertTrue(error.startsWith("ERROR:"));
            assertTrue(error.contains(unknownCommand));
            assertEquals("Invalid exit code", 0, shell.getExitStatus());
        } finally {
            System.setIn(standardConsoleInput);
            System.setErr(standardConsoleError);
        }

    }

    /**
     * Checks the run method against a right entered command.
     */
    @Test
    public final void testRun_RegisteredCommand() throws IOException, DuplicatedCommandException {

        final PrintStream standardConsoleError = System.err;
        final InputStream standardConsoleInput = System.in;

        try {
            final ByteArrayOutputStream errBAIS = new ByteArrayOutputStream();
            System.setErr(new PrintStream(errBAIS));
            System.setIn(new ByteArrayInputStream(
                    (CommandNoArg.class.getSimpleName().toLowerCase() + "\n").getBytes()));
            final AbstractShell shell = new PetalsInteractiveCli(false, false);

            final CommandResult cmdRes = new CommandResult();

            shell.registersCommand(new CommandNoArg(cmdRes));

            shell.run();

            final String error = errBAIS.toString();
            assertTrue(error.isEmpty());
            assertTrue(cmdRes.isExecuted());
            assertEquals("Invalid exit code", 0, shell.getExitStatus());
        } finally {
            System.setIn(standardConsoleInput);
            System.setErr(standardConsoleError);
        }

    }

    /**
     * Checks the run method against a command entered with error.
     */
    @Test
    public final void testRun_CommandWithError() throws IOException, DuplicatedCommandException {

        final PrintStream standardConsoleError = System.err;
        final InputStream standardConsoleInput = System.in;

        try {
            final CommandResult cmdRes = new CommandResult();
            final AbstractCommand command = new CommandNoArg(cmdRes);

            final ByteArrayOutputStream errBAIS = new ByteArrayOutputStream();
            System.setErr(new PrintStream(errBAIS));
            System.setIn(new ByteArrayInputStream((command.getName() + " arg1\n").getBytes()));
            final AbstractShell shell = new PetalsInteractiveCli(false, false);

            shell.registersCommand(command);
            shell.run();

            final String error = errBAIS.toString();
            assertFalse(error.isEmpty());
            assertTrue("The command is missing in error message",
                    error.startsWith("ERROR on command '" + command.getName() + "':"));
            assertTrue("The command usage is missing in error message",
                    error.contains(command.getUsage()));
            assertEquals("Invalid exit code", 0, shell.getExitStatus());
        } finally {
            System.setIn(standardConsoleInput);
            System.setErr(standardConsoleError);
        }

    }

    /**
     * Checks the run method against a command executed with error.
     * @throws ShellException
     */
    @Test
    public final void testRun_CommandExecutedWithError() throws IOException, ShellException {

        final PrintStream standardConsoleError = System.err;
        final InputStream standardConsoleInput = System.in;

        try {
            final CommandResult cmdRes = new CommandResult();
            final AbstractCommand command = new CommandExecutedWithError(cmdRes);

            final ByteArrayOutputStream errBAIS = new ByteArrayOutputStream();
            System.setErr(new PrintStream(errBAIS));
            System.setIn(new ByteArrayInputStream((command.getName() + " -"
                    + CommandExecutedWithError.DUMMY_SHORT_OPTION + "\n").getBytes()));
            final AbstractShell shell = new PetalsInteractiveCli(false, false);

            shell.registersCommand(command);

            shell.run();

            final String error = errBAIS.toString();
            assertFalse(error.isEmpty());
            assertTrue("The command is missing in error message",
                    error.startsWith("ERROR on command '" + command.getName() + "':"));
            assertTrue("The command usage is contained in error message",
                    !error.contains(command.getUsage()));
            assertEquals("Invalid exit code", 0, shell.getExitStatus());
        } finally {
            System.setIn(standardConsoleInput);
            System.setErr(standardConsoleError);
        }
    }

    /**
     * Sets a specific property on Windows, so that interactive unit tests work.
     * <p>
     * This problem is met neither with other shells, nor in real conditions.
     * </p>
     * <p>
     * If the current OS is not part of the Windows family, then this method does nothing.
     * </p>
     *
     * @param enable true to enable the hack, false otherwise
     */
    public static void hackForWindows( boolean enable ) {

    	// Hack for Windows and make unit tests work
        // Do not short cut the access to System.in
        String osName = System.getProperty( "os.name" );
        if( osName != null
        		&& osName.toLowerCase().contains( "win" ))
        	System.setProperty( "jline.WindowsTerminal.directConsole", String.valueOf( ! enable ));
        // End of hack
    }
}
