/*
 * Copyright (C) 2015 Red Hat, Inc. and/or its affiliates.
 *
 * 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 org.jboss.errai.ioc.rebind.ioc.bootstrapper;

import static java.util.Arrays.stream;
import static java.util.Collections.emptyList;
import static org.jboss.errai.codegen.Parameter.of;
import static org.jboss.errai.codegen.util.Stmt.invokeStatic;
import static org.jboss.errai.codegen.util.Stmt.loadLiteral;
import static org.jboss.errai.codegen.util.Stmt.nestedCall;
import static org.jboss.errai.codegen.util.Stmt.newObject;
import static org.jboss.errai.ioc.rebind.ioc.extension.builtin.JsTypeAntiInliningExtension.numberOfRequiredAntiInliningDummies;
import static org.jboss.errai.ioc.rebind.ioc.extension.builtin.JsTypeAntiInliningExtension.requiresAntiInliningDummy;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.jboss.errai.codegen.Statement;
import org.jboss.errai.codegen.builder.AnonymousClassStructureBuilder;
import org.jboss.errai.codegen.builder.ClassStructureBuilder;
import org.jboss.errai.codegen.builder.impl.ObjectBuilder;
import org.jboss.errai.codegen.meta.MetaClass;
import org.jboss.errai.codegen.util.Stmt;
import org.jboss.errai.ioc.client.WindowInjectionContextImpl;
import org.jboss.errai.ioc.client.WindowInjectionContextStorage;
import org.jboss.errai.ioc.client.api.ActivatedBy;
import org.jboss.errai.ioc.client.api.builtin.DummyJsTypeProvider;
import org.jboss.errai.ioc.client.container.BeanActivator;
import org.jboss.errai.ioc.client.container.FactoryHandleImpl;
import org.jboss.errai.ioc.rebind.ioc.graph.api.DependencyGraph;
import org.jboss.errai.ioc.rebind.ioc.graph.api.Injectable;
import org.jboss.errai.ioc.rebind.ioc.injector.api.InjectionContext;

/**
 * Generates factories that lookup types from the {@link WindowInjectionContextImpl}
 * , allowing the injection of types between dynamic runtime modules.
 *
 * @see FactoryBodyGenerator
 * @see AbstractBodyGenerator
 * @author Max Barkley <mbarkley@redhat.com>
 */
public class JsTypeFactoryBodyGenerator extends AbstractBodyGenerator {

  @Override
  protected List<Statement> generateFactoryInitStatements(final ClassStructureBuilder<?> bodyBlockBuilder,
          final Injectable injectable, final DependencyGraph graph, final InjectionContext injectionContext) {
    final MetaClass type = injectable.getInjectedType();
    if (IOCProcessor.isJsInteropSupportEnabled() && requiresAntiInliningDummy(type)) {
      final int count = numberOfRequiredAntiInliningDummies(type);
      final List<Statement> stmts = new ArrayList<>(count);

      for (int i = 0; i < count; i++) {
        stmts.add(invokeStatic(WindowInjectionContextStorage.class, "createOrGet")
                    .invoke("addBeanProvider", "$$_anti_inlining_dummy_$$", createJsTypeProvider(type)));
      }

      return stmts;
    }
    else {
      return emptyList();
    }

  }

  private ObjectBuilder createJsTypeProvider(final MetaClass type) {
    return newObject(DummyJsTypeProvider.class)
      .extend()
      .publicOverridesMethod("getInstance")
      .append(nestedCall(createAnonymousImpl(type)).returnValue())
      .finish()
      .publicOverridesMethod("getName")
      .append(loadLiteral("Anti-inlining impl for: " + type.getFullyQualifiedName()).returnValue())
      .finish()
      .finish();
  }

  private ObjectBuilder createAnonymousImpl(final MetaClass type) {
    final AnonymousClassStructureBuilder builder = newObject(type).extend();
    stream(type.getMethods())
      .filter(m -> m.isPublic() && m.isAbstract())
      .forEach(m -> {
        builder
          .publicOverridesMethod(m.getName(), of(m.getParameters()))
          .append(Stmt.throw_(RuntimeException.class))
          .finish();
      });
    return builder.finish();
  }

  @Override
  protected List<Statement> generateCreateInstanceStatements(final ClassStructureBuilder<?> bodyBlockBuilder,
          final Injectable injectable, final DependencyGraph graph, final InjectionContext injectionContext) {
    return Collections.<Statement> singletonList(
            Stmt.castTo(injectable.getInjectedType(), invokeStatic(WindowInjectionContextStorage.class, "createOrGet")
                    .invoke("getBean", injectable.getInjectedType().getFullyQualifiedName())).returnValue());
  }

  @Override
  protected Statement generateFactoryHandleStatement(final Injectable injectable) {
    final Object[] args;
    if (injectable.getInjectedType().isAnnotationPresent(ActivatedBy.class)) {
      final Class<? extends BeanActivator> activatorType = injectable.getInjectedType().getAnnotation(ActivatedBy.class).value();
      args =  new Object[] {
          loadLiteral(injectable.getInjectedType()),
          injectable.getFactoryName(),
          injectable.getScope(),
          isEager(injectable.getInjectedType()),
          injectable.getBeanName(),
          loadLiteral(false),
          loadLiteral(activatorType)
      };
    } else {
      args =  new Object[] {
          loadLiteral(injectable.getInjectedType()),
          injectable.getFactoryName(),
          injectable.getScope(),
          isEager(injectable.getInjectedType()),
          injectable.getBeanName(),
          loadLiteral(false)
      };
    }

    return newObject(FactoryHandleImpl.class, args);
  }

}
