package org.thoughtcrime.securesms.mediaoverview;

import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ActionMode;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.Observer;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.RecyclerView;

import com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager;

import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.MediaPreviewActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.components.menu.ActionItem;
import org.thoughtcrime.securesms.components.menu.SignalBottomActionBar;
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController;
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
import org.thoughtcrime.securesms.database.MediaDatabase;
import org.thoughtcrime.securesms.database.loaders.GroupedThreadMediaLoader;
import org.thoughtcrime.securesms.database.loaders.MediaLoader;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;

import java.util.Arrays;

public final class MediaOverviewPageFragment extends Fragment
  implements MediaGalleryAllAdapter.ItemClickListener,
             MediaGalleryAllAdapter.AudioItemListener,
             LoaderManager.LoaderCallbacks<GroupedThreadMediaLoader.GroupedThreadMedia>
{

  private static final String TAG = Log.tag(MediaOverviewPageFragment.class);

  private static final String THREAD_ID_EXTRA  = "thread_id";
  private static final String MEDIA_TYPE_EXTRA = "media_type";
  private static final String GRID_MODE        = "grid_mode";

  private final ActionModeCallback            actionModeCallback = new ActionModeCallback();
  private       MediaDatabase.Sorting         sorting            = MediaDatabase.Sorting.Newest;
  private       MediaLoader.MediaType         mediaType          = MediaLoader.MediaType.GALLERY;
  private       long                          threadId;
  private       TextView                      noMedia;
  private       RecyclerView                  recyclerView;
  private       StickyHeaderGridLayoutManager gridManager;
  private       ActionMode                    actionMode;
  private       boolean                       detail;
  private       MediaGalleryAllAdapter        adapter;
  private       GridMode                      gridMode;
  private       VoiceNoteMediaController      voiceNoteMediaController;
  private       SignalBottomActionBar         bottomActionBar;

  public static @NonNull Fragment newInstance(long threadId,
                                              @NonNull MediaLoader.MediaType mediaType,
                                              @NonNull GridMode gridMode)
  {
    MediaOverviewPageFragment mediaOverviewAllFragment = new MediaOverviewPageFragment();
    Bundle args = new Bundle();
    args.putLong(THREAD_ID_EXTRA, threadId);
    args.putInt(MEDIA_TYPE_EXTRA, mediaType.ordinal());
    args.putInt(GRID_MODE, gridMode.ordinal());
    mediaOverviewAllFragment.setArguments(args);

    return mediaOverviewAllFragment;
  }

  @Override
  public void onCreate(Bundle bundle) {
    super.onCreate(bundle);

    Bundle arguments = requireArguments();

    threadId  = arguments.getLong(THREAD_ID_EXTRA, Long.MIN_VALUE);
    mediaType = MediaLoader.MediaType.values()[arguments.getInt(MEDIA_TYPE_EXTRA)];
    gridMode  = GridMode.values()[arguments.getInt(GRID_MODE)];

    if (threadId == Long.MIN_VALUE) throw new AssertionError();

    LoaderManager.getInstance(this).initLoader(0, null, this);
  }

  @Override
  public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    voiceNoteMediaController = new VoiceNoteMediaController((AppCompatActivity) requireActivity());
  }

  @Override
  public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    Context context = requireContext();
    View    view    = inflater.inflate(R.layout.media_overview_page_fragment, container, false);
    int     spans   = getResources().getInteger(R.integer.media_overview_cols);

    this.recyclerView    = view.findViewById(R.id.media_grid);
    this.noMedia         = view.findViewById(R.id.no_images);
    this.bottomActionBar = view.findViewById(R.id.media_overview_bottom_action_bar);
    this.gridManager     = new StickyHeaderGridLayoutManager(spans);

    this.adapter = new MediaGalleryAllAdapter(context,
                                              GlideApp.with(this),
                                              new GroupedThreadMediaLoader.EmptyGroupedThreadMedia(),
                                              this,
                                              this,
                                              sorting.isRelatedToFileSize(),
                                              threadId == MediaDatabase.ALL_THREADS);
    this.recyclerView.setAdapter(adapter);
    this.recyclerView.setLayoutManager(gridManager);
    this.recyclerView.setHasFixedSize(true);
    this.recyclerView.addItemDecoration(new MediaGridDividerDecoration(spans, ViewUtil.dpToPx(4), adapter));

    MediaOverviewViewModel viewModel = MediaOverviewViewModel.getMediaOverviewViewModel(requireActivity());

    viewModel.getSortOrder()
      .observe(getViewLifecycleOwner(), sorting -> {
        if (sorting != null) {
          this.sorting = sorting;
          adapter.setShowFileSizes(sorting.isRelatedToFileSize());
          LoaderManager.getInstance(this).restartLoader(0, null, this);
          updateMultiSelect();
        }
      });

    if (gridMode == GridMode.FOLLOW_MODEL) {
      viewModel.getDetailLayout()
               .observe(getViewLifecycleOwner(), this::setDetailView);
    } else {
      setDetailView(gridMode == GridMode.FIXED_DETAIL);
    }

    return view;
  }

  private void setDetailView(boolean detail) {
    this.detail = detail;
    adapter.setDetailView(detail);
    refreshLayoutManager();
    updateMultiSelect();
  }

  @Override
  public void onConfigurationChanged(@NonNull Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    if (gridManager != null) {
      refreshLayoutManager();
    }
  }

  private void refreshLayoutManager() {
    this.gridManager = new StickyHeaderGridLayoutManager(detail ? 1 : getResources().getInteger(R.integer.media_overview_cols));
    this.recyclerView.setLayoutManager(gridManager);
  }

  @Override
  public @NonNull Loader<GroupedThreadMediaLoader.GroupedThreadMedia> onCreateLoader(int i, Bundle bundle) {
    return new GroupedThreadMediaLoader(requireContext(), threadId, mediaType, sorting);
  }

  @Override
  public void onLoadFinished(@NonNull Loader<GroupedThreadMediaLoader.GroupedThreadMedia> loader, GroupedThreadMediaLoader.GroupedThreadMedia groupedThreadMedia) {
    ((MediaGalleryAllAdapter) recyclerView.getAdapter()).setMedia(groupedThreadMedia);
    ((MediaGalleryAllAdapter) recyclerView.getAdapter()).notifyAllSectionsDataSetChanged();

    noMedia.setVisibility(recyclerView.getAdapter().getItemCount() > 0 ? View.GONE : View.VISIBLE);
    getActivity().invalidateOptionsMenu();
  }

  @Override
  public void onLoaderReset(@NonNull Loader<GroupedThreadMediaLoader.GroupedThreadMedia> cursorLoader) {
    ((MediaGalleryAllAdapter) recyclerView.getAdapter()).setMedia(new GroupedThreadMediaLoader.EmptyGroupedThreadMedia());
  }

  @Override
  public void onMediaClicked(@NonNull MediaDatabase.MediaRecord mediaRecord) {
    if (actionMode != null) {
      handleMediaMultiSelectClick(mediaRecord);
    } else {
      handleMediaPreviewClick(mediaRecord);
    }
  }

  @Override
  public void onDestroy() {
    super.onDestroy();
    int childCount = recyclerView.getChildCount();
    for (int i = 0; i < childCount; i++) {
      adapter.detach(recyclerView.getChildViewHolder(recyclerView.getChildAt(i)));
    }
  }

  private void handleMediaMultiSelectClick(@NonNull MediaDatabase.MediaRecord mediaRecord) {
    MediaGalleryAllAdapter adapter = getListAdapter();

    adapter.toggleSelection(mediaRecord);
    if (adapter.getSelectedMediaCount() == 0) {
      actionMode.finish();
    } else {
      updateMultiSelect();
    }
  }

  private void handleMediaPreviewClick(@NonNull MediaDatabase.MediaRecord mediaRecord) {
    if (mediaRecord.getAttachment().getUri() == null) {
      return;
    }

    Context context = getContext();
    if (context == null) {
      return;
    }

    DatabaseAttachment attachment = mediaRecord.getAttachment();

    if (MediaUtil.isVideo(attachment) || MediaUtil.isImage(attachment)) {
      Intent intent = new Intent(context, MediaPreviewActivity.class);
      intent.putExtra(MediaPreviewActivity.DATE_EXTRA, mediaRecord.getDate());
      intent.putExtra(MediaPreviewActivity.SIZE_EXTRA, mediaRecord.getAttachment().getSize());
      intent.putExtra(MediaPreviewActivity.THREAD_ID_EXTRA, threadId);
      intent.putExtra(MediaPreviewActivity.LEFT_IS_RECENT_EXTRA, true);
      intent.putExtra(MediaPreviewActivity.HIDE_ALL_MEDIA_EXTRA, true);
      intent.putExtra(MediaPreviewActivity.SHOW_THREAD_EXTRA, threadId == MediaDatabase.ALL_THREADS);
      intent.putExtra(MediaPreviewActivity.SORTING_EXTRA, sorting.ordinal());
      intent.putExtra(MediaPreviewActivity.IS_VIDEO_GIF, attachment.isVideoGif());

      intent.setDataAndType(mediaRecord.getAttachment().getUri(), mediaRecord.getContentType());
      context.startActivity(intent);
    } else {
      if (!MediaUtil.isAudio(attachment)) {
        showFileExternally(context, mediaRecord);
      }
    }
  }

  private static void showFileExternally(@NonNull Context context, @NonNull MediaDatabase.MediaRecord mediaRecord) {
      Uri uri = mediaRecord.getAttachment().getUri();

      Intent intent = new Intent(Intent.ACTION_VIEW);
      intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
      intent.setDataAndType(PartAuthority.getAttachmentPublicUri(uri), mediaRecord.getContentType());
      try {
        context.startActivity(intent);
      } catch (ActivityNotFoundException e) {
        Log.w(TAG, "No activity existed to view the media.");
        Toast.makeText(context, R.string.ConversationItem_unable_to_open_media, Toast.LENGTH_LONG).show();
      }
    }

  @Override
  public void onMediaLongClicked(MediaDatabase.MediaRecord mediaRecord) {
    ((MediaGalleryAllAdapter) recyclerView.getAdapter()).toggleSelection(mediaRecord);

    if (actionMode == null) {
      enterMultiSelect();
    }
  }

  private void handleSelectAllMedia() {
    getListAdapter().selectAllMedia();
    updateMultiSelect();
  }

  private String getActionModeTitle() {
    MediaGalleryAllAdapter adapter       = getListAdapter();
    int                    mediaCount    = adapter.getSelectedMediaCount();
    long                   totalFileSize = adapter.getSelectedMediaTotalFileSize();

    return getResources().getQuantityString(R.plurals.MediaOverviewActivity_d_selected_s,
                                          mediaCount,
                                          mediaCount,
                                          Util.getPrettyFileSize(totalFileSize));
  }

  private MediaGalleryAllAdapter getListAdapter() {
    return (MediaGalleryAllAdapter) recyclerView.getAdapter();
  }

  private void enterMultiSelect() {
    FragmentActivity activity = requireActivity();
    actionMode = ((AppCompatActivity) activity).startSupportActionMode(actionModeCallback);
    ((MediaOverviewActivity) activity).onEnterMultiSelect();
    ViewUtil.animateIn(bottomActionBar, bottomActionBar.getEnterAnimation());
    updateMultiSelect();
  }

  private void exitMultiSelect() {
    actionMode.finish();
    actionMode = null;
    ViewUtil.animateOut(bottomActionBar, bottomActionBar.getExitAnimation());
  }

  private void updateMultiSelect() {
    if (actionMode != null) {
      actionMode.setTitle(getActionModeTitle());

      int selectionCount = getListAdapter().getSectionCount();

      bottomActionBar.setItems(Arrays.asList(
          new ActionItem(R.drawable.ic_save_24, getResources().getQuantityString(R.plurals.MediaOverviewActivity_save_plural, selectionCount), () -> {
            MediaActions.handleSaveMedia(MediaOverviewPageFragment.this,
                                         getListAdapter().getSelectedMedia(),
                                         this::exitMultiSelect);
          }),
          new ActionItem(R.drawable.ic_select_24, getString(R.string.MediaOverviewActivity_select_all), this::handleSelectAllMedia),
          new ActionItem(R.drawable.ic_delete_24, getResources().getQuantityString(R.plurals.MediaOverviewActivity_delete_plural, selectionCount), () -> {
            MediaActions.handleDeleteMedia(requireContext(), getListAdapter().getSelectedMedia());
            exitMultiSelect();
          })
      ));
    }
  }


  @Override
  public void onPlay(@NonNull Uri audioUri, double progress, long messageId) {
    voiceNoteMediaController.startSinglePlayback(audioUri, messageId, progress);
  }

  @Override
  public void onPause(@NonNull Uri audioUri) {
    voiceNoteMediaController.pausePlayback(audioUri);
  }

  @Override
  public void onSeekTo(@NonNull Uri audioUri, double progress) {
    voiceNoteMediaController.seekToPosition(audioUri, progress);
  }

  @Override
  public void onStopAndReset(@NonNull Uri audioUri) {
    voiceNoteMediaController.stopPlaybackAndReset(audioUri);
  }

  @Override
  public void registerPlaybackStateObserver(@NonNull Observer<VoiceNotePlaybackState> observer) {
    voiceNoteMediaController.getVoiceNotePlaybackState().observe(getViewLifecycleOwner(), observer);
  }

  @Override
  public void unregisterPlaybackStateObserver(@NonNull Observer<VoiceNotePlaybackState> observer) {
    voiceNoteMediaController.getVoiceNotePlaybackState().removeObserver(observer);
  }

  private class ActionModeCallback implements ActionMode.Callback {

    @Override
    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
      mode.setTitle(getActionModeTitle());
      return true;
    }

    @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
      return false;
    }

    @Override
    public boolean onActionItemClicked(ActionMode mode, MenuItem menuItem) {
      return false;
    }

    @Override
    public void onDestroyActionMode(ActionMode mode) {
      getListAdapter().clearSelection();

      FragmentActivity activity = requireActivity();
      ((MediaOverviewActivity) activity).onExitMultiSelect();

      exitMultiSelect();
    }
  }

  public enum GridMode {
    FIXED_DETAIL,
    FOLLOW_MODEL
  }
}
