package org.thoughtcrime.securesms.reactions.any;

import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProviders;
import androidx.loader.app.LoaderManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.android.material.shape.CornerFamily;
import com.google.android.material.shape.MaterialShapeDrawable;
import com.google.android.material.shape.ShapeAppearanceModel;

import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiEventListener;
import org.thoughtcrime.securesms.components.emoji.EmojiPageView;
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardPageCategoriesAdapter;
import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardPageCategoryMappingModel;
import org.thoughtcrime.securesms.keyboard.emoji.KeyboardPageSearchView;
import org.thoughtcrime.securesms.reactions.edit.EditReactionsActivity;
import org.thoughtcrime.securesms.util.LifecycleDisposable;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel;

import java.util.Optional;

import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;

public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomSheetDialogFragment implements EmojiEventListener,
                                                                                                           EmojiPageViewGridAdapter.VariationSelectorListener
{

  private static final String REACTION_STORAGE_KEY = "reactions_recent_emoji";
  private static final String ABOUT_STORAGE_KEY    = TextSecurePreferences.RECENT_STORAGE_KEY;

  private static final String ARG_MESSAGE_ID = "arg_message_id";
  private static final String ARG_IS_MMS     = "arg_is_mms";
  private static final String ARG_START_PAGE = "arg_start_page";
  private static final String ARG_SHADOWS    = "arg_shadows";
  private static final String ARG_RECENT_KEY = "arg_recent_key";
  private static final String ARG_EDIT       = "arg_edit";

  private ReactWithAnyEmojiViewModel viewModel;
  private Callback                   callback;
  private EmojiPageView              emojiPageView;
  private KeyboardPageSearchView     search;
  private View                       tabBar;

  private final LifecycleDisposable disposables = new LifecycleDisposable();

  private final UpdateCategorySelectionOnScroll categoryUpdateOnScroll = new UpdateCategorySelectionOnScroll();

  public static DialogFragment createForStory() {
    DialogFragment fragment = new ReactWithAnyEmojiBottomSheetDialogFragment();
    Bundle         args     = new Bundle();

    args.putLong(ARG_MESSAGE_ID, -1);
    args.putBoolean(ARG_IS_MMS, false);
    args.putInt(ARG_START_PAGE, -1);
    args.putString(ARG_RECENT_KEY, REACTION_STORAGE_KEY);
    args.putBoolean(ARG_EDIT, false);
    fragment.setArguments(args);

    return fragment;
  }

  public static DialogFragment createForMessageRecord(@NonNull MessageRecord messageRecord, int startingPage) {
    DialogFragment fragment = new ReactWithAnyEmojiBottomSheetDialogFragment();
    Bundle         args     = new Bundle();

    args.putLong(ARG_MESSAGE_ID, messageRecord.getId());
    args.putBoolean(ARG_IS_MMS, messageRecord.isMms());
    args.putInt(ARG_START_PAGE, startingPage);
    args.putString(ARG_RECENT_KEY, REACTION_STORAGE_KEY);
    args.putBoolean(ARG_EDIT, true);
    fragment.setArguments(args);

    return fragment;
  }

  public static DialogFragment createForAboutSelection() {
    DialogFragment fragment = new ReactWithAnyEmojiBottomSheetDialogFragment();
    Bundle         args     = new Bundle();

    args.putLong(ARG_MESSAGE_ID, -1);
    args.putBoolean(ARG_IS_MMS, false);
    args.putInt(ARG_START_PAGE, -1);
    args.putString(ARG_RECENT_KEY, ABOUT_STORAGE_KEY);
    fragment.setArguments(args);

    return fragment;
  }

  public static DialogFragment createForEditReactions() {
    DialogFragment fragment = new ReactWithAnyEmojiBottomSheetDialogFragment();
    Bundle         args     = new Bundle();

    args.putLong(ARG_MESSAGE_ID, -1);
    args.putBoolean(ARG_IS_MMS, false);
    args.putInt(ARG_START_PAGE, -1);
    args.putBoolean(ARG_SHADOWS, false);
    args.putString(ARG_RECENT_KEY, REACTION_STORAGE_KEY);
    fragment.setArguments(args);

    return fragment;
  }

  @Override
  public void onAttach(@NonNull Context context) {
    super.onAttach(context);

    if (getParentFragment() instanceof Callback) {
      callback = (Callback) getParentFragment();
    } else {
      callback = (Callback) context;
    }
  }

  @Override
  public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setStyle(DialogFragment.STYLE_NORMAL, R.style.Widget_Signal_ReactWithAny);
  }

  @Override
  public @NonNull Dialog onCreateDialog(Bundle savedInstanceState) {
    BottomSheetDialog dialog = (BottomSheetDialog) super.onCreateDialog(savedInstanceState);
    dialog.getBehavior().setPeekHeight((int) (getResources().getDisplayMetrics().heightPixels * 0.50));

    ShapeAppearanceModel shapeAppearanceModel = ShapeAppearanceModel.builder()
                                                                    .setTopLeftCorner(CornerFamily.ROUNDED, ViewUtil.dpToPx(requireContext(), 18))
                                                                    .setTopRightCorner(CornerFamily.ROUNDED, ViewUtil.dpToPx(requireContext(), 18))
                                                                    .build();

    MaterialShapeDrawable dialogBackground = new MaterialShapeDrawable(shapeAppearanceModel);

    dialogBackground.setTint(ContextCompat.getColor(requireContext(), R.color.react_with_any_background));

    dialog.getBehavior().addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
      @Override
      public void onStateChanged(@NonNull View bottomSheet, int newState) {
        if (bottomSheet.getBackground() != dialogBackground) {
          ViewCompat.setBackground(bottomSheet, dialogBackground);
        }
      }

      @Override
      public void onSlide(@NonNull View bottomSheet, float slideOffset) { }
    });

    boolean shadows = requireArguments().getBoolean(ARG_SHADOWS, true);
    if (!shadows) {
      Window window = dialog.getWindow();
      if (window != null) {
        window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
      }
    }

    return dialog;
  }

  @Override
  public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    return inflater.inflate(R.layout.react_with_any_emoji_bottom_sheet_dialog_fragment, container, false);
  }

  @Override
  public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    disposables.bindTo(getViewLifecycleOwner());

    emojiPageView = view.findViewById(R.id.react_with_any_emoji_page_view);
    emojiPageView.initialize(this, this, true);
    emojiPageView.addOnScrollListener(categoryUpdateOnScroll);

    search = view.findViewById(R.id.react_with_any_emoji_search);

    initializeViewModel();

    EmojiKeyboardPageCategoriesAdapter categoriesAdapter = new EmojiKeyboardPageCategoriesAdapter(key -> {
      scrollTo(key);
      viewModel.selectPage(key);
    });

    FrameLayout container = requireDialog().findViewById(R.id.container);
    tabBar = LayoutInflater.from(requireContext())
                           .inflate(R.layout.react_with_any_emoji_tabs,
                                    container,
                                    false);
    RecyclerView categoriesRecycler = tabBar.findViewById(R.id.emoji_categories_recycler);
    categoriesRecycler.setAdapter(categoriesAdapter);

    if (requireArguments().getBoolean(ARG_EDIT, false)) {
      View customizeReactions = tabBar.findViewById(R.id.customize_reactions_frame);
      customizeReactions.setVisibility(View.VISIBLE);
      customizeReactions.setOnClickListener(v -> startActivity(new Intent(requireContext(), EditReactionsActivity.class)));
    }

    container.addView(tabBar);

    emojiPageView.addOnScrollListener(new TopAndBottomShadowHelper(requireView().findViewById(R.id.react_with_any_emoji_top_shadow),
                                                                   tabBar.findViewById(R.id.react_with_any_emoji_bottom_shadow)));

    disposables.add(viewModel.getEmojiList().subscribe(pages -> emojiPageView.setList(pages, null)));
    disposables.add(viewModel.getCategories().subscribe(categoriesAdapter::submitList));
    disposables.add(viewModel.getSelectedKey().subscribe(key -> categoriesRecycler.post(() -> {
      int index = categoriesAdapter.indexOfFirst(EmojiKeyboardPageCategoryMappingModel.class, m -> m.getKey().equals(key));

      if (index != -1) {
        categoriesRecycler.smoothScrollToPosition(index);
      }
    })));

    search.setCallbacks(new SearchCallbacks());
  }

  private void scrollTo(@NonNull String key) {
    if (emojiPageView.getAdapter() != null) {
      int index = emojiPageView.getAdapter().indexOfFirst(EmojiPageViewGridAdapter.EmojiHeader.class, m -> m.getKey().equals(key));

      if (index != -1) {
        ((BottomSheetDialog) requireDialog()).getBehavior().setState(BottomSheetBehavior.STATE_EXPANDED);
        categoryUpdateOnScroll.startAutoScrolling();
        emojiPageView.smoothScrollToPositionTop(index);
      }
    }
  }

  @Override
  public void onDestroyView() {
    super.onDestroyView();
    LoaderManager.getInstance(requireActivity()).destroyLoader((int) requireArguments().getLong(ARG_MESSAGE_ID));
  }

  @Override
  public void onDismiss(@NonNull DialogInterface dialog) {
    super.onDismiss(dialog);

    callback.onReactWithAnyEmojiDialogDismissed();
  }

  private void initializeViewModel() {
    Bundle                             args       = requireArguments();
    ReactWithAnyEmojiRepository        repository = new ReactWithAnyEmojiRepository(requireContext(), args.getString(ARG_RECENT_KEY, REACTION_STORAGE_KEY));
    ReactWithAnyEmojiViewModel.Factory factory    = new ReactWithAnyEmojiViewModel.Factory(repository, args.getLong(ARG_MESSAGE_ID), args.getBoolean(ARG_IS_MMS));

    viewModel = ViewModelProviders.of(this, factory).get(ReactWithAnyEmojiViewModel.class);
  }

  @Override
  public void onEmojiSelected(String emoji) {
    viewModel.onEmojiSelected(emoji);
    callback.onReactWithAnyEmojiSelected(emoji);
    dismiss();
  }

  @Override
  public void onKeyEvent(KeyEvent keyEvent) {
  }

  @Override
  public void onVariationSelectorStateChanged(boolean open) { }

  public interface Callback {
    void onReactWithAnyEmojiDialogDismissed();

    void onReactWithAnyEmojiSelected(@NonNull String emoji);
  }

  private class UpdateCategorySelectionOnScroll extends RecyclerView.OnScrollListener {

    private boolean doneScrolling = true;

    public void startAutoScrolling() {
      doneScrolling = false;
    }

    @Override
    public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
      if (newState == SCROLL_STATE_IDLE && !doneScrolling) {
        doneScrolling = true;
        onScrolled(recyclerView, 0, 0);
      }
    }

    @Override
    public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
      if (doneScrolling && recyclerView.getLayoutManager() != null && emojiPageView.getAdapter() != null) {
        LinearLayoutManager       layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
        int                       index         = layoutManager.findFirstCompletelyVisibleItemPosition();
        Optional<MappingModel<?>> item          = emojiPageView.getAdapter().getModel(index);
        if (item.isPresent() && item.get() instanceof EmojiPageViewGridAdapter.HasKey) {
          viewModel.selectPage(((EmojiPageViewGridAdapter.HasKey) item.get()).getKey());
        }
      }
    }
  }

  private class SearchCallbacks implements KeyboardPageSearchView.Callbacks {
    @Override
    public void onQueryChanged(@NonNull String query) {
      boolean hasQuery = !TextUtils.isEmpty(query);
      search.enableBackNavigation(hasQuery);
      if (hasQuery) {
        ViewUtil.fadeOut(tabBar, 250, View.INVISIBLE);
      } else {
        ViewUtil.fadeIn(tabBar, 250);
      }
      viewModel.onQueryChanged(query);
    }

    @Override
    public void onNavigationClicked() {
      search.clearQuery();
      search.clearFocus();
      ViewUtil.hideKeyboard(requireContext(), requireView());
    }

    @Override
    public void onFocusGained() {
      ((BottomSheetDialog) requireDialog()).getBehavior().setState(BottomSheetBehavior.STATE_EXPANDED);
    }

    @Override
    public void onClicked() { }

    @Override
    public void onFocusLost() { }
  }
}
