/**
 * Copyright (C) 2008 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.google.inject.internal;

import com.google.common.collect.ImmutableSet;
import com.google.inject.Binder;
import com.google.inject.Key;
import com.google.inject.binder.AnnotatedConstantBindingBuilder;
import com.google.inject.binder.ConstantBindingBuilder;
import com.google.inject.spi.Element;
import com.google.inject.spi.InjectionPoint;

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

/**
 * Bind a constant.
 *
 * @author jessewilson@google.com (Jesse Wilson)
 */
public final class ConstantBindingBuilderImpl<T>
    extends AbstractBindingBuilder<T>
    implements AnnotatedConstantBindingBuilder, ConstantBindingBuilder {

  @SuppressWarnings("unchecked") // constant bindings start out with T unknown
  public ConstantBindingBuilderImpl(Binder binder, List<Element> elements, Object source) {
    super(binder, elements, source, (Key<T>) NULL_KEY);
  }

  public ConstantBindingBuilder annotatedWith(Class<? extends Annotation> annotationType) {
    annotatedWithInternal(annotationType);
    return this;
  }

  public ConstantBindingBuilder annotatedWith(Annotation annotation) {
    annotatedWithInternal(annotation);
    return this;
  }

  public void to(final String value) {
    toConstant(String.class, value);
  }

  public void to(final int value) {
    toConstant(Integer.class, value);
  }

  public void to(final long value) {
    toConstant(Long.class, value);
  }

  public void to(final boolean value) {
    toConstant(Boolean.class, value);
  }

  public void to(final double value) {
    toConstant(Double.class, value);
  }

  public void to(final float value) {
    toConstant(Float.class, value);
  }

  public void to(final short value) {
    toConstant(Short.class, value);
  }

  public void to(final char value) {
    toConstant(Character.class, value);
  }

  public void to(final byte value) {
    toConstant(Byte.class, value);
  }  

  public void to(final Class<?> value) {
    toConstant(Class.class, value);
  }

  public <E extends Enum<E>> void to(final E value) {
    toConstant(value.getDeclaringClass(), value);
  }

  private void toConstant(Class<?> type, Object instance) {
    // this type will define T, so these assignments are safe
    @SuppressWarnings("unchecked")
    Class<T> typeAsClassT = (Class<T>) type;
    @SuppressWarnings("unchecked")
    T instanceAsT = (T) instance;

    if (keyTypeIsSet()) {
      binder.addError(CONSTANT_VALUE_ALREADY_SET);
      return;
    }

    BindingImpl<T> base = getBinding();
    Key<T> key;
    if (base.getKey().getAnnotation() != null) {
      key = Key.get(typeAsClassT, base.getKey().getAnnotation());
    } else if (base.getKey().getAnnotationType() != null) {
      key = Key.get(typeAsClassT, base.getKey().getAnnotationType());
    } else {
      key = Key.get(typeAsClassT);
    }

    if (instanceAsT == null) {
      binder.addError(BINDING_TO_NULL);
    }

    setBinding(new InstanceBindingImpl<T>(
        base.getSource(), key, base.getScoping(), ImmutableSet.<InjectionPoint>of(), instanceAsT));
  }

  @Override public String toString() {
    return "ConstantBindingBuilder";
  }
}