/*
 * Copyright 2018 Google LLC
 *
 * 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
 *
 *     https://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.android.gnd.ui.common;

import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Provider;

/**
 * Generic factory class to create ViewModels using Dagger 2 injections, working around the fact
 * that we cannot inject view models directly. Based on Android Architecture Components example:
 * https://github.com/googlesamples/android-architecture-components/blob/b1a194c1ae267258cd002e2e1c102df7180be473/GithubBrowserSample/app/src/main/java/com/android/example/github/viewmodel/GithubViewModelFactory.java
 */
public class ViewModelFactory implements ViewModelProvider.Factory {
  private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;

  @Inject
  public ViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
    this.creators = creators;
  }

  /**
   * Instantiates a new instance of the specified view model, injecting required dependencies.
   */
  @Override
  public <T extends ViewModel> T create(Class<T> modelClass) {
    Provider<? extends ViewModel> creator = creators.get(modelClass);
    if (creator == null) {
      throw new IllegalArgumentException("Unknown model class " + modelClass);
    }
    return (T) creator.get();
  }

  /**
   * Returns an instance of the specified view model, which is scoped to the activity if annotated
   * with {@link SharedViewModel}, or scoped to the Fragment if not.
   */
  public <T extends ViewModel> T get(Fragment fragment, Class<T> modelClass) {
    if (modelClass.getAnnotation(SharedViewModel.class) == null) {
      return ViewModelProviders.of(fragment, this).get(modelClass);
    } else {
      return get(fragment.getActivity(), modelClass);
    }
  }

  /**
   * Returns an instance of the specified view model scoped to the provided activity.
   */
  public <T extends ViewModel> T get(FragmentActivity activity, Class<T> modelClass) {
    return ViewModelProviders.of(activity, this).get(modelClass);
  }
}