/*
 * $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.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.util.Vector;

import com.sun.javatest.util.I18NResourceBundle;

/**
 * A basic implementation of Parameters for all except the EnvParameters
 * subsection.
 */
public abstract class BasicParameters
    implements
        Parameters,
        Parameters.MutableTestsParameters,
        Parameters.MutableExcludeListParameters,
        Parameters.MutableKeywordsParameters,
        Parameters.MutablePriorStatusParameters,
        Parameters.MutableConcurrencyParameters,
        Parameters.MutableTimeoutFactorParameters
{
    //---------------------------------------------------------------------

    public TestSuite getTestSuite() {
        return testSuite;
    }

    /**
     * Set the test suite for the test run. The test suite may only be set once.
     * If the test suite cannot be opened, isValid will return false, and
     * getErrorMessage will contain an error message.
     * @param file a path defining the test suite to be opened and set as the test
     * suite for the test run.
     * @see #getTestSuite
     * @see #setTestSuite(TestSuite)
     */
    public void setTestSuite(File file) {
        if (file == null)
            testSuiteError = i18n.getString("bp.noTestSuite");
        else {
            try {
                setTestSuite(TestSuite.open(file));
                //System.err.println("BP.setTestSuite: " + file + " opened");
            }
            catch (FileNotFoundException e) {
                testSuiteError = i18n.getString("bp.cantFindTestSuite", file);
            }
            catch (TestSuite.Fault e) {
                testSuiteError = i18n.getString("bp.badTestSuite", e.getMessage());
            }
        }
    }

    /**
     * Set the test suite for the test run. The test suite may only be set once.
     * @param ts the test suite to be set.
     * @see #getTestSuite
     * @throws NullPointerException if ts is null
     * @throws IllegalStateException if the test suite has already been set to
     * something different
     */
    public void setTestSuite(TestSuite ts) {
        if (ts == null)
            throw new NullPointerException();

        if (testSuite != null && testSuite != ts)
            throw new IllegalStateException();

        testSuite = ts;
        testSuiteError = null;
    }

    private boolean isTestSuiteOK() {
        return (testSuiteError == null);
    }


    private TestSuite testSuite;

    /**
     * A string to identify any errors that may have occurred when
     * setting the test suite, or null if there were no such errors.
     */
    protected String testSuiteError;

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

    public WorkDirectory getWorkDirectory() {
        return workDir;
    }

    /**
     * Set the work directory for the test run. The work directory may only
     * be set once.
     * If the work directory cannot be opened, isValid will return false, and
     * getErrorMessage will contain an error message.
     * The test suite must already be set before this method is called.
     * @param dir a path defining the work directory to be opened and set as the
     * work directory for the test run.
     * @see #getWorkDirectory
     * @see #setWorkDirectory(WorkDirectory)
     */
    public void setWorkDirectory(File dir) {
        if (dir == null)
            workDirError = i18n.getString("bp.workDirMissing");
        else if (isTestSuiteOK()) {
            try {
                TestSuite ts = getTestSuite();
                if (dir.exists()) {
                    if (WorkDirectory.isWorkDirectory(dir)) {
                        setWorkDirectory(WorkDirectory.open(dir, ts));
                        workDirError = null;
                    }
                    else if (WorkDirectory.isEmptyDirectory(dir)) {
                        workDir = WorkDirectory.create(dir, ts);
                        workDirError = null;
                    }
                    else
                        workDirError = i18n.getString("bp.badWorkDir", dir.getPath());
                }
                else
                    workDirError = i18n.getString("bp.cantFindWorkDir", dir.getPath());
            }
            catch (FileNotFoundException e) {
                workDirError = i18n.getString("bp.cantFindWorkDir", dir.getPath());
            }
            catch (WorkDirectory.Fault e) {
                workDirError = i18n.getString("bp.workDirError", e.getMessage());
            }
        }
        else
            workDirError = i18n.getString("bp.noTestSuite");
    }

    /**
     * Set the work directory for the test run.
     * The work directory may only be set once.
     * If the test suite has already been set, it must exactly match the test suite
     * for the work directory; if the test suite has not yet been set, it will
     * be set to the test suite for this work directory.
     * @param wd the work directory to be set.
     * @see #getWorkDirectory
     * @throws NullPointerException if wd is null
     * @throws IllegalStateException if the work directory has already been set to
     * something different
     */
    public void setWorkDirectory(WorkDirectory wd) {
        if (wd == null)
            throw new NullPointerException();

        if (workDir != null && workDir != wd)
            throw new IllegalStateException();

        if (testSuite != null && wd.getTestSuite() != testSuite)
            throw new IllegalArgumentException();

        if (testSuite == null)
            setTestSuite(wd.getTestSuite());
        workDir = wd;
    }

    private boolean isWorkDirectoryOK() {
        return (workDirError == null);
    }

    private WorkDirectory workDir;

    /**
     * A string to identify any errors that may have occurred when
     * setting the work directory, or null if there were no such errors.
     */
    protected String workDirError;

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

    public Parameters.TestsParameters getTestsParameters() {
        return this;
    }

    public String[] getTests() {
        return tests;
    }

    public void setTests(String[] tests) {
        if (tests == null)
            testsMode = MutableTestsParameters.ALL_TESTS;
        else {
            testsMode = MutableTestsParameters.SPECIFIED_TESTS;
            this.tests = tests;
        }
    }

    public int getTestsMode() {
        return testsMode;
    }

    public void setTestsMode(int mode) {
        if (mode != ALL_TESTS &&
            mode != SPECIFIED_TESTS)
            throw new IllegalArgumentException();

        testsMode = mode;
    }

    public String[] getSpecifiedTests() {
        return tests;
    }

    public void setSpecifiedTests(String[] tests) {
        if (tests == null)
            throw new NullPointerException();

        this.tests = tests;
    }

    private boolean isTestsOK() {
        return true;
    }

    private int testsMode = MutableTestsParameters.ALL_TESTS;
    private String[] tests;

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

    public Parameters.ExcludeListParameters getExcludeListParameters() {
        return this;
    }

    public File[] getExcludeFiles() {
        TestSuite ts = getTestSuite();
        switch (excludeMode) {
        case INITIAL_EXCLUDE_LIST:
            if (ts == null)
                return null;
            File df = ts.getInitialExcludeList();
            if (df == null)
                return null;
            return new File[] { df };

        case LATEST_EXCLUDE_LIST:
            if (ts == null)
                return null;
            URL u = ts.getLatestExcludeList();
            if (u == null)
                return null;
            WorkDirectory wd = getWorkDirectory();
            if (wd == null)
                return null;
            return new File[] { wd.getSystemFile("latest.jtx") };

        case CUSTOM_EXCLUDE_LIST:
            return customExcludeFiles;

        default:
            return null;
        }
    }

    public void setExcludeFiles(File[] files) {
        if (files == null || files.length == 0)
            setExcludeMode(NO_EXCLUDE_LIST);
        else {
            setExcludeMode(CUSTOM_EXCLUDE_LIST);
            setCustomExcludeFiles(files);
        }
    }

    public ExcludeList getExcludeList() {
        updateExcludeList();
        return cachedExcludeList;
    }

    public TestFilter getExcludeListFilter() {
        updateExcludeList();
        return cachedExcludeListFilter;
    }

    public int getExcludeMode() {
        return excludeMode;
    }

    public void setExcludeMode(int mode) {
        excludeMode = mode;
    }

    public File[] getCustomExcludeFiles() {
        return customExcludeFiles;
    }

    public void setCustomExcludeFiles(File[] files) {
        customExcludeFiles = files;
    }

    public boolean isLatestExcludeAutoCheckEnabled() {
        return false;
    }

    public void setLatestExcludeAutoCheckEnabled(boolean b) {
        latestExcludeAutoCheck = b;
    }

    public int getLatestExcludeAutoCheckMode() {
        return latestExcludeAutoCheckMode;
    }

    public void setLatestExcludeAutoCheckMode(int mode) {
        latestExcludeAutoCheckMode = mode;
    }

    public int getLatestExcludeAutoCheckInterval() {
        return latestExcludeAutoCheckInterval;
    }

    public void setLatestExcludeAutoCheckInterval(int days) {
        latestExcludeAutoCheckInterval = days;;
    }

    private boolean isExcludeListOK() {
        return (excludeListError == null);
    }

    private File[] getAbsoluteExcludeFiles() {
        updateAbsoluteExcludeFiles();
        return cachedAbsExcludeFiles;
    }
    private void updateAbsoluteExcludeFiles() {
        TestSuite ts = getTestSuite();
        File base = (ts == null ? null : ts.getRootDir());
        File[] excludeFiles = getExcludeFiles();
        if (cachedAbsExcludeFiles == null ||
            cachedAbsExcludeFiles_base != base ||
            cachedAbsExcludeFiles_excludeFiles != excludeFiles) {
            cachedAbsExcludeFiles = getAbsoluteFiles(base, excludeFiles);
        }
    }

    private void updateExcludeList() {
        File[] absExclFiles = getAbsoluteExcludeFiles();
        if (cachedExcludeList == null
            || !equal(cachedExcludeList_absExclFiles, absExclFiles)) {
            try {
                if (absExclFiles == null)
                    cachedExcludeList = new ExcludeList();
                else
                    cachedExcludeList = new ExcludeList(cachedAbsExcludeFiles);
                cachedExcludeList_absExclFiles = cachedAbsExcludeFiles;
                cachedExcludeListFilter = new ExcludeListFilter(cachedExcludeList);
                excludeListError = null;
            }
            catch (FileNotFoundException e) {
                cachedExcludeList = null;
                cachedExcludeListFilter = null;
                excludeListError = i18n.getString("bp.exclListNotFound", e.getMessage());
            }
            catch (IOException e) {
                cachedExcludeList = null;
                cachedExcludeListFilter = null;
                excludeListError = i18n.getString("bp.exclListFault", e);
            }
            catch (ExcludeList.Fault e) {
                cachedExcludeList = null;
                cachedExcludeListFilter = null;
                excludeListError = i18n.getString("bp.exclListFault", e.getMessage());
            }
        }
    }

    private int excludeMode = NO_EXCLUDE_LIST;
    private boolean latestExcludeAutoCheck;
    private int latestExcludeAutoCheckMode;
    private int latestExcludeAutoCheckInterval;
    private File[] customExcludeFiles = { };


    private File[] cachedAbsExcludeFiles;
    private File cachedAbsExcludeFiles_base;
    private File[] cachedAbsExcludeFiles_excludeFiles;
    private ExcludeList cachedExcludeList;
    private File[] cachedExcludeList_absExclFiles;
    private ExcludeListFilter cachedExcludeListFilter;
    /**
     * A string to identify any errors that may have occurred when
     * setting the exclude list parameters, or null if there were no such errors.
     */
    protected String excludeListError;

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

    public Parameters.KeywordsParameters getKeywordsParameters() {
        return this;
    }

    public Keywords getKeywords() {
        updateCachedKeywords();
        return cachedKeywords;
    }

    public void setKeywords(int mode, String value) {
        if (value == null)
            keywordsMode = NO_KEYWORDS;
        else {
            keywordsMode = MATCH_KEYWORDS;
            keywordsMatchMode = mode;
            keywordsMatchValue = value;
        }
    }

    public TestFilter getKeywordsFilter() {
        updateCachedKeywords();
        if (keywordsMode == NO_KEYWORDS)
            return null;
        else
            return cachedKeywordsFilter;
    }

    public int getKeywordsMode() {
        return keywordsMode;
    }

    public void setKeywordsMode(int mode) {
        keywordsMode = mode;
    }

    public int getMatchKeywordsMode() {
        return keywordsMatchMode;
    }

    public String getMatchKeywordsValue() {
        return keywordsMatchValue;
    }

    public void setMatchKeywords(int mode, String value) {
        keywordsMatchMode = mode;
        keywordsMatchValue = value;
    }

    private void updateCachedKeywords() {
        if (keywordsMode == NO_KEYWORDS) {
            cachedKeywordsMatchMode = -1;
            cachedKeywordsMatchValue = null;
            cachedKeywords = null;
            cachedKeywordsFilter = null;
            keywordsError = null;
        }
        else {
            if (cachedKeywordsMatchMode != keywordsMatchMode
                || cachedKeywordsMatchValue == null
                || !cachedKeywordsMatchValue.equals(keywordsMatchValue)) {
                try {
                    cachedKeywordsMatchMode = keywordsMatchMode;
                    cachedKeywordsMatchValue = keywordsMatchValue;
                    String op = (keywordsMatchMode == EXPR ? "expr"
                                 : keywordsMatchMode == ALL_OF ? "all of"
                                 : "any of");
                    cachedKeywords = Keywords.create(op, keywordsMatchValue);
                    cachedKeywordsFilter = new KeywordsFilter(cachedKeywords);
                }
                catch (Keywords.Fault e) {
                    cachedKeywords = null;
                    cachedKeywordsFilter = null;
                    keywordsError = i18n.getString("bp.badKeywords", e.getMessage());
                }
            }
        }
    }

    private boolean isKeywordsOK() {
        updateCachedKeywords();
        return (keywordsError == null);
    }

    private int keywordsMode = NO_KEYWORDS;
    private int keywordsMatchMode = EXPR;
    private String keywordsMatchValue;

    private int cachedKeywordsMatchMode;
    private String cachedKeywordsMatchValue;
    private Keywords cachedKeywords;
    private TestFilter cachedKeywordsFilter;
    /**
     * A string to identify any errors that may have occurred when
     * setting the keywords parameters, or null if there were no such errors.
     */
    protected String keywordsError;

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

    public Parameters.PriorStatusParameters getPriorStatusParameters() {
        return this;
    }

    public boolean[] getPriorStatusValues() {
        if (priorStatusMode == NO_PRIOR_STATUS)
            return null;
        else
            return priorStatusValues;
    }

    public void setPriorStatusValues(boolean[] values) {
        if (values == null)
            priorStatusMode = NO_PRIOR_STATUS;
        else {
            priorStatusMode = MATCH_PRIOR_STATUS;
            priorStatusValues = values;
        }
    }

    public TestFilter getPriorStatusFilter() {
        WorkDirectory wd = getWorkDirectory();
        TestResultTable r = (wd == null ? null : wd.getTestResultTable());
        boolean[] s = getPriorStatusValues();
        if (r == null || s == null)
            cachedPriorStatusFilter = null;
        else if (cachedPriorStatusFilter == null
                 || cachedPriorStatusFilter.getTestResultTable() != r
                 || !equal(cachedPriorStatusFilter.getStatusValues(), s)) {
            cachedPriorStatusFilter = new StatusFilter(s, r);
        }

        return cachedPriorStatusFilter;
    }

    public int getPriorStatusMode() {
        return priorStatusMode;
    }

    public void setPriorStatusMode(int mode) {
        if (mode != NO_PRIOR_STATUS &&
            mode != MATCH_PRIOR_STATUS)
            throw new IllegalArgumentException();

        priorStatusMode = mode;
    }

    public boolean[] getMatchPriorStatusValues() {
        return priorStatusValues;
    }

    public void setMatchPriorStatusValues(boolean[] v) {
        if (v == null)
            throw new NullPointerException();

        if (v.length != Status.NUM_STATES)
            throw new IllegalArgumentException();

        priorStatusValues = v;
    }

    private boolean isPriorStatusOK() {
        return true;
    }

    private int priorStatusMode = NO_PRIOR_STATUS;
    private boolean[] priorStatusValues = new boolean[Status.NUM_STATES];
    private StatusFilter cachedPriorStatusFilter;

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

    public Parameters.ConcurrencyParameters getConcurrencyParameters() {
        return this;
    }

    public int getConcurrency() {
        return concurrency;
    }

    public void setConcurrency(int conc) {
        if (conc <= 0) {
            concurrencyError =
                i18n.getString("bp.badConcurrency", new Integer(conc));
            concurrency = 1;
        }
        else {
            concurrencyError = null;
            concurrency = conc;
        }
    }

    private boolean isConcurrencyOK() {
        return (concurrencyError == null);
    }

    private int concurrency = 1;
    /**
     * A string to identify any errors that may have occurred when
     * setting the concurrency, or null if there were no such errors.
     */
    protected String concurrencyError;

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

    public Parameters.TimeoutFactorParameters getTimeoutFactorParameters() {
        return this;
    }

    public float getTimeoutFactor() {
        return timeoutFactor;
    }

    public void setTimeoutFactor(float tf) {
        if (tf <= 0) {
            timeoutFactorError = i18n.getString("bp.badTimeout", new Float(tf));
            timeoutFactor = 1;
        }
        else {
            timeoutFactorError = null;
            timeoutFactor = tf;
        }
    }

    private boolean isTimeoutFactorOK() {
        return (timeoutFactorError == null);
    }

    private float timeoutFactor = 1;
    /**
     * A string to identify any errors that may have occurred when
     * setting the timeout factor, or null if there were no such errors.
     */
    protected String timeoutFactorError;

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

    public TestFilter getRelevantTestFilter() {
        TestSuite ts = getTestSuite();
        TestEnvironment env = getEnv();
        if (ts == null || env == null)
            cachedRelevantTestFilter = null;
        else if (cachedRelevantTestFilter == null ||
                 ts != cachedRelevantTestFilterTestSuite ||
                 env != cachedRelevantTestFilterEnv) {
            cachedRelevantTestFilter = ts.createTestFilter(env);
        }
        return cachedRelevantTestFilter;
    }

    private TestFilter cachedRelevantTestFilter;
    private TestSuite cachedRelevantTestFilterTestSuite; // do we need this?
    private TestEnvironment cachedRelevantTestFilterEnv;

    public synchronized TestFilter[] getFilters() {
        Vector v = new Vector();

        TestFilter excludeFilter = getExcludeListFilter();
        if (excludeFilter != null)
            v.addElement(excludeFilter);

        TestFilter keywordFilter = getKeywordsFilter();
        if (keywordFilter != null)
            v.addElement(keywordFilter);

        TestFilter statusFilter = getPriorStatusFilter();
        if (statusFilter != null)
            v.addElement(statusFilter);

        TestFilter testSuiteFilter = getRelevantTestFilter();
        if (testSuiteFilter != null)
        v.addElement(testSuiteFilter);

        if (v.size() == 0)
            return null;
        else if (equal(v, cachedTestFilters))
            return cachedTestFilters;
        else {
            TestFilter[] filters = new TestFilter[v.size()];
            v.copyInto(filters);
            return filters;
        }

    }

    private TestFilter[] cachedTestFilters;

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

    public boolean isValid() {
        return (   isTestSuiteOK()
                && isWorkDirectoryOK()
                && isTestsOK()
                && isExcludeListOK()
                && isKeywordsOK()
                && isPriorStatusOK()
                && isConcurrencyOK()
                && isTimeoutFactorOK());
    }

    public String getErrorMessage() {
        return (  testSuiteError != null ? testSuiteError
                : workDirError != null ? workDirError
                : excludeListError != null ? excludeListError
                : keywordsError != null ? keywordsError
                : concurrencyError != null ? concurrencyError
                : timeoutFactorError != null ? timeoutFactorError
                : null);
    }

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

    /**
     * Convert a set of files to be absolute files. Files that are already
     * absolute are left unchanged; relative files are evaluated relative to
     * a specified base directory.
     * @param baseDir The base directory for any relative files
     * @param files The files to be made absolute, or null if none
     * @return the given files with any relative files having been evaluated
     * relative to the given base directory, or null if files was null.
     */
    protected static File[] getAbsoluteFiles(File baseDir, File[] files) {
        if (files == null)
            return null;

        boolean allAbsolute = true;
        for (int i = 0; i < files.length && allAbsolute; i++)
            allAbsolute = files[i].isAbsolute();

        if (allAbsolute)
            return files;

        File[] absoluteFiles = new File[files.length];
        for (int i = 0; i < files.length; i++) {
            File f = files[i];
            absoluteFiles[i] = (f.isAbsolute() ? f : new File(baseDir, f.getPath()));
        }

        return absoluteFiles;
    }

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

    /**
     * Compare two boolean arrays for equality.
     * @param b1 the first array to be compared
     * @param b2 the second array to be compared
     * @return true and only if both arguments are null, or if both are not null
     * and are element-wise equal.
     */
    protected static boolean equal(boolean[] b1, boolean[] b2) {
        if (b1 == null || b2 == null)
            return (b1 == b2);

        if (b1.length != b2.length)
            return false;

        for (int i = 0; i < b1.length; i++) {
            if (b1[i] != b2[i])
                return false;
        }

        return true;
    }

    /**
     * Compare two arrays of Files for equality.
     * @param f1 the first array to be compared
     * @param f2 the second array to be compared
     * @return true and only if both arguments are null, or if both are not null
     * and are element-wise equal.
     */
    protected static boolean equal(File[] f1, File[] f2) {
        if (f1 == null || f2 == null)
            return (f1 == f2);

        if (f1.length != f2.length)
            return false;

        for (int i = 0; i < f1.length; i++) {
            if (f1[i] != f2[i])
                return false;
        }

        return true;
    }

    private static boolean equal(Vector v, TestFilter[] f) {
        if (f == null || v.size() != f.length)
            return false;
        for (int i = 0; i < v.size(); i++) {
            if (!v.elementAt(i).equals(f[i]))
                return false;
        }
        return true;
    }

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

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