/*
 * $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.interview;

import java.io.File;
import java.util.Arrays;
import java.util.Iterator;
import com.sun.interview.ErrorQuestion;
import com.sun.interview.ExtensionFileFilter;
import com.sun.interview.FileListQuestion;
import com.sun.interview.FinalQuestion;
import com.sun.interview.Interview;
import com.sun.interview.Question;
import com.sun.interview.StringQuestion;
import com.sun.javatest.InterviewParameters;
import com.sun.javatest.Parameters;
import com.sun.javatest.TestEnvContext;
import com.sun.javatest.TestEnvironment;
import com.sun.javatest.TestSuite;


/**
 * This interview collects the environment parameter, by means of environment (jte) files
 * and an environment name. It is normally used as one of a series of sub-interviews
 * that collect the parameter information for a test run. It is suitable for use with
 * legacy test suites that still rely on environments being provided with .jte files;
 * more sophisticated interviews should create a custom interview that collects the
 * environment data directly.
 */
public class EnvironmentInterview
    extends Interview
    implements Parameters.LegacyEnvParameters
{
    /**
     * Create an interview.
     * @param parent The parent interview of which this is a child.
     * @throws Interview.Fault if there is a problem while creating the interview.
     */
    public EnvironmentInterview(InterviewParameters parent)
        throws Interview.Fault
    {
        super(parent, "environment");
        this.parent = parent;
        setResourceBundle("i18n");
        setFirstQuestion(qEnvFiles);
    }

    /**
     * Get the environment files specified in the interview.
     * @return the list of files specified in the interview
     * @see #setEnvFiles
     */
    public File[] getEnvFiles() {
        return qEnvFiles.getValue();
    }

    public File[] getAbsoluteEnvFiles() {
        TestSuite ts = parent.getTestSuite();
        File tsRootDir = (ts == null ? null : ts.getRootDir());
        return getAbsoluteFiles(tsRootDir, getEnvFiles());
    }

    /**
     * Set the environment files for the interview.
     * @param files the environment files for the interview
     * @see #getEnvFiles
     */
    public void setEnvFiles(File[] files) {
        qEnvFiles.setValue(files);
    }

    /**
     * Get the environment name specified in the interview.
     * @return the environment name specified in the interview
     * @see #setEnvName
     */
    public String getEnvName() {
        return qEnv.getValue();
    }

    /**
     * Set the environment name for the interview.
     * @param name the environment name for the interview
     * @see #getEnvName
     */
    public void setEnvName(String name) {
        qEnv.setValue(name);
    }

    /**
     * Get the environment specified by the environment files and environment name,
     * or null, if it cannot be determined.
     * @return the environment determined by the interview, or null if it cannot be determined.
     * @see #getEnvFiles
     * @see #getEnvName
     */
    public TestEnvironment getEnv() {
        updateCachedEnv();
        return cachedEnv;
    }


    //----------------------------------------------------------------------------
    //
    // Env files

    private FileListQuestion qEnvFiles = new FileListQuestion(this, "envFiles") {
        {
            // I18N...
            setFilter(new ExtensionFileFilter(".jte", "Environment File"));
            setDuplicatesAllowed(false);
        }

        public File getBaseDirectory() {
            TestSuite ts = parent.getTestSuite();
            if (ts == null)
                return null;
            else {
                File r = ts.getRoot();
                return r.isDirectory() ? r : r.getParentFile();
            }
        }

        protected Question getNext() {
            updateCachedEnvTable();
            if (cachedEnvTableError != null)
                return qEnvTableError;
            else if (cachedEnvTable == null || cachedEnvTable.getEnvNames().length == 0)
                return qNoEnvs;
            else
                return qEnv;
        }
    };

    private TestEnvContext getEnvTable() {
        updateCachedEnvTable();
        return cachedEnvTable;
    }

    private void updateCachedEnvTable() {
        File[] absFiles = getAbsoluteEnvFiles();
        if (!equal(cachedEnvTable_absFiles, absFiles)) {
            try {
                cachedEnvTable = new TestEnvContext(absFiles);
                cachedEnvTableError = null;
            }
            catch (TestEnvContext.Fault e) {
                cachedEnvTable = null;
                cachedEnvTableError = e.getMessage();
            }
            cachedEnvTable_absFiles = absFiles;
        }
    }

    private TestEnvContext cachedEnvTable;
    private File[] cachedEnvTable_absFiles;
    private String cachedEnvTableError;


    //----------------------------------------------------------------------------
    //
    // No Env

    private ErrorQuestion qNoEnvs = new ErrorQuestion(this, "noEnvs");


    //----------------------------------------------------------------------------
    //
    // Env Table Error

    private ErrorQuestion qEnvTableError = new ErrorQuestion(this, "envTableError") {
        protected Object[] getTextArgs() {
            return new Object[] { cachedEnvTableError };
        }
    };


    //----------------------------------------------------------------------------
    //
    // Env

    private StringQuestion qEnv = new StringQuestion(this, "env") {
        public String[] getSuggestions() {
            // ensure the choices are up to date with envTable;
            // note that setting choices may smash the current value
            // if it's not a valid choice in the new set
            TestEnvContext envTable = getEnvTable();
            if (envTable != cachedEnvTable) {
                String[] envNames;
                if (envTable == null)
                    envNames = new String[0];
                else {
                    String[] names = envTable.getEnvMenuNames();
                    Arrays.sort(names);
                    envNames = names;
                }
                setSuggestions(envNames);
                cachedEnvTable = envTable;
            }
            return super.getSuggestions();
        }

        protected Question getNext() {
            if (value == null)
                return null;
            else {
                updateCachedEnv();
                if (cachedEnv == null)
                    return cachedEnvError;
                else
                    return qEnd;
            }
        }

        private TestEnvContext cachedEnvTable;
    };

    private void updateCachedEnv() {
        TestEnvContext envTable = getEnvTable();
        String envName = getEnvName();
        if (cachedEnv_envTable != envTable || !equal(cachedEnv_envName, envName)) {
            try {
                if (envTable == null || envName == null || envName.length() == 0) {
                    cachedEnv = null;
                    cachedEnvError = null;
                }
                else {
                    cachedEnv = envTable.getEnv(envName);
                    if (cachedEnv == null) {
                        cachedEnvError = qEnvNotFound;
                        cachedEnvErrorArgs = new Object[] { envName };
                    }
                    else {
                        // verify all entries defined
                        cachedEnvError = null;
                        cachedEnvErrorArgs = null;
                        for (Iterator i = cachedEnv.elements().iterator();
                             i.hasNext() && cachedEnvError == null; ) {
                            TestEnvironment.Element entry = (TestEnvironment.Element) (i.next());
                            if (entry.getValue().indexOf("VALUE_NOT_DEFINED") >= 0) {
                                cachedEnv = null;
                                String eText =
                                    ( (entry.getDefinedInEnv() == null ? "" : "env." +  entry.getDefinedInEnv() + ".") +
                                      entry.getKey() + "=" + entry.getValue());
                                cachedEnvError = qEnvUndefinedEntry;
                                cachedEnvErrorArgs = new Object[] {eText, entry.getDefinedInFile()};
                            }
                        }
                    }
                }

            }
            catch (TestEnvironment.Fault e) {
                cachedEnv = null;
                cachedEnvError = qEnvError;
                cachedEnvErrorArgs = new Object[] { e.getMessage() };
            }
            cachedEnv_envTable = envTable;
            cachedEnv_envName = envName;
        }
    }

    private TestEnvironment cachedEnv;
    private TestEnvContext cachedEnv_envTable;
    private String cachedEnv_envName;
    private Question cachedEnvError;
    private Object[] cachedEnvErrorArgs;



    //----------------------------------------------------------------------------
    //
    // Env Error

    private ErrorQuestion qEnvError = new ErrorQuestion(this, "envError") {
        protected Object[] getTextArgs() {
            return cachedEnvErrorArgs;
        }
    };

    //----------------------------------------------------------------------------
    //
    // Env Not Found

    private ErrorQuestion qEnvNotFound = new ErrorQuestion(this, "envNotFound") {
        protected Object[] getTextArgs() {
            return cachedEnvErrorArgs;
        }
    };

    //----------------------------------------------------------------------------
    //
    // Env Undefined Entry

    private ErrorQuestion qEnvUndefinedEntry = new ErrorQuestion(this, "envUndefinedEntry") {
        protected Object[] getTextArgs() {
            return cachedEnvErrorArgs;
        }
    };

    //----------------------------------------------------------------------------
    //
    // End

    private Question qEnd = new FinalQuestion(this);

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

    private static File[] getAbsoluteFiles(File baseDir, File[] files) {
        if (files == null)
            return null;

        if (baseDir == null)
            return files;

        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;
    }

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

    private static boolean equal(File f1, File f2) {
        return (f1 == null ? f2 == null : f1.equals(f2));
    }

    private 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 (!equal(f1[i], f2[i]))
                return false;
        }

        return true;
    }

    private static boolean equal(String s1, String s2) {
        return (s1 == null ? s2 == null : s1.equals(s2));
    }


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

    private InterviewParameters parent;
}
