/*
 * $Id$
 *
 * Copyright 1996-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
package com.sun.javatest;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Iterator;

import com.sun.javatest.util.I18NResourceBundle;
import com.sun.javatest.util.Properties;
import com.sun.javatest.util.StringArray;

/**
 * An implementation of Parameters, using data read from a .jtp file.
 */
public class FileParameters
    extends
        BasicParameters
    implements
        Parameters.LegacyEnvParameters
{
    /**
     * Determine if the specified file is a parameter file,
     * as determined by whether its extension is .jtp or not.
     * @param file the file to be checked
     * @return true if the specified file is a parameter file,
     * and false otherwise
     */
    public static boolean isParameterFile(File file) {
        return file.getPath().endsWith(PARAMFILE_EXTN);
    }

    /**
     * Create an empty FileParameters object.
     */
    public FileParameters() {
    }

    /**
     * Create a FileParameters object, based on data read from a parameter file.
     * @param file the file to be read to initialize this object
     * @throws FileNotFoundException if the file does not exist
     * @throws IOException if there is a problem reading the file
     */
    public FileParameters(File file)
        throws FileNotFoundException, IOException
    {
        Properties p = new Properties();
        Reader in = new BufferedReader(new FileReader(file));
        p.load(in);
        in.close();

        setTestSuite(adjustPath(p.getProperty("javasoft.sqe.javatest.selection.testSuite")));
        setWorkDirectory(p.getProperty("javasoft.sqe.javatest.results.workDir"));
        setTests(p.getProperty("javasoft.sqe.javatest.selection.tests"));
        setExcludeFiles(p.getProperty("javasoft.sqe.javatest.selection.excludeList"));
        String keywordOp = p.getProperty("javasoft.sqe.javatest.selection.keywordOp");
        String keywords = p.getProperty("javasoft.sqe.javatest.selection.keywords");
        setKeywords(keywordOp, keywords);
        String statusOp = p.getProperty("javasoft.sqe.javatest.selection.status");
        String[] statusTests = new String[Status.NUM_STATES];
        statusTests[Status.PASSED] = p.getProperty("javasoft.sqe.javatest.selection.prev.passed");
        statusTests[Status.FAILED] = p.getProperty("javasoft.sqe.javatest.selection.prev.failed");
        statusTests[Status.ERROR] = p.getProperty("javasoft.sqe.javatest.selection.prev.error");
        statusTests[Status.NOT_RUN] = p.getProperty("javasoft.sqe.javatest.selection.prev.notRun");
        setPriorStatusValues(statusOp, statusTests);
        setEnvFiles(p.getProperty("javasoft.sqe.javatest.execution.envFiles"));
        setEnvName(p.getProperty("javasoft.sqe.javatest.execution.environment"));
        setConcurrency(p.getProperty("javasoft.sqe.javatest.execution.concurrency"));
        setTimeoutFactor(p.getProperty("javasoft.sqe.javatest.execution.timeFactor"));
        setReportDir(p.getProperty("javasoft.sqe.javatest.results.reportDir"));
    }

    /**
     * Create a FileParameters object, based on command-line-like args.
     * The args that are accepted are:
     * <dl>
     * <dt><code>-t</code> <i>testsuite</i><br><code>-testsuite</code> <i>testsuite</i>
     * <dd>Specify the test suite
     * <dt><code>-keywords</code> <i>expr</i>
     * <dd>Specify a keyword expression, used to filter the tests to be run.
     * <dt><code>-status</code> <i>status-list></i>
     * <dd>Specify the status values used to select tests at runtime.
     * <i>status-list</i> should be a comma-separated list of words from
     * the following list:
     *    <code>passed</code>,
     *    <code>failed</code>,
     *    <code>error</code>,
     *    <code>notRun</code>
     * <dt><code>-exclude</code> <i>exclude-list-file</i>
     * <dd>Specify an exclude-list file containing a list of tests to
     * be excluded from the test run.
     * The option can be specified more than once, with different files.
     * <dt><code>-envFile</code> <i>environment-file</i>
     * <dd>Specify an environment file, containing environment entries
     * providing details on how to run tests.
     * The option can be specified more than once, with different files.
     * <dt><code>-env</code> <i>environment-name</i>
     * <dd>Specify the name of the environment to be used from the
     * set of environment files.
     * <dt><code>-concurrency</code> <i>number</i>
     * <dd>Specify how many tests JT Harness may run at once. The default
     * is 1.
     * <dt><code>-timeoutFactor</code> <i>number</i>
     * <dd>Specify a scale factor to be used to multiply the timeout
     * value for each test, to allow for running on slow CPUs.
     * <dt><code>-report</code> <i>report-dir</i><br><code>-r</code> <i>report-dir</i>
     * <dd>Specify a directory in which to write reports at the end of the test run.
     * <dt><code>-workDir</code> <i>work-dir</i><br><code>-w</code> <i>work-dir</i>
     * <dd>Specify a directory in which to write the results of the individual tests.
     * <dt><i>initial-files</i>
     * <dd>Trailing file arguments are treated as initial files, used to select
     * which parts of the test suite should be run.
     * </dl>
     * The test suite, work directory and report directory are evaluated
     * relative to the user's current directory, unless the location specified
     * is an absolute path.  The exclude list and environment files are located
     * relative to the test suite location, unless they are absolute paths.
     * @param args The args used to initialize the FileParameters object.
     * @throws IllegalArgumentException If an unrecognized argument is found.
     */
    public FileParameters(String[] args) {
        String testSuiteArg = null;
        String workDirArg = null;
        String testsArgs = null;
        String exclFilesArgs = null;
        String keywordsExprArg = null;
        String priorStatusValuesArg = null;
        String envFilesArgs = null;
        String envNameArg = null;
        String concurrencyArg = null;
        String timeoutFactorArg = null;
        String reportDirArg = null;

        for (int i = 0; i < args.length; i++) {
            if ("-testSuite".equalsIgnoreCase(args[i]) || "-t".equalsIgnoreCase(args[i]))
                testSuiteArg = args[++i];
            else if ("-keywords".equalsIgnoreCase(args[i]))
                keywordsExprArg = args[++i];
            else if ("-status".equalsIgnoreCase(args[i]))
                priorStatusValuesArg = args[++i].toLowerCase();
            else if ("-exclude".equalsIgnoreCase(args[i])) {
                if (exclFilesArgs == null)
                    exclFilesArgs = args[++i];
                else
                    exclFilesArgs += " " + args[++i];
            }
            else if ("-envFile".equalsIgnoreCase(args[i])) {
                if (envFilesArgs == null)
                    envFilesArgs = args[++i];
                else
                    envFilesArgs += " " + args[++i];
            }
            else if ("-env".equalsIgnoreCase(args[i]))
                envNameArg = args[++i];
            else if ("-concurrency".equalsIgnoreCase(args[i]))
                concurrencyArg = args[++i];
            else if ("-timeoutFactor".equalsIgnoreCase(args[i]))
                timeoutFactorArg = args[++i];
            else if ("-report".equalsIgnoreCase(args[i]) || "-r".equalsIgnoreCase(args[i]))
                reportDirArg = args[++i];
            else if ("-workDir".equalsIgnoreCase(args[i]) || "-w".equalsIgnoreCase(args[i]))
                workDirArg = args[++i];
            else if (args[i].startsWith("-"))
                throw new IllegalArgumentException(args[i]);
            else {
                String[] tests = new String[args.length - i];
                System.arraycopy(args, i, tests, 0, tests.length);
                testsArgs = StringArray.join(tests);
                i = args.length;
            }
        }

        setTestSuite(adjustPath(testSuiteArg));
        setWorkDirectory(adjustPath(workDirArg));
        setTests(testsArgs);
        setExcludeFiles(exclFilesArgs);
        setKeywords("expr", keywordsExprArg);
        setPriorStatusValues(priorStatusValuesArg);
        setEnvFiles(envFilesArgs);
        setEnvName(envNameArg);
        setConcurrency(concurrencyArg);
        setTimeoutFactor(timeoutFactorArg);
        setReportDir(adjustPath(reportDirArg));
    }

    //---------------------------------------------------------------------

    public Parameters.EnvParameters getEnvParameters() {
        return this;
    }

    public File[] getEnvFiles() {
        return envFiles;
    }

    public File[] getAbsoluteEnvFiles() {
        updateAbsoluteEnvFiles();
        return cachedAbsEnvFiles;
    }

    public void setEnvFiles(File[] files) {
        envFiles = files;
    }

    private void setEnvFiles(String files) {
        String[] f = StringArray.split(files);
        File[] ff = new File[f.length];

        // legacy behavior requires that paths be relative to the testSuite
        // location specified in the JTE or on the cmd line
        for (int i = 0; i < ff.length; i++)  {
            ff[i] = new File(makeLegacyTsRelative(f[i]));
        }
        setEnvFiles(ff);
    }

    public String getEnvName() {
        return envName;
    }

    public void setEnvName(String name) {
        envName = name;
    }

    /**
     * Get an object containing the environments read from the environment files.
     * @return an object containing all the environments read from the environment files.
     * @see #setEnvFiles
     * @see #setEnvName
     */
    public TestEnvContext getEnvTable() {
        updateEnvTable();
        return cachedEnvTable;
    }

    public TestEnvironment getEnv() {
        updateEnv();
        return cachedEnv;
    }

    private void updateAbsoluteEnvFiles() {
        TestSuite ts = getTestSuite();
        File base = (ts == null ? null : ts.getRootDir());
        if (cachedAbsEnvFiles == null ||
            cachedAbsEnvFiles_base != base ||
            cachedAbsEnvFiles_envFiles != envFiles) {
            cachedAbsEnvFiles = getAbsoluteFiles(base, envFiles);
        }
    }

    private void updateEnvTable() {
        updateAbsoluteEnvFiles();
        File[] absEnvFiles = cachedAbsEnvFiles;
        if (cachedEnvTable == null
           || !equal(absEnvFiles, cachedEnvTable_absEnvFiles)) {
            try {
                cachedEnvTable = new TestEnvContext(absEnvFiles);
                cachedEnvTable_absEnvFiles = absEnvFiles;
                envTableError = null;
            }
            catch (TestEnvContext.Fault e) {
                cachedEnvTable = null;
                envTableError = e.getMessage();
            }
        }
    }

    private void updateEnv() {
        TestEnvContext envTable = getEnvTable();
        if (envTable == null) {
            cachedEnv = null;
            envError = i18n.getString("fp.noEnvs", envName);
            return;
        }

        TestEnvironment env;
        try {
            if (envName == null)
                envName = "";
            env = envTable.getEnv(envName);
            if (env == null) {
                // note envName==null is always a valid environment
                cachedEnv = null;
                envError = i18n.getString("fp.envNotFound", envName);
                return;
            }
            for (Iterator i = env.elements().iterator(); i.hasNext(); ) {
                TestEnvironment.Element entry = (TestEnvironment.Element) (i.next());
                if (entry.value.indexOf("VALUE_NOT_DEFINED") >= 0) {
                    String eText =
                        ( (entry.definedInEnv == null ? "" : "env." +  entry.definedInEnv + ".") +
                          entry.key + "=" + entry.value);
                    cachedEnv = null;
                    envError = i18n.getString("fp.undefinedEntry",
                                       new Object[] {eText, entry.definedInFile});
                    return;
                }
            }
        }
        catch (TestEnvironment.Fault e) {
            cachedEnv = null;
            envError = i18n.getString("fp.badEnv", new Object[] {envName, e.getMessage()});
            return;
        }
        cachedEnv = env;
        envError = null;
    }

    private boolean isEnvOK() {
        updateEnv();
        return (envTableError == null && envError == null);
    }

    private File[] envFiles;
    private File[] cachedAbsEnvFiles;
    private File cachedAbsEnvFiles_base;
    private File[] cachedAbsEnvFiles_envFiles;
    private String envName;

    private TestEnvContext cachedEnvTable;
    private File[] cachedEnvTable_absEnvFiles;
    private String envTableError;

    private TestEnvironment cachedEnv;
    private TestEnvContext cachedEnv_envTable;
    private String cachedEnv_envName;
    private String envError;

    //---------------------------------------------------------------------

    public boolean isValid() {
        return ( super.isValid() && isEnvOK() );
    }

    public String getErrorMessage() {
        String basicError = super.getErrorMessage();
        return (basicError != null ? basicError
                : envTableError != null ? envTableError
                : null);
    }

    //---------------------------------------------------------------------

    private void setTestSuite(String path) {
        legacyTsPath = path;
        File p = (path == null ? null : new File(path));

        // we assume that the path has already be made non-relative
        // i.e. absolute
        if (p != null && p.isFile())
            legacyTsPath = p.getParent();

        // this strange arrangement of checks is here because we
        // are trying to ensure that users playing around with legacy
        // configurations get reasonable results
        if (p == null || TestSuite.isTestSuite(p)) {
            setTestSuite(p);
        }
        else {
            File parent = p.getParentFile();
            // fallback check, check parent of specified location
            if (parent != null && TestSuite.isTestSuite(parent)) {
                setTestSuite(parent);
            }
            else {
                // must do this to ensure proper errors occur
                setTestSuite(p);
            }
        }
    }

    //---------------------------------------------------------------------

    private void setWorkDirectory(String path) {
        setWorkDirectory(path == null ? null : new File(path));
    }

    //---------------------------------------------------------------------

    private void setTests(String tests) {
        setTests(StringArray.split(tests));
    }

    //---------------------------------------------------------------------

    private void setExcludeFiles(String files) {
        String[] f = StringArray.split(files);
        File[] ff = new File[f.length];

        // legacy behavior requires that paths be relative to the testSuite
        // location specified in the JTE or on the cmd line
        for (int i = 0; i < ff.length; i++)  {
            ff[i] = new File(makeLegacyTsRelative(f[i]));
        }
        setExcludeFiles(ff);
    }

    //---------------------------------------------------------------------

    private void setKeywords(String op, String value) {
        if (op == null || op.equals("ignore") )
            setKeywordsMode(NO_KEYWORDS);
        else if (op.equals("expr"))
            setKeywords(EXPR, value);
        else if (op.equals("allOf"))
            setKeywords(ALL_OF, value);
        else if (op.equals("anyOf"))
            setKeywords(ANY_OF, value);
        else
            setKeywordsMode(NO_KEYWORDS);
    }

    //---------------------------------------------------------------------

    private void setPriorStatusValues(String op, String[] values) {
        if (op == null || !op.equals("allOf"))
            setPriorStatusValues((boolean[]) null);
        else {
            boolean[] b = new boolean[Status.NUM_STATES];
            for (int i = 0; i < values.length; i++)
                b[i] = "true".equals(values[i]);
            setPriorStatusValues(b);
        }
    }

    private void setPriorStatusValues(String values) {
        if (values == null || values.length() == 0)
            setPriorStatusValues((boolean[]) null);
        else {
            boolean[] b = new boolean[Status.NUM_STATES];
            b[Status.PASSED]  = (values.indexOf("pass") != -1);
            b[Status.FAILED]  = (values.indexOf("fail") != -1);
            b[Status.ERROR]   = (values.indexOf("erro") != -1);
            b[Status.NOT_RUN] = (values.indexOf("notr") != -1);
            setPriorStatusValues(b);
        }
    }

    //---------------------------------------------------------------------

    private void setConcurrency(String conc) {
        if (conc == null)
            setConcurrency(1);
        else {
            try {
                setConcurrency(Integer.parseInt(conc));
            }
            catch (NumberFormatException e) {
                concurrencyError = i18n.getString("fp.badConcurrency", conc);
            }
        }
    }

    //---------------------------------------------------------------------

    private void setTimeoutFactor(String tf) {
        if (tf == null)
            setTimeoutFactor(1);
        else {
            try {
                setTimeoutFactor(Float.parseFloat(tf));
            }
            catch (NumberFormatException e) {
                timeoutFactorError = i18n.getString("fp.badTimeoutFactor", tf);
            }
        }
    }

    //---------------------------------------------------------------------

    /**
     * Get the report directory given in the parameters.
     * @return the report directory
     * @see #setReportDir
     */
    public File getReportDir() {
        return reportDir;
    }

    /**
     * Set the report directory.
     * @param dir the report directory
     * @see #getReportDir
     */
    public void setReportDir(File dir) {
        // check report dir exists?
        reportDir = dir;
    }

    private void setReportDir(String dir) {
        if (dir == null)
            setReportDir((File) (null));
        else
            setReportDir(new File(dir));
    }

    private File reportDir;

    //---------------------------------------------------------------------

    /**
     * Makes the given path relative to the user's CWD if it is a
     * relative value.  So "tests" may be turned into
     * <code>/home/me/mytck/tests</code>.
     */
    private String adjustPath(String path) {
        if (path == null)
            return path;

        File p = new File(path);
        if (p.isAbsolute())
            return path;
        else {
            String userDir = System.getProperty("user.dir");
            if (userDir == null)
                return path;
            else
                return userDir + File.separator + path;
        }
    }

    private String makeLegacyTsRelative(String path) {
        if (path == null)
            return path;

        File p = new File(path);
        if (p.isAbsolute())
            return path;
        else {
            if (legacyTsPath == null)
                return path;
            else
                return legacyTsPath + File.separator + path;
        }
    }

    private String legacyTsPath;        // always a directory
    private static final String PARAMFILE_EXTN = ".jtp";

    private static final I18NResourceBundle i18n =
        I18NResourceBundle.getBundleForClass(FileParameters.class);
}
