package net.sf.expectit.ant;

/*
 * #%L
 * ExpectIt
 * %%
 * Copyright (C) 2014 Alexey Gavrilov and contributors
 * %%
 * Licensed 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.
 * #L%
 */

import static org.junit.Assert.fail;
import static org.junit.runners.Parameterized.Parameters;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.io.Resources;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.URL;
import java.util.TreeSet;
import org.apache.ftpserver.FtpServer;
import org.apache.ftpserver.FtpServerFactory;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.ftpserver.listener.Listener;
import org.apache.ftpserver.listener.ListenerFactory;
import org.apache.ftpserver.usermanager.impl.BaseUser;
import org.apache.sshd.SshServer;
import org.apache.sshd.server.PasswordAuthenticator;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
import org.apache.sshd.server.session.ServerSession;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DefaultLogger;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectHelper;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

/**
 * A test harness for running Ant targets.
 */
@RunWith(Parameterized.class)
public class AntHarnessTest {

    private static FtpServer ftpServer;
    private static int ftpPort;

    private static SshServer sshServer;
    private static int sshPort;


    @BeforeClass
    public static void startFtpServer() throws FtpException {
        FtpServerFactory serverFactory = new FtpServerFactory();
        BaseUser user = new BaseUser();
        user.setName("ftp");
        user.setPassword("secret");
        serverFactory.getUserManager().save(user);
        ListenerFactory factory = new ListenerFactory();
        factory.setPort(0);
        Listener listener = factory.createListener();
        serverFactory.addListener("default", listener);
        ftpServer = serverFactory.createServer();
        ftpServer.start();
        ftpPort = listener.getPort();
    }

    @BeforeClass
    public static void startSshServer() throws IOException {
        sshServer = SshServer.setUpDefaultServer();
        ServerSocket serverSocket = new ServerSocket(0);
        sshPort = serverSocket.getLocalPort();
        serverSocket.close();
        sshServer.setPort(sshPort);
        sshServer.setPasswordAuthenticator(
                new PasswordAuthenticator() {
                    @Override
                    public boolean authenticate(
                            String username,
                            String password,
                            ServerSession session) {
                        return "ssh".equals(username) && "secret".equals(password);
                    }
                });
        sshServer.setShellFactory(new SshEchoCommandFactory());
        sshServer.setKeyPairProvider(new SimpleGeneratorHostKeyProvider());
        sshServer.start();
    }

    @AfterClass
    public static void stopServers() throws FtpException, InterruptedException {
        ftpServer.stop();
        sshServer.stop();
    }

    private static File buildFile;

    private static void setupBuildFile() throws IOException {
        if (buildFile != null) {
            return;
        }
        buildFile = File.createTempFile("build", ".xml");
        buildFile.deleteOnExit();
        URL url = Resources.getResource("build-test.xml");
        Resources.copy(url, new FileOutputStream(buildFile));
    }

    @Parameters(name = "{0}")
    public static Iterable<Object[]> data() throws IOException {
        Project project = newProject();
        Function<String, Object[]> toTargetArray = new Function<String, Object[]>() {
            @Override
            public Object[] apply(String input) {
                return new Object[]{input};
            }
        };
        Iterable<String> filtered = Iterables.filter(
                new TreeSet<String>(project.getTargets().keySet()),
                new Predicate<String>() {
                    @Override
                    public boolean apply(String input) {
                        return !input.isEmpty();
                    }
                }
        );
        return Iterables.transform(filtered, toTargetArray);
    }

    private static Project newProject() throws IOException {
        setupBuildFile();
        Project project = new Project();
        project.setUserProperty("ant.file", buildFile.getAbsolutePath());
        project.init();
        DefaultLogger listener = new DefaultLogger();
        listener.setErrorPrintStream(System.err);
        listener.setOutputPrintStream(System.out);
        listener.setMessageOutputLevel(Project.MSG_INFO);
        ProjectHelper helper = ProjectHelper.getProjectHelper();
        project.addReference("ant.projectHelper", helper);
        project.setProperty("ftp.port", String.valueOf(ftpPort));
        project.setProperty("ssh.port", String.valueOf(sshPort));
        helper.parse(project, buildFile);
        project.addBuildListener(listener);
        return project;
    }

    private final String target;

    public AntHarnessTest(String target) {
        this.target = target;
    }

    @Test
    public void runTarget() throws IOException {
        Project project = newProject();
        project.log("started: " + target);
        // prepare
        project.executeTarget("");
        boolean negative = target.endsWith("-negative");
        // run test
        try {
            project.executeTarget(target);
            if (negative) {
                fail("Negative test fails");
            }
        } catch (BuildException e) {
            e.printStackTrace();
            if (!negative) {
                fail("Positive test fails");
            }
        } finally {
            project.log("finished");
        }
    }

}