package chat.rocket.android.fragment.chatroom;

import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v13.view.inputmethod.InputConnectionCompat;
import android.support.v13.view.inputmethod.InputContentInfoCompat;
import android.support.v4.app.DialogFragment;
import android.support.v4.os.BuildCompat;
import android.support.v4.util.Pair;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v4.widget.SlidingPaneLayout;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;

import chat.rocket.core.models.User;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.R;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.fragment.chatroom.dialog.FileUploadProgressDialogFragment;
import chat.rocket.android.fragment.chatroom.dialog.MessageOptionsDialogFragment;
import chat.rocket.android.fragment.chatroom.dialog.UsersOfRoomDialogFragment;
import chat.rocket.android.helper.AbsoluteUrlHelper;
import chat.rocket.android.helper.FileUploadHelper;
import chat.rocket.android.helper.LoadMoreScrollListener;
import chat.rocket.android.helper.Logger;
import chat.rocket.android.helper.OnBackPressListener;
import chat.rocket.android.helper.RecyclerViewAutoScrollManager;
import chat.rocket.android.helper.RecyclerViewScrolledToBottomListener;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.layouthelper.chatroom.AbstractNewMessageIndicatorManager;
import chat.rocket.android.layouthelper.chatroom.MessageFormManager;
import chat.rocket.android.layouthelper.chatroom.MessageListAdapter;
import chat.rocket.android.layouthelper.chatroom.ModelListAdapter;
import chat.rocket.android.layouthelper.chatroom.PairedMessage;
import chat.rocket.android.layouthelper.extra_action.AbstractExtraActionItem;
import chat.rocket.android.layouthelper.extra_action.MessageExtraActionBehavior;
import chat.rocket.android.layouthelper.extra_action.upload.AbstractUploadActionItem;
import chat.rocket.android.layouthelper.extra_action.upload.AudioUploadActionItem;
import chat.rocket.android.layouthelper.extra_action.upload.ImageUploadActionItem;
import chat.rocket.android.layouthelper.extra_action.upload.VideoUploadActionItem;
import chat.rocket.android.log.RCLog;
import chat.rocket.android.renderer.RocketChatUserStatusProvider;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.android.service.temp.DeafultTempSpotlightRoomCaller;
import chat.rocket.android.service.temp.DefaultTempSpotlightUserCaller;
import chat.rocket.android.widget.internal.ExtraActionPickerDialogFragment;
import chat.rocket.android.widget.message.MessageFormLayout;
import chat.rocket.android.widget.message.autocomplete.AutocompleteManager;
import chat.rocket.android.widget.message.autocomplete.channel.ChannelSource;
import chat.rocket.android.widget.message.autocomplete.user.UserSource;
import chat.rocket.core.interactors.AutocompleteChannelInteractor;
import chat.rocket.core.interactors.AutocompleteUserInteractor;
import chat.rocket.core.interactors.MessageInteractor;
import chat.rocket.core.interactors.SessionInteractor;
import chat.rocket.core.models.Message;
import chat.rocket.core.models.Room;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.repositories.RealmMessageRepository;
import chat.rocket.persistence.realm.repositories.RealmRoomRepository;
import chat.rocket.persistence.realm.repositories.RealmServerInfoRepository;
import chat.rocket.persistence.realm.repositories.RealmSessionRepository;
import chat.rocket.persistence.realm.repositories.RealmSpotlightRoomRepository;
import chat.rocket.persistence.realm.repositories.RealmSpotlightUserRepository;
import chat.rocket.persistence.realm.repositories.RealmUserRepository;

import com.hadisatrio.optional.Optional;
import com.jakewharton.rxbinding2.support.v4.widget.RxDrawerLayout;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import permissions.dispatcher.NeedsPermission;
import permissions.dispatcher.RuntimePermissions;

/**
 * Chat room screen.
 */
@RuntimePermissions
public class RoomFragment extends AbstractChatRoomFragment implements
        OnBackPressListener,
        ExtraActionPickerDialogFragment.Callback,
        ModelListAdapter.OnItemClickListener<PairedMessage>,
        ModelListAdapter.OnItemLongClickListener<PairedMessage>,
        RoomContract.View {

  private static final int DIALOG_ID = 1;
  private static final String HOSTNAME = "hostname";
  private static final String ROOM_ID = "roomId";

  private String hostname;
  private String roomId;
  private LoadMoreScrollListener scrollListener;
  private MessageFormManager messageFormManager;
  private RecyclerView messageRecyclerView;
  private RecyclerViewAutoScrollManager recyclerViewAutoScrollManager;
  protected AbstractNewMessageIndicatorManager newMessageIndicatorManager;
  protected Snackbar unreadIndicator;
  private boolean previousUnreadMessageExists;
  private MessageListAdapter messageListAdapter;
  private AutocompleteManager autocompleteManager;

  private List<AbstractExtraActionItem> extraActionItems;

  private CompositeDisposable compositeDisposable = new CompositeDisposable();

  protected RoomContract.Presenter presenter;

  private RealmRoomRepository roomRepository;
  private RealmUserRepository userRepository;
  private MethodCallHelper methodCallHelper;
  private AbsoluteUrlHelper absoluteUrlHelper;

  private Message edittingMessage = null;

  public RoomFragment() {}

  /**
   * create fragment with roomId.
   */
  public static RoomFragment create(String hostname, String roomId) {
    Bundle args = new Bundle();
    args.putString(HOSTNAME, hostname);
    args.putString(ROOM_ID, roomId);

    RoomFragment fragment = new RoomFragment();
    fragment.setArguments(args);

    return fragment;
  }

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

    Bundle args = getArguments();
    hostname = args.getString(HOSTNAME);
    roomId = args.getString(ROOM_ID);

    roomRepository = new RealmRoomRepository(hostname);

    MessageInteractor messageInteractor = new MessageInteractor(
        new RealmMessageRepository(hostname),
        roomRepository
    );

    userRepository = new RealmUserRepository(hostname);

    absoluteUrlHelper = new AbsoluteUrlHelper(
        hostname,
        new RealmServerInfoRepository(),
        userRepository,
        new SessionInteractor(new RealmSessionRepository(hostname))
    );

    methodCallHelper = new MethodCallHelper(getContext(), hostname);

    presenter = new RoomPresenter(
        roomId,
        userRepository,
        messageInteractor,
        roomRepository,
        absoluteUrlHelper,
        methodCallHelper,
        ConnectivityManager.getInstance(getContext())
    );

    if (savedInstanceState == null) {
      presenter.loadMessages();
    }
  }

  @Override
  protected int getLayout() {
    return R.layout.fragment_room;
  }

  @Override
  protected void onSetupView() {
    messageRecyclerView = rootView.findViewById(R.id.messageRecyclerView);

    messageListAdapter = new MessageListAdapter(getContext(), hostname);
    messageRecyclerView.setAdapter(messageListAdapter);
    messageListAdapter.setOnItemClickListener(this);
    messageListAdapter.setOnItemLongClickListener(this);

    LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, true);
    messageRecyclerView.setLayoutManager(linearLayoutManager);

    recyclerViewAutoScrollManager = new RecyclerViewAutoScrollManager(linearLayoutManager) {
      @Override
      protected void onAutoScrollMissed() {
        if (newMessageIndicatorManager != null) {
          presenter.onUnreadCount();
        }
      }
    };
    messageListAdapter.registerAdapterDataObserver(recyclerViewAutoScrollManager);

    scrollListener = new LoadMoreScrollListener(linearLayoutManager, 40) {
      @Override
      public void requestMoreItem() {
        presenter.loadMoreMessages();
      }
    };
    messageRecyclerView.addOnScrollListener(scrollListener);
    messageRecyclerView.addOnScrollListener(new RecyclerViewScrolledToBottomListener(linearLayoutManager, 1, this::markAsReadIfNeeded));

    newMessageIndicatorManager = new AbstractNewMessageIndicatorManager() {
      @Override
      protected void onShowIndicator(int count, boolean onlyAlreadyShown) {
        if ((onlyAlreadyShown && unreadIndicator != null && unreadIndicator.isShown()) || !onlyAlreadyShown) {
          unreadIndicator = getUnreadCountIndicatorView(count);
          unreadIndicator.show();
        }
      }

      @Override
      protected void onHideIndicator() {
        if (unreadIndicator != null && unreadIndicator.isShown()) {
          unreadIndicator.dismiss();
        }
      }
    };

    setupSideMenu();
    setupMessageComposer();
    setupMessageActions();
  }

  private void setupMessageActions() {
    extraActionItems = new ArrayList<>(3); // fixed number as of now
    extraActionItems.add(new ImageUploadActionItem());
    extraActionItems.add(new AudioUploadActionItem());
    extraActionItems.add(new VideoUploadActionItem());
  }

  private void scrollToLatestMessage() {
    if (messageRecyclerView != null)
      messageRecyclerView.scrollToPosition(0);
  }

  protected Snackbar getUnreadCountIndicatorView(int count) {
    // TODO: replace with another custom View widget, not to hide message composer.
    final String caption = getResources().getQuantityString(
        R.plurals.fmt_dialog_view_latest_message_title, count, count);

    return Snackbar.make(rootView, caption, Snackbar.LENGTH_LONG)
        .setAction(R.string.dialog_view_latest_message_action, view -> scrollToLatestMessage());
  }

  @Override
  public void onDestroyView() {
    RecyclerView.Adapter adapter = messageRecyclerView.getAdapter();
      if (adapter != null)
        adapter.unregisterAdapterDataObserver(recyclerViewAutoScrollManager);

    compositeDisposable.clear();

    if (autocompleteManager != null) {
      autocompleteManager.dispose();
      autocompleteManager = null;
    }

    super.onDestroyView();
  }

  @Override
  public void onItemClick(PairedMessage pairedMessage) {
    presenter.onMessageSelected(pairedMessage.target);
  }

  @Override
  public boolean onItemLongClick(PairedMessage pairedMessage) {
    MessageOptionsDialogFragment messageOptionsDialogFragment = MessageOptionsDialogFragment
        .create(pairedMessage.target);

    messageOptionsDialogFragment.setOnMessageOptionSelectedListener(message -> {
      messageOptionsDialogFragment.dismiss();
      onEditMessage(message);
    });

    messageOptionsDialogFragment.show(getChildFragmentManager(), "MessageOptionsDialogFragment");
    return true;
  }

  private void setupSideMenu() {
    View sideMenu = rootView.findViewById(R.id.room_side_menu);
    sideMenu.findViewById(R.id.btn_users).setOnClickListener(view -> {
      UsersOfRoomDialogFragment.create(roomId, hostname)
          .show(getFragmentManager(), "UsersOfRoomDialogFragment");
      closeSideMenuIfNeeded();
    });

    DrawerLayout drawerLayout = rootView.findViewById(R.id.drawer_layout);
    SlidingPaneLayout pane = getActivity().findViewById(R.id.sliding_pane);
    if (drawerLayout != null && pane != null) {
      compositeDisposable.add(RxDrawerLayout.drawerOpen(drawerLayout, GravityCompat.END)
          .compose(bindToLifecycle())
          .subscribe(
              opened -> {
                try {
                  Field fieldSlidable = pane.getClass().getDeclaredField("mCanSlide");
                  fieldSlidable.setAccessible(true);
                  fieldSlidable.setBoolean(pane, !opened);
                } catch (Exception exception) {
                  RCLog.w(exception);
                }
              },
              Logger::report
          )
      );
    }
  }

  private boolean closeSideMenuIfNeeded() {
    DrawerLayout drawerLayout = (DrawerLayout) rootView.findViewById(R.id.drawer_layout);
    if (drawerLayout != null && drawerLayout.isDrawerOpen(GravityCompat.END)) {
      drawerLayout.closeDrawer(GravityCompat.END);
      return true;
    }
    return false;
  }

  private void setupMessageComposer() {
    final MessageFormLayout messageFormLayout = (MessageFormLayout) rootView.findViewById(R.id.messageComposer);
    messageFormManager = new MessageFormManager(messageFormLayout, this::showExtraActionSelectionDialog);
    messageFormManager.setSendMessageCallback(this::sendMessage);
    messageFormLayout.setEditTextCommitContentListener(this::onCommitContent);

    autocompleteManager = new AutocompleteManager((ViewGroup) rootView.findViewById(R.id.messageListRelativeLayout));

    autocompleteManager.registerSource(
        new ChannelSource(
            new AutocompleteChannelInteractor(
                roomRepository,
                new RealmSpotlightRoomRepository(hostname),
                new DeafultTempSpotlightRoomCaller(methodCallHelper)
            ),
            AndroidSchedulers.from(BackgroundLooper.get()),
            AndroidSchedulers.mainThread()
        )
    );

    Disposable disposable = Single.zip(
        absoluteUrlHelper.getRocketChatAbsoluteUrl(),
        roomRepository.getById(roomId).first(Optional.absent()),
        Pair::create
    )
        .subscribe(
            pair -> {
              if (pair.first.isPresent() && pair.second.isPresent()) {
                autocompleteManager.registerSource(
                    new UserSource(
                        new AutocompleteUserInteractor(
                            pair.second.get(),
                            userRepository,
                            new RealmMessageRepository(hostname),
                            new RealmSpotlightUserRepository(hostname),
                            new DefaultTempSpotlightUserCaller(methodCallHelper)
                        ),
                        pair.first.get(),
                        RocketChatUserStatusProvider.INSTANCE,
                        AndroidSchedulers.from(BackgroundLooper.get()),
                        AndroidSchedulers.mainThread()
                    )
                );
              }
            },
            throwable -> {
            }
        );

    compositeDisposable.add(disposable);

    autocompleteManager.bindTo(
        messageFormLayout.getEditText(),
        messageFormLayout
    );
  }

  @Override
  public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode != AbstractUploadActionItem.RC_UPL || resultCode != Activity.RESULT_OK) {
      return;
    }

    if (data == null || data.getData() == null) {
      return;
    }

    uploadFile(data.getData());
  }

  private void uploadFile(Uri uri) {
    String uplId = new FileUploadHelper(getContext(), RealmStore.get(hostname))
        .requestUploading(roomId, uri);
    if (!TextUtils.isEmpty(uplId)) {
      FileUploadProgressDialogFragment.create(hostname, roomId, uplId)
          .show(getFragmentManager(), "FileUploadProgressDialogFragment");
    } else {
      // show error.
    }
  }

  private void markAsReadIfNeeded() {
    presenter.onMarkAsRead();
  }

  @Override
  public void onResume() {
    super.onResume();
    presenter.bindView(this);
    closeSideMenuIfNeeded();
  }

  @Override
  public void onPause() {
    presenter.release();
    super.onPause();
  }

  private void showExtraActionSelectionDialog() {
    final DialogFragment fragment = ExtraActionPickerDialogFragment
        .create(new ArrayList<>(extraActionItems));
    fragment.setTargetFragment(this, DIALOG_ID);
    fragment.show(getFragmentManager(), "ExtraActionPickerDialogFragment");
  }

  @Override
  public void onItemSelected(int itemId) {
    for (AbstractExtraActionItem extraActionItem : extraActionItems) {
      if (extraActionItem.getItemId() == itemId) {
        RoomFragmentPermissionsDispatcher.onExtraActionSelectedWithCheck(RoomFragment.this, extraActionItem);
        return;
      }
    }
  }

  @Override
  public boolean onBackPressed() {
    if (edittingMessage != null) {
      edittingMessage = null;
      messageFormManager.clearComposingText();
      return true;
    }
    return closeSideMenuIfNeeded();
  }

  @Override
  public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                         @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    RoomFragmentPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
  }

  @NeedsPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
  protected void onExtraActionSelected(MessageExtraActionBehavior action) {
    action.handleItemSelectedOnFragment(RoomFragment.this);
  }

  private boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags,
                                  Bundle opts, String[] supportedMimeTypes) {
    boolean supported = false;
    for (final String mimeType : supportedMimeTypes) {
      if (inputContentInfo.getDescription().hasMimeType(mimeType)) {
        supported = true;
        break;
      }
    }

    if (!supported) {
      return false;
    }

    if (BuildCompat.isAtLeastNMR1()
        && (flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
      try {
        inputContentInfo.requestPermission();
      } catch (Exception e) {
        return false;
      }
    }

    Uri linkUri = inputContentInfo.getLinkUri();
    if (linkUri == null) {
      return false;
    }

    sendMessage(linkUri.toString());

    try {
      inputContentInfo.releasePermission();
    } catch (Exception e) {
    }

    return true;
  }

  private void sendMessage(String messageText) {
    if (edittingMessage == null) {
      presenter.sendMessage(messageText);
    } else {
      presenter.updateMessage(edittingMessage, messageText);
    }
  }

  @Override
  public void setupWith(RocketChatAbsoluteUrl rocketChatAbsoluteUrl) {
    messageListAdapter.setAbsoluteUrl(rocketChatAbsoluteUrl);
  }

  @Override
  public void render(Room room) {
    setToolbarTitle(room.getName());

    boolean unreadMessageExists = room.isAlert();
    if (newMessageIndicatorManager != null && previousUnreadMessageExists && !unreadMessageExists) {
      newMessageIndicatorManager.reset();
    }
    previousUnreadMessageExists = unreadMessageExists;

    if (room.isChannel()) {
      showToolbarPublicChannelIcon();
      return;
    }

    if (room.isPrivate()) {
      showToolbarPrivateChannelIcon();
    }
  }

  @Override
  public void showUserStatus(User user) {
    showToolbarUserStatuslIcon(user.getStatus());
  }

  @Override
  public void updateHistoryState(boolean hasNext, boolean isLoaded) {
    if (messageRecyclerView == null || !(messageRecyclerView.getAdapter() instanceof MessageListAdapter)) {
      return;
    }

    MessageListAdapter adapter = (MessageListAdapter) messageRecyclerView.getAdapter();
    if (isLoaded) {
      scrollListener.setLoadingDone();
    }
    adapter.updateFooter(hasNext, isLoaded);
  }

  @Override
  public void onMessageSendSuccessfully() {
    scrollToLatestMessage();
    messageFormManager.onMessageSend();
    edittingMessage = null;
  }

  @Override
  public void showUnreadCount(int count) {
    newMessageIndicatorManager.updateNewMessageCount(count);
  }

  @Override
  public void showMessages(List<Message> messages) {
    if (messageListAdapter == null) {
      return;
    }
    messageListAdapter.updateData(messages);
  }

  @Override
  public void showMessageSendFailure(Message message) {
    new AlertDialog.Builder(getContext())
        .setPositiveButton(R.string.resend,
            (dialog, which) -> presenter.resendMessage(message))
        .setNegativeButton(android.R.string.cancel, null)
        .setNeutralButton(R.string.discard,
            (dialog, which) -> presenter.deleteMessage(message))
        .show();
  }

  @Override
  public void autoloadImages() {
    messageListAdapter.setAutoloadImages(true);
  }

  @Override
  public void manualLoadImages() {
    messageListAdapter.setAutoloadImages(false);
  }

  private void onEditMessage(Message message) {
    edittingMessage = message;
    messageFormManager.setEditMessage(message.getMessage());
  }
}