/*
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html.
 * 
 * This file is a derivative of code released under the terms listed below.  
 *
 */
/**
 *
 * Copyright (c) 2009-2012,
 *
 *  Adam Fuchs          <afuchs@cs.umd.edu>
 *  Avik Chaudhuri      <avik@cs.umd.edu>
 *  Steve Suh           <suhsteve@gmail.com>
 *  Galois, Inc. (Adam Foltzer <acfoltzer@galois.com)
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The names of the contributors may not be used to endorse or promote
 * products derived from this software without specific prior written
 * permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 *
 */

package org.scandroid.util;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.common.collect.Lists;
import com.google.common.collect.Queues;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.Language;
import com.ibm.wala.dalvik.util.AndroidAnalysisScope;
import com.ibm.wala.ipa.callgraph.AnalysisOptions;
import com.ibm.wala.ipa.callgraph.AnalysisScope;
import com.ibm.wala.ipa.callgraph.ClassTargetSelector;
import com.ibm.wala.ipa.callgraph.ContextSelector;
import com.ibm.wala.ipa.callgraph.IAnalysisCacheView;
import com.ibm.wala.ipa.callgraph.MethodTargetSelector;
import com.ibm.wala.ipa.callgraph.impl.Util;
import com.ibm.wala.ipa.callgraph.propagation.SSAContextInterpreter;
import com.ibm.wala.ipa.callgraph.propagation.SSAPropagationCallGraphBuilder;
import com.ibm.wala.ipa.callgraph.propagation.cfa.ZeroXCFABuilder;
import com.ibm.wala.ipa.callgraph.propagation.cfa.ZeroXInstanceKeys;
import com.ibm.wala.ipa.cha.ClassHierarchy;
import com.ibm.wala.ipa.cha.ClassHierarchyException;
import com.ibm.wala.ipa.cha.ClassHierarchyFactory;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import com.ibm.wala.ipa.summaries.BypassClassTargetSelector;
import com.ibm.wala.ipa.summaries.BypassMethodTargetSelector;
import com.ibm.wala.ipa.summaries.MethodSummary;
import com.ibm.wala.ipa.summaries.XMLMethodSummaryReader;
import com.ibm.wala.types.MethodReference;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.collections.HashMapFactory;
import com.ibm.wala.util.collections.HashSetFactory;
import com.ibm.wala.util.io.FileProvider;
import com.ibm.wala.util.strings.Atom;
import com.ibm.wala.util.warnings.Warnings;

public class AndroidAnalysisContext {
	private static final String methodSpec = "MethodSummaries.xml";
	private static final String pathToSpec = "data";

	private final ISCanDroidOptions options;
	private final AnalysisScope scope;
	private final ClassHierarchy cha;
	
	public AndroidAnalysisContext() {
		throw new IllegalArgumentException();
	}

	public AndroidAnalysisContext(ISCanDroidOptions options)
			throws IllegalArgumentException, ClassHierarchyException,
			IOException {
		this(options, "Java60RegressionExclusions.txt");
	}

	public AndroidAnalysisContext(ISCanDroidOptions options, String exclusions)
			throws IOException, IllegalArgumentException, ClassHierarchyException {
		
		this.options = options;
		scope = AndroidAnalysisScope.setUpAndroidAnalysisScope(options.getClasspath(), exclusions, getClass().getClassLoader(), options.getAndroidLibrary());
		
		cha = ClassHierarchyFactory.make(scope);

		/*
		if (options.classHierarchyWarnings()) {
			// log ClassHierarchy warnings
			for (Warning w : Iterator2Iterable.make(Warnings.iterator())) {
				
			}
		}
		*/
		Warnings.clear();
	}
	
	

	public static SSAPropagationCallGraphBuilder makeVanillaZeroOneCFABuilder(
			AnalysisOptions options, IAnalysisCacheView cache, IClassHierarchy cha,
			AnalysisScope scope, ContextSelector customSelector,
			SSAContextInterpreter customInterpreter,
			InputStream summariesStream, MethodSummary extraSummary) {

		if (options == null) {
			throw new IllegalArgumentException("options is null");
		}
		Util.addDefaultSelectors(options, cha);
		// addDefaultBypassLogic(options, scope, Util.class.getClassLoader(),
		// cha);
		// addBypassLogic(options, scope,
		// AndroidAppLoader.class.getClassLoader(), methodSpec, cha);
		addBypassLogic(options, scope, summariesStream, cha, extraSummary);

		return ZeroXCFABuilder.make(Language.JAVA, cha, options, cache, customSelector,
				customInterpreter, ZeroXInstanceKeys.ALLOCATIONS
						| ZeroXInstanceKeys.CONSTANT_SPECIFIC);
	}

	/**
	 * @param options
	 *            options that govern call graph construction
	 * @param cha
	 *            governing class hierarchy
	 * @param scope
	 *            representation of the analysis scope
	 * @param customSelector
	 *            user-defined context selector, or null if none
	 * @param customInterpreter
	 *            user-defined context interpreter, or null if none
	 * @return a 0-CFA Call Graph Builder.
	 * @throws IllegalArgumentException
	 *             if options is null
	 *             
	 * TODO: move
	 */
	public static SSAPropagationCallGraphBuilder makeZeroCFABuilder(
			AnalysisOptions options, IAnalysisCacheView cache, IClassHierarchy cha,
			AnalysisScope scope, ContextSelector customSelector,
			SSAContextInterpreter customInterpreter,
			List<InputStream> arrayList, MethodSummary extraSummary) {
				return makeZeroCFABuilder(options, cache, cha, scope,
						customSelector, customInterpreter, Lists.newArrayList(arrayList),
						extraSummary);
			}

	/**
	 * @param options
	 *            options that govern call graph construction
	 * @param cha
	 *            governing class hierarchy
	 * @param scope
	 *            representation of the analysis scope
	 * @param customSelector
	 *            user-defined context selector, or null if none
	 * @param customInterpreter
	 *            user-defined context interpreter, or null if none
	 * @return a 0-CFA Call Graph Builder.
	 * @throws IllegalArgumentException
	 *             if options is null
	 *             
     * TODO: move
	 */
	public static SSAPropagationCallGraphBuilder makeZeroCFABuilder(
			AnalysisOptions options, IAnalysisCacheView cache, IClassHierarchy cha,
			AnalysisScope scope, ContextSelector customSelector,
			SSAContextInterpreter customInterpreter,
			Collection<InputStream> summariesStreams, MethodSummary extraSummary) {

		if (options == null) {
			throw new IllegalArgumentException("options is null");
		}
		Util.addDefaultSelectors(options, cha);
		for (InputStream stream : summariesStreams) {
			addBypassLogic(options, scope, stream, cha, extraSummary);
		}

		return ZeroXCFABuilder.make(Language.JAVA, cha, options, cache, customSelector,
				customInterpreter, ZeroXInstanceKeys.NONE);
	}

	// public static void addBypassLogic(AnalysisOptions options, AnalysisScope
	// scope, ClassLoader cl, String xmlFile,
	// IClassHierarchy cha) throws IllegalArgumentException {
	public static void addBypassLogic(AnalysisOptions options,
			AnalysisScope scope, InputStream xmlIStream, IClassHierarchy cha,
			MethodSummary extraSummary) throws IllegalArgumentException {

		if (scope == null) {
			throw new IllegalArgumentException("scope is null");
		}
		if (options == null) {
			throw new IllegalArgumentException("options is null");
		}
		// if (cl == null) {
		// throw new IllegalArgumentException("cl is null");
		// }
		if (cha == null) {
			throw new IllegalArgumentException("cha cannot be null");
		}

		try {
			Set<TypeReference> summaryClasses = HashSetFactory.make();
			Map<MethodReference, MethodSummary> summaries = HashMapFactory.make();

			if (null != xmlIStream) {
				XMLMethodSummaryReader newSummaryXML = loadMethodSummaries(
						scope, xmlIStream);
				summaryClasses.addAll(newSummaryXML.getAllocatableClasses());
				summaries.putAll(newSummaryXML.getSummaries());
			}
			// for (MethodReference mr : summaries.keySet()) {
			// 
			// }    

			try (final InputStream s = new FileProvider().getInputStreamFromClassLoader(pathToSpec
					+ File.separator + methodSpec,
					AndroidAnalysisContext.class.getClassLoader())) {

				XMLMethodSummaryReader nativeSummaries = loadMethodSummaries(scope,
						s);

				summaries.putAll(nativeSummaries.getSummaries());
				summaryClasses.addAll(nativeSummaries.getAllocatableClasses());
				if (extraSummary != null) {
					summaries.put((MethodReference) extraSummary.getMethod(),
							extraSummary);
				}

				MethodTargetSelector ms = new BypassMethodTargetSelector(
						options.getMethodTargetSelector(), summaries,
						nativeSummaries.getIgnoredPackages(), cha);
				options.setSelector(ms);

				ClassTargetSelector cs = new BypassClassTargetSelector(
						options.getClassTargetSelector(), summaryClasses, cha,
						cha.getLoader(scope.getLoader(Atom
								.findOrCreateUnicodeAtom("Synthetic"))));
				options.setSelector(cs);
				
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	private static XMLMethodSummaryReader loadMethodSummaries(
			AnalysisScope scope, InputStream xmlIStream) {
		try (InputStream s = xmlIStream != null ? xmlIStream :
			AndroidAnalysisContext.class.getClassLoader()
				.getResourceAsStream(
					pathToSpec + File.separator + methodSpec)) {
			return new XMLMethodSummaryReader(s, scope);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * Returns all concrete classes implementing the given interface or any subinterfaces
	 */
	public Collection<IClass> concreteClassesForInterface(IClass iRoot) {
		Set<IClass> clazzes = HashSetFactory.make();
		Set<IClass> done = HashSetFactory.make();
		Deque<IClass> todo = Queues.newArrayDeque();
		todo.push(iRoot);
		
		while (!todo.isEmpty()) {
			IClass i = todo.pop();
			for (IClass clazz : cha.getImplementors(i.getReference())) {
				if (clazz.isInterface() && !done.contains(clazz)) {
					done.add(i);
					todo.push(clazz);
				} else if (!clazz.isAbstract()) {
					clazzes.add(clazz);
				}
			}
		}
		
		return clazzes;
	}
	
	public ISCanDroidOptions getOptions() {
		return options;
	}
	
	public AnalysisScope getScope() {
		return scope;
	}
	
	public ClassHierarchy getClassHierarchy() {
		return cha;
	}
}
