/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * 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.android.messaging.ui.mediapicker;

import android.app.Activity;
import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import androidx.appcompat.app.ActionBar;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.LinearLayout;

import com.android.messaging.Factory;
import com.android.messaging.R;
import com.android.messaging.datamodel.DataModel;
import com.android.messaging.datamodel.binding.Binding;
import com.android.messaging.datamodel.binding.BindingBase;
import com.android.messaging.datamodel.binding.ImmutableBindingRef;
import com.android.messaging.datamodel.data.DraftMessageData;
import com.android.messaging.datamodel.data.MediaPickerData;
import com.android.messaging.datamodel.data.MessagePartData;
import com.android.messaging.datamodel.data.PendingAttachmentData;
import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider;
import com.android.messaging.ui.BugleActionBarActivity;
import com.android.messaging.ui.FixedViewPagerAdapter;
import com.android.messaging.util.AccessibilityUtil;
import com.android.messaging.util.Assert;
import com.android.messaging.util.UiUtils;
import com.google.common.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * Fragment used to select or capture media to be added to the message
 */
public class MediaPicker extends Fragment implements DraftMessageSubscriptionDataProvider {
    /** The listener interface for events from the media picker */
    public interface MediaPickerListener {
        /** Called when the media picker is opened so the host can accommodate the UI */
        void onOpened();

        /**
         * Called when the media picker goes into or leaves full screen mode so the host can
         * accommodate the fullscreen UI
         */
        void onFullScreenChanged(boolean fullScreen);

        /**
         * Called when the user selects one or more items
         * @param items The list of items which were selected
         */
        void onItemsSelected(Collection<MessagePartData> items, boolean dismissMediaPicker);

        /**
         * Called when the user unselects one item.
         */
        void onItemUnselected(MessagePartData item);

        /**
         * Called when the media picker is closed.  Always called immediately after onItemsSelected
         */
        void onDismissed();

        /**
         * Called when media item selection is confirmed in a multi-select action.
         */
        void onConfirmItemSelection();

        /**
         * Called when a pending attachment is added.
         * @param pendingItem the pending attachment data being loaded.
         */
        void onPendingItemAdded(PendingAttachmentData pendingItem);

        /**
         * Called when a new media chooser is selected.
         */
        void onChooserSelected(final int chooserIndex);
    }

    /** The tag used when registering and finding this fragment */
    public static final String FRAGMENT_TAG = "mediapicker";

    // Media type constants that the media picker supports
    public static final int MEDIA_TYPE_DEFAULT     = 0x0000;
    public static final int MEDIA_TYPE_NONE        = 0x0000;
    public static final int MEDIA_TYPE_IMAGE       = 0x0001;
    public static final int MEDIA_TYPE_VIDEO       = 0x0002;
    public static final int MEDIA_TYPE_AUDIO       = 0x0004;
    public static final int MEDIA_TYPE_VCARD       = 0x0008;
    public static final int MEDIA_TYPE_LOCATION    = 0x0010;
    private static final int MEDA_TYPE_INVALID     = 0x0020;
    public static final int MEDIA_TYPE_ALL         = 0xFFFF;

    /** The listener to call when events occur */
    private MediaPickerListener mListener;

    /** The handler used to dispatch calls to the listener */
    private Handler mListenerHandler;

    /** The bit flags of media types supported */
    private int mSupportedMediaTypes;

    /** The list of choosers which could be within the media picker */
    private final MediaChooser[] mChoosers;

    /** The list of currently enabled choosers */
    private final ArrayList<MediaChooser> mEnabledChoosers;

    /** The currently selected chooser */
    private MediaChooser mSelectedChooser;

    /** The main panel that controls the custom layout */
    private MediaPickerPanel mMediaPickerPanel;

    /** The linear layout that holds the icons to select individual chooser tabs */
    private LinearLayout mTabStrip;

    /** The view pager to swap between choosers */
    private ViewPager mViewPager;

    /** The current pager adapter for the view pager */
    private FixedViewPagerAdapter<MediaChooser> mPagerAdapter;

    /** True if the media picker is visible */
    private boolean mOpen;

    /** The theme color to use to make the media picker match the rest of the UI */
    private int mThemeColor;

    @VisibleForTesting
    final Binding<MediaPickerData> mBinding = BindingBase.createBinding(this);

    /** Provides subscription-related data to access per-subscription configurations. */
    private DraftMessageSubscriptionDataProvider mSubscriptionDataProvider;

    /** Provides access to DraftMessageData associated with the current conversation */
    private ImmutableBindingRef<DraftMessageData> mDraftMessageDataModel;

    public MediaPicker() {
        this(Factory.get().getApplicationContext());
    }

    public MediaPicker(final Context context) {
        mBinding.bind(DataModel.get().createMediaPickerData(context));
        mEnabledChoosers = new ArrayList<MediaChooser>();
        mChoosers = new MediaChooser[] {
            new CameraMediaChooser(this),
            new GalleryMediaChooser(this),
            new AudioMediaChooser(this),
            new ContactMediaChooser(this),
        };

        mOpen = false;
        setSupportedMediaTypes(MEDIA_TYPE_ALL);
    }

    private boolean mIsAttached;
    private int mStartingMediaTypeOnAttach = MEDA_TYPE_INVALID;
    private boolean mAnimateOnAttach;

    @Override
    public void onAttach (final Activity activity) {
        super.onAttach(activity);
        mIsAttached = true;
        if (mStartingMediaTypeOnAttach != MEDA_TYPE_INVALID) {
            // open() was previously called. Do the pending open now.
            doOpen(mStartingMediaTypeOnAttach, mAnimateOnAttach);
        }
    }

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding.getData().init(getLoaderManager());
    }

    @Override
    public View onCreateView(
            final LayoutInflater inflater,
            final ViewGroup container,
            final Bundle savedInstanceState) {
        mMediaPickerPanel = (MediaPickerPanel) inflater.inflate(
                R.layout.mediapicker_fragment,
                container,
                false);
        mMediaPickerPanel.setMediaPicker(this);
        mTabStrip = (LinearLayout) mMediaPickerPanel.findViewById(R.id.mediapicker_tabstrip);
        mTabStrip.setBackgroundColor(mThemeColor);
        for (final MediaChooser chooser : mChoosers) {
            chooser.onCreateTabButton(inflater, mTabStrip);
            final boolean enabled = (chooser.getSupportedMediaTypes() & mSupportedMediaTypes) !=
                    MEDIA_TYPE_NONE;
            final ImageButton tabButton = chooser.getTabButton();
            if (tabButton != null) {
                tabButton.setVisibility(enabled ? View.VISIBLE : View.GONE);
                mTabStrip.addView(tabButton);
            }
        }

        mViewPager = (ViewPager) mMediaPickerPanel.findViewById(R.id.mediapicker_view_pager);
        mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(
                    final int position,
                    final float positionOffset,
                    final int positionOffsetPixels) {
            }

            @Override
            public void onPageSelected(int position) {
                // The position returned is relative to if we are in RtL mode. This class never
                // switches the indices of the elements if we are in RtL mode so we need to
                // translate the index back. For example, if the user clicked the item most to the
                // right in RtL mode we would want the index to appear as 0 here, however the
                // position returned would the last possible index.
                if (UiUtils.isRtlMode()) {
                    position = mEnabledChoosers.size() - 1 - position;
                }
                selectChooser(mEnabledChoosers.get(position));
            }

            @Override
            public void onPageScrollStateChanged(final int state) {
            }
        });
        // Camera initialization is expensive, so don't realize offscreen pages if not needed.
        mViewPager.setOffscreenPageLimit(0);
        mViewPager.setAdapter(mPagerAdapter);
        final boolean isTouchExplorationEnabled = AccessibilityUtil.isTouchExplorationEnabled(
                getActivity());
        mMediaPickerPanel.setFullScreenOnly(isTouchExplorationEnabled);
        mMediaPickerPanel.setExpanded(mOpen, true, mEnabledChoosers.indexOf(mSelectedChooser));
        return mMediaPickerPanel;
    }

    @Override
    public void onPause() {
        super.onPause();
        CameraManager.get().onPause();
        for (final MediaChooser chooser : mEnabledChoosers) {
            chooser.onPause();
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        CameraManager.get().onResume();

        for (final MediaChooser chooser : mEnabledChoosers) {
            chooser.onResume();
        }
    }

    @Override
    public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        mSelectedChooser.onActivityResult(requestCode, resultCode, data);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mBinding.unbind();
    }

    /**
     * Sets the theme color to make the media picker match the surrounding UI
     * @param themeColor The new theme color
     */
    public void setConversationThemeColor(final int themeColor) {
        mThemeColor = themeColor;
        if (mTabStrip != null) {
            mTabStrip.setBackgroundColor(mThemeColor);
        }

        for (final MediaChooser chooser : mEnabledChoosers) {
            chooser.setThemeColor(mThemeColor);
        }
    }

    /**
     * Gets the current conversation theme color.
     */
    public int getConversationThemeColor() {
        return mThemeColor;
    }

    public void setDraftMessageDataModel(final BindingBase<DraftMessageData> draftBinding) {
        mDraftMessageDataModel = Binding.createBindingReference(draftBinding);
    }

    public ImmutableBindingRef<DraftMessageData> getDraftMessageDataModel() {
        return mDraftMessageDataModel;
    }

    public void setSubscriptionDataProvider(final DraftMessageSubscriptionDataProvider provider) {
        mSubscriptionDataProvider = provider;
    }

    @Override
    public int getConversationSelfSubId() {
        return mSubscriptionDataProvider.getConversationSelfSubId();
    }

    /**
     * Opens the media picker and optionally shows the chooser for the supplied media type
     * @param startingMediaType The media type of the chooser to open if {@link #MEDIA_TYPE_DEFAULT}
     *                          is used, then the default chooser from saved shared prefs is opened
     */
    public void open(final int startingMediaType, final boolean animate) {
        mOpen = true;
        if (mIsAttached) {
            doOpen(startingMediaType, animate);
        } else {
            // open() can get called immediately after the MediaPicker is created. In that case,
            // we defer doing work as it may require an attached fragment (eg. calling
            // Fragment#requestPermission)
            mStartingMediaTypeOnAttach = startingMediaType;
            mAnimateOnAttach = animate;
        }
    }

    private void doOpen(int startingMediaType, final boolean animate) {
        final boolean isTouchExplorationEnabled = AccessibilityUtil.isTouchExplorationEnabled(
                // getActivity() will be null at this point
                Factory.get().getApplicationContext());

        // If no specific starting type is specified (i.e. MEDIA_TYPE_DEFAULT), try to get the
        // last opened chooser index from shared prefs.
        if (startingMediaType == MEDIA_TYPE_DEFAULT) {
            final int selectedChooserIndex = mBinding.getData().getSelectedChooserIndex();
            if (selectedChooserIndex >= 0 && selectedChooserIndex < mEnabledChoosers.size()) {
                selectChooser(mEnabledChoosers.get(selectedChooserIndex));
            } else {
                // This is the first time the picker is being used
                if (isTouchExplorationEnabled) {
                    // Accessibility defaults to audio attachment mode.
                    startingMediaType = MEDIA_TYPE_AUDIO;
                }
            }
        }

        if (mSelectedChooser == null) {
            for (final MediaChooser chooser : mEnabledChoosers) {
                if (startingMediaType == MEDIA_TYPE_DEFAULT ||
                        (startingMediaType & chooser.getSupportedMediaTypes()) != MEDIA_TYPE_NONE) {
                    selectChooser(chooser);
                    break;
                }
            }
        }

        if (mSelectedChooser == null) {
            // Fall back to the first chooser.
            selectChooser(mEnabledChoosers.get(0));
        }

        if (mMediaPickerPanel != null) {
            mMediaPickerPanel.setFullScreenOnly(isTouchExplorationEnabled);
            mMediaPickerPanel.setExpanded(true, animate,
                    mEnabledChoosers.indexOf(mSelectedChooser));
        }
    }

    /** @return True if the media picker is open */
    public boolean isOpen() {
        return mOpen;
    }

    /**
     * Sets the list of media types to allow the user to select
     * @param mediaTypes The bit flags of media types to allow.  Can be any combination of the
     *                   MEDIA_TYPE_* values
     */
    void setSupportedMediaTypes(final int mediaTypes) {
        mSupportedMediaTypes = mediaTypes;
        mEnabledChoosers.clear();
        boolean selectNextChooser = false;
        for (final MediaChooser chooser : mChoosers) {
            final boolean enabled = (chooser.getSupportedMediaTypes() & mSupportedMediaTypes) !=
                    MEDIA_TYPE_NONE;
            if (enabled) {
                // TODO Add a way to inform the chooser which media types are supported
                mEnabledChoosers.add(chooser);
                if (selectNextChooser) {
                    selectChooser(chooser);
                    selectNextChooser = false;
                }
            } else if (mSelectedChooser == chooser) {
                selectNextChooser = true;
            }
            final ImageButton tabButton = chooser.getTabButton();
            if (tabButton != null) {
                tabButton.setVisibility(enabled ? View.VISIBLE : View.GONE);
            }
        }

        if (selectNextChooser && mEnabledChoosers.size() > 0) {
            selectChooser(mEnabledChoosers.get(0));
        }
        final MediaChooser[] enabledChoosers = new MediaChooser[mEnabledChoosers.size()];
        mEnabledChoosers.toArray(enabledChoosers);
        mPagerAdapter = new FixedViewPagerAdapter<MediaChooser>(enabledChoosers);
        if (mViewPager != null) {
            mViewPager.setAdapter(mPagerAdapter);
        }

        // Only rebind data if we are currently bound. Otherwise, we must have not
        // bound to any data yet and should wait until onCreate() to bind data.
        if (mBinding.isBound() && getActivity() != null) {
            mBinding.unbind();
            mBinding.bind(DataModel.get().createMediaPickerData(getActivity()));
            mBinding.getData().init(getLoaderManager());
        }
    }

    ViewPager getViewPager() {
        return mViewPager;
    }

    /** Hides the media picker, and frees up any resources it’s using */
    public void dismiss(final boolean animate) {
        mOpen = false;
        if (mMediaPickerPanel != null) {
            mMediaPickerPanel.setExpanded(false, animate, MediaPickerPanel.PAGE_NOT_SET);
        }
        mSelectedChooser = null;
    }

    /**
     * Sets the listener for the media picker events
     * @param listener The listener which will receive events
     */
    public void setListener(final MediaPickerListener listener) {
        Assert.isMainThread();
        mListener = listener;
        mListenerHandler = listener != null ? new Handler() : null;
    }

    /** @return True if the media picker is in full-screen mode */
    public boolean isFullScreen() {
        return mMediaPickerPanel != null && mMediaPickerPanel.isFullScreen();
    }

    public void setFullScreen(final boolean fullScreen) {
        mMediaPickerPanel.setFullScreenView(fullScreen, true);
    }

    public void updateActionBar(final ActionBar actionBar) {
        if (getActivity() == null) {
            return;
        }
        if (isFullScreen() && mSelectedChooser != null) {
            mSelectedChooser.updateActionBar(actionBar);
        } else {
            actionBar.hide();
        }
    }

    /**
     * Selects a new chooser
     * @param newSelectedChooser The newly selected chooser
     */
    void selectChooser(final MediaChooser newSelectedChooser) {
        if (mSelectedChooser == newSelectedChooser) {
            return;
        }

        if (mSelectedChooser != null) {
            mSelectedChooser.setSelected(false);
        }
        mSelectedChooser = newSelectedChooser;
        if (mSelectedChooser != null) {
            mSelectedChooser.setSelected(true);
        }

        final int chooserIndex = mEnabledChoosers.indexOf(mSelectedChooser);
        if (mViewPager != null) {
            mViewPager.setCurrentItem(chooserIndex, true /* smoothScroll */);
        }

        if (isFullScreen()) {
            invalidateOptionsMenu();
        }

        // Save the newly selected chooser's index so we may directly switch to it the
        // next time user opens the media picker.
        mBinding.getData().saveSelectedChooserIndex(chooserIndex);
        if (mMediaPickerPanel != null) {
            mMediaPickerPanel.onChooserChanged();
        }
        dispatchChooserSelected(chooserIndex);
    }

    public boolean canShowIme() {
        if (mSelectedChooser != null) {
            return mSelectedChooser.canShowIme();
        }
        return false;
    }

    public boolean onBackPressed() {
        return mSelectedChooser != null && mSelectedChooser.onBackPressed();
    }

    void invalidateOptionsMenu() {
        ((BugleActionBarActivity) getActivity()).supportInvalidateOptionsMenu();
    }

    void dispatchOpened() {
        setHasOptionsMenu(false);
        mOpen = true;
        mPagerAdapter.notifyDataSetChanged();
        if (mListener != null) {
            mListenerHandler.post(new Runnable() {
                @Override
                public void run() {
                    mListener.onOpened();
                }
            });
        }
        if (mSelectedChooser != null) {
            mSelectedChooser.onFullScreenChanged(false);
            mSelectedChooser.onOpenedChanged(true);
        }
    }

    void dispatchDismissed() {
        setHasOptionsMenu(false);
        mOpen = false;
        if (mListener != null) {
            mListenerHandler.post(new Runnable() {
                @Override
                public void run() {
                    mListener.onDismissed();
                }
            });
        }
        if (mSelectedChooser != null) {
            mSelectedChooser.onOpenedChanged(false);
        }
    }

    void dispatchFullScreen(final boolean fullScreen) {
        setHasOptionsMenu(fullScreen);
        if (mListener != null) {
            mListenerHandler.post(new Runnable() {
                @Override
                public void run() {
                    mListener.onFullScreenChanged(fullScreen);
                }
            });
        }
        if (mSelectedChooser != null) {
            mSelectedChooser.onFullScreenChanged(fullScreen);
        }
    }

    void dispatchItemsSelected(final MessagePartData item, final boolean dismissMediaPicker) {
        final List<MessagePartData> items = new ArrayList<MessagePartData>(1);
        items.add(item);
        dispatchItemsSelected(items, dismissMediaPicker);
    }

    void dispatchItemsSelected(final Collection<MessagePartData> items,
            final boolean dismissMediaPicker) {
        if (mListener != null) {
            mListenerHandler.post(new Runnable() {
                @Override
                public void run() {
                    mListener.onItemsSelected(items, dismissMediaPicker);
                }
            });
        }

        if (isFullScreen() && !dismissMediaPicker) {
            invalidateOptionsMenu();
        }
    }

    void dispatchItemUnselected(final MessagePartData item) {
        if (mListener != null) {
            mListenerHandler.post(new Runnable() {
                @Override
                public void run() {
                    mListener.onItemUnselected(item);
                }
            });
        }

        if (isFullScreen()) {
            invalidateOptionsMenu();
        }
    }

    void dispatchConfirmItemSelection() {
        if (mListener != null) {
            mListenerHandler.post(new Runnable() {
                @Override
                public void run() {
                    mListener.onConfirmItemSelection();
                }
            });
        }
    }

    void dispatchPendingItemAdded(final PendingAttachmentData pendingItem) {
        if (mListener != null) {
            mListenerHandler.post(new Runnable() {
                @Override
                public void run() {
                    mListener.onPendingItemAdded(pendingItem);
                }
            });
        }

        if (isFullScreen()) {
            invalidateOptionsMenu();
        }
    }

    void dispatchChooserSelected(final int chooserIndex) {
        if (mListener != null) {
            mListenerHandler.post(new Runnable() {
                @Override
                public void run() {
                    mListener.onChooserSelected(chooserIndex);
                }
            });
        }
    }

    public boolean canSwipeDownChooser() {
        return mSelectedChooser == null ? false : mSelectedChooser.canSwipeDown();
    }

    public boolean isChooserHandlingTouch() {
        return mSelectedChooser == null ? false : mSelectedChooser.isHandlingTouch();
    }

    public void stopChooserTouchHandling() {
        if (mSelectedChooser != null) {
            mSelectedChooser.stopTouchHandling();
        }
    }

    boolean getChooserShowsActionBarInFullScreen() {
        return mSelectedChooser == null ? false : mSelectedChooser.getActionBarTitleResId() != 0;
    }

    @Override
    public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
        if (mSelectedChooser != null) {
            mSelectedChooser.onCreateOptionsMenu(inflater, menu);
        }
    }

    @Override
    public boolean onOptionsItemSelected(final MenuItem item) {
        return (mSelectedChooser != null && mSelectedChooser.onOptionsItemSelected(item)) ||
                super.onOptionsItemSelected(item);
    }

    PagerAdapter getPagerAdapter() {
        return mPagerAdapter;
    }

    public void resetViewHolderState() {
        mPagerAdapter.resetState();
    }

    public ImmutableBindingRef<MediaPickerData> getMediaPickerDataBinding() {
        return BindingBase.createBindingReference(mBinding);
    }

    protected static final int CAMERA_PERMISSION_REQUEST_CODE = 1;
    protected static final int LOCATION_PERMISSION_REQUEST_CODE = 2;
    protected static final int RECORD_AUDIO_PERMISSION_REQUEST_CODE = 3;
    protected static final int GALLERY_PERMISSION_REQUEST_CODE = 4;
    protected static final int READ_CONTACT_PERMISSION_REQUEST_CODE = 5;

    @Override
    public void onRequestPermissionsResult(
            final int requestCode, final String permissions[], final int[] grantResults) {
        if (mSelectedChooser != null) {
            mSelectedChooser.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
}
