/*
 * Copyright 2011 JBoss, by Red Hat, 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 org.jboss.errai.ioc.rebind.ioc.injector.api;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.List;

import org.jboss.errai.codegen.meta.MetaClass;
import org.jboss.errai.codegen.meta.MetaConstructor;
import org.jboss.errai.codegen.meta.MetaField;
import org.jboss.errai.codegen.meta.MetaMethod;
import org.jboss.errai.codegen.meta.MetaParameter;
import org.jboss.errai.codegen.util.PrivateAccessType;
import org.jboss.errai.ioc.rebind.ioc.injector.InjectUtil;
import org.jboss.errai.ioc.rebind.ioc.injector.Injector;
import org.jboss.errai.ioc.rebind.ioc.metadata.QualifyingMetadata;

/**
 * @author Mike Brock
 */
public class InjectionPoint<T> {
  protected T annotation;
  protected TaskType taskType;

  protected MetaConstructor constructor;
  protected MetaMethod method;
  protected MetaField field;
  protected MetaClass type;
  protected MetaParameter parm;
  protected Injector injector;
  protected InjectionContext injectionContext;

  public InjectionPoint(final T annotation, final TaskType taskType, final MetaConstructor constructor,
          final MetaMethod method, final MetaField field, final MetaClass type, final MetaParameter parm,
          final Injector injector, final InjectionContext injectionContext) {

    this.annotation = annotation;
    this.taskType = taskType;
    this.constructor = constructor;
    this.method = method;
    this.field = field;
    this.type = type;
    this.parm = parm;
    this.injector = injector;
    this.injectionContext = injectionContext;
  }

  public T getAnnotation() {
    return annotation;
  }

  public MetaConstructor getConstructor() {
    return constructor;
  }

  public TaskType getTaskType() {
    return taskType;
  }

  public MetaMethod getMethod() {
    return method;
  }

  public MetaField getField() {
    return field;
  }

  public MetaClass getType() {
    if (type != null) {
      return type;
    }

    switch (taskType) {
    case PrivateField:
    case Field:
      return type = field.getType();
    case PrivateMethod:
    case Parameter:
      return type = parm.getType();
    case Type:
      return type;
    default:
      throw new RuntimeException("unsupported operation: getType for task: " + taskType);
    }
  }

  public MetaClass getElementTypeOrMethodReturnType() {
    switch (taskType) {
    case PrivateField:
    case Field:
      return type = field.getType();
    case PrivateMethod:
    case Method:
      return type = method.getReturnType();
    case Parameter:
      return type = parm.getType();
    case Type:
      return type;
    default:
      throw new RuntimeException("unsupported operation: getType for task: " + taskType);
    }
  }

  public MetaParameter getParm() {
    return parm;
  }

  public Injector getInjector() {
    return injector;
  }

  public InjectionContext getInjectionContext() {
    return injectionContext;
  }

  public void ensureMemberExposed() {
    ensureMemberExposed(PrivateAccessType.Both);
  }

  public void ensureMemberExposed(final PrivateAccessType accessType) {
    switch (taskType) {
      case Parameter:
        if (parm.getDeclaringMember() instanceof MetaMethod) {
          final MetaMethod declMeth = (MetaMethod) parm.getDeclaringMember();
          injectionContext.addExposedMethod(declMeth);
        }
        break;
      case PrivateMethod:
        injectionContext.addExposedMethod(method);
        break;
      case PrivateField:
        injectionContext.addExposedField(field, accessType);
        break;
    }
  }

  public String getMemberName() {
    switch (taskType) {
    case PrivateField:
    case Field:
      return field.getName();

    case Parameter:
      return parm.getName();
    case PrivateMethod:
    case Method:
      return method.getName();

    case Type:
      return type.getName();

    default:
      return null;
    }
  }

  public MetaClass getEnclosingType() {
    switch (taskType) {
    case PrivateField:
    case Field:
      return field.getDeclaringClass();
    case PrivateMethod:
    case Method:
      return method.getDeclaringClass();
    case Type:
      return type;
    case Parameter:
      return parm.getDeclaringMember().getDeclaringClass();
    default:
      throw new RuntimeException("unsupported operation: getEncodingType for task: " + taskType);
    }
  }

  public Annotation[] getQualifiers() {
    final List<Annotation> annotations;
    switch (taskType) {
    case PrivateField:
    case Field:
      annotations = InjectUtil.getQualifiersFromAnnotations(field.getAnnotations());
      return annotations.toArray(new Annotation[annotations.size()]);

    case Parameter:
      annotations = InjectUtil.getQualifiersFromAnnotations(parm.getAnnotations());
      return annotations.toArray(new Annotation[annotations.size()]);

    case PrivateMethod:
    case Method:
      annotations = InjectUtil.getQualifiersFromAnnotations(method.getAnnotations());
      return annotations.toArray(new Annotation[annotations.size()]);

    case Type:
      annotations = InjectUtil.getQualifiersFromAnnotations(type.getAnnotations());
      return annotations.toArray(new Annotation[annotations.size()]);

    default:
      return new Annotation[0];
    }
  }

  public QualifyingMetadata getQualifyingMetadata() {
    return injectionContext.getProcessingContext().getQualifyingMetadataFactory().createFrom(getQualifiers());
  }

  public boolean isProxy() {
    return injectionContext.isProxiedInjectorRegistered(getEnclosingType(), getQualifyingMetadata());
  }

  public boolean isAnnotationPresent(Class<? extends Annotation> annotation) {
    return getAnnotation(annotation) != null;
  }

  public <A extends Annotation> A getAnnotation(Class<A> annotation) {
    for (Annotation a : Arrays.asList(getAnnotations())) {
      if (annotation != null && annotation.isAssignableFrom(a.annotationType())) {
        return (A) a;
      }
    }
    return null;
  }

  public Annotation[] getAnnotations() {
    switch (taskType) {
    case PrivateField:
    case Field:
      return field.getAnnotations();

    case Parameter:
      return parm.getAnnotations();

    case PrivateMethod:
    case Method:
      return method.getAnnotations();

    case Type:
      return type.getAnnotations();

    default:
      return new Annotation[0];
    }
  }
}
