/*
 * Copyright 2016 Peter Kenji Yamanaka
 *
 * 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.pyamsoft.pydroid.util;

import android.os.Bundle;
import android.support.annotation.CheckResult;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.pyamsoft.pydroid.base.app.Destroyable;
import com.pyamsoft.pydroid.base.app.PersistLoader;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import timber.log.Timber;

public final class PersistentCache {

  private PersistentCache() {
    throw new RuntimeException("No instances");
  }

  /**
   * Get a key for a given instance, either stored in the savedInstanceState or generated
   */
  @CheckResult private static long generateKey(@Nullable Bundle savedInstanceState,
      @NonNull String instanceId) {
    final long key;
    if (savedInstanceState == null) {
      // Generate a new key
      key = Persist.INSTANCE.generateKey();
      Timber.d("Generate a new key: %d", key);
    } else {
      // Retrieve the key from the saved instance
      key = savedInstanceState.getLong(instanceId);
      Timber.d("Retrieve stored key from %s: %d", instanceId, key);
    }

    return key;
  }

  /**
   * Saves the generated key into a bundle which will be restored later in the lifecycle
   */
  public static void saveKey(@NonNull Bundle outState, long key, @NonNull String instanceId) {
    outState.putLong(instanceId, key);
  }

  /**
   * Load a piece of data that the user wishes to persist over the lifecycle
   *
   * NOTE: If you always pass in NULL for the savedInstanceState, your state will never be cached.
   * You must pass THE savedInstanceState from the Android lifecycle
   */
  @CheckResult public static <T> long load(@Nullable Bundle savedInstanceState,
      @NonNull String instanceId, @NonNull PersistLoader.Callback<T> callback) {

    // Attempt to fetch the persistent object from the cache
    final long key = generateKey(savedInstanceState, instanceId);

    //noinspection unchecked
    T persist = (T) Persist.INSTANCE.getCachedObject(key);

    // If the persistent object is NULL it did not exist in the cache
    if (persist == null) {
      // Load a fresh object
      persist = callback.createLoader().loadPersistent();
      Timber.d("Created new persistable: %s [%d]", persist, key);

      // Save the presenter to the cache
      Persist.INSTANCE.persist(key, persist);
    } else {
      Timber.d("Loaded cached persistable: %s [%d]", persist, key);
    }

    callback.onPersistentLoaded(persist);

    // Return the key to the caller
    return key;
  }

  /**
   * Removes the persistent object from the cache
   *
   * This call is meant to be guarded using checks for Activity.isChangingConfigurations()
   */
  public static void unload(long key) {
    Persist.INSTANCE.remove(key);
  }

  enum Persist {

    INSTANCE;

    /**
     * KLUDGE Use a more efficient data structure that doesn't do all this unboxing
     */
    @NonNull private final Map<Long, Object> cache;
    @NonNull private final AtomicLong count;

    Persist() {
      cache = new HashMap<>(10);
      count = new AtomicLong(0);
    }

    @CheckResult final long generateKey() {
      return count.getAndIncrement();
    }

    @Nullable @CheckResult final Object getCachedObject(long key) {
      return cache.get(key);
    }

    final void persist(long key, @NonNull Object persistable) {
      cache.put(key, persistable);
    }

    final void remove(long key) {
      final Object persist = cache.remove(key);
      if (persist != null) {
        Timber.d("Remove persistable from cache: %s [%d]", persist, key);
        if (persist instanceof Destroyable) {
          final Destroyable destroyable = (Destroyable) persist;
          destroyable.destroy();
        }
      } else {
        Timber.e("Persisted object was NULL [%d]", key);
        Timber.e("This is usually indicative of a lifecycle error. Check your Fragments!");
      }
    }
  }
}

@@ -1294,41 +1294,32 @@
 y(@N
-ullable Bundle savedI
+onNull String i
 nstance
-State
+Id
 ,%0A  
@@ -1328,32 +1328,41 @@
   @N
-onNull String i
+ullable Bundle savedI
 nstance
-Id
+State
 ) %7B%0A
@@ -1892,61 +1892,61 @@
 ull 
-Bundle outState, long key, @NonNull String instanceId
+String instanceId, @NonNull Bundle outState, long key
 ) %7B%0A
@@ -2294,16 +2294,44 @@
 ng load(
+@NonNull String instanceId, 
 @Nullabl
@@ -2368,36 +2368,8 @@
     
- @NonNull String instanceId,
  @No
@@ -2507,16 +2507,28 @@
 rateKey(
+instanceId, 
 savedIns
@@ -2537,28 +2537,16 @@
 nceState
-, instanceId
 );%0A%0A    

Reorder parameters