/**
 * 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.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.ow2.petals.cli.api.command.exception.CommandException;
import org.ow2.petals.cli.api.command.exception.CommandInvalidException;
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;
import org.ow2.petals.cli.shell.exception.UnknownCommandException;

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

    /**
     * A concrete script class to test {@link PetalsScript}.
     * 
     * @author Christophe DENEUX - EBM Websourcing
     */
    private class TestPetalsScript extends PetalsScript {

        final private InputStream is;
        
        private boolean unknownCommandErrorOccurs = false;

        private boolean commandInvalidErrorOccurs = false;

        private boolean commandErrorOccurs = false;

        public TestPetalsScript(final InputStream is) {
            super(System.out, System.err, false, false);
            this.is = is;
        }

        @Override
        protected InputStream getCommandStream() throws ShellException {
            return this.is;
        }

        @Override
        protected void onStart() {
            // NOP
        }

        @Override
        protected void onLine() {
            // NOP
        }

        @Override
        protected void onError(Exception e, int lineNumber) throws ShellException {

            if (e instanceof UnknownCommandException) {
                this.unknownCommandErrorOccurs = true;
                throw new ShellException(e);
            } else if (e instanceof ShellException) {
                throw (ShellException) e;
            } else if (e instanceof CommandInvalidException) {
                this.commandInvalidErrorOccurs = true;
                throw new ShellException(e);
            } else if (e instanceof CommandException) {
                this.commandErrorOccurs = true;
                throw new ShellException(e);
            } else {
                throw new ShellException(e);
            }
        }

        public boolean isUnknownCommandErrorOccurs() {
            return this.unknownCommandErrorOccurs;
        }

        public boolean isCommandInvalidErrorOccurs() {
            return this.commandInvalidErrorOccurs;
        }

        public boolean isCommandErrorOccurs() {
            return this.commandErrorOccurs;
        }

        @Override
        public String askQuestion(String question, boolean isReplyPassword) throws ShellException {
            return null;
        }

        @Override
        public boolean confirms(String message) throws ShellException {
            return false;
        }
    }

    @Before
    public void setUp() throws Exception {
        // NOP
    }

    @After
    public void tearDown() throws Exception {
        // NOP
    }

    /**
     * Check the run method against a null input stream.
     * 
     * @throws ShellException
     */
    @Test
    public final void testRun_NullInputStream() throws ShellException {
        final AbstractShell shell = new TestPetalsScript(null);

        shell.run();
    }

    /**
     * Check the run method against an empty input stream.
     * 
     * @throws ShellException
     */
    @Test
    public final void testRun_EmptyInputStream() throws ShellException {
        final InputStream bais = new ByteArrayInputStream(new byte[] {});
        final AbstractShell shell = new TestPetalsScript(bais);

        shell.run();
    }

    /**
     * Another test command without argument. If an argument is provided, an
     * error is raised.
     * 
     * @author Christophe DENEUX - EBM Websourcing
     */
    private class TestCommandNotExecuted extends AbstractCommand {

        private final CommandResult cmdRes;

        public TestCommandNotExecuted(final CommandResult cmdRes) {
            super();
            this.cmdRes = cmdRes;
        }

        @Override
        public void execute(String[] args) throws CommandException {

            cmdRes.setExecuted(true);

            if (args.length > 0) {
                throw new CommandInvalidException(this, "Invalid parameter");
            }
        }
    }

    /**
     * Check the run method against an input stream with a not-registered
     * command.
     */
    @Test
    public final void testRun_UnregisteredCommandInputStream() {
        final InputStream bais = new ByteArrayInputStream("unknowncommand".getBytes());
        final TestPetalsScript shell = new TestPetalsScript(bais);

        shell.run();

        assertTrue("The unknown command was not detgected", shell.isUnknownCommandErrorOccurs());
    }

    /**
     * Check the run method against an input stream with an right command.
     */
    @Test
    public final void testRun_RightCommandInputStream() throws DuplicatedCommandException {
        final InputStream bais = new ByteArrayInputStream(CommandNoArg.class.getSimpleName()
                .toLowerCase().getBytes());
        final AbstractShell shell = new TestPetalsScript(bais);
        final CommandResult cmdRes = new CommandResult();

        shell.registersCommand(new CommandNoArg(cmdRes));

        shell.run();

        assertTrue(cmdRes.isExecuted());

    }

    /**
     * Check the run method against an input stream with an command with error.
     */
    @Test
    public final void testRun_CommandWithErrorInputStream() throws DuplicatedCommandException {
        final InputStream bais = new ByteArrayInputStream((CommandNoArg.class.getSimpleName()
                .toLowerCase() + " arg1").getBytes());
        final AbstractShell shell = new TestPetalsScript(bais);
        final CommandResult cmdRes = new CommandResult();

        shell.registersCommand(new CommandNoArg(cmdRes));

        shell.run();
        
        assertTrue(cmdRes.isExecuted());
    
        final Throwable error = cmdRes.getError();
        assertNotNull(error);
        assertTrue(error instanceof CommandInvalidException);
        
    }

    /**
     * Check the run method against an input stream of several commands is
     * stopped when an unknown command occurs.
     */
    @Test
    public final void testRun_CommandSuiteWithUnknwonCommandInputStream()
            throws DuplicatedCommandException {
        final StringBuffer commandsFlow = new StringBuffer();
        commandsFlow.append(CommandNoArg.class.getSimpleName().toLowerCase());
        commandsFlow.append(System.getProperty( "line.separator" ));
        commandsFlow.append("unknowncommand");
        commandsFlow.append(System.getProperty( "line.separator" ));
        commandsFlow.append(TestCommandNotExecuted.class.getSimpleName().toLowerCase());

        final CommandResult cmdRes1 = new CommandResult();
        final CommandResult cmdRes2 = new CommandResult();

        final InputStream bais = new ByteArrayInputStream(commandsFlow.toString().getBytes());
        final TestPetalsScript shell = new TestPetalsScript(bais);
        shell.registersCommand(new CommandNoArg(cmdRes1));
        shell.registersCommand(new TestCommandNotExecuted(cmdRes2));

        shell.run();

        assertTrue("The first command was not executed", cmdRes1.isExecuted());
        assertTrue("The unknown command was not found", shell.isUnknownCommandErrorOccurs());
        assertFalse("The second command was executed", cmdRes2.isExecuted());

    }

    /**
     * Check the run method against an input stream of several commands is
     * stopped when an error occurs on a command parsing.
     */
    @Test
    public final void testRun_CommandSuiteWithCommandParsingErrorInputStream()
            throws DuplicatedCommandException {
        final StringBuffer commandsFlow = new StringBuffer();
        commandsFlow.append(CommandNoArg.class.getSimpleName().toLowerCase());
        commandsFlow.append(System.getProperty( "line.separator" ));
        commandsFlow.append(CommandNoArg.class.getSimpleName().toLowerCase() + " arg1");
        commandsFlow.append(System.getProperty( "line.separator" ));
        commandsFlow.append(TestCommandNotExecuted.class.getSimpleName().toLowerCase());

        final CommandResult cmdRes1 = new CommandResult();
        final CommandResult cmdRes2 = new CommandResult();

        final InputStream bais = new ByteArrayInputStream(commandsFlow.toString().getBytes());
        final TestPetalsScript shell = new TestPetalsScript(bais);
        shell.registersCommand(new CommandNoArg(cmdRes1));
        shell.registersCommand(new TestCommandNotExecuted(cmdRes2));

        shell.run();

        assertTrue("The first command was not executed", cmdRes1.isExecuted());
        assertTrue("No error occurs on the parsing of the second command",
                shell.isCommandInvalidErrorOccurs());
        assertFalse("The second command was executed", cmdRes2.isExecuted());
    }

    /**
     * Check the run method against an input stream of several commands is
     * stopped when an error occurs on a command execution.
     */
    @Test
    public final void testRun_CommandSuiteWithCommandExecutionErrorInputStream()
            throws DuplicatedCommandException {
        final StringBuffer commandsFlow = new StringBuffer();
        commandsFlow.append(CommandNoArg.class.getSimpleName().toLowerCase());
        commandsFlow.append(System.getProperty( "line.separator" ));
        commandsFlow.append(CommandExecutedWithError.class.getSimpleName().toLowerCase() + " -"
                + CommandExecutedWithError.DUMMY_SHORT_OPTION);
        commandsFlow.append(System.getProperty( "line.separator" ));
        commandsFlow.append(TestCommandNotExecuted.class.getSimpleName().toLowerCase());

        final CommandResult cmdRes1 = new CommandResult();
        final CommandResult cmdRes2 = new CommandResult();
        final CommandResult cmdRes3 = new CommandResult();

        final InputStream bais = new ByteArrayInputStream(commandsFlow.toString().getBytes());
        final TestPetalsScript shell = new TestPetalsScript(bais);
        shell.registersCommand(new CommandNoArg(cmdRes1));
        shell.registersCommand(new CommandExecutedWithError(cmdRes2));
        shell.registersCommand(new TestCommandNotExecuted(cmdRes3));

        shell.run();

        assertTrue("The first command was not executed", cmdRes1.isExecuted());
        assertTrue("No error occurs on the execution of the second command",
                shell.isCommandErrorOccurs());
        assertFalse("The third command was executed", cmdRes3.isExecuted());
    }

    /**
     * A concrete script class to test {@link PetalsScript} according to the
     * callback order.
     */
    private class TestPetalsScriptCallback extends TestPetalsScript {

        private int counterInvokation = 0;

        public TestPetalsScriptCallback(final InputStream is) {
            super(is);
        }

        @Override
        protected void onStart() {
            // onStart is always invoked in first position
            assertEquals(0, this.counterInvokation++);
        }

        @Override
        protected void onLine() {
            // onLine is always invoked at least in 2nd position
            assertTrue(this.counterInvokation++ > 0);
        }

        @Override
        protected void onError(Exception e, int lineNumber) throws ShellException {

            // onError is always invoked at least in third position
            assertTrue(this.counterInvokation++ > 1);
            assertTrue(lineNumber >= 1);

            super.onError(e, lineNumber);
        }
    }

    /**
     * Check the run method against the callback methods.
     * 
     * @throws ShellException
     */
    @Test
    public final void testRun_CallbackWithoutError() throws ShellException {
        final StringBuffer commandsFlow = new StringBuffer();
        commandsFlow.append(CommandNoArg.class.getSimpleName().toLowerCase());
        commandsFlow.append(System.getProperty( "line.separator" ));
        commandsFlow.append(CommandNoArg.class.getSimpleName().toLowerCase());
        commandsFlow.append(System.getProperty( "line.separator" ));
        commandsFlow.append(CommandNoArg.class.getSimpleName().toLowerCase());

        final CommandResult cmdRes = new CommandResult();

        final InputStream bais = new ByteArrayInputStream(commandsFlow.toString().getBytes());
        final AbstractShell shell = new TestPetalsScriptCallback(bais);
        shell.registersCommand(new CommandNoArg(cmdRes));

        shell.run();

        assertTrue(cmdRes.isExecuted());

    }

    /**
     * Check the run method against the callback methods.
     * 
     * @throws ShellException
     */
    @Test
    public final void testRun_CallbackWithError() throws ShellException {
        final StringBuffer commandsFlow = new StringBuffer();
        commandsFlow.append(CommandNoArg.class.getSimpleName().toLowerCase());
        commandsFlow.append(System.getProperty( "line.separator" ));
        commandsFlow.append(CommandNoArg.class.getSimpleName().toLowerCase() + " arg1");
        commandsFlow.append(System.getProperty( "line.separator" ));
        commandsFlow.append(TestCommandNotExecuted.class.getSimpleName().toLowerCase());

        final CommandResult cmdRes1 = new CommandResult();
        final CommandResult cmdRes2 = new CommandResult();

        final InputStream bais = new ByteArrayInputStream(commandsFlow.toString().getBytes());
        final TestPetalsScriptCallback shell = new TestPetalsScriptCallback(bais);
        shell.registersCommand(new CommandNoArg(cmdRes1));
        shell.registersCommand(new TestCommandNotExecuted(cmdRes2));

        shell.run();

        assertTrue(cmdRes1.isExecuted());
        assertTrue(shell.isCommandInvalidErrorOccurs());
        assertFalse(cmdRes2.isExecuted());
    }

}
