/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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
 *
 * https://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 org.apache.ant.antunit.listener;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.NumberFormat;

import org.apache.ant.antunit.AntUnitListener;
import org.apache.ant.antunit.AssertionFailedException;
import org.apache.tools.ant.BuildEvent;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.BuildListener;
import org.apache.tools.ant.Location;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.LogOutputStream;
import org.apache.tools.ant.types.EnumeratedAttribute;
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.util.KeepAliveOutputStream;
import org.apache.tools.ant.util.TeeOutputStream;

/**
 * A test listener for &lt;antunit&gt; modeled aftern the Plain JUnit
 * test listener that is part of Ant.
 */
public abstract class BaseAntUnitListener implements AntUnitListener {

    /**
     * Formatter for timings.
     */
    protected static final NumberFormat nf = NumberFormat.getInstance();

    /**
     * Create a new {@link BaseAntUnitListener} instance.
     * @param defaultReportTarget
     * @param extension
     */
    protected BaseAntUnitListener(SendLogTo defaultReportTarget, String extension) {
        logTo = defaultReportTarget;
        this.extension = extension;
        logLevel = BaseAntUnitListener.AntUnitLogLevel.NONE;
    }

    /**
     * Directory to write reports to.
     */
    private File toDir;

    /**
     * Extension for report files.
     */
    private String extension;

    /**
     * Directory to write reports to.
     * @return directory to write reports to
     */
    protected final File getToDir() {
        return toDir;
    }

    /**
     * Sets the directory to write test reports to.
     * @param f directory to write reports to
     */
    public void setToDir(File f) {
        toDir = f;
    }

    /**
     * Where to send log.
     */
    private SendLogTo logTo;

    /**
     * Where to send the test report.
     * @param logTo where to send the test report
     */
    protected void setSendLogTo(SendLogTo logTo) {
        this.logTo = logTo;
    }

    private Task parentTask;

    public void setParentTask(Task t) {
        parentTask = t;
    }

    /**
     * keeps track of the numer of executed targets, the failures an errors.
     */
    protected int runCount, failureCount, errorCount;
    /**
     * time for the starts of the current test-suite and test-target.
     */
    protected long start, testStart;

    public void startTestSuite(Project testProject, String buildFile) {
        start = System.currentTimeMillis();
        runCount = failureCount = errorCount = 0;
    }

    protected final void close(OutputStream out) {
        FileUtils.close(out);
    }

    public void startTest(String target) {
        testStart = System.currentTimeMillis();
        runCount++;
    }
    public void addFailure(String target, AssertionFailedException ae) {
        failureCount++;
    }
    public void addError(String target, Throwable ae) {
        errorCount++;
    }

    protected final OutputStream getOut(String buildFile) {
        final String dest = logTo.getValue();

        if (logTo.getIndex() < 0) {
            throw new BuildException(String.format("Invalid @sendlogto value '%s'", dest));
        }
        OutputStream l;

        if (SendLogTo.ANT_LOG.equals(dest) || SendLogTo.BOTH.equals(dest)) {
            if (parentTask != null) {
                l = new LogOutputStream(parentTask, Project.MSG_INFO);
            } else {
                l = new KeepAliveOutputStream(System.out);
            }
            if (SendLogTo.ANT_LOG.equals(dest)) {
                return l;
            }
        } else {
            l = null;
        }
        OutputStream f;

        String fileName = "TEST-" + normalize(buildFile) + "." + extension;
        File file;
        if (toDir != null) {
            file = new File(toDir, fileName);
        } else if (parentTask == null) {
            file = new File(fileName);
        } else {
            file = parentTask.getProject().resolveFile(fileName);
        }
        try {
            f = new FileOutputStream(file);
        } catch (IOException e) {
            throw new BuildException(e);
        }
        if (SendLogTo.FILE.equals(dest)) {
            return f;
        }
        return new TeeOutputStream(l, f);
    }

    /**
     * Turns the build file name into something that vaguely looks
     * like a Java classname.  Close enough to be suitable for
     * junitreport.
     * @param buildFile the test file name
     * @return the normalized name
     */
    protected final String normalize(String buildFile) {
        File base = parentTask != null ? parentTask.getProject().getBaseDir()
            : new File(System.getProperty("user.dir"));
        buildFile = FileUtils.getFileUtils().removeLeadingPath(base, new File(buildFile));
        if (buildFile.length() > 0 && buildFile.charAt(0) == File.separatorChar) {
            buildFile = buildFile.substring(1);
        }
        return buildFile.replace('.', '_').replace(':', '_').replace(File.separatorChar, '.');
    }

    protected final Location getLocation(Throwable t) {
        Location l = Location.UNKNOWN_LOCATION;
        if (t instanceof BuildException) {
            Location l2 = ((BuildException) t).getLocation();
            if (l2 != null) {
                l = l2;
            }
        }
        return l;
    }

    private Project currentTest;

    public void setCurrentTestProject(Project p) {
        currentTest = p;
        p.addBuildListener(new LogGrabber());
    }

    protected Project getCurrentTestProject() {
        return currentTest;
    }

    /**
     * The minimum level a log message must be logged at to be
     * included in the output.
     */
    private AntUnitLogLevel logLevel;

    /**
     * Sets the minimum level a log message must be logged at to be
     * included in the output.
     * @param l minimum level
     */
    public void setLogLevel(AntUnitLogLevel l) {
        logLevel = l;
    }

    /**
     * Gets messages from the project running the test target if their
     * level is at least of the level specified with {@link
     * #setLogLevel setLogLevel}.
     *
     * <p>This implementation is empty.</p>
     * @param event the logged message
     */
    protected void messageLogged(BuildEvent event) {}

    public static class SendLogTo extends EnumeratedAttribute {
        public static final String ANT_LOG = "ant";
        public static final String FILE = "file";
        public static final String BOTH = "both";

        public SendLogTo() {}

        public SendLogTo(String s) {
            setValue(s);
        }

        public String[] getValues() {
            return new String[] {ANT_LOG, FILE, BOTH};
        }
    }

    public static class AntUnitLogLevel extends EnumeratedAttribute {
        public static final AntUnitLogLevel NONE = new AntUnitLogLevel("none");

        public AntUnitLogLevel() {
            super();
        }

        private AntUnitLogLevel(String value) {
            super();
            setValue(value);
        }

        public String[] getValues() {
            return new String[] {
                "none",
                "error",
                "warn",
                "warning",
                "info",
                "verbose",
                "debug"};
        }

        private static int[] levels = {
            Project.MSG_ERR - 1,
            Project.MSG_ERR,
            Project.MSG_WARN,
            Project.MSG_WARN,
            Project.MSG_INFO,
            Project.MSG_VERBOSE,
            Project.MSG_DEBUG
        };

        public int getLevel() {
            return levels[getIndex()];
        }
    }

    public class LogGrabber implements BuildListener {
        public void buildStarted(BuildEvent event) {}
        public void buildFinished(BuildEvent event) {}
        public void targetStarted(BuildEvent event) {}
        public void targetFinished(BuildEvent event) {}
        public void taskStarted(BuildEvent event) {}
        public void taskFinished(BuildEvent event) {}
        public void messageLogged(BuildEvent event) {
            int priority = event.getPriority();
            // Filter out messages based on priority
            if (priority <= logLevel.getLevel()) {
                BaseAntUnitListener.this.messageLogged(event);
            }
        }
    }
}
