/*
 * Copyright 2017 dmfs GmbH
 *
 * 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 org.dmfs.tasks;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
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.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.ExpandableListView.OnGroupCollapseListener;
import android.widget.ListView;
import android.widget.TextView;

import com.google.android.material.snackbar.Snackbar;

import org.dmfs.android.bolts.color.Color;
import org.dmfs.android.bolts.color.elementary.ValueColor;
import org.dmfs.android.retentionmagic.SupportFragment;
import org.dmfs.android.retentionmagic.annotations.Parameter;
import org.dmfs.android.retentionmagic.annotations.Retain;
import org.dmfs.provider.tasks.AuthorityUtil;
import org.dmfs.tasks.contract.TaskContract;
import org.dmfs.tasks.contract.TaskContract.Instances;
import org.dmfs.tasks.contract.TaskContract.Tasks;
import org.dmfs.tasks.groupings.filters.AbstractFilter;
import org.dmfs.tasks.groupings.filters.ConstantFilter;
import org.dmfs.tasks.model.Model;
import org.dmfs.tasks.model.Sources;
import org.dmfs.tasks.model.TaskFieldAdapters;
import org.dmfs.tasks.utils.ExpandableGroupDescriptor;
import org.dmfs.tasks.utils.ExpandableGroupDescriptorAdapter;
import org.dmfs.tasks.utils.FlingDetector;
import org.dmfs.tasks.utils.FlingDetector.OnFlingListener;
import org.dmfs.tasks.utils.OnChildLoadedListener;
import org.dmfs.tasks.utils.OnModelLoadedListener;
import org.dmfs.tasks.utils.RetainExpandableListView;
import org.dmfs.tasks.utils.SafeFragmentUiRunnable;
import org.dmfs.tasks.utils.SearchHistoryDatabaseHelper.SearchHistoryColumns;

import androidx.annotation.NonNull;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;


/**
 * A list fragment representing a list of Tasks. This fragment also supports tablet devices by allowing list items to be given an 'activated' state upon
 * selection. This helps indicate which item is currently being viewed in a {@link ViewTaskFragment}.
 * <p>
 * Activities containing this fragment MUST implement the {@link Callbacks} interface
 *
 * @author Tobias Reinsch <[email protected]>
 */
public class TaskListFragment extends SupportFragment
        implements LoaderManager.LoaderCallbacks<Cursor>, OnChildLoadedListener, OnModelLoadedListener, OnFlingListener
{

    @SuppressWarnings("unused")
    private static final String TAG = "org.dmfs.tasks.TaskListFragment";

    private final static String ARG_INSTANCE_ID = "instance_id";

    private static final long INTERVAL_LISTVIEW_REDRAW = 60000;

    /**
     * A filter to hide completed tasks.
     */
    private final static AbstractFilter COMPLETED_FILTER = new ConstantFilter(Tasks.IS_CLOSED + "=0");

    /**
     * The group descriptor to use.
     */
    private ExpandableGroupDescriptor mGroupDescriptor;

    /**
     * The fragment's current callback object, which is notified of list item clicks.
     */
    private Callbacks mCallbacks;

    @Retain(permanent = true, instanceNSField = "mInstancePosition")
    private int mActivatedPositionGroup = ExpandableListView.INVALID_POSITION;
    @Retain(permanent = true, instanceNSField = "mInstancePosition")
    private int mActivatedPositionChild = ExpandableListView.INVALID_POSITION;

    private RetainExpandableListView mExpandableListView;
    private Context mAppContext;
    private ExpandableGroupDescriptorAdapter mAdapter;
    private Handler mHandler;
    @Retain(permanent = true, instanceNSField = "mInstancePosition")
    private long[] mSavedExpandedGroups = null;
    @Retain(permanent = true, instanceNSField = "mInstancePosition")
    private boolean mSavedCompletedFilter;

    @Parameter(key = ARG_INSTANCE_ID)
    private int mInstancePosition;

    private Loader<Cursor> mCursorLoader;
    private String mAuthority;

    private Uri mSelectedTaskUri;

    private boolean mTwoPaneLayout;

    /**
     * The child position to open when the fragment is displayed.
     **/
    private ListPosition mSelectedChildPosition;

    @Retain
    private int mPageId = -1;

    private final OnChildClickListener mTaskItemClickListener = new OnChildClickListener()
    {

        @Override
        public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id)
        {
            selectChildView(parent, groupPosition, childPosition, true);

            mActivatedPositionGroup = groupPosition;
            mActivatedPositionChild = childPosition;
            /*
             * In contrast to a ListView an ExpandableListView does not set the activated item on it's own. So we have to do that here.
             */
            setActivatedItem(groupPosition, childPosition);
            return true;
        }

    };

    private final OnGroupCollapseListener mTaskListCollapseListener = new OnGroupCollapseListener()
    {

        @Override
        public void onGroupCollapse(int groupPosition)
        {
            if (groupPosition == mActivatedPositionGroup)
            {
                mActivatedPositionChild = ExpandableListView.INVALID_POSITION;
                mActivatedPositionGroup = ExpandableListView.INVALID_POSITION;
            }

        }
    };


    /**
     * A callback interface that all activities containing this fragment must implement. This mechanism allows activities to be notified of item selections.
     */
    public interface Callbacks
    {
        /**
         * Callback for when an item has been selected.
         *
         * @param taskUri
         *         The {@link Uri} of the selected task.
         * @param taskListColor
         *         the color of the task list (used for toolbars)
         * @param forceReload
         *         Whether to reload the task or not.
         */
        void onItemSelected(@NonNull Uri taskUri, @NonNull Color taskListColor, boolean forceReload, int pagePosition);

        /**
         * Called when a task has been removed from the list.
         * <p>
         * TODO It's only called when task is deleted by the swipe out, and not when it is completed.
         * It should probably be called that time, too. See https://github.com/dmfs/opentasks/issues/641.
         *
         * @param taskUri
         *         the content uri of the task that has been removed
         */
        void onItemRemoved(@NonNull Uri taskUri);

        void onAddNewTask();

        ExpandableGroupDescriptor getGroupDescriptor(int position);
    }


    /**
     * A runnable that periodically updates the list. We need that to update relative dates & times. TODO: we probably should move that to the adapter to update
     * only the date & times fields, not the entire list.
     */
    private Runnable mListRedrawRunnable = new SafeFragmentUiRunnable(this, new Runnable()
    {

        @Override
        public void run()
        {
            mExpandableListView.invalidateViews();
            mHandler.postDelayed(mListRedrawRunnable, INTERVAL_LISTVIEW_REDRAW);
        }
    });


    public static TaskListFragment newInstance(int instancePosition)
    {
        TaskListFragment result = new TaskListFragment();
        Bundle args = new Bundle();
        args.putInt(ARG_INSTANCE_ID, instancePosition);
        result.setArguments(args);
        return result;
    }


    /**
     * Mandatory empty constructor for the fragment manager to instantiate the fragment (e.g. upon screen orientation changes).
     */
    public TaskListFragment()
    {
    }


    @Override
    public void onAttach(Activity activity)
    {
        super.onAttach(activity);

        mTwoPaneLayout = activity.getResources().getBoolean(R.bool.has_two_panes);

        mAuthority = AuthorityUtil.taskAuthority(activity);

        mAppContext = activity.getBaseContext();

        // Activities containing this fragment must implement its callbacks.
        if (!(activity instanceof Callbacks))
        {
            throw new IllegalStateException("Activity must implement fragment's callbacks.");
        }

        mCallbacks = (Callbacks) activity;

        // load accounts early
        Sources.loadModelAsync(activity, TaskContract.LOCAL_ACCOUNT_TYPE, this);
    }


    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        mHandler = new Handler();
        setHasOptionsMenu(true);
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        View rootView = inflater.inflate(R.layout.fragment_expandable_task_list, container, false);
        mExpandableListView = (RetainExpandableListView) rootView.findViewById(android.R.id.list);

        if (!mTwoPaneLayout)
        {
            // Add a footer to make sure the floating action button doesn't hide anything.
            mExpandableListView.addFooterView(inflater.inflate(R.layout.task_list_group, mExpandableListView, false));
        }

        if (mGroupDescriptor == null)
        {
            loadGroupDescriptor();
        }

        // setup the views
        this.prepareReload();

        // expand lists
        if (mSavedExpandedGroups != null)
        {
            mExpandableListView.expandGroups(mSavedExpandedGroups);
        }

        FlingDetector swiper = new FlingDetector(mExpandableListView, mGroupDescriptor.getElementViewDescriptor().getFlingContentViewId());
        swiper.setOnFlingListener(this);

        return rootView;
    }


    @Override
    public void onViewCreated(View view, Bundle savedInstanceState)
    {
        super.onViewCreated(view, savedInstanceState);
    }


    @Override
    public void onStart()
    {
        reloadCursor();
        super.onStart();
    }


    @Override
    public void onResume()
    {
        super.onResume();
        mExpandableListView.invalidateViews();
        startAutomaticRedraw();
        openSelectedChild();

        if (mTwoPaneLayout)
        {
            setListViewScrollbarPositionLeft(true);
            setActivateOnItemClick(true);
        }
    }


    @Override
    public void onPause()
    {
        // we can't rely on save instance state being called before onPause, so we get the expanded groups here again
        if (!((TaskListActivity) getActivity()).isInTransientState())
        {
            mSavedExpandedGroups = mExpandableListView.getExpandedGroups();
        }
        stopAutomaticRedraw();
        super.onPause();
    }


    @Override
    public void onDetach()
    {
        super.onDetach();

    }


    @Override
    public void onSaveInstanceState(Bundle outState)
    {
        if (!((TaskListActivity) getActivity()).isInTransientState())
        {
            mSavedExpandedGroups = mExpandableListView.getExpandedGroups();
        }
        super.onSaveInstanceState(outState);
    }


    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
    {
        // create menu
        inflater.inflate(R.menu.task_list_fragment_menu, menu);

        // restore menu state
        MenuItem item = menu.findItem(R.id.menu_show_completed);
        if (item != null)
        {
            item.setChecked(mSavedCompletedFilter);
        }
    }


    @Override
    public boolean onOptionsItemSelected(MenuItem item)
    {
        int itemId = item.getItemId();
        if (itemId == R.id.menu_show_completed)
        {

            mSavedCompletedFilter = !mSavedCompletedFilter;
            item.setChecked(mSavedCompletedFilter);
            mAdapter.setChildCursorFilter(mSavedCompletedFilter ? null : COMPLETED_FILTER);

            // reload the child cursors only
            for (int i = 0; i < mAdapter.getGroupCount(); ++i)
            {
                mAdapter.reloadGroup(i);
            }
            return true;
        }
        else if (itemId == R.id.menu_sync_now)
        {
            doSyncNow();
            return true;
        }
        else
        {
            return super.onOptionsItemSelected(item);
        }
    }


    @Override
    public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1)
    {

        if (mGroupDescriptor != null)
        {
            mCursorLoader = mGroupDescriptor.getGroupCursorLoader(mAppContext);
        }
        return mCursorLoader;

    }


    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor)
    {

        if (mSavedExpandedGroups == null)
        {
            mSavedExpandedGroups = mExpandableListView.getExpandedGroups();
        }

        mAdapter.setGroupCursor(cursor);

        if (mSavedExpandedGroups != null)
        {
            mExpandableListView.expandGroups(mSavedExpandedGroups);
            if (!((TaskListActivity) getActivity()).isInTransientState())
            {
                mSavedExpandedGroups = null;
            }
        }

        mHandler.post(new SafeFragmentUiRunnable(this, () -> mAdapter.reloadLoadedGroups()));
    }


    @Override
    public void onLoaderReset(Loader<Cursor> loader)
    {
        mAdapter.changeCursor(new MatrixCursor(new String[] { "_id" }));
    }


    @Override
    public void onChildLoaded(final int pos, Cursor childCursor)
    {
        if (mActivatedPositionChild != ExpandableListView.INVALID_POSITION)
        {
            if (pos == mActivatedPositionGroup && mActivatedPositionChild != ExpandableListView.INVALID_POSITION)
            {
                mHandler.post(setOpenHandler);
            }
        }
        // check for child to select
        if (mTwoPaneLayout)
        {
            selectChild(pos, childCursor);
        }
    }


    @Override
    public void onModelLoaded(Model model)
    {
        // nothing to do, we've just loaded the default model to speed up loading the detail view and the editor view.
    }


    private void selectChildView(ExpandableListView expandLV, int groupPosition, int childPosition, boolean force)
    {
        if (groupPosition < mAdapter.getGroupCount() && childPosition < mAdapter.getChildrenCount(groupPosition))
        {
            // a task instance element has been clicked, get it's instance id and notify the activity
            ExpandableListAdapter listAdapter = expandLV.getExpandableListAdapter();
            Cursor cursor = (Cursor) listAdapter.getChild(groupPosition, childPosition);

            if (cursor == null)
            {
                return;
            }

            Uri taskUri = ContentUris.withAppendedId(Instances.getContentUri(mAuthority), (long) TaskFieldAdapters.TASK_ID.get(cursor));
            Color taskListColor = new ValueColor(TaskFieldAdapters.LIST_COLOR.get(cursor));
            mCallbacks.onItemSelected(taskUri, taskListColor, force, mInstancePosition);
        }
    }


    /**
     * prepares the update of the view after the group descriptor was changed
     */
    public void prepareReload()
    {
        mAdapter = new ExpandableGroupDescriptorAdapter(new MatrixCursor(new String[] { "_id" }), getActivity(), getLoaderManager(), mGroupDescriptor);
        mExpandableListView.setAdapter(mAdapter);
        mExpandableListView.setOnChildClickListener(mTaskItemClickListener);
        mExpandableListView.setOnGroupCollapseListener(mTaskListCollapseListener);
        mAdapter.setOnChildLoadedListener(this);
        mAdapter.setChildCursorFilter(COMPLETED_FILTER);
        restoreFilterState();

    }


    private void reloadCursor()
    {
        getLoaderManager().restartLoader(-1, null, this);
    }


    public void restoreFilterState()
    {
        if (mSavedCompletedFilter)
        {
            mAdapter.setChildCursorFilter(mSavedCompletedFilter ? null : COMPLETED_FILTER);
            // reload the child cursors only
            for (int i = 0; i < mAdapter.getGroupCount(); ++i)
            {
                mAdapter.reloadGroup(i);
            }
        }

    }


    /**
     * Trigger a synchronization for all accounts.
     */
    private void doSyncNow()
    {
        AccountManager accountManager = AccountManager.get(mAppContext);
        Account[] accounts = accountManager.getAccounts();
        for (Account account : accounts)
        {
            // TODO: do we need a new bundle for each account or can we reuse it?
            Bundle extras = new Bundle();
            extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
            ContentResolver.requestSync(account, mAuthority, extras);
        }
    }


    /**
     * Remove the task with the given {@link Uri} and title, asking for confirmation first.
     *
     * @param taskUri
     *         The {@link Uri} of the atsk to remove.
     * @param taskTitle
     *         the title of the task to remove.
     *
     * @return
     */
    private void removeTask(final Uri taskUri, final String taskTitle)
    {
        new AlertDialog.Builder(getActivity()).setTitle(R.string.confirm_delete_title).setCancelable(true)
                .setNegativeButton(android.R.string.cancel, new OnClickListener()
                {
                    @Override
                    public void onClick(DialogInterface dialog, int which)
                    {
                        // nothing to do here
                    }
                }).setPositiveButton(android.R.string.ok, new OnClickListener()
        {
            @Override
            public void onClick(DialogInterface dialog, int which)
            {
                // TODO: remove the task in a background task
                mAppContext.getContentResolver().delete(taskUri, null, null);
                Snackbar.make(mExpandableListView, getString(R.string.toast_task_deleted, taskTitle), Snackbar.LENGTH_SHORT).show();
                mCallbacks.onItemRemoved(taskUri);
            }
        }).setMessage(getString(R.string.confirm_delete_message_with_title, taskTitle)).create().show();
    }


    /**
     * Opens the task editor for the selected Task.
     *
     * @param taskUri
     *         The {@link Uri} of the task.
     */
    private void openTaskEditor(final Uri taskUri, final String accountType)
    {
        Intent editTaskIntent = new Intent(Intent.ACTION_EDIT);
        editTaskIntent.setData(taskUri);
        editTaskIntent.putExtra(EditTaskActivity.EXTRA_DATA_ACCOUNT_TYPE, accountType);
        startActivity(editTaskIntent);
    }


    @Override
    public int canFling(ListView v, int pos)
    {
        long packedPos = mExpandableListView.getExpandableListPosition(pos);
        if (packedPos != ExpandableListView.PACKED_POSITION_VALUE_NULL
                && ExpandableListView.getPackedPositionType(packedPos) == ExpandableListView.PACKED_POSITION_TYPE_CHILD)
        {
            return FlingDetector.RIGHT_FLING | FlingDetector.LEFT_FLING;
        }
        else
        {
            return 0;
        }
    }


    @Override
    public void onFlingStart(ListView listView, View listElement, int position, int direction)
    {

        // control the visibility of the views that reveal behind a flinging element regarding the fling direction
        int rightFlingViewId = mGroupDescriptor.getElementViewDescriptor().getFlingRevealRightViewId();
        int leftFlingViewId = mGroupDescriptor.getElementViewDescriptor().getFlingRevealLeftViewId();
        TextView rightFlingView = null;
        TextView leftFlingView = null;

        if (rightFlingViewId != -1)
        {
            rightFlingView = (TextView) listElement.findViewById(rightFlingViewId);
        }
        if (leftFlingViewId != -1)
        {
            leftFlingView = (TextView) listElement.findViewById(leftFlingViewId);
        }

        Resources resources = getActivity().getResources();

        // change title and icon regarding the task status
        long packedPos = mExpandableListView.getExpandableListPosition(position);
        if (ExpandableListView.getPackedPositionType(packedPos) == ExpandableListView.PACKED_POSITION_TYPE_CHILD)
        {
            ExpandableListAdapter listAdapter = mExpandableListView.getExpandableListAdapter();
            Cursor cursor = (Cursor) listAdapter.getChild(ExpandableListView.getPackedPositionGroup(packedPos),
                    ExpandableListView.getPackedPositionChild(packedPos));

            if (cursor != null)
            {
                int taskStatus = cursor.getInt(cursor.getColumnIndex(Instances.STATUS));
                if (leftFlingView != null && rightFlingView != null)
                {
                    if (taskStatus == Instances.STATUS_COMPLETED)
                    {
                        leftFlingView.setText(R.string.fling_task_delete);
                        leftFlingView.setCompoundDrawablesWithIntrinsicBounds(resources.getDrawable(R.drawable.content_discard), null, null, null);
                        rightFlingView.setText(R.string.fling_task_uncomplete);
                        rightFlingView.setCompoundDrawablesWithIntrinsicBounds(null, null, resources.getDrawable(R.drawable.content_remove_light), null);
                    }
                    else
                    {
                        leftFlingView.setText(R.string.fling_task_complete);
                        leftFlingView.setCompoundDrawablesWithIntrinsicBounds(resources.getDrawable(R.drawable.ic_action_complete), null, null, null);
                        rightFlingView.setText(R.string.fling_task_edit);
                        rightFlingView.setCompoundDrawablesWithIntrinsicBounds(null, null, resources.getDrawable(R.drawable.content_edit), null);
                    }
                }
            }
        }

        if (rightFlingView != null)
        {
            rightFlingView.setVisibility(direction != FlingDetector.LEFT_FLING ? View.GONE : View.VISIBLE);
        }
        if (leftFlingView != null)
        {
            leftFlingView.setVisibility(direction != FlingDetector.RIGHT_FLING ? View.GONE : View.VISIBLE);
        }

    }


    @Override
    public boolean onFlingEnd(ListView v, View listElement, int pos, int direction)
    {
        long packedPos = mExpandableListView.getExpandableListPosition(pos);
        if (ExpandableListView.getPackedPositionType(packedPos) == ExpandableListView.PACKED_POSITION_TYPE_CHILD)
        {
            ExpandableListAdapter listAdapter = mExpandableListView.getExpandableListAdapter();
            Cursor cursor = (Cursor) listAdapter.getChild(ExpandableListView.getPackedPositionGroup(packedPos),
                    ExpandableListView.getPackedPositionChild(packedPos));

            if (cursor != null)
            {
                long instanceId = cursor.getLong(cursor.getColumnIndex(Instances._ID));

                boolean closed = cursor.getLong(cursor.getColumnIndex(Instances.IS_CLOSED)) > 0;
                String title = cursor.getString(cursor.getColumnIndex(Instances.TITLE));
                // TODO: use the instance URI once we support recurrence
                Uri taskUri = ContentUris.withAppendedId(Instances.getContentUri(mAuthority), instanceId);

                if (direction == FlingDetector.RIGHT_FLING)
                {
                    if (closed)
                    {
                        removeTask(taskUri, title);
                        // we do not know for sure if the task has been removed since the user is asked for confirmation first, so return false

                        return false;

                    }
                    else
                    {
                        return setCompleteTask(taskUri, title, true);
                    }
                }
                else if (direction == FlingDetector.LEFT_FLING)
                {
                    if (closed)
                    {
                        return setCompleteTask(taskUri, title, false);
                    }
                    else
                    {
                        openTaskEditor(taskUri, cursor.getString(cursor.getColumnIndex(Instances.ACCOUNT_TYPE)));
                        return false;
                    }
                }
            }
        }

        return false;
    }


    @Override
    public void onFlingCancel(int direction)
    {
        // TODO Auto-generated method stub

    }


    public void loadGroupDescriptor()
    {
        if (getActivity() != null)
        {
            TaskListActivity activity = (TaskListActivity) getActivity();
            if (activity != null)
            {
                mGroupDescriptor = activity.getGroupDescriptor(mPageId);
            }
        }
    }


    /**
     * Starts the automatic list view redraw (e.g. to display changing time values) on the next minute.
     */
    public void startAutomaticRedraw()
    {
        long now = System.currentTimeMillis();
        long millisToInterval = INTERVAL_LISTVIEW_REDRAW - (now % INTERVAL_LISTVIEW_REDRAW);

        mHandler.postDelayed(mListRedrawRunnable, millisToInterval);
    }


    /**
     * Stops the automatic list view redraw.
     */
    public void stopAutomaticRedraw()
    {
        mHandler.removeCallbacks(mListRedrawRunnable);
    }


    public int getOpenChildPosition()
    {
        return mActivatedPositionChild;
    }


    public int getOpenGroupPosition()
    {
        return mActivatedPositionGroup;
    }


    /**
     * Turns on activate-on-click mode. When this mode is on, list items will be given the 'activated' state when touched.
     * <p>
     * Note: this does not work 100% with {@link ExpandableListView}, it doesn't check touched items automatically.
     * </p>
     *
     * @param activateOnItemClick
     *         Whether to enable single choice mode or not.
     */
    public void setActivateOnItemClick(boolean activateOnItemClick)
    {
        mExpandableListView.setChoiceMode(activateOnItemClick ? ListView.CHOICE_MODE_SINGLE : ListView.CHOICE_MODE_NONE);
    }


    public void setListViewScrollbarPositionLeft(boolean left)
    {
        if (left)
        {
            mExpandableListView.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_LEFT);
            // expandLV.setScrollBarStyle(style);
        }
        else
        {
            mExpandableListView.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_RIGHT);
        }
    }


    public void setExpandableGroupDescriptor(ExpandableGroupDescriptor groupDescriptor)
    {
        mGroupDescriptor = groupDescriptor;
    }


    /**
     * Mark the given task as completed.
     *
     * @param taskUri
     *         The {@link Uri} of the task.
     * @param taskTitle
     *         The name/title of the task.
     * @param completedValue
     *         The value to be set for the completed status.
     *
     * @return <code>true</code> if the operation was successful, <code>false</code> otherwise.
     */
    private boolean setCompleteTask(Uri taskUri, String taskTitle, boolean completedValue)
    {
        ContentValues values = new ContentValues();
        values.put(Tasks.STATUS, completedValue ? Tasks.STATUS_COMPLETED : Tasks.STATUS_IN_PROCESS);
        if (!completedValue)
        {
            values.put(Tasks.PERCENT_COMPLETE, 50);
        }

        boolean completed = mAppContext.getContentResolver().update(taskUri, values, null, null) != 0;
        if (completed)
        {
            if (completedValue)
            {
                Snackbar.make(mExpandableListView, getString(R.string.toast_task_completed, taskTitle), Snackbar.LENGTH_SHORT).show();
            }
            else
            {
                Snackbar.make(mExpandableListView, getString(R.string.toast_task_uncompleted, taskTitle), Snackbar.LENGTH_SHORT).show();
            }
        }
        return completed;
    }


    public void setOpenChildPosition(int openChildPosition)
    {
        mActivatedPositionChild = openChildPosition;

    }


    public void setOpenGroupPosition(int openGroupPosition)
    {
        mActivatedPositionGroup = openGroupPosition;

    }


    public void notifyDataSetChanged(boolean expandFirst)
    {
        getLoaderManager().restartLoader(-1, null, this);
    }


    private Runnable setOpenHandler = new SafeFragmentUiRunnable(this, new Runnable()
    {
        @Override
        public void run()
        {
            selectChildView(mExpandableListView, mActivatedPositionGroup, mActivatedPositionChild, false);
            mExpandableListView.expandGroups(mSavedExpandedGroups);
            setActivatedItem(mActivatedPositionGroup, mActivatedPositionChild);
        }
    });


    public void setActivatedItem(int groupPosition, int childPosition)
    {
        if (groupPosition != ExpandableListView.INVALID_POSITION && groupPosition < mAdapter.getGroupCount()
                && childPosition != ExpandableListView.INVALID_POSITION && childPosition < mAdapter.getChildrenCount(groupPosition))
        {
            try
            {
                mExpandableListView
                        .setItemChecked(mExpandableListView.getFlatListPosition(ExpandableListView.getPackedPositionForChild(groupPosition, childPosition)),
                                true);
            }
            catch (NullPointerException e)
            {
                // for now we just catch the NPE until we've found the reason
                // just catching it won't hurt, it's just that the list selection won't be updated properly

                // FIXME: find the actual cause and fix it
            }
        }
    }


    public void expandCurrentSearchGroup()
    {
        if (mPageId == R.id.task_group_search && mAdapter.getGroupCount() > 0)
        {
            Cursor c = mAdapter.getGroup(0);
            if (c != null && c.getInt(c.getColumnIndex(SearchHistoryColumns.HISTORIC)) < 1)
            {
                mExpandableListView.expandGroup(0);
            }
        }
    }


    public void setPageId(int pageId)
    {
        mPageId = pageId;
    }


    private void selectChild(final int groupPosition, Cursor childCursor)
    {
        mSelectedTaskUri = ((TaskListActivity) getActivity()).getSelectedTaskUri();
        if (mSelectedTaskUri != null)
        {
            new AsyncSelectChildTask().execute(new SelectChildTaskParams(groupPosition, childCursor, mSelectedTaskUri));
        }
    }


    public void openSelectedChild()
    {
        if (mSelectedChildPosition != null)
        {
            // post delayed to allow the list view to finish creation
            mExpandableListView.postDelayed(new SafeFragmentUiRunnable(this, () ->
            {
                mExpandableListView.expandGroup(mSelectedChildPosition.groupPosition);
                mSelectedChildPosition.flatListPosition = mExpandableListView.getFlatListPosition(
                        RetainExpandableListView.getPackedPositionForChild(mSelectedChildPosition.groupPosition, mSelectedChildPosition.childPosition));

                setActivatedItem(mSelectedChildPosition.groupPosition, mSelectedChildPosition.childPosition);
                selectChildView(mExpandableListView, mSelectedChildPosition.groupPosition, mSelectedChildPosition.childPosition, true);
                mExpandableListView.smoothScrollToPosition(mSelectedChildPosition.flatListPosition);
            }), 0);
        }
    }


    /**
     * Returns the position of the task in the cursor. Returns -1 if the task is not in the cursor
     **/
    private int getSelectedChildPostion(Uri taskUri, Cursor listCursor)
    {
        if (taskUri != null && listCursor != null && listCursor.moveToFirst())
        {
            Long taskIdToSelect = Long.valueOf(taskUri.getLastPathSegment());
            do
            {
                Long taskId = listCursor.getLong(listCursor.getColumnIndex(Tasks._ID));
                if (taskId.equals(taskIdToSelect))
                {
                    return listCursor.getPosition();
                }
            } while (listCursor.moveToNext());
        }
        return -1;
    }


    private static class SelectChildTaskParams
    {
        int groupPosition;
        Uri taskUriToSelect;
        Cursor childCursor;


        SelectChildTaskParams(int groupPosition, Cursor childCursor, Uri taskUriToSelect)
        {
            this.groupPosition = groupPosition;
            this.childCursor = childCursor;
            this.taskUriToSelect = taskUriToSelect;
        }
    }


    private static class ListPosition
    {
        int groupPosition;
        int childPosition;
        int flatListPosition;


        ListPosition(int groupPosition, int childPosition)
        {
            this.groupPosition = groupPosition;
            this.childPosition = childPosition;
        }
    }


    private class AsyncSelectChildTask extends AsyncTask<SelectChildTaskParams, Void, Void>
    {

        @Override
        protected Void doInBackground(SelectChildTaskParams... params)
        {
            int count = params.length;
            for (int i = 0; i < count; i++)
            {
                final SelectChildTaskParams param = params[i];

                final int childPosition = getSelectedChildPostion(param.taskUriToSelect, param.childCursor);
                if (childPosition > -1)
                {
                    mSelectedChildPosition = new ListPosition(param.groupPosition, childPosition);
                    openSelectedChild();
                }
            }
            return null;
        }

    }
}