package nz.bradcampbell.paperparcel;

import android.os.Parcelable;
import java.lang.reflect.Field;
import java.util.Map;

/**
 * Central mechanism for {@link #wrap(Object)}ing/{@link #unwrap(TypedParcelable)}ping arbitrary
 * PaperParcel objects.
 */
public final class PaperParcels {
  static final Map<Class, Delegator> FROM_ORIGINAL;
  static final Map<Class, Delegator> FROM_PARCELABLE;

  static {
    //noinspection TryWithIdenticalCatches
    try {
      Class clazz = Class.forName("nz.bradcampbell.paperparcel.PaperParcelMapping");
      FROM_ORIGINAL = getFieldValue(clazz, "FROM_ORIGINAL");
      FROM_PARCELABLE = getFieldValue(clazz, "FROM_PARCELABLE");
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    } catch (NoSuchFieldException e) {
      throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Wrap the specified object in its PaperParcel wrapper.
   * @throws IllegalArgumentException if wrapper is not found (no @PaperParcel annotation on type).
   */
  public static <T, TP extends TypedParcelable<T>> TP wrap(T originalObj) {
    Class<?> type = originalObj.getClass();
    //noinspection unchecked
    Delegator<T, TypedParcelable<T>> delegator = FROM_ORIGINAL.get(type);
    if (delegator == null) {
      throw new IllegalArgumentException("Unable to find PaperParcel for type '" + type.getSimpleName() + "', did you forget to annotate it with @PaperParcel?");
    }
    //noinspection unchecked
    return (TP) delegator.wrap(originalObj);
  }

  /**
   * Unwrap the specified Parcelable to its original @PaperParcel'd object.
   * @throws IllegalArgumentException if the provided TypedParcelable was not generated by @PaperParcel.
   */
  public static <T> T unwrap(TypedParcelable<T> parcelableObj) {
    Class<?> type = parcelableObj.getClass();
    //noinspection unchecked
    Delegator<T, TypedParcelable<T>> delegator = FROM_PARCELABLE.get(type);
    if (delegator == null) {
      throw new IllegalArgumentException("Unable to find PaperParcel for parcel type '" + type.getSimpleName() + "'...did you manually implement TypedParcelable instead of using @PaperParcel?");
    }
    return delegator.unwrap(parcelableObj);
  }

  /**
   * Unwrap the specified Parcelable to its original @PaperParcel'd object.
   * @throws IllegalArgumentException if there is not a wrapper (no @PaperParcel annotation on original type).
   */
  public static <T> T unsafeUnwrap(Parcelable parcelableObj) {
    TypedParcelable<T> unsafeCast;
    try {
      //noinspection unchecked
      unsafeCast = (TypedParcelable<T>) parcelableObj;
    } catch (ClassCastException e) {
      throw new IllegalArgumentException("Unable to cast '" + parcelableObj.getClass().getSimpleName() + "' to TypedParcelable, are you sure this Parcelable was generated by @PaperParcel?");
    }
    return unwrap(unsafeCast);
  }

  interface Delegator<ORIG, PARCEL extends TypedParcelable<ORIG>> {
    ORIG unwrap(PARCEL parcelableObj);

    TypedParcelable<ORIG> wrap(ORIG originalObj);
  }

  private static Map<Class, Delegator> getFieldValue(Class clazz, String fieldName)
      throws NoSuchFieldException, IllegalAccessException {
    Field field = clazz.getDeclaredField(fieldName);
    field.setAccessible(true);
    //noinspection unchecked
    return (Map<Class, Delegator>) field.get(null);
  }
}

@@ -1147,24 +1147,80 @@
 ginalObj) %7B%0A
+    if (originalObj == null) %7B%0A      return null;%0A    %7D%0A
     Class%3C?%3E
@@ -1884,24 +1884,82 @@
 lableObj) %7B%0A
+    if (parcelableObj == null) %7B%0A      return null;%0A    %7D%0A
     Class%3C?%3E

Fix #19: Handle null values in PaperParcels.wrap and PaperParcels.unwrap