/*
 * 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.gson;

import com.google.common.base.Objects;
import com.google.gson.internal.$Gson$Types;
import com.google.gson.internal.Primitives;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
 * This class contains some test fixtures for Parameterized types. These classes should ideally
 * belong either in the common or functional package, but they are placed here because they need
 * access to package protected elements of com.google.gson.
 *
 * @author Inderjeet Singh
 * @author Joel Leitch
 */
public class ParameterizedTypeFixtures {
  private ParameterizedTypeFixtures() {}

  public static final class MyParameterizedType<T> {
    public final T value;

    public MyParameterizedType(T value) {
      this.value = value;
    }

    public T getValue() {
      return value;
    }

    public String getExpectedJson() {
      String valueAsJson = getExpectedJson(value);
      return String.format("{\"value\":%s}", valueAsJson);
    }

    private static String getExpectedJson(Object obj) {
      Class<?> clazz = obj.getClass();
      if (Primitives.isWrapperType(Primitives.wrap(clazz))) {
        return obj.toString();
      } else if (obj.getClass().equals(String.class)) {
        return "\"" + obj.toString() + "\"";
      } else {
        // Try invoking a getExpectedJson() method if it exists
        try {
          Method method = clazz.getMethod("getExpectedJson");
          Object results = method.invoke(obj);
          return (String) results;
        } catch (ReflectiveOperationException e) {
          throw new RuntimeException(e);
        }
      }
    }

    @Override
    public int hashCode() {
      return value == null ? 0 : value.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
      if (this == obj) {
        return true;
      }
      if (!(obj instanceof MyParameterizedType<?>)) {
        return false;
      }
      MyParameterizedType<?> that = (MyParameterizedType<?>) obj;
      return Objects.equal(getValue(), that.getValue());
    }
  }

  public static class MyParameterizedTypeInstanceCreator<T>
      implements InstanceCreator<MyParameterizedType<T>> {
    private final T instanceOfT;

    /**
     * Caution the specified instance is reused by the instance creator for each call. This means
     * that the fields of the same objects will be overwritten by Gson. This is usually fine in
     * tests since there we deserialize just once, but quite dangerous in practice.
     */
    public MyParameterizedTypeInstanceCreator(T instanceOfT) {
      this.instanceOfT = instanceOfT;
    }

    @Override
    public MyParameterizedType<T> createInstance(Type type) {
      return new MyParameterizedType<>(instanceOfT);
    }
  }

  public static final class MyParameterizedTypeAdapter<T>
      implements JsonSerializer<MyParameterizedType<T>>, JsonDeserializer<MyParameterizedType<T>> {
    @SuppressWarnings("unchecked")
    public static <T> String getExpectedJson(MyParameterizedType<T> obj) {
      Class<T> clazz = (Class<T>) obj.value.getClass();
      boolean addQuotes = !clazz.isArray() && !Primitives.unwrap(clazz).isPrimitive();
      StringBuilder sb = new StringBuilder("{\"");
      sb.append(obj.value.getClass().getSimpleName()).append("\":");
      if (addQuotes) {
        sb.append("\"");
      }
      sb.append(obj.value.toString());
      if (addQuotes) {
        sb.append("\"");
      }
      sb.append("}");
      return sb.toString();
    }

    @Override
    public JsonElement serialize(
        MyParameterizedType<T> src, Type classOfSrc, JsonSerializationContext context) {
      JsonObject json = new JsonObject();
      T value = src.getValue();
      json.add(value.getClass().getSimpleName(), context.serialize(value));
      return json;
    }

    @SuppressWarnings("unchecked")
    @Override
    public MyParameterizedType<T> deserialize(
        JsonElement json, Type typeOfT, JsonDeserializationContext context)
        throws JsonParseException {
      Type genericClass = ((ParameterizedType) typeOfT).getActualTypeArguments()[0];
      Class<?> rawType = $Gson$Types.getRawType(genericClass);
      String className = rawType.getSimpleName();
      JsonElement jsonElement = json.getAsJsonObject().get(className);

      T value;
      if (genericClass == Integer.class) {
        value = (T) Integer.valueOf(jsonElement.getAsInt());
      } else if (genericClass == String.class) {
        value = (T) jsonElement.getAsString();
      } else {
        value = (T) jsonElement;
      }

      if (Primitives.isPrimitive(genericClass)) {
        PrimitiveTypeAdapter typeAdapter = new PrimitiveTypeAdapter();
        value = (T) typeAdapter.adaptType(value, rawType);
      }
      return new MyParameterizedType<>(value);
    }
  }
}
