/**
 * Copyright 2011-2021 Asakusa Framework Team.
 *
 * 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.
 */
package com.asakusafw.yaess.basic;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.lang.SystemUtils;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.Assume;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.asakusafw.yaess.core.ExecutionContext;
import com.asakusafw.yaess.core.ExecutionMonitor;
import com.asakusafw.yaess.core.ExecutionPhase;
import com.asakusafw.yaess.core.ExecutionScript;
import com.asakusafw.yaess.core.ExecutionScriptHandler;

/**
 * Common features for testing this project.
 */
public class BasicScriptHandlerTestRoot {

    static final Logger LOG = LoggerFactory.getLogger(BasicScriptHandlerTestRoot.class);

    /**
     * Temporary folder.
     */
    @Rule
    public final TemporaryFolder folder = new TemporaryFolder();

    /**
     * Executes a script.
     * @param <T> script kind
     * @param script target script
     * @param handler target handler
     */
    protected <T extends ExecutionScript> void execute(T script, ExecutionScriptHandler<T> handler) {
        ExecutionContext context = new ExecutionContext("b", "f", "e", ExecutionPhase.MAIN, map());
        execute(context, script, handler);
    }

    /**
     * Executes a script.
     * @param <T> script kind
     * @param context current context
     * @param script target script
     * @param handler target handler
     */
    protected <T extends ExecutionScript>
    void execute(ExecutionContext context, T script, ExecutionScriptHandler<T> handler) {
        try {
            handler.execute(ExecutionMonitor.NULL, context, script);
        } catch (InterruptedException e) {
            throw new AssertionError(e);
        } catch (IOException e) {
            e.printStackTrace();
            Assume.assumeNoException(e);
        }
    }

    /**
     * Get scripts result.
     * @param copier copier path
     * @return result lines
     * @throws IOException if failed
     */
    protected List<String> getOutput(File copier) throws IOException {
        File output = new File(copier.getParentFile(), copier.getName() + ".out");
        List<String> results = new ArrayList<>();
        try (Scanner scanner = new Scanner(output)) {
            while (scanner.hasNextLine()) {
                results.add(scanner.nextLine());
            }
        }
        return results;
    }

    /**
     * Returns Asakusa home.
     * @return path
     */
    protected File getAsakusaHome() {
        return folder.getRoot();
    }

    /**
     * Puts source file into the path as an executable file.
     * @param source original contents on classpath
     * @param path target path on Asakusa home
     * @return the put file
     * @throws IOException if failed
     */
    protected File putScript(String source, String path) throws IOException {
        File file = new File(getAsakusaHome(), path);
        return putScript(source, file);
    }

    /**
     * Puts source file into the path as an executable file.
     * @param source original contents on classpath
     * @param file target file path
     * @return the put file
     * @throws IOException if failed
     */
    protected File putScript(String source, File file) throws IOException {
        Assume.assumeThat("Windows does not supported", SystemUtils.IS_OS_WINDOWS, is(false));
        LOG.debug("Deploy script: {} -> {}", source, file);
        try (InputStream in = getClass().getResourceAsStream(source)) {
            assertThat(source, in, is(notNullValue()));
            copyTo(in, file);
        }
        file.setExecutable(true);
        return file;
    }

    /**
     * Returns set.
     * @param values elements
     * @return result
     */
    protected Set<String> set(String... values) {
        return new TreeSet<>(Arrays.asList(values));
    }

    /**
     * Returns map.
     * @param keyValuePairs key value pairs
     * @return result
     */
    protected Map<String, String> map(String... keyValuePairs) {
        assert keyValuePairs.length % 2 == 0;
        Map<String, String> conf = new HashMap<>();
        for (int i = 0; i < keyValuePairs.length - 1; i += 2) {
            conf.put(keyValuePairs[i], keyValuePairs[i + 1]);
        }
        return conf;
    }

    private void copyTo(InputStream input, File target) throws IOException {
        assert input != null;
        assert target != null;
        File parent = target.getParentFile();
        assertThat(parent.getAbsolutePath(), parent, not(nullValue()));
        if (parent.isDirectory() == false) {
            assertThat(parent.getAbsolutePath(), parent.mkdirs(), is(true));
        }
        try (FileOutputStream output = new FileOutputStream(target)) {
            byte[] buf = new byte[1024];
            while (true) {
                int read = input.read(buf);
                if (read < 0) {
                    break;
                }
                output.write(buf, 0, read);
            }
        }
    }

    /**
     * Returns a matcher which tests whether RHS is in LHS.
     * @param matcher RHS
     * @param <T> the test target data type
     * @return the matcher
     */
    protected static <T> Matcher<Iterable<T>> has(Matcher<T> matcher) {
        return new BaseMatcher<Iterable<T>>() {
            @Override
            public boolean matches(Object item) {
                for (Object o : (Iterable<?>) item) {
                    if (matcher.matches(o)) {
                        return true;
                    }
                }
                return false;
            }
            @Override
            public void describeTo(Description description) {
                description.appendText("has ").appendDescriptionOf(matcher);
            }
        };
    }
}
