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

import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

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

/**
 * This class allows a new tag "@expand" which allows a single test
 * description to be expanded into multiple test descriptions using
 * variable substitution.  Variables are declared in the .jte file.
 *
 * The list of valid entries is identical to those found for JCK.
 * There is no list of pre-defined keywords.  If keyword checking 
 * is needed, then the list of allowed keywords must be
 * provided via the <code>-allowKeyword</code> option. 
 *
 * @see TagTestFinder
 */
public class ExpandTestFinder extends TagTestFinder
{
    //--------------------------------------------------------------------------
    
    // This code is only needed to ensure that this test finder
    // behaves the same as JCKTagTestFinder.  If that finder ever
    // derives from this one, it may be removed.
    
    public ExpandTestFinder() {
	validEntries = initTable(stdValidEntries);
	validEntries = addTableItem(validEntries, "test", TRUE);
	validKeywords = initTable(stdValidKeywords);
	validKeywords = initTable(new String[] { });
 	addExtension(".jasm", JavaCommentStream.class);
 	addExtension(".jcod", JavaCommentStream.class);
    } // ExpandTestFinder()

    protected int decodeArg(String[] args, int i) throws Fault {
	if (args[i].equals("-verify")) {
	    verify = true;
	    return 1;
	}
	else if (args[i].equals("-allowEntry")) {
	    String e = args[i+1];
	    validEntries.put(e.toLowerCase(), e);
	    return 2;
	}
	else if (args[i].equals("-allowKeyword")) {
	    String k = args[i+1];
	    validKeywords.put(k.toLowerCase(), k);
	    return 2;
	}
	else
	    return super.decodeArg(args, i);
    } // decodeArg()

    private Map initTable(String[] entries) {
	Map map = new HashMap();
	for (int i = 0; i < entries.length; i++)
	    map.put(entries[i].toLowerCase(), entries[i]);
	return map;
    } // initTable()

    private Map addTableItem(Map entries, String name, String value) {
	entries.put(name, value);
	return entries;
    } // addTableItem()

    private boolean verify;
    private Map validEntries;
    private Map validKeywords;

    private static final String TESTSUITE_HTML = "testsuite.html";
    // end of code for JCKTagTestFinder emulation    

    //--------------------------------------------------------------------------
    
    public void init(String [] args, File testSuiteRoot, TestEnvironment env) throws Fault {
	// grab all environment variables open for expansion
	if (expandVars == null) {
	    expandVars   = new HashMap(3);
	    expandVarLen = new HashMap(3);
	    
	    for (Iterator i = env.keys().iterator(); i.hasNext(); ) {
		try {
		    String n = (String) (i.next());
		    
		    if (! n.startsWith("expand."))
			continue;
		    String[] v =  env.lookup(n);
		    String fqvName = n.substring("expand.".length());
		    // add to hashtable of fully-qualified varNames
		    expandVars.put(fqvName, v);

		    int pos;
		    if ((pos = fqvName.indexOf(".")) == -1)
			continue;
		    String stem = fqvName.substring(0, pos);

		    // checking lengths of co-joined variables
		    Integer len = (Integer) expandVarLen.get(stem);
		    if (len == null) {
			// add to hashtable of valid stems
			expandVarLen.put(stem, new Integer(v.length));
		    } else {
			if (v.length != len.intValue()) {
			    error(i18n, "expand.lengthMismatch", 
				  new Object[] {stem, fqvName.substring(pos+1)} );
			}
		    }
		} catch (TestEnvironment.Fault f) {
		    error(i18n, "expand.noDefn", f.getMessage());
		}
	    }
	}

	// This part of this method is only needed to ensure that this
	// test finder behaves the same as JCKTagTestFinder.  If that
	// finder ever derives from this one, it may be removed.
	if (testSuiteRoot.isDirectory()) {
	    File f = new File(testSuiteRoot, TESTSUITE_HTML);
	    if (!(f.exists() && !f.isDirectory() && f.canRead())) {
		throw new Fault(i18n, "expand.badRootDir", 
				new Object[] {TESTSUITE_HTML, testSuiteRoot.getPath()});
	    }
	}
	else {
	    String name = testSuiteRoot.getName();
	    if (!name.equals(TESTSUITE_HTML)) {
		throw new Fault(i18n, "expand.badRootFile");
	    }
	    // force the test suite root to be the directory
	    testSuiteRoot = new File(testSuiteRoot.getParent());
	}
	// end of code for JCKTagTestFinder emulation
	
	super.init(args, testSuiteRoot, env);
    } // init()

    protected void foundTestDescription(Map entries, File file, int line) {
	// cross-product and loop call up
	String origId = (String) entries.get("id");
	if (origId == null)
	    origId = "";
	Map loopVars = new HashMap(3);
	foundTestDescription_1(entries, file, line, loopVars, origId);
    } // foundTestDescription()

    private void foundTestDescription_1(Map entries, File file, int line, Map loopVars, String id) {
	for (Iterator iter = entries.keySet().iterator(); iter.hasNext(); ) {
	    String name  = (String) (iter.next());
	    String value = (String) entries.get(name);
// 	    System.out.println("------ NAME:  " + name + " VALUE: " + value);

	    if (name.equals("title") || name.equals("test") || name.equals("id"))
		// don't tokenize
		continue;

	    String [] words = StringArray.split(value);
	    for (int i = 0; i < words.length; i++) {
		if (words[i].startsWith("$")) {
		    String varName = words[i].substring(1);

		    // strip out {}'s
		    if (varName.startsWith("{")) {
			if (! varName.endsWith("}")) {
			    error(i18n, "expand.missingCloseCurly", words[i]);
			    continue;
			}
			varName = varName.substring(1, varName.length()-1);
		    }

		    // separate foo.bar into stem=foo and qualifier=bar
		    String stem;
		    String qualifier;
		    int pos;
		    if ((pos = varName.indexOf(".")) == -1) {
			stem = varName;
			qualifier = "";
		    } else {
			stem = varName.substring(0, pos);
			qualifier = varName.substring(pos+1);
		    }

// 		    System.out.println("stem: " + stem + " qual: " + qualifier);

		    if (testStems.get(stem) == null)
			// this is something we shouldn't expand
			continue;

		    String [] valueList;
		    if ((valueList = (String []) expandVars.get(varName)) == null) {
			//String [] msgs = {"unable to find `expand' definition in environment",
			//words[i]};
			//error(msgs);
			continue;
		    }

		    String saveId = id;
		    Integer idx = (Integer) loopVars.get(stem);
		    if (idx != null){
			int j = idx.intValue();
			words[i] = valueList[j];

			id = saveId + "_" + words[i];
			entries.put("id", id);

			entries.put(name, StringArray.join(words));
		    } else {
			for (int j = 0; j < valueList.length; j++) {
			    words[i] = valueList[j];

			    id = saveId + "_" + words[i];
			    entries.put("id", id);

			    entries.put(name, StringArray.join(words));
			    boolean loopy = !(qualifier.equals(""));
			    if (loopy) loopVars.put(stem, new Integer(j));
			    // clone needed here because we over-wrote words[i]
			    foundTestDescription_1((Map)(((HashMap)entries).clone()), file, line,
						   loopVars, id);
			    if (loopy) loopVars.remove(stem);

			}
			return;
		    }
		}
	    }
	}
        // clone may not be necessary, check for TestDescription upgrades
	// super.foundTestDescription(entries, file, line);
	super.foundTestDescription((Map)(((HashMap)entries).clone()), file, line);
    } // foundTestDescription_1()

    protected void processEntry(Map entries, String name, String value) {
// 	System.out.println("NAME: " + name + " VALUE: " + value);
	if (name.equals("expand")) {
	    if (testStems != null) {
		error(i18n, "expand.multipleTags");
	    }
	    testStems = new HashMap(3);
	    String [] stems = StringArray.split(value);
	    for (int i = 0 ; i < stems.length; i++)
		testStems.put(stems[i], TRUE);
	} else {

	    // This part of this method is only needed to ensure that this
	    // test finder behaves the same as JCKTagTestFinder.  If that
	    // finder ever derives from this one, it may be removed.
	    boolean valid = (validEntries.get(name.toLowerCase()) != null);

	    if (verify) {
		if (!valid) {
		    error(i18n, "expand.unknownEntry", 
			  new Object[] {name, getCurrentFile()} );
		}
		if (name.equalsIgnoreCase("keywords")) {
		    String[] keys = StringArray.split(value);
		    if (keys != null) {
			for (int i = 0; i < keys.length; i++) {
			    String key = keys[i];
			    // minor modification here to allow no keywords
			    //if (validKeywords.get(key.toLowerCase()) == null) {
			    if ((validKeywords.size() > 0) && (validKeywords.get(key.toLowerCase()) == null)) {
				error(i18n, "expand.unknownKeyword", 
				      new Object[] {key, getCurrentFile()} );
			    }
			}
		    }
		}
	    }
	    
	    if (valid)
		entries.put(name, value);
	    // end of code for JCKTagTestFinder emulation
	    
	    // if emulation code is removed, still need to do a put
	    // super.processEntry(entries, name, value);
	}
    } // processEntry()

    //----------member variables------------------------------------------------

    private static final String TRUE = "true";
    private Map testStems = null;
    private Map expandVars;
    private Map expandVarLen;

    private String[] stdValidEntries = {
	// required
	"keywords",
	"source",
	"title",
	// optional
	"context",
	"executeArgs",
	"executeClass",
	"executeNative",
	"id",		// defined and used internally by JT Harness
	"rmicClasses",
	"timeout"
    };
    
    private String[] stdValidKeywords = {
	// approved
	"compiler",
	"runtime",
	"positive",
	"negative",
	"idl_inherit",
	"idl_tie",
	"interactive",
	"jniinvocationapi",
	"optionalPJava",
	// will eventually be superceded/deprecated
	"serial"
    };
    private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(ExpandTestFinder.class);
}
