/*
 * $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.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.util.Map;
import java.util.Vector;
import com.sun.interview.ChoiceQuestion;
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.IntQuestion;
import com.sun.interview.Question;
import com.sun.interview.YesNoQuestion;
import com.sun.javatest.ExcludeList;
import com.sun.javatest.ExcludeListFilter;
import com.sun.javatest.InterviewParameters;
import com.sun.javatest.Parameters;
import com.sun.javatest.TestFilter;
import com.sun.javatest.TestSuite;
import com.sun.javatest.WorkDirectory;

/**
 * This interview collects the "exclude list" test filter parameters.
 * It is normally used as one of a series of sub-interviews that collect
 * the parameter information for a test run.
 */
public class ExcludeListInterview
    extends Interview
    implements Parameters.MutableExcludeListParameters
{
    /**
     * 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 ExcludeListInterview(InterviewParameters parent)
        throws Interview.Fault
    {
        super(parent, "excludeList");
        this.parent = parent;
        setResourceBundle("i18n");

        // had to wait to init parent before doing the following:
        qNeedExcludeLists = new NeedExcludeListsQuestion();
        qExcludeListType = new ExcludeListTypeQuestion();

        setFirstQuestion(qNeedExcludeLists);

    }

    public File[] getExcludeFiles() {
        if (qNeedExcludeLists.getValue() == YesNoQuestion.YES) {
            TestSuite ts = parent.getTestSuite();
            String t = qExcludeListType.getValue();
            if (t == INITIAL) {
                File f = (ts == null ? null : ts.getInitialExcludeList());
                if (f == null)
                    return null;
                else
                    return new File[] { f };
            }
            else if (t == LATEST) {
                URL u = (ts == null ? null : ts.getLatestExcludeList());
                if (u == null)
                    return null;
                else {
                    WorkDirectory wd = parent.getWorkDirectory();
                    return new File[] { wd.getSystemFile("latest.jtx") };
                }
            }
            else
                return qCustomFiles.getValue();
        }
        else
            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 int getExcludeMode() {
        if (qNeedExcludeLists.getValue() == YesNoQuestion.YES) {
            String t = qExcludeListType.getValue();
            if (t == INITIAL)
                return INITIAL_EXCLUDE_LIST;
            else if (t == LATEST)
                return LATEST_EXCLUDE_LIST;
            else
                return CUSTOM_EXCLUDE_LIST;
        }
        else
            return NO_EXCLUDE_LIST;
    }

    public void setExcludeMode(int mode) {
        if (mode == NO_EXCLUDE_LIST)
            qNeedExcludeLists.setValue(YesNoQuestion.NO);
        else {
            qNeedExcludeLists.setValue(YesNoQuestion.YES);
            switch (mode) {
            case INITIAL_EXCLUDE_LIST:
                qExcludeListType.setValue(INITIAL);
                break;
            case LATEST_EXCLUDE_LIST:
                qExcludeListType.setValue(LATEST);
                break;
            default:
                qExcludeListType.setValue(CUSTOM);
                break;
            }
        }
    }

    public File[] getCustomExcludeFiles() {
        return qCustomFiles.getValue();
    }

    public void setCustomExcludeFiles(File[] files) {
        qCustomFiles.setValue(files);
    }

    public boolean isLatestExcludeAutoCheckEnabled() {
        return (qLatestAutoCheck.getValue() == YesNoQuestion.YES);
    }

    public void setLatestExcludeAutoCheckEnabled(boolean b) {
        qLatestAutoCheck.setValue(b ? YesNoQuestion.YES : YesNoQuestion.NO);
    }

    public int getLatestExcludeAutoCheckMode() {
        return (qLatestAutoCheckMode.getValue() == EVERY_X_DAYS
                ? CHECK_EVERY_X_DAYS : CHECK_EVERY_RUN);
    }

    public void setLatestExcludeAutoCheckMode(int mode) {
        if (mode == CHECK_EVERY_X_DAYS)
            qLatestAutoCheckMode.setValue(EVERY_X_DAYS);
        else
            qLatestAutoCheckMode.setValue(EVERY_RUN);
    }

    public int getLatestExcludeAutoCheckInterval() {
        return qLatestAutoCheckInterval.getValue();
    }

    public void setLatestExcludeAutoCheckInterval(int days) {
        qLatestAutoCheckInterval.setValue(days);
    }

    /**
     * Get the exclude list generated from the exclude list files in the interview.
     * @return the exclude list generated from the exclude list files in the interview
     * @see #getExcludeFiles
     */
    public ExcludeList getExcludeList() {
        updateCachedExcludeListData();
        return cachedExcludeList;
    }


    /**
     * Get a test filter generated from the exclude list files in the interview.
     * @return a test filter generated from the exclude list files in the interview
     *     or null, if no filter is required
     * @see #getExcludeFiles
     */
    public TestFilter getExcludeFilter() {
        updateCachedExcludeListData();
        return cachedExcludeListFilter;
    }

    private void ensureInitializedForTestSuite() {
        if (initializedForTestSuite)
            return;

        TestSuite ts = parent.getTestSuite();
        if (ts == null)
            return;

        File ijtx = ts.getInitialExcludeList();
        hasInitialJTX = (ijtx != null);
        hasValidInitialJTX = (hasInitialJTX && ijtx.exists());

        //System.err.println("ELI initialJTX=" + ijtx);
        //System.err.println("ELI hasInitialJTX=" + hasInitialJTX);
        //System.err.println("ELI hasValidInitialJTX=" + hasInitialJTX);

        URL ljtx = ts.getLatestExcludeList();
        hasLatestJTX = (ljtx != null);
        if (hasLatestJTX) {
            WorkDirectory wd = parent.getWorkDirectory();
            hasValidLatestJTX = (wd != null && wd.getSystemFile("latest.jtx").exists());
        }
        else
            hasValidLatestJTX = false;

        //System.err.println("ELI latestJTX=" + ljtx);
        //System.err.println("ELI hasLatestJTX=" + hasLatestJTX);
        //System.err.println("ELI hasValidInitialJTX=" + haveInitialJTX);

        initializedForTestSuite = true;
    }

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

    private InterviewParameters parent;
    private boolean initializedForTestSuite;
    private boolean hasInitialJTX;      // defined in testsuite.jtt
    private boolean hasValidInitialJTX; // file exists as specified
    private boolean hasLatestJTX;       // defined in testsuite.jtt
    private boolean hasValidLatestJTX;  // have downloaded copy in wd/jtData/latest.jtx

    //----------------------------------------------------------------------------
    //
    // Need exclude list

    private class NeedExcludeListsQuestion extends YesNoQuestion {
        NeedExcludeListsQuestion() {
            super(ExcludeListInterview.this, "needExcludeList");
            doneSuper = true;
            clear();
        }

        public void clear() {
            // clear will be called from the constructor once the choices have been set,
            // but we can't call out to the enclosing class before super() completes (NPE)
            if (!doneSuper)
                return;

            ensureInitializedForTestSuite();

            // default answer to yes if there is a reasonable valid exclude list already available
            setValue(hasValidInitialJTX || hasValidLatestJTX ? YesNoQuestion.YES : YesNoQuestion.NO);
            //System.err.println("ELI.needExcludeLists.clear value=" + value);
        }

        protected Question getNext() {
            if (value == null)
                return null;
            else if (value == YES)
                return qExcludeListType;
            else
                return qEnd;
        }

        private boolean doneSuper;
    };

    private NeedExcludeListsQuestion qNeedExcludeLists; // defer initialization

    //----------------------------------------------------------------------------
    //
    // Type of exclude list

    private static final String INITIAL = "initial";
    private static final String LATEST = "latest";
    private static final String CUSTOM = "custom";

    private class ExcludeListTypeQuestion extends ChoiceQuestion {
        ExcludeListTypeQuestion() {
            super(ExcludeListInterview.this, "excludeListType");

            // Difficulty here is that these depend on the test suite, which
            // will not have been set yet. So, set these full set of choices
            // for now, and refine the choices when the test suite gets set.
            setChoices(new String[] { null, INITIAL, LATEST, CUSTOM }, true);
            clear();
        }

        public void clear() {
            ensureInitialized();
            setValue(defaultValue);
            //System.err.println("ELI.excludeListType.clear value=" + value);
        }

        public String[] getChoices() {
            ensureInitialized();
            return super.getChoices();
        }

        public void save(Map data) {
            ensureInitialized();
            super.save(data);
        }


        public boolean isHidden() {
            // the following will implicitly call ensureInitialized()
            return (getChoices().length == 2); // null and CUSTOM
        }

        public String getValue() {
            // the following will implicitly call ensureInitialized()
            return (isHidden() ? CUSTOM : super.getValue());
        }

        protected Question getNext() {
            // the following will implicitly call ensureInitialized()
            if (isHidden())
                return qCustomFiles;
            else if (value == null || value.length() == 0)
                return null;
            else if (value.equals(INITIAL))
                return checkExcludeList();
            else if (value.equals(LATEST))
                return qLatestAutoCheck;
            else
                return qCustomFiles;
        }

        private void ensureInitialized() {
            if (initialized)
                return;

            if (parent.getTestSuite() == null)
                return;

            ensureInitializedForTestSuite();

            String defaultValue = CUSTOM;
            Vector v = new Vector(4);

            v.add(null); // always

            if (hasValidInitialJTX) {
                // require it to be valid, cos no way for user to make it become valid
                //if it isn't already
                v.add(INITIAL);
            }

            if (hasLatestJTX) {
                // don't require it to be valid, because they can download it if needed
                v.add(LATEST);
            }

            v.add(CUSTOM); // always

            String[] choices = new String[v.size()];
            v.copyInto(choices);
            setChoices(choices, true);

            // set the default to the best valid exclude list that is already available
            defaultValue = (hasValidLatestJTX ? LATEST : hasValidInitialJTX ? INITIAL : null);
            setValue(defaultValue);

            initialized = true;
        }

        private boolean initialized;
        private String defaultValue;
    };

    private ExcludeListTypeQuestion qExcludeListType; // defer initialization

    //----------------------------------------------------------------------------
    //
    // Auto check latest

    private YesNoQuestion qLatestAutoCheck = new YesNoQuestion(this, "latestAutoCheck", YesNoQuestion.NO) {
        protected Question getNext() {
            if (value == null)
                return null;
            else if (value == YES)
                return qLatestAutoCheckMode;
            else
                return checkExcludeList();
        }
    };

    //----------------------------------------------------------------------------
    //
    // Auto check latest mode

    private static final String EVERY_X_DAYS = "everyXDays";
    private static final String EVERY_RUN = "everyRun";

    private ChoiceQuestion qLatestAutoCheckMode = new ChoiceQuestion(this, "latestAutoCheckMode") {
        {
            setChoices(new String[] { EVERY_X_DAYS, EVERY_RUN }, true);
        }

        protected Question getNext() {
            if (value == null)
                return null;
            else if (value.equals(EVERY_X_DAYS))
                return qLatestAutoCheckInterval;
            else
                return checkExcludeList();
        }
    };

    //----------------------------------------------------------------------------
    //
    // Auto check latest interval

    private IntQuestion qLatestAutoCheckInterval = new IntQuestion(this, "latestAutoCheckInterval") {
        {
            setBounds(1, 365);
        }

        public void clear() {
            setValue(7);
        }

        protected Question getNext() {
            return checkExcludeList();
        }
    };



    //----------------------------------------------------------------------------
    //
    // Exclude List

    private FileListQuestion qCustomFiles = new FileListQuestion(this, "customFiles") {
        {
            // I18N...
            setFilter(new ExtensionFileFilter(".jtx", "Exclude List"));
            setDuplicatesAllowed(false);
        }

        protected Question getNext() {
            if (value == null || value.length == 0)
                return null;

            return checkExcludeList();
        }

        public File getBaseDirectory() {
            TestSuite ts = parent.getTestSuite();
            return (ts == null ? null : ts.getRootDir());
        }
    };

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

    private void updateCachedExcludeListData() {
        TestSuite ts = parent.getTestSuite();
        File tsRootDir = (ts == null ? null : ts.getRootDir());
        File[] files = getAbsoluteFiles(tsRootDir, getExcludeFiles());
        if (!equal(cachedExcludeList_files, files) || cachedExcludeList_testSuite != ts) {
            try {
                if (ts == null || files == null || files.length == 0)
                    setCachedExcludeList(new ExcludeList());
                else
                    setCachedExcludeList(new ExcludeList(files));
            }
            catch (FileNotFoundException e) {
                setCachedExcludeListError(qExcludeListFileNotFound, e.getMessage());
            }
            catch (IOException e) {
                setCachedExcludeListError(qExcludeListIOError, e.toString());
            }
            catch (ExcludeList.Fault e) {
                setCachedExcludeListError(qExcludeListError, e.getMessage());
            }

            cachedExcludeList_files = files;
            cachedExcludeList_testSuite = ts;
        }
    }

    private void setCachedExcludeList(ExcludeList l) {
        cachedExcludeList = l;
        cachedExcludeListFilter = (l.isEmpty() ? null : new ExcludeListFilter(l));
        cachedExcludeListError = null;
        cachedExcludeListErrorArgs = null;
    }

    private void setCachedExcludeListError(Question q, String arg) {
        cachedExcludeList = new ExcludeList();
        cachedExcludeListFilter = null;
        cachedExcludeListError = q;
        cachedExcludeListErrorArgs = new String[] { arg };
    }


    private ExcludeList cachedExcludeList;
    private ExcludeListFilter cachedExcludeListFilter;
    private Question cachedExcludeListError;
    private Object[] cachedExcludeListErrorArgs;
    private TestSuite cachedExcludeList_testSuite;
    private File[] cachedExcludeList_files;


    //----------------------------------------------------------------------------
    //
    // Exclude List Error

    private ErrorQuestion qExcludeListFileNotFound = new ErrorQuestion(this, "excludeListFileNotFound") {
        protected Object[] getTextArgs() {
            return cachedExcludeListErrorArgs;
        }
    };

    private ErrorQuestion qExcludeListIOError = new ErrorQuestion(this, "excludeListIOError") {
        protected Object[] getTextArgs() {
            return cachedExcludeListErrorArgs;
        }
    };

    private ErrorQuestion qExcludeListError = new ErrorQuestion(this, "excludeListError") {
        protected Object[] getTextArgs() {
            return cachedExcludeListErrorArgs;
        }
    };

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

    private Question checkExcludeList() {
        updateCachedExcludeListData();
        if (cachedExcludeListError != null)
            return cachedExcludeListError;
        else
            return qEnd;
    }

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

        return true;
    }
}
