/*
 *
 * Copyright (c) 2009-2012,
 *
 *  Jonathan Bardin     <astrosus@gmail.com>
 *  Steve Suh           <suhsteve@gmail.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 com.ibm.wala.dalvik.classLoader;

import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ibm.wala.classLoader.ClassLoaderImpl;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IClassLoader;
import com.ibm.wala.classLoader.Module;
import com.ibm.wala.classLoader.ModuleEntry;
import com.ibm.wala.ipa.callgraph.impl.SetOfClasses;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import com.ibm.wala.types.ClassLoaderReference;
import com.ibm.wala.types.TypeName;
import com.ibm.wala.util.collections.HashCodeComparator;
import com.ibm.wala.util.collections.HashSetFactory;
import com.ibm.wala.util.collections.Iterator2Collection;
import com.ibm.wala.util.warnings.Warning;
import com.ibm.wala.util.warnings.Warnings;

/**
 * ClassLoader for Java & Dalvik.
 *
 */
public class WDexClassLoaderImpl extends ClassLoaderImpl {
	private static final Logger logger = LoggerFactory.getLogger(WDexClassLoaderImpl.class);
	
    private SetOfClasses lExclusions;
    private IClassLoader lParent;

    
    //Commented out until IBM fixes ClassLoaderFactoryImpl "protected IClassLoader makeNewClassLoader"
    
//    public WDexClassLoaderImpl(ClassLoaderReference loader,
//            ArrayClassLoader arrayClassLoader, IClassLoader parent,
//            SetOfClasses exclusions, IClassHierarchy cha) {
//        super(loader, arrayClassLoader, parent, exclusions, cha);
//        lParent = parent;
//        lExclusions = exclusions;
//        //DEBUG_LEVEL = 0;
//    }
    
    public WDexClassLoaderImpl(ClassLoaderReference loader,IClassLoader parent,
            SetOfClasses exclusions, IClassHierarchy cha) {
        super(loader, cha.getScope().getArrayClassLoader(), parent, exclusions, cha);
        lParent = parent;
        lExclusions = exclusions;
        //DEBUG_LEVEL = 0;
    }
    
    @Override
    public void init(List<Module> modules) throws IOException {
    	super.init(modules);
        // module are loaded according to the given order (same as in Java VM)
        Set<ModuleEntry> classModuleEntries = HashSetFactory.make();
        
        for (Iterator<Module> it = modules.iterator(); it.hasNext();) {
            Module archive = it.next();
            logger.debug("add archive: : "+archive);
            Set<ModuleEntry> classFiles = getDexFiles(archive);
            
            removeClassFiles(classFiles, classModuleEntries);
            loadAllDexClasses(classFiles);
            
            for (Iterator<ModuleEntry> it2 = classFiles.iterator(); it2.hasNext();) {
            	ModuleEntry file = it2.next();
            	classModuleEntries.add(file);
            }
        }                       
    }
    

    /**
     * Remove from s any class file module entries which already are in t
     */
    private void removeClassFiles(Set<ModuleEntry> s, Set<ModuleEntry> t) {
    	Set<String> old = HashSetFactory.make();
    	for (Iterator<ModuleEntry> it = t.iterator(); it.hasNext();) {
    		ModuleEntry m = it.next();
    		old.add(m.getClassName());
    	}
    	HashSet<ModuleEntry> toRemove = HashSetFactory.make();
    	for (Iterator<ModuleEntry> it = s.iterator(); it.hasNext();) {
    		ModuleEntry m = it.next();
    		if (old.contains(m.getClassName())) {
    			toRemove.add(m);
    		}
    	}
    	s.removeAll(toRemove);
    }
    
    private Set<ModuleEntry> getDexFiles(Module M) throws IOException {
    	TreeSet<ModuleEntry> sortedEntries = new TreeSet<ModuleEntry>(HashCodeComparator.instance());
    	sortedEntries.addAll(Iterator2Collection.toSet(M.getEntries()));
    	HashSet<ModuleEntry> result = HashSetFactory.make();
    	for (Iterator<ModuleEntry> it = sortedEntries.iterator(); it.hasNext();) {
    		ModuleEntry entry = (ModuleEntry) it.next();
    		if (entry instanceof DexModuleEntry) {    		
    			result.add(entry);
    		}
    	}
    	return result;
    }
    
    
    private void loadAllDexClasses(Collection<ModuleEntry> moduleEntries) {
    	
    	for (Iterator<ModuleEntry> it = moduleEntries.iterator(); it.hasNext();) {
    		ModuleEntry entry = it.next();

    		// Dalvik class
    		if (entry instanceof DexModuleEntry) {
    			DexModuleEntry dexEntry = ((DexModuleEntry) entry);
    			TypeName tName = TypeName.string2TypeName(dexEntry.getClassName());

    			//if (DEBUG_LEVEL > 0) {
    			//  System.err.println("Consider dex class: " + tName);
    			//}

    			//System.out.println("Typename: " + tName.toString());
    			//System.out.println(tName.getClassName());
    			if (loadedClasses.get(tName) != null) {
    				Warnings.add(MultipleDexImplementationsWarning
    						.create(dexEntry.getClassName()));
    			} else if (lParent != null && lParent.lookupClass(tName) != null) {
    				Warnings.add(MultipleDexImplementationsWarning
    						.create(dexEntry.getClassName()));
    			}
    			//if the class is empty, ie an interface
    			//                  else if (dexEntry.getClassDefItem().getClassData() == null) {
    			//                      System.out.println("Jumping over (classdata null): "+dexEntry.getClassName());
    			//                      Warnings.add(MultipleDexImplementationsWarning
    			//                              .create(dexEntry.getClassName()));
    			//                  }
    			else {
    				IClass iClass = new DexIClass(this, cha, dexEntry);
    				if (iClass.getReference().getName().equals(tName)) {
    					logger.debug("Load class: " + dexEntry.getClassName());
    					loadedClasses.put(tName, iClass);
    				} else {
    					Warnings.add(InvalidDexFile.create(dexEntry.getClassName()));
    				}
    			}
    		}
    	}
    }


    /**
     * @return the IClassHierarchy of this classLoader.
     */
    public IClassHierarchy getClassHierarcy() {
        return cha;
    }

  /**
   * A warning when we find more than one implementation of a given class name
   */
  private static class MultipleDexImplementationsWarning extends Warning {

    final String className;

    MultipleDexImplementationsWarning(String className) {
      super(Warning.SEVERE);
      this.className = className;
    }

    @Override
    public String getMsg() {
      return getClass().toString() + " : " + className;
    }

    public static MultipleDexImplementationsWarning create(String className) {
      return new MultipleDexImplementationsWarning(className);
    }
  }

  /**
   * A warning when we encounter InvalidClassFileException
   */
  private static class InvalidDexFile extends Warning {

    final String className;

    InvalidDexFile(String className) {
      super(Warning.SEVERE);
      this.className = className;
    }

    @Override
    public String getMsg() {
      return getClass().toString() + " : " + className;
    }
    public static InvalidDexFile create(String className) {
      return new InvalidDexFile(className);
    }
  }
}
