/*
 * Copyright (C) 2013 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.gallery3d.ingest;

import com.android.gallery3d.R;
import com.android.gallery3d.ingest.adapter.CheckBroker;
import com.android.gallery3d.ingest.adapter.MtpAdapter;
import com.android.gallery3d.ingest.adapter.MtpPagerAdapter;
import com.android.gallery3d.ingest.data.ImportTask;
import com.android.gallery3d.ingest.data.IngestObjectInfo;
import com.android.gallery3d.ingest.data.MtpBitmapFetch;
import com.android.gallery3d.ingest.data.MtpDeviceIndex;
import com.android.gallery3d.ingest.ui.DateTileView;
import com.android.gallery3d.ingest.ui.IngestGridView;
import com.android.gallery3d.ingest.ui.IngestGridView.OnClearChoicesListener;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.res.Configuration;
import android.database.DataSetObserver;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import androidx.viewpager.widget.ViewPager;
import android.util.SparseBooleanArray;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.TextView;

import java.lang.ref.WeakReference;
import java.util.Collection;

/**
 * MTP importer, main activity.
 */
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public class IngestActivity extends Activity implements
    MtpDeviceIndex.ProgressListener, ImportTask.Listener {

  private IngestService mHelperService;
  private boolean mActive = false;
  private IngestGridView mGridView;
  private MtpAdapter mAdapter;
  private Handler mHandler;
  private ProgressDialog mProgressDialog;
  private ActionMode mActiveActionMode;

  private View mWarningView;
  private TextView mWarningText;
  private int mLastCheckedPosition = 0;

  private ViewPager mFullscreenPager;
  private MtpPagerAdapter mPagerAdapter;
  private boolean mFullscreenPagerVisible = false;

  private MenuItem mMenuSwitcherItem;
  private MenuItem mActionMenuSwitcherItem;

  // The MTP framework components don't give us fine-grained file copy
  // progress updates, so for large photos and videos, we will be stuck
  // with a dialog not updating for a long time. To give the user feedback,
  // we switch to the animated indeterminate progress bar after the timeout
  // specified by INDETERMINATE_SWITCH_TIMEOUT_MS. On the next update from
  // the framework, we switch back to the normal progress bar.
  private static final int INDETERMINATE_SWITCH_TIMEOUT_MS = 3000;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    doBindHelperService();

    setContentView(R.layout.ingest_activity_item_list);
    mGridView = (IngestGridView) findViewById(R.id.ingest_gridview);
    mAdapter = new MtpAdapter(this);
    mAdapter.registerDataSetObserver(mPrimaryObserver);
    mGridView.setAdapter(mAdapter);
    mGridView.setMultiChoiceModeListener(mMultiChoiceModeListener);
    mGridView.setOnItemClickListener(mOnItemClickListener);
    mGridView.setOnClearChoicesListener(mPositionMappingCheckBroker);

    mFullscreenPager = (ViewPager) findViewById(R.id.ingest_view_pager);

    mHandler = new ItemListHandler(this);

    MtpBitmapFetch.configureForContext(this);
  }

  private OnItemClickListener mOnItemClickListener = new OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> adapterView, View itemView, int position,
        long arg3) {
      mLastCheckedPosition = position;
      mGridView.setItemChecked(position, !mGridView.getCheckedItemPositions().get(position));
    }
  };

  private MultiChoiceModeListener mMultiChoiceModeListener = new MultiChoiceModeListener() {
    private boolean mIgnoreItemCheckedStateChanges = false;

    private void updateSelectedTitle(ActionMode mode) {
      int count = mGridView.getCheckedItemCount();
      mode.setTitle(getResources().getQuantityString(
          R.plurals.ingest_number_of_items_selected, count, count));
    }

    @Override
    public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
        boolean checked) {
      if (mIgnoreItemCheckedStateChanges) {
        return;
      }
      if (mAdapter.itemAtPositionIsBucket(position)) {
        SparseBooleanArray checkedItems = mGridView.getCheckedItemPositions();
        mIgnoreItemCheckedStateChanges = true;
        mGridView.setItemChecked(position, false);

        // Takes advantage of the fact that SectionIndexer imposes the
        // need to clamp to the valid range
        int nextSectionStart = mAdapter.getPositionForSection(
            mAdapter.getSectionForPosition(position) + 1);
        if (nextSectionStart == position) {
          nextSectionStart = mAdapter.getCount();
        }

        boolean rangeValue = false; // Value we want to set all of the bucket items to

        // Determine if all the items in the bucket are currently checked, so that we
        // can uncheck them, otherwise we will check all items in the bucket.
        for (int i = position + 1; i < nextSectionStart; i++) {
          if (!checkedItems.get(i)) {
            rangeValue = true;
            break;
          }
        }

        // Set all items in the bucket to the desired state
        for (int i = position + 1; i < nextSectionStart; i++) {
          if (checkedItems.get(i) != rangeValue) {
            mGridView.setItemChecked(i, rangeValue);
          }
        }

        mPositionMappingCheckBroker.onBulkCheckedChange();
        mIgnoreItemCheckedStateChanges = false;
      } else {
        mPositionMappingCheckBroker.onCheckedChange(position, checked);
      }
      mLastCheckedPosition = position;
      updateSelectedTitle(mode);
    }

    @Override
    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
      return onOptionsItemSelected(item);
    }

    @Override
    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
      MenuInflater inflater = mode.getMenuInflater();
      inflater.inflate(R.menu.ingest_menu_item_list_selection, menu);
      updateSelectedTitle(mode);
      mActiveActionMode = mode;
      mActionMenuSwitcherItem = menu.findItem(R.id.ingest_switch_view);
      setSwitcherMenuState(mActionMenuSwitcherItem, mFullscreenPagerVisible);
      return true;
    }

    @Override
    public void onDestroyActionMode(ActionMode mode) {
      mActiveActionMode = null;
      mActionMenuSwitcherItem = null;
      mHandler.sendEmptyMessage(ItemListHandler.MSG_BULK_CHECKED_CHANGE);
    }

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

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    int id = item.getItemId();
    if (id == R.id.ingest_import_items) {
      if (mActiveActionMode != null) {
        mHelperService.importSelectedItems(
            mGridView.getCheckedItemPositions(),
            mAdapter);
        mActiveActionMode.finish();
      }
      return true;
    } else if (id == R.id.ingest_switch_view) {
      setFullscreenPagerVisibility(!mFullscreenPagerVisible);
      return true;
    } else {
      return false;
    }
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.ingest_menu_item_list_selection, menu);
    mMenuSwitcherItem = menu.findItem(R.id.ingest_switch_view);
    menu.findItem(R.id.ingest_import_items).setVisible(false);
    setSwitcherMenuState(mMenuSwitcherItem, mFullscreenPagerVisible);
    return true;
  }

  @Override
  protected void onDestroy() {
    doUnbindHelperService();
    super.onDestroy();
  }

  @Override
  protected void onResume() {
    DateTileView.refreshLocale();
    mActive = true;
    if (mHelperService != null) {
      mHelperService.setClientActivity(this);
    }
    updateWarningView();
    super.onResume();
  }

  @Override
  protected void onPause() {
    if (mHelperService != null) {
      mHelperService.setClientActivity(null);
    }
    mActive = false;
    cleanupProgressDialog();
    super.onPause();
  }

  @Override
  public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    MtpBitmapFetch.configureForContext(this);
  }

  private void showWarningView(int textResId) {
    if (mWarningView == null) {
      mWarningView = findViewById(R.id.ingest_warning_view);
      mWarningText =
          (TextView) mWarningView.findViewById(R.id.ingest_warning_view_text);
    }
    mWarningText.setText(textResId);
    mWarningView.setVisibility(View.VISIBLE);
    setFullscreenPagerVisibility(false);
    mGridView.setVisibility(View.GONE);
    setSwitcherMenuVisibility(false);
  }

  private void hideWarningView() {
    if (mWarningView != null) {
      mWarningView.setVisibility(View.GONE);
      setFullscreenPagerVisibility(false);
    }
    setSwitcherMenuVisibility(true);
  }

  private PositionMappingCheckBroker mPositionMappingCheckBroker =
      new PositionMappingCheckBroker();

  private class PositionMappingCheckBroker extends CheckBroker
      implements OnClearChoicesListener {
    private int mLastMappingPager = -1;
    private int mLastMappingGrid = -1;

    private int mapPagerToGridPosition(int position) {
      if (position != mLastMappingPager) {
        mLastMappingPager = position;
        mLastMappingGrid = mAdapter.translatePositionWithoutLabels(position);
      }
      return mLastMappingGrid;
    }

    private int mapGridToPagerPosition(int position) {
      if (position != mLastMappingGrid) {
        mLastMappingGrid = position;
        mLastMappingPager = mPagerAdapter.translatePositionWithLabels(position);
      }
      return mLastMappingPager;
    }

    @Override
    public void setItemChecked(int position, boolean checked) {
      mGridView.setItemChecked(mapPagerToGridPosition(position), checked);
    }

    @Override
    public void onCheckedChange(int position, boolean checked) {
      if (mPagerAdapter != null) {
        super.onCheckedChange(mapGridToPagerPosition(position), checked);
      }
    }

    @Override
    public boolean isItemChecked(int position) {
      return mGridView.getCheckedItemPositions().get(mapPagerToGridPosition(position));
    }

    @Override
    public void onClearChoices() {
      onBulkCheckedChange();
    }
  }

  private DataSetObserver mPrimaryObserver = new DataSetObserver() {
    @Override
    public void onChanged() {
      if (mPagerAdapter != null) {
        mPagerAdapter.notifyDataSetChanged();
      }
    }

    @Override
    public void onInvalidated() {
      if (mPagerAdapter != null) {
        mPagerAdapter.notifyDataSetChanged();
      }
    }
  };

  private int pickFullscreenStartingPosition() {
    int firstVisiblePosition = mGridView.getFirstVisiblePosition();
    if (mLastCheckedPosition <= firstVisiblePosition
        || mLastCheckedPosition > mGridView.getLastVisiblePosition()) {
      return firstVisiblePosition;
    } else {
      return mLastCheckedPosition;
    }
  }

  private void setSwitcherMenuState(MenuItem menuItem, boolean inFullscreenMode) {
    if (menuItem == null) {
      return;
    }
    if (!inFullscreenMode) {
      menuItem.setIcon(android.R.drawable.ic_menu_zoom);
      menuItem.setTitle(R.string.ingest_switch_photo_fullscreen);
    } else {
      menuItem.setIcon(android.R.drawable.ic_dialog_dialer);
      menuItem.setTitle(R.string.ingest_switch_photo_grid);
    }
  }

  private void setFullscreenPagerVisibility(boolean visible) {
    mFullscreenPagerVisible = visible;
    if (visible) {
      if (mPagerAdapter == null) {
        mPagerAdapter = new MtpPagerAdapter(this, mPositionMappingCheckBroker);
        mPagerAdapter.setMtpDeviceIndex(mAdapter.getMtpDeviceIndex());
      }
      mFullscreenPager.setAdapter(mPagerAdapter);
      mFullscreenPager.setCurrentItem(mPagerAdapter.translatePositionWithLabels(
          pickFullscreenStartingPosition()), false);
    } else if (mPagerAdapter != null) {
      mGridView.setSelection(mAdapter.translatePositionWithoutLabels(
          mFullscreenPager.getCurrentItem()));
      mFullscreenPager.setAdapter(null);
    }
    mGridView.setVisibility(visible ? View.INVISIBLE : View.VISIBLE);
    mFullscreenPager.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
    if (mActionMenuSwitcherItem != null) {
      setSwitcherMenuState(mActionMenuSwitcherItem, visible);
    }
    setSwitcherMenuState(mMenuSwitcherItem, visible);
  }

  private void setSwitcherMenuVisibility(boolean visible) {
    if (mActionMenuSwitcherItem != null) {
      mActionMenuSwitcherItem.setVisible(visible);
    }
    if (mMenuSwitcherItem != null) {
      mMenuSwitcherItem.setVisible(visible);
    }
  }

  private void updateWarningView() {
    if (!mAdapter.deviceConnected()) {
      showWarningView(R.string.ingest_no_device);
    } else if (mAdapter.indexReady() && mAdapter.getCount() == 0) {
      showWarningView(R.string.ingest_empty_device);
    } else {
      hideWarningView();
    }
  }

  private void uiThreadNotifyIndexChanged() {
    mAdapter.notifyDataSetChanged();
    if (mActiveActionMode != null) {
      mActiveActionMode.finish();
      mActiveActionMode = null;
    }
    updateWarningView();
  }

  protected void notifyIndexChanged() {
    mHandler.sendEmptyMessage(ItemListHandler.MSG_NOTIFY_CHANGED);
  }

  private static class ProgressState {
    String message;
    String title;
    int current;
    int max;

    public void reset() {
      title = null;
      message = null;
      current = 0;
      max = 0;
    }
  }

  private ProgressState mProgressState = new ProgressState();

  @Override
  public void onObjectIndexed(IngestObjectInfo object, int numVisited) {
    // Not guaranteed to be called on the UI thread
    mProgressState.reset();
    mProgressState.max = 0;
    mProgressState.message = getResources().getQuantityString(
        R.plurals.ingest_number_of_items_scanned, numVisited, numVisited);
    mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_UPDATE);
  }

  @Override
  public void onSortingStarted() {
    // Not guaranteed to be called on the UI thread
    mProgressState.reset();
    mProgressState.max = 0;
    mProgressState.message = getResources().getString(R.string.ingest_sorting);
    mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_UPDATE);
  }

  @Override
  public void onIndexingFinished() {
    // Not guaranteed to be called on the UI thread
    mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_HIDE);
    mHandler.sendEmptyMessage(ItemListHandler.MSG_NOTIFY_CHANGED);
  }

  @Override
  public void onImportProgress(final int visitedCount, final int totalCount,
      String pathIfSuccessful) {
    // Not guaranteed to be called on the UI thread
    mProgressState.reset();
    mProgressState.max = totalCount;
    mProgressState.current = visitedCount;
    mProgressState.title = getResources().getString(R.string.ingest_importing);
    mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_UPDATE);
    mHandler.removeMessages(ItemListHandler.MSG_PROGRESS_INDETERMINATE);
    mHandler.sendEmptyMessageDelayed(ItemListHandler.MSG_PROGRESS_INDETERMINATE,
        INDETERMINATE_SWITCH_TIMEOUT_MS);
  }

  @Override
  public void onImportFinish(Collection<IngestObjectInfo> objectsNotImported,
      int numVisited) {
    // Not guaranteed to be called on the UI thread
    mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_HIDE);
    mHandler.removeMessages(ItemListHandler.MSG_PROGRESS_INDETERMINATE);
    // TODO(georgescu): maybe show an extra dialog listing the ones that failed
    // importing, if any?
  }

  private ProgressDialog getProgressDialog() {
    if (mProgressDialog == null || !mProgressDialog.isShowing()) {
      mProgressDialog = new ProgressDialog(this);
      mProgressDialog.setCancelable(false);
    }
    return mProgressDialog;
  }

  private void updateProgressDialog() {
    ProgressDialog dialog = getProgressDialog();
    boolean indeterminate = (mProgressState.max == 0);
    dialog.setIndeterminate(indeterminate);
    dialog.setProgressStyle(indeterminate ? ProgressDialog.STYLE_SPINNER
        : ProgressDialog.STYLE_HORIZONTAL);
    if (mProgressState.title != null) {
      dialog.setTitle(mProgressState.title);
    }
    if (mProgressState.message != null) {
      dialog.setMessage(mProgressState.message);
    }
    if (!indeterminate) {
      dialog.setProgress(mProgressState.current);
      dialog.setMax(mProgressState.max);
    }
    if (!dialog.isShowing()) {
      dialog.show();
    }
  }

  private void makeProgressDialogIndeterminate() {
    ProgressDialog dialog = getProgressDialog();
    dialog.setIndeterminate(true);
  }

  private void cleanupProgressDialog() {
    if (mProgressDialog != null) {
      mProgressDialog.dismiss();
      mProgressDialog = null;
    }
  }

  // This is static and uses a WeakReference in order to avoid leaking the Activity
  private static class ItemListHandler extends Handler {
    public static final int MSG_PROGRESS_UPDATE = 0;
    public static final int MSG_PROGRESS_HIDE = 1;
    public static final int MSG_NOTIFY_CHANGED = 2;
    public static final int MSG_BULK_CHECKED_CHANGE = 3;
    public static final int MSG_PROGRESS_INDETERMINATE = 4;

    WeakReference<IngestActivity> mParentReference;

    public ItemListHandler(IngestActivity parent) {
      super();
      mParentReference = new WeakReference<IngestActivity>(parent);
    }

    @Override
    public void handleMessage(Message message) {
      IngestActivity parent = mParentReference.get();
      if (parent == null || !parent.mActive) {
        return;
      }
      switch (message.what) {
        case MSG_PROGRESS_HIDE:
          parent.cleanupProgressDialog();
          break;
        case MSG_PROGRESS_UPDATE:
          parent.updateProgressDialog();
          break;
        case MSG_NOTIFY_CHANGED:
          parent.uiThreadNotifyIndexChanged();
          break;
        case MSG_BULK_CHECKED_CHANGE:
          parent.mPositionMappingCheckBroker.onBulkCheckedChange();
          break;
        case MSG_PROGRESS_INDETERMINATE:
          parent.makeProgressDialogIndeterminate();
          break;
        default:
          break;
      }
    }
  }

  private ServiceConnection mHelperServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName className, IBinder service) {
      mHelperService = ((IngestService.LocalBinder) service).getService();
      mHelperService.setClientActivity(IngestActivity.this);
      MtpDeviceIndex index = mHelperService.getIndex();
      mAdapter.setMtpDeviceIndex(index);
      if (mPagerAdapter != null) {
        mPagerAdapter.setMtpDeviceIndex(index);
      }
    }

    @Override
    public void onServiceDisconnected(ComponentName className) {
      mHelperService = null;
    }
  };

  private void doBindHelperService() {
    bindService(new Intent(getApplicationContext(), IngestService.class),
        mHelperServiceConnection, Context.BIND_AUTO_CREATE);
  }

  private void doUnbindHelperService() {
    if (mHelperService != null) {
      mHelperService.setClientActivity(null);
      unbindService(mHelperServiceConnection);
    }
  }
}
