package com.google.inject.spi;

import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.Module;

import junit.framework.TestCase;

/**
 * Tests for {@link ModuleSource}.
 */
public class ModuleSourceTest extends TestCase {

  private static final StackTraceElement BINDER_INSTALL = 
      new StackTraceElement("com.google.inject.spi.Elements$RecordingBinder", "install", 
          "Unknown Source", 235 /* line number*/);
  
  public void testOneModule() {
    ModuleSource moduleSource = createWithSizeOne();
    checkSizeOne(moduleSource);    
  }
  
  public void testTwoModules() {
    ModuleSource moduleSource = createWithSizeTwo();
    checkSizeTwo(moduleSource);
    moduleSource = moduleSource.getParent();
    checkSizeOne(moduleSource);
  }
  
  public void testThreeModules() {
    ModuleSource moduleSource = createWithSizeThree();
    checkSizeThree(moduleSource);
    moduleSource = moduleSource.getParent();
    checkSizeTwo(moduleSource);
    moduleSource = moduleSource.getParent();
    checkSizeOne(moduleSource);
  }

  private void checkSizeOne(ModuleSource moduleSource) {
    assertEquals(1, moduleSource.size());
    assertEquals(1, moduleSource.getStackTraceSize());
    // Check call stack
    StackTraceElement[] callStack = moduleSource.getStackTrace();
    assertEquals(BINDER_INSTALL, callStack[0]);    
  }
  
  private void checkSizeTwo(ModuleSource moduleSource) {
    assertEquals(2, moduleSource.size());
    assertEquals(3, moduleSource.getStackTraceSize());
    // Check call stack
    StackTraceElement[] callStack = moduleSource.getStackTrace();
    assertEquals(BINDER_INSTALL, callStack[0]);    
    assertEquals(
        new StackTraceElement(
            "com.google.inject.spi.moduleSourceTest$A", "configure", "Unknown Source", 100),
        callStack[1]);
    assertEquals(BINDER_INSTALL, callStack[2]);    
  }
  
  private void checkSizeThree(ModuleSource moduleSource) {
    assertEquals(3, moduleSource.size());
    assertEquals(7, moduleSource.getStackTraceSize());
    // Check call stack
    StackTraceElement[] callStack = moduleSource.getStackTrace();
    assertEquals(BINDER_INSTALL, callStack[0]);    
    assertEquals(new StackTraceElement("class1", "method1", "Unknown Source", 1), callStack[1]);
    assertEquals(new StackTraceElement("class2", "method2", "Unknown Source", 2), callStack[2]);   
    assertEquals(
        new StackTraceElement(
            "com.google.inject.spi.moduleSourceTest$B", "configure", "Unknown Source", 200),
            callStack[3]); 
    assertEquals(BINDER_INSTALL, callStack[4]);    

    assertEquals(
        new StackTraceElement(
            "com.google.inject.spi.moduleSourceTest$A", "configure", "Unknown Source", 100),
        callStack[5]);
    assertEquals(BINDER_INSTALL, callStack[6]);    
  }
  
  private ModuleSource createWithSizeOne() {
    StackTraceElement[] partialCallStack = new StackTraceElement[1];
    partialCallStack[0] = BINDER_INSTALL;    
    return new ModuleSource(new A(), partialCallStack);
  }
  
  private ModuleSource createWithSizeTwo() {
    ModuleSource moduleSource = createWithSizeOne();
    StackTraceElement[] partialCallStack = new StackTraceElement[2];
    partialCallStack[0] = BINDER_INSTALL;
    partialCallStack[1] = new StackTraceElement(
        "com.google.inject.spi.moduleSourceTest$A", "configure", "moduleSourceTest.java", 100);
    return moduleSource.createChild(new B(), partialCallStack);
  }
    
  private ModuleSource createWithSizeThree() {
    ModuleSource moduleSource = createWithSizeTwo();
    StackTraceElement[] partialCallStack = new StackTraceElement[4];
    partialCallStack[0] = BINDER_INSTALL;
    partialCallStack[1] = new StackTraceElement("class1", "method1", "Class1.java", 1);
    partialCallStack[2] = new StackTraceElement("class2", "method2", "Class2.java", 2);
    partialCallStack[3] = new StackTraceElement(
        "com.google.inject.spi.moduleSourceTest$B", "configure", "moduleSourceTest.java", 200);
    return moduleSource.createChild(new C(), partialCallStack);
  }

  private static class A extends AbstractModule {
    @Override
    public void configure() {
      install(new B());
    }
  }
  
  private static class B implements Module {
    @Override
    public void configure(Binder binder) {
      binder.install(new C());
    }
  }
  
  private static class C extends AbstractModule {
    @Override
    public void configure() {}
  }
}
