/*
 * This file is part of muCommander, http://www.mucommander.com
 *
 * muCommander is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * muCommander 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.mucommander.command;

import com.mucommander.commons.file.AbstractFile;
import com.mucommander.commons.file.FileFactory;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import java.io.File;

/**
 * Runs tests on {@link Command}.
 * @author Nicolas Rinaudo
 */
public class CommandTest {
    // - Constants -------------------------------------------------------------
    // -------------------------------------------------------------------------
    /** Test command's alias. */
    private static final String ALIAS        = "alias";
    /** Test command's command. */
    private static final String COMMAND      = "ls -la";
    /** Test command's display name. */
    private static final String DISPLAY_NAME = "test";



    // - Instance fields -------------------------------------------------------
    // -------------------------------------------------------------------------
    /** Used while testing keyword substitution. */
    private AbstractFile[] files;

    /** The instance of {@link Command} to test with */
    private Command command;

    // - Initialisation --------------------------------------------------------
    // -------------------------------------------------------------------------
    /**
     * Creates a batch of files used for testing.
     */
    @BeforeClass
    public void setUp() {
        command = new Command(null, null);
        try {
            files = new AbstractFile[6];

            files[0] = FileFactory.getFile(System.getProperty("user.dir"));
            files[1] = FileFactory.getFile(System.getProperty("user.dir") + System.getProperty("file.separator") + "test.txt");
            files[2] = FileFactory.getFile(System.getProperty("user.home"));
            files[3] = FileFactory.getFile(System.getProperty("user.home") + System.getProperty("file.separator") + "test.txt");
            files[4] = FileFactory.getFile(System.getProperty("java.home"));
            files[5] = FileFactory.getFile(System.getProperty("java.home") + System.getProperty("file.separator") + "test.txt");
        }
        // This is assumed never to happen.
        catch(Exception e) {}
    }



    // - Simple keyword substitution -------------------------------------------
    // -------------------------------------------------------------------------
    /**
     * Tests the <code>$f</code> keyword.
     */
    @Test
    public void testPathSubstitution() {
        String[] tokens;

        // Makes sure single file substitution works.
        tokens = command.getTokens("$f", files[0]);
        assert 1 == tokens.length;
        assert files[0].getAbsolutePath().equals(tokens[0]);

        // Makes sure multiple file substitution works.
        tokens = command.getTokens("$f", files);
        assert files.length == tokens.length;
        for(int i = 0; i < 3; i++)
            assert files[i].getAbsolutePath().equals(tokens[i]);
    }

    /**
     * Returns the specified file's parent, or an empty string if it doesn't have one.
     * @param  file file whose parent should be returned.
     * @return      the specified file's parent, or an empty string if it doesn't have one.
     */
    private String getParent(AbstractFile file) {
        AbstractFile parent;

        if((parent = file.getParent()) == null)
            return "";
        return parent.getAbsolutePath();
    }

    /**
     * Tests the <code>$p</code> keyword.
     */
    @Test
    public void testParentSubstitution() {
        String[] tokens;

        // Makes sure single file substitution works.
        tokens = command.getTokens("$p", files[0]);
        assert 1 == tokens.length;
        assert getParent(files[0]).equals(tokens[0]);

        // Makes sure multiple file substitution works.
        tokens = command.getTokens("$p", files);
        assert files.length == tokens.length;
        for(int i = 0; i < 3; i++)
            assert getParent(files[i]).equals(tokens[i]);
    }

    /**
     * Returns the specified file's extension, or <code>""</code> if it doesn't have one.
     * @return the specified file's extension.
     */
    private String getExtension(AbstractFile file) {
        String ext;

        if((ext = file.getExtension()) == null)
            return "";
        return ext;
    }

    /**
     * Tests the <code>$e</code> keyword.
     */
    @Test
    public void testExtensionSubstitution() {
        String[] tokens;

        // Makes sure single file substitution works (on directory).
        tokens = command.getTokens("$e", files[0]);
        assert 1 == tokens.length;
        assert getExtension(files[0]).equals(tokens[0]);

        // Makes sure single file substitution works (on file).
        tokens = command.getTokens("$e", files[1]);
        assert 1 == tokens.length;
        assert getExtension(files[1]).equals(tokens[0]);

        // Makes sure multiple file substitution works.
        tokens = command.getTokens("$e", files);
        assert files.length == tokens.length;
        for(int i = 0; i < 3; i++)
            assert getExtension(files[i]).equals(tokens[i]);
    }

    /**
     * Tests the <code>$b</code> keyword.
     */
    @Test
    public void testBasenameSubstitution() {
        String[] tokens;

        // Makes sure single file substitution works.
        tokens = command.getTokens("$b", files[0]);
        assert 1 == tokens.length;
        assert files[0].getNameWithoutExtension().equals(tokens[0]);

        // Makes sure multiple file substitution works.
        tokens = command.getTokens("$b", files);
        assert files.length == tokens.length;
        for(int i = 0; i < 3; i++)
            assert files[i].getNameWithoutExtension().equals(tokens[i]);
    }

    /**
     * Tests the <code>$n</code> keyword.
     */
    @Test
    public void testNameSubstitution() {
        String[] tokens;

        // Makes sure single file substitution works.
        tokens = command.getTokens("$n", files[0]);
        assert 1 == tokens.length;
        assert files[0].getName().equals(tokens[0]);

        // Makes sure multiple file substitution works.
        tokens = command.getTokens("$n", files);
        assert files.length == tokens.length;
        for(int i = 0; i < 3; i++)
            assert files[i].getName().equals(tokens[i]);
    }

    /**
     * Tests the <code>$j</code> keyword.
     */
    @Test
    public void testCurrentDirSubstitution() {
        String[] tokens;

        // Makes sure single file substitution works.
        tokens = command.getTokens("$j", files[0]);
        assert 1 == tokens.length;
        assert new File(System.getProperty("user.dir")).getAbsolutePath().equals(tokens[0]);

        // Makes sure multiple file substitution works.
        tokens = command.getTokens("$j", files);
        assert 1 == tokens.length;
        assert new File(System.getProperty("user.dir")).getAbsolutePath().equals(tokens[0]);
    }



    // - Tokenisation ----------------------------------------------------------
    // -------------------------------------------------------------------------
    /**
     * Runs tests on parsing behaviour with illegal keywords.
     */
    @Test
    public void testIllegalKeywords() {
        String[] tokens;

        // Makes sure unfinished keywords at the end of a command are kept.
        tokens = command.getTokens("ls -la $", files);
        assert 3 == tokens.length;
        assert "ls".equals(tokens[0]);
        assert "-la".equals(tokens[1]);
        assert "$".equals(tokens[2]);

        // Makes sure illegal keywords are not replaced.
        tokens = Command.getTokens("ls $a");
        assert 2 == tokens.length;
        assert "ls".equals(tokens[0]);
        assert "$a".equals(tokens[1]);

        // Makes sure unfinished keywords are not replaced.
        tokens = Command.getTokens("ls $ la");
        assert 3 == tokens.length;
        assert "ls".equals(tokens[0]);
        assert "$".equals(tokens[1]);
        assert "la".equals(tokens[2]);
    }

    /**
     * Runs tests on command parsing (with keyword substitution).
     */
    @Test
    public void testParsingWithSubstitution() {
        String[]      tokens;
        StringBuilder buffer;

        // Makes sure keywords are tokenised when not escaped.
        tokens = command.getTokens("ls $f", files);
        assert 1 + files.length == tokens.length;
        assert "ls".equals(tokens[0]);
        for(int i = 0; i < files.length; i++)
            assert tokens[i + 1].equals(files[i].getAbsolutePath());

        // Makes sure keywords are not tokenised when escaped.
        tokens = command.getTokens("ls \"$f\"", files);
        buffer = new StringBuilder("\"");
        buffer.append(files[0].getAbsolutePath());
        for(int i = 1; i < files.length; i++) {
            buffer.append(' ');
            buffer.append(files[i].getAbsolutePath());
        }
        buffer.append("\"");
        assert 2 == tokens.length;
        assert "ls".equals(tokens[0]);
        assert buffer.toString().equals(tokens[1]);
        
        // Makes sure that keyword substitution happens even if the keyword
        // is not a single token.
        tokens = command.getTokens("ls$fla", files[0]);
        assert 1 == tokens.length;
        assert ("ls" + files[0].getAbsolutePath() + "la").equals(tokens[0]);

        tokens = command.getTokens("ls$fla", files);
        assert files.length == tokens.length;
        assert ("ls" + files[0].getAbsolutePath()).equals(tokens[0]);
        for(int i = 1; i < files.length - 1; i++)
            assert files[i].getAbsolutePath().equals(tokens[i]);
        assert (files[files.length - 1].getAbsolutePath() + "la").equals(tokens[tokens.length - 1]);
    }

    /**
     * Runs tests on command parsing (without keyword substitution).
     */
    @Test
    public void testParsingWithoutSubstitution() {
        String[] tokens;

        // Makes sure simple command parsing works.
        tokens = Command.getTokens("ls -la");
        assert 2 == tokens.length;
        assert "ls".equals(tokens[0]);
        assert "-la".equals(tokens[1]);

        // Makes sure spaces are trimmed when they're expected to.
        tokens = Command.getTokens("ls     -la     ");
        assert 2 == tokens.length;
        assert "ls".equals(tokens[0]);
        assert "-la".equals(tokens[1]);

        // Makes sure quotes:
        // - escape spaces.
        // - are not removed from the command.
        tokens = Command.getTokens("ls \"- l a\"");
        assert 2 == tokens.length;
        assert "ls".equals(tokens[0]);
        assert "\"- l a\"".equals(tokens[1]);

        // Makes sure spaces are not trimmed when they're not expected to.
        tokens = Command.getTokens("ls \"-    l    a   \"");
        assert 2 == tokens.length;
        assert "ls".equals(tokens[0]);
        assert "\"-    l    a   \"".equals(tokens[1]);

        // Makes sure \s:
        // - escape quotes.
        // - are removed from the command.
        tokens = Command.getTokens("ls \\\"- l a");
        assert 4 == tokens.length;
        assert "ls".equals(tokens[0]);
        assert "\"-".equals(tokens[1]);
        assert "l".equals(tokens[2]);
        assert "a".equals(tokens[3]);

        // Makes sure \s:
        // - escape spaces.
        // - are remoed from the command.
        tokens = Command.getTokens("ls My\\ Documents");
        assert 2 ==tokens.length;
        assert "ls".equals(tokens[0]);
        assert "My Documents".equals(tokens[1]);

        // Makes sure 'complex' tokenisation works.
        tokens = Command.getTokens("/usr/bin/find . -name \\\\*.java -exec sh -c \"echo {}; wc {}\" \\\\;");
        assert 9 == tokens.length;
        assert "/usr/bin/find".equals(tokens[0]);
        assert ".".equals(tokens[1]);
        assert "-name".equals(tokens[2]);
        assert "\\*.java".equals(tokens[3]);
        assert "-exec".equals(tokens[4]);
        assert "sh".equals(tokens[5]);
        assert "-c".equals(tokens[6]);
        assert "\"echo {}; wc {}\"".equals(tokens[7]);
        assert "\\;".equals(tokens[8]);
    }



    // - Constructors tests ----------------------------------------------------
    // -------------------------------------------------------------------------
    /**
     * Makes sure the specified command matches the specified arguments.
     */
    private void checkCommand(Command command, CommandType type, boolean isDisplayNameSet) {
        // Tests common values.
        assert ALIAS.equals(command.getAlias());
        assert COMMAND.equals(command.getCommand());
        assert type == command.getType();

        // Tests context dependant values.
        if(isDisplayNameSet) {
            assert command.isDisplayNameSet();
            assert DISPLAY_NAME.equals(command.getDisplayName());
        }
        else {
            assert !command.isDisplayNameSet();
            assert ALIAS.equals(command.getDisplayName());
        }
    }

    /**
     * Makes sure all constructors initialise a command to the right values.
     */
    @Test
    public void testConstructors() {
        // Tests the 2 arguments constructor.
        checkCommand(new Command(ALIAS, COMMAND), CommandType.NORMAL_COMMAND, false);

        // Tests the 3 arguments constructor.
        checkCommand(new Command(ALIAS, COMMAND, CommandType.NORMAL_COMMAND), CommandType.NORMAL_COMMAND, false);
        checkCommand(new Command(ALIAS, COMMAND, CommandType.SYSTEM_COMMAND), CommandType.SYSTEM_COMMAND, false);
        checkCommand(new Command(ALIAS, COMMAND, CommandType.INVISIBLE_COMMAND), CommandType.INVISIBLE_COMMAND, false);

        // Tests the 4 arguments constructor.
        checkCommand(new Command(ALIAS, COMMAND, CommandType.NORMAL_COMMAND, DISPLAY_NAME), CommandType.NORMAL_COMMAND, true);
        checkCommand(new Command(ALIAS, COMMAND, CommandType.SYSTEM_COMMAND, DISPLAY_NAME), CommandType.SYSTEM_COMMAND, true);
        checkCommand(new Command(ALIAS, COMMAND, CommandType.INVISIBLE_COMMAND, DISPLAY_NAME), CommandType.INVISIBLE_COMMAND, true);
    }
}
