package org.apache.jmeter.threads;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.jmeter.assertions.Assertion;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.config.ConfigTestElement;
import org.apache.jmeter.config.Modifier;
import org.apache.jmeter.config.ResponseBasedModifier;
import org.apache.jmeter.control.GenericController;
import org.apache.jmeter.functions.Function;
import org.apache.jmeter.functions.InvalidVariableException;
import org.apache.jmeter.protocol.http.sampler.HTTPSampler;
import org.apache.jmeter.protocol.http.util.HTTPArgument;
import org.apache.jmeter.samplers.SampleEvent;
import org.apache.jmeter.samplers.SampleListener;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.samplers.Sampler;
import org.apache.jmeter.testelement.PerSampleClonable;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.timers.Timer;
import org.apache.log.Hierarchy;
import org.apache.log.Logger;
import org.apache.jorphan.collections.HashTree;
import org.apache.jorphan.collections.HashTreeTraverser;
import org.apache.jorphan.collections.ListedHashTree;

/****************************************
 * <p>
 *
 * Title: </p> <p>
 *
 * Description: </p> <p>
 *
 * Copyright: Copyright (c) 2001</p> <p>
 *
 * Company: </p>
 *
 *@author    unascribed
 *@created   $Date: 2002/10/17 19:47:17 $
 *@version   1.0
 ***************************************/

public class TestCompiler implements HashTreeTraverser, SampleListener
{
	transient private static Logger log = Hierarchy.getDefaultHierarchy().getLoggerFor(
			"jmeter.engine");
	LinkedList stack = new LinkedList();
	Map samplerConfigMap = new HashMap();
	Set objectsWithFunctions = new HashSet();
	HashTree testTree;
	SampleResult previousResult;
	Sampler currentSampler;
	JMeterVariables threadVars;
	private static Set pairing = new HashSet();

	/****************************************
	 * !ToDo (Constructor description)
	 *
	 *@param testTree  !ToDo (Parameter description)
	 ***************************************/
	public TestCompiler(HashTree testTree,
			JMeterVariables vars)
	{
		threadVars = vars;
		this.testTree = testTree;
	}

	/****************************************
	 * !ToDo (Method description)
	 ***************************************/
	public static void initialize()
	{
		pairing.clear();
	}

	/****************************************
	 * !ToDo (Method description)
	 *
	 *@param e  !ToDo (Parameter description)
	 ***************************************/
	public void sampleOccurred(SampleEvent e)
	{
		previousResult = e.getResult();
	}

	/****************************************
	 * !ToDo (Method description)
	 *
	 *@param e  !ToDo (Parameter description)
	 ***************************************/
	public void sampleStarted(SampleEvent e) { }

	/****************************************
	 * !ToDo (Method description)
	 *
	 *@param e  !ToDo (Parameter description)
	 ***************************************/
	public void sampleStopped(SampleEvent e) { }

	/****************************************
	 * !ToDo (Method description)
	 *
	 *@param sampler  !ToDo (Parameter description)
	 *@return         !ToDo (Return description)
	 ***************************************/
	public SamplePackage configureSampler(Sampler sampler)
	{
		currentSampler = sampler;
		SamplePackage ret = new SamplePackage();
		Sampler clonedSampler = sampler;
		SamplerConfigs configs = (SamplerConfigs)samplerConfigMap.get(sampler);
		if(sampler instanceof PerSampleClonable)
		{
			clonedSampler = (Sampler)sampler.clone();
		}
		if(objectsWithFunctions.contains(sampler))
		{
			replaceValues(clonedSampler);
		}
		ret.setSampler(clonedSampler);
		configureWithConfigElements(clonedSampler,configs.getConfigs());
		configureWithResponseModifiers(clonedSampler,configs.getResponseModifiers());
		configureWithModifiers(clonedSampler,configs.getModifiers());
		configureSamplerPackage(ret,configs);
		//replaceStatics(ret);
		return ret;
	}

	/****************************************
	 * !ToDo
	 *
	 *@param node     !ToDo
	 *@param subTree  !ToDo
	 ***************************************/
	public void addNode(Object node, HashTree subTree)
	{
		stack.addLast(node);
		log.debug("Added "+node+" to stack.  Stack size = "+stack.size());
	}

	/****************************************
	 * !ToDo (Method description)
	 ***************************************/
	public void subtractNode()
	{
		log.debug("Subtracting node, stack size = "+stack.size());
		TestElement child = (TestElement)stack.getLast();
		if(child instanceof Sampler)
		{
			log.debug("Saving configs for sampler: "+child);
			saveSamplerConfigs((Sampler)child);
		}
		stack.removeLast();
		if(stack.size() > 0)
		{
			ObjectPair pair = new ObjectPair(child, stack.getLast());
			if(!pairing.contains(pair))
			{
				((TestElement)stack.getLast()).addTestElement(child);
				pairing.add(pair);
			}
		}
	}

	/****************************************
	 * !ToDo (Method description)
	 ***************************************/
	public void processPath() { }

	private void saveSamplerConfigs(Sampler sam)
	{
		List configs = new LinkedList();
		List modifiers = new LinkedList();
		List responseModifiers = new LinkedList();
		List listeners = new LinkedList();
		List timers = new LinkedList();
		List assertions = new LinkedList();
		log.debug("Full stack = " + stack);
		for(int i = stack.size(); i > 0; i--)
		{
			log.debug("looping, i = "+i);
			log.debug("sub-stack = "+stack.subList(0,i));
			Iterator iter = testTree.list(stack.subList(0, i)).iterator();
			while(iter.hasNext())
			{
				TestElement item = (TestElement)iter.next();
				synchronized(item)
				{
					if(hasFunctions(item))
					{
						objectsWithFunctions.add(item);
					}
				}
				if((item instanceof ConfigTestElement))
				{
					configs.add(item);
				}
				if(item instanceof Modifier)
				{
					modifiers.add(item);
				}
				if(item instanceof ResponseBasedModifier)
				{
					responseModifiers.add(item);
				}
				if(item instanceof SampleListener)
				{
					listeners.add(item);
				}
				if(item instanceof Timer)
				{
					timers.add(item);
				}
				if(item instanceof Assertion)
				{
					assertions.add(item);
				}			
			}
		}
		synchronized(sam)
		{
			if(hasFunctions(sam))
			{
				objectsWithFunctions.add(sam);
			}
		}
		SamplerConfigs samplerConfigs = new SamplerConfigs(configs,modifiers,responseModifiers,
				listeners,timers,assertions);
		samplerConfigMap.put(sam, samplerConfigs);
	}
	
	private class SamplerConfigs
	{
		List configs;
		List modifiers;
		List listeners;
		List assertions;
		List timers;
		List responseModifiers;
		
		public SamplerConfigs(List configs,List modifiers,List responseModifiers,
				List listeners,List timers,List assertions)
		{
			this.configs = configs;
			this.modifiers = modifiers;
			this.responseModifiers = responseModifiers;
			this.listeners = listeners;
			this.timers = timers;
			this.assertions = assertions;
		}
		
		public List getConfigs()
		{
			return configs;
		}
		
		public List getModifiers()
		{
			return modifiers;
		}
		
		public List getResponseModifiers()
		{
			return responseModifiers;
		}
		
		public List getListeners()
		{
			return listeners;
		}
		
		public List getAssertions()
		{
			return assertions;
		}
		
		public List getTimers()
		{
			return timers;
		}
	}

	/****************************************
	 * !ToDo (Class description)
	 *
	 *@author    $Author: mstover1 $
	 *@created   $Date: 2002/10/17 19:47:17 $
	 *@version   $Revision: 1.10 $
	 ***************************************/
	public static class Test extends junit.framework.TestCase
	{
		/****************************************
		 * !ToDo (Constructor description)
		 *
		 *@param name  !ToDo (Parameter description)
		 ***************************************/
		public Test(String name)
		{
			super(name);
		}

		/****************************************
		 * !ToDo
		 *
		 *@exception Exception  !ToDo (Exception description)
		 ***************************************/
		public void testConfigGathering() throws Exception
		{
			ListedHashTree testing = new ListedHashTree();
			GenericController controller = new GenericController();
			ConfigTestElement config1 = new ConfigTestElement();
			config1.setName("config1");
			config1.setProperty(HTTPSampler.DOMAIN, "www.jarkarta.org");
			HTTPSampler sampler = new HTTPSampler();
			sampler.setName("sampler");
			Arguments args = new Arguments();
			args.addArgument("param1", "value1");
			HTTPSampler sampler2 = new HTTPSampler();
			sampler.setName("sampler2");
			Arguments args2 = new Arguments();
			args2.addArgument(new HTTPArgument("xml","<data>1234</data>"));
			sampler2.setArguments(args2);
			testing.add(controller, config1);
			testing.add(controller, sampler);
			testing.add(controller,sampler2);
			testing.getTree(controller).add(sampler, args);
			TestCompiler.initialize();

			TestCompiler compiler = new TestCompiler(testing,new JMeterVariables());
			testing.traverse(compiler);
			sampler = (HTTPSampler)compiler.configureSampler(sampler).getSampler();
			assertEquals(config1.getProperty(HTTPSampler.DOMAIN), sampler.getDomain());
			assertEquals(args.getArgument(0).getName(), sampler.getArguments().getArgument(0).getName());
			assertEquals(1,sampler.getArguments().getArguments().size());
		}
	}

	/****************************************
	 * !ToDo (Class description)
	 *
	 *@author    $Author: mstover1 $
	 *@created   $Date: 2002/10/17 19:47:17 $
	 *@version   $Revision: 1.10 $
	 ***************************************/
	private class ObjectPair
	{
		Object one, two;

		/****************************************
		 * !ToDo (Constructor description)
		 *
		 *@param one  !ToDo (Parameter description)
		 *@param two  !ToDo (Parameter description)
		 ***************************************/
		public ObjectPair(Object one, Object two)
		{
			this.one = one;
			this.two = two;
		}

		/****************************************
		 * !ToDo (Method description)
		 *
		 *@return   !ToDo (Return description)
		 ***************************************/
		public int hashCode()
		{
			return one.hashCode() + two.hashCode();
		}

		/****************************************
		 * !ToDo (Method description)
		 *
		 *@param o  !ToDo (Parameter description)
		 *@return   !ToDo (Return description)
		 ***************************************/
		public boolean equals(Object o)
		{
			if(o instanceof ObjectPair)
			{
				return one == ((ObjectPair)o).one && two == ((ObjectPair)o).two;
			}
			return false;
		}
	}
	
	private void configureWithConfigElements(Sampler sam,List configs)
	{
		Iterator iter = configs.iterator();
		while(iter.hasNext())
		{
			ConfigTestElement config = (ConfigTestElement)iter.next();
			TestElement clonedConfig = (TestElement)cloneIfNecessary(config);
			if(objectsWithFunctions.contains(config))
			{
				replaceValues(clonedConfig);
			}
			sam.addTestElement(clonedConfig);
		}
	}
	
	private void configureWithModifiers(Sampler sam,List modifiers)
	{
		Iterator iter = modifiers.iterator();
		while(iter.hasNext())
		{
			Modifier mod = (Modifier)iter.next();
			TestElement cloned = (TestElement)cloneIfNecessary(mod);
			if(objectsWithFunctions.contains(mod))
			{
				replaceValues(cloned);
			}
			((Modifier)cloned).modifyEntry(sam);
		}
	}
	
	private void configureWithResponseModifiers(Sampler sam,List responseModifiers)
	{
		Iterator iter = responseModifiers.iterator();
		while(iter.hasNext())
		{
			ResponseBasedModifier mod = (ResponseBasedModifier)iter.next();
			TestElement cloned = (TestElement)cloneIfNecessary(mod);
			if(objectsWithFunctions.contains(mod))
			{
				replaceValues(cloned);
			}
			if(previousResult != null)
			{
				((ResponseBasedModifier)cloned).modifyEntry(sam,previousResult);
			}
		}
	}
	
	private Object cloneIfNecessary(Object el)
	{
		if(el instanceof PerSampleClonable || objectsWithFunctions.contains(el))
		{
			return ((TestElement)el).clone();
		}
		else
		{
			return el;
		}
	}

	private void configureSamplerPackage(SamplePackage ret,SamplerConfigs configs)
	{
		Iterator iter = configs.getAssertions().iterator();
		while(iter.hasNext())
		{
			Assertion assertion = (Assertion)iter.next();
			TestElement cloned = (TestElement)cloneIfNecessary(assertion);
			if(objectsWithFunctions.contains(assertion))
			{
				replaceValues(cloned);
			}
			ret.addAssertion((Assertion)cloned);
		}
		iter = configs.getTimers().iterator();
		while(iter.hasNext())
		{
			Timer timer = (Timer)iter.next();
			TestElement cloned = (TestElement)cloneIfNecessary(timer);
			if(objectsWithFunctions.contains(timer))
			{
				replaceValues(cloned);
			}
			ret.addTimer((Timer)cloned);
		}
		
		iter = configs.getListeners().iterator();
		while(iter.hasNext())
		{
			SampleListener lis = (SampleListener)iter.next();
			TestElement cloned = (TestElement)cloneIfNecessary(lis);
			if(objectsWithFunctions.contains(lis))
			{
				replaceValues(cloned);
			}
			ret.addSampleListener((SampleListener)cloned);
		}
	}
	
	private boolean hasFunctions(TestElement el)
	{
		boolean hasFunctions = false;
		Iterator iter = el.getPropertyNames().iterator();
		while(iter.hasNext())
		{
			String propName = (String)iter.next();
			Object propValue = el.getProperty(propName);
			if(propValue instanceof Function)
			{
				((Function)propValue).setJMeterVariables(threadVars);
				hasFunctions = true;
			}
			else if(propValue instanceof TestElement)
			{
				if(hasFunctions((TestElement)propValue))
				{
					hasFunctions = true;
				}
			}
			else if(propValue instanceof Collection)
			{
				if(hasFunctions((Collection)propValue))
				{
					hasFunctions = true;
				}
			}
		}
		return hasFunctions;
	}
	
	private boolean hasFunctions(Collection values)
	{
		Iterator iter = new LinkedList(values).iterator();
		boolean hasFunctions = false;
		while(iter.hasNext())
		{
			Object val = iter.next();
			if(val instanceof TestElement)
			{
				if(hasFunctions((TestElement)val))
				{
					hasFunctions = true;
				}
			}
			else if(val instanceof Function)
			{
				((Function)val).setJMeterVariables(threadVars);
				hasFunctions = true;
			}
			else if(val instanceof Collection)
			{
				if(hasFunctions((Collection)val))
				{
					hasFunctions = true;
				}
			}
		}
		return hasFunctions;
	}	
	
	private void replaceValues(TestElement el)
	{
		Iterator iter = el.getPropertyNames().iterator();
		while(iter.hasNext())
		{
			String propName = (String)iter.next();
			Object propValue = el.getProperty(propName);
			if(propValue instanceof Function)
			{
				try
				{
					el.setProperty(propName,((Function)propValue).execute(previousResult,currentSampler));
				}
				catch(InvalidVariableException e)
				{}
			}
			else if(propValue instanceof TestElement)
			{
				replaceValues((TestElement)propValue);
			}
			else if(propValue instanceof Collection)
			{
				el.setProperty(propName,replaceValues((Collection)propValue));
			}
		}
	}
	
	private Collection replaceValues(Collection values)
	{
		Collection newColl = null;
		try {
			newColl = (Collection)values.getClass().newInstance();
		} catch(Exception e) {
			log.error("",e);
			return values;
		} 
		Iterator iter = values.iterator();
		while(iter.hasNext())
		{
			Object val = iter.next();
			if(val instanceof TestElement)
			{
				replaceValues((TestElement)val);
			}
			else if(val instanceof Function)
			{
				try
				{
					val = ((Function)val).execute(previousResult,currentSampler);
				}
				catch(InvalidVariableException e)
				{}
			}
			else if(val instanceof Collection)
			{
				val = replaceValues((Collection)val);
			}
			newColl.add(val);
		}
		return newColl;
	}
}
