/**
 * Copyright (C) 2009 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.guice.bundle;

import static com.google.inject.name.Names.named;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.matcher.AbstractMatcher;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Random;

/**
 * Test Guice from inside an OSGi bundle activator.
 * 
 * @author mcculls@gmail.com (Stuart McCulloch)
 */
@SuppressWarnings("unused") public class OSGiTestActivator
    implements BundleActivator {

  // varying visibilities to test our code-generation support

  public static class Undefined {}

  public interface A {}

  protected interface B {}

  interface C {}

  private interface D {}

  public static class AA
      implements A {

    public AA() {}

    @Inject public void setA(Undefined undefined) {}

    @Inject protected void setB(Undefined undefined) {}

    @Inject void setC(Undefined undefined) {}

    @Inject private void setD(Undefined undefined) {}

    @Inject public Undefined a;

    @Inject protected Undefined b;

    @Inject Undefined c;

    @Inject private Undefined d;
  }

  protected static class AB
      implements A {

    public AB() {}

    @Inject public void setA(Undefined undefined) {}

    @Inject protected void setB(Undefined undefined) {}

    @Inject void setC(Undefined undefined) {}

    @Inject private void setD(Undefined undefined) {}

    @Inject public Undefined a;

    @Inject protected Undefined b;

    @Inject Undefined c;

    @Inject private Undefined d;
  }

  static class AC
      implements A {

    public AC() {}

    @Inject public void setA(Undefined undefined) {}

    @Inject protected void setB(Undefined undefined) {}

    @Inject void setC(Undefined undefined) {}

    @Inject private void setD(Undefined undefined) {}

    @Inject public Undefined a;

    @Inject protected Undefined b;

    @Inject Undefined c;

    @Inject private Undefined d;
  }

  private static class AD
      implements A {

    public AD() {}

    @Inject public void setA(Undefined undefined) {}

    @Inject protected void setB(Undefined undefined) {}

    @Inject void setC(Undefined undefined) {}

    @Inject private void setD(Undefined undefined) {}

    @Inject public Undefined a;

    @Inject protected Undefined b;

    @Inject Undefined c;

    @Inject private Undefined d;
  }

  public static class BA
      implements B {

    protected BA() {}

    @Inject public void setA(Undefined undefined) {}

    @Inject protected void setB(Undefined undefined) {}

    @Inject void setC(Undefined undefined) {}

    @Inject private void setD(Undefined undefined) {}

    @Inject public Undefined a;

    @Inject protected Undefined b;

    @Inject Undefined c;

    @Inject private Undefined d;
  }

  protected static class BB
      implements B {

    protected BB() {}

    @Inject public void setA(Undefined undefined) {}

    @Inject protected void setB(Undefined undefined) {}

    @Inject void setC(Undefined undefined) {}

    @Inject private void setD(Undefined undefined) {}

    @Inject public Undefined a;

    @Inject protected Undefined b;

    @Inject Undefined c;

    @Inject private Undefined d;
  }

  static class BC
      implements B {

    protected BC() {}

    @Inject public void setA(Undefined undefined) {}

    @Inject protected void setB(Undefined undefined) {}

    @Inject void setC(Undefined undefined) {}

    @Inject private void setD(Undefined undefined) {}

    @Inject public Undefined a;

    @Inject protected Undefined b;

    @Inject Undefined c;

    @Inject private Undefined d;
  }

  private static class BD
      implements B {

    protected BD() {}

    @Inject public void setA(Undefined undefined) {}

    @Inject protected void setB(Undefined undefined) {}

    @Inject void setC(Undefined undefined) {}

    @Inject private void setD(Undefined undefined) {}

    @Inject public Undefined a;

    @Inject protected Undefined b;

    @Inject Undefined c;

    @Inject private Undefined d;
  }

  public static class CA
      implements C {

    CA() {}

    @Inject public void setA(Undefined undefined) {}

    @Inject protected void setB(Undefined undefined) {}

    @Inject void setC(Undefined undefined) {}

    @Inject private void setD(Undefined undefined) {}

    @Inject public Undefined a;

    @Inject protected Undefined b;

    @Inject Undefined c;

    @Inject private Undefined d;
  }

  protected static class CB
      implements C {

    CB() {}

    @Inject public void setA(Undefined undefined) {}

    @Inject protected void setB(Undefined undefined) {}

    @Inject void setC(Undefined undefined) {}

    @Inject private void setD(Undefined undefined) {}

    @Inject public Undefined a;

    @Inject protected Undefined b;

    @Inject Undefined c;

    @Inject private Undefined d;
  }

  static class CC
      implements C {

    CC() {}

    @Inject public void setA(Undefined undefined) {}

    @Inject protected void setB(Undefined undefined) {}

    @Inject void setC(Undefined undefined) {}

    @Inject private void setD(Undefined undefined) {}

    @Inject public Undefined a;

    @Inject protected Undefined b;

    @Inject Undefined c;

    @Inject private Undefined d;
  }

  private static class CD
      implements C {

    CD() {}

    @Inject public void setA(Undefined undefined) {}

    @Inject protected void setB(Undefined undefined) {}

    @Inject void setC(Undefined undefined) {}

    @Inject private void setD(Undefined undefined) {}

    @Inject public Undefined a;

    @Inject protected Undefined b;

    @Inject Undefined c;

    @Inject private Undefined d;
  }

  public static class DA
      implements D {

    @Inject private DA() {}

    @Inject public void setA(Undefined undefined) {}

    @Inject protected void setB(Undefined undefined) {}

    @Inject void setC(Undefined undefined) {}

    @Inject private void setD(Undefined undefined) {}

    @Inject public Undefined a;

    @Inject protected Undefined b;

    @Inject Undefined c;

    @Inject private Undefined d;
  }

  protected static class DB
      implements D {

    @Inject private DB() {}

    @Inject public void setA(Undefined undefined) {}

    @Inject protected void setB(Undefined undefined) {}

    @Inject void setC(Undefined undefined) {}

    @Inject private void setD(Undefined undefined) {}

    @Inject public Undefined a;

    @Inject protected Undefined b;

    @Inject Undefined c;

    @Inject private Undefined d;
  }

  static class DC
      implements D {

    @Inject private DC() {}

    @Inject public void setA(Undefined undefined) {}

    @Inject protected void setB(Undefined undefined) {}

    @Inject void setC(Undefined undefined) {}

    @Inject private void setD(Undefined undefined) {}

    @Inject public Undefined a;

    @Inject protected Undefined b;

    @Inject Undefined c;

    @Inject private Undefined d;
  }

  private static class DD
      implements D {

    private DD() {}

    @Inject public void setA(Undefined undefined) {}

    @Inject protected void setB(Undefined undefined) {}

    @Inject void setC(Undefined undefined) {}

    @Inject private void setD(Undefined undefined) {}

    @Inject public Undefined a;

    @Inject protected Undefined b;

    @Inject Undefined c;

    @Inject private Undefined d;
  }

  enum Visibility {
    PUBLIC, PROTECTED, PACKAGE_PRIVATE, PRIVATE
  }

  static final Class<?>[] TEST_CLAZZES = {A.class, B.class, C.class, D.class};

  // registers all the class combinations
  static class TestModule
      extends AbstractModule {

    final Bundle bundle;

    TestModule(Bundle bundle) {
      this.bundle = bundle;
    }

    @Override @SuppressWarnings("unchecked") protected void configure() {
      for (Class<?> api : TEST_CLAZZES) {
        for (Visibility visibility : Visibility.values()) {
          try {

            // this registers: A + PUBLIC -> AA, A + PROTECTED -> AB, etc...
            String suffix = TEST_CLAZZES[visibility.ordinal()].getSimpleName();
            Class imp = bundle.loadClass(api.getName() + suffix);
            bind(api).annotatedWith(named(visibility.name())).to(imp);

          } catch (ClassNotFoundException e) {
            throw new RuntimeException("Unable to load test class", e);
          }
        }
      }
    }
  }

/*if[AOP]*/
  // applies method-interception to classes with enough visibility
  static class InterceptorModule
      extends AbstractModule {
    @Override protected void configure() {
      bindInterceptor(new AbstractMatcher<Class<?>>() {
        public boolean matches(Class<?> clazz) {
          try {

            // the class and constructor must be visible
            int clazzModifiers = clazz.getModifiers();
            int ctorModifiers = clazz.getConstructor().getModifiers();
            return (clazzModifiers & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0
                && (ctorModifiers & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0;

          } catch (NoSuchMethodException e) {
            return false;
          }
        }
      }, new AbstractMatcher<Method>() {
        public boolean matches(Method method) {

          // the intercepted method must also be visible
          int methodModifiers = method.getModifiers();
          return (methodModifiers & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0;

        }
      }, new org.aopalliance.intercept.MethodInterceptor() {
        public Object invoke(org.aopalliance.intercept.MethodInvocation mi)
            throws Throwable {

          return mi.proceed();
        }
      });
    }
  }
/*end[AOP]*/

  // called from OSGi when bundle starts
  public void start(BundleContext context)
      throws BundleException {

    final Bundle bundle = context.getBundle();

    Injector injector = Guice.createInjector(new TestModule(bundle));
/*if[AOP]*/
    Injector aopInjector = Guice.createInjector(new TestModule(bundle), new InterceptorModule());
/*end[AOP]*/

    // test code-generation support
    for (Class<?> api : TEST_CLAZZES) {
      for (Visibility vis : Visibility.values()) {
        injector.getInstance(Key.get(api, named(vis.name())));
/*if[AOP]*/
        aopInjector.getInstance(Key.get(api, named(vis.name())));
/*end[AOP]*/
      }
    }

    // test injection of system class (issue 343)
    injector.getInstance(Random.class);
/*if[AOP]*/
    aopInjector.getInstance(Random.class);
/*end[AOP]*/
  }

  // called from OSGi when bundle stops
  public void stop(BundleContext context) {}
}
