/*
 * Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl.html
 */

package at.bitfire.davdroid.ui;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.LoaderManager;
import android.content.AsyncTaskLoader;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.Loader;
import android.content.ServiceConnection;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.provider.CalendarContract;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.CardView;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.ProgressBar;
import android.widget.RadioButton;
import android.widget.TextView;

import org.apache.commons.lang3.BooleanUtils;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;

import at.bitfire.cert4android.CustomCertManager;
import at.bitfire.davdroid.App;
import at.bitfire.davdroid.DavService;
import at.bitfire.davdroid.R;
import at.bitfire.davdroid.model.CollectionInfo;
import at.bitfire.davdroid.model.ServiceDB;
import at.bitfire.davdroid.model.ServiceDB.Collections;
import at.bitfire.davdroid.model.ServiceDB.OpenHelper;
import at.bitfire.davdroid.model.ServiceDB.Services;
import at.bitfire.ical4android.TaskProvider;
import lombok.Cleanup;

public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenuItemClickListener, PopupMenu.OnMenuItemClickListener, LoaderManager.LoaderCallbacks<AccountActivity.AccountInfo> {
    public static final String EXTRA_ACCOUNT = "account";

    private Account account;
    private AccountInfo accountInfo;

    ListView listCalDAV, listCardDAV;
    Toolbar tbCardDAV, tbCalDAV;

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

        account = getIntent().getParcelableExtra(EXTRA_ACCOUNT);
        setTitle(account.name);

        setContentView(R.layout.activity_account);

        Drawable icMenu = Build.VERSION.SDK_INT >= 21 ? getDrawable(R.drawable.ic_menu_light) :
                getResources().getDrawable(R.drawable.ic_menu_light);

        // CardDAV toolbar
        tbCardDAV = (Toolbar)findViewById(R.id.carddav_menu);
        tbCardDAV.setOverflowIcon(icMenu);
        tbCardDAV.inflateMenu(R.menu.carddav_actions);
        tbCardDAV.setOnMenuItemClickListener(this);

        // CalDAV toolbar
        tbCalDAV = (Toolbar)findViewById(R.id.caldav_menu);
        tbCalDAV.setOverflowIcon(icMenu);
        tbCalDAV.inflateMenu(R.menu.caldav_actions);
        tbCalDAV.setOnMenuItemClickListener(this);

        // load CardDAV/CalDAV collections
        getLoaderManager().initLoader(0, getIntent().getExtras(), this);
    }

    @Override
    protected void onPause() {
        super.onPause();
        CustomCertManager certManager = App.getCertManager();
        if (certManager != null)
            certManager.appInForeground = false;
    }

    @Override
    protected void onResume() {
        super.onResume();
        CustomCertManager certManager = App.getCertManager();
        if (certManager != null)
            certManager.appInForeground = true;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_account, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.sync_now:
                requestSync();
                break;
            case R.id.settings:
                Intent intent = new Intent(this, AccountSettingsActivity.class);
                intent.putExtra(AccountSettingsActivity.EXTRA_ACCOUNT, account);
                startActivity(intent);
                break;
            case R.id.delete_account:
                new AlertDialog.Builder(AccountActivity.this)
                        .setIcon(R.drawable.ic_error_dark)
                        .setTitle(R.string.account_delete_confirmation_title)
                        .setMessage(R.string.account_delete_confirmation_text)
                        .setNegativeButton(android.R.string.no, null)
                        .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                deleteAccount();
                            }
                        })
                        .show();
                break;
            default:
                return super.onOptionsItemSelected(item);
        }
        return true;
    }

    @Override
    public boolean onMenuItemClick(MenuItem item) {
        Intent intent;
        switch (item.getItemId()) {
            case R.id.refresh_address_books:
                if (accountInfo.carddav != null) {
                    intent = new Intent(this, DavService.class);
                    intent.setAction(DavService.ACTION_REFRESH_COLLECTIONS);
                    intent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, accountInfo.carddav.id);
                    startService(intent);
                }
                break;
            case R.id.create_address_book:
                intent = new Intent(this, CreateAddressBookActivity.class);
                intent.putExtra(CreateAddressBookActivity.EXTRA_ACCOUNT, account);
                startActivity(intent);
                break;
            case R.id.refresh_calendars:
                if (accountInfo.caldav != null) {
                    intent = new Intent(this, DavService.class);
                    intent.setAction(DavService.ACTION_REFRESH_COLLECTIONS);
                    intent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, accountInfo.caldav.id);
                    startService(intent);
                }
                break;
            case R.id.create_calendar:
                intent = new Intent(this, CreateCalendarActivity.class);
                intent.putExtra(CreateCalendarActivity.EXTRA_ACCOUNT, account);
                startActivity(intent);
                break;
        }
        return false;
    }


    private AdapterView.OnItemClickListener onItemClickListener = new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            final ListView list = (ListView)parent;
            final ArrayAdapter<CollectionInfo> adapter = (ArrayAdapter)list.getAdapter();
            final CollectionInfo info = adapter.getItem(position);

            boolean nowChecked = !info.selected;

            if (list.getChoiceMode() == AbsListView.CHOICE_MODE_SINGLE)
                // clear all other checked items
                for (int i = adapter.getCount()-1; i >= 0; i--)
                    adapter.getItem(i).selected = false;

            OpenHelper dbHelper = new OpenHelper(AccountActivity.this);
            try {
                SQLiteDatabase db = dbHelper.getWritableDatabase();
                db.beginTransactionNonExclusive();

                if (list.getChoiceMode() == AbsListView.CHOICE_MODE_SINGLE) {
                    // disable all other collections
                    ContentValues values = new ContentValues(1);
                    values.put(Collections.SYNC, 0);
                    db.update(Collections._TABLE, values, Collections.SERVICE_ID + "=?", new String[] { String.valueOf(info.serviceID) });
                }

                ContentValues values = new ContentValues(1);
                values.put(Collections.SYNC, nowChecked ? 1 : 0);
                db.update(Collections._TABLE, values, Collections.ID + "=?", new String[] { String.valueOf(info.id) });

                db.setTransactionSuccessful();
                db.endTransaction();

                info.selected = nowChecked;
                adapter.notifyDataSetChanged();
            } finally {
                dbHelper.close();
            }
        }
    };

    private AdapterView.OnItemLongClickListener onItemLongClickListener = new AdapterView.OnItemLongClickListener() {
        @Override
        public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
            final ListView list = (ListView)parent;
            final ArrayAdapter<CollectionInfo> adapter = (ArrayAdapter)list.getAdapter();
            final CollectionInfo info = adapter.getItem(position);

            PopupMenu popup = new PopupMenu(AccountActivity.this, view);
            popup.inflate(R.menu.account_collection_operations);
            popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
                @Override
                public boolean onMenuItemClick(MenuItem item) {
                    switch (item.getItemId()) {
                        case R.id.delete_collection:
                            DeleteCollectionFragment.ConfirmDeleteCollectionFragment.newInstance(account, info).show(getSupportFragmentManager(), null);
                            break;
                    }
                    return true;
                }
            });
            popup.show();

            // long click was handled
            return true;
        }
    };


    /* LOADERS AND LOADED DATA */

    protected static class AccountInfo {
        ServiceInfo carddav, caldav;

        public static class ServiceInfo {
            long id;
            boolean refreshing;

            boolean hasHomeSets;
            List<CollectionInfo> collections;
        }
    }

    @Override
    public Loader<AccountInfo> onCreateLoader(int id, Bundle args) {
        return new AccountLoader(this, account.name);
    }

    public void reload() {
        getLoaderManager().restartLoader(0, getIntent().getExtras(), this);
    }

    @Override
    public void onLoadFinished(Loader<AccountInfo> loader, final AccountInfo info) {
        accountInfo = info;

        if (accountInfo == null) {
            // account doesn't exist anymore
            finish();
            return;
        }

        CardView card = (CardView)findViewById(R.id.carddav);
        if (info.carddav != null) {
            ProgressBar progress = (ProgressBar)findViewById(R.id.carddav_refreshing);
            progress.setVisibility(info.carddav.refreshing ? View.VISIBLE : View.GONE);

            listCardDAV = (ListView)findViewById(R.id.address_books);
            listCardDAV.setEnabled(!info.carddav.refreshing);
            listCardDAV.setAlpha(info.carddav.refreshing ? 0.5f : 1);

            tbCardDAV.getMenu().findItem(R.id.create_address_book).setEnabled(info.carddav.hasHomeSets);

            AddressBookAdapter adapter = new AddressBookAdapter(this);
            adapter.addAll(info.carddav.collections);
            listCardDAV.setAdapter(adapter);
            listCardDAV.setOnItemClickListener(onItemClickListener);
            listCardDAV.setOnItemLongClickListener(onItemLongClickListener);
        } else
            card.setVisibility(View.GONE);

        card = (CardView)findViewById(R.id.caldav);
        if (info.caldav != null) {
            ProgressBar progress = (ProgressBar)findViewById(R.id.caldav_refreshing);
            progress.setVisibility(info.caldav.refreshing ? View.VISIBLE : View.GONE);

            listCalDAV = (ListView)findViewById(R.id.calendars);
            listCalDAV.setEnabled(!info.caldav.refreshing);
            listCalDAV.setAlpha(info.caldav.refreshing ? 0.5f : 1);

            tbCalDAV.getMenu().findItem(R.id.create_calendar).setEnabled(info.caldav.hasHomeSets);

            final CalendarAdapter adapter = new CalendarAdapter(this);
            adapter.addAll(info.caldav.collections);
            listCalDAV.setAdapter(adapter);
            listCalDAV.setOnItemClickListener(onItemClickListener);
            listCalDAV.setOnItemLongClickListener(onItemLongClickListener);
        } else
            card.setVisibility(View.GONE);
    }

    @Override
    public void onLoaderReset(Loader<AccountInfo> loader) {
        if (listCardDAV != null)
            listCardDAV.setAdapter(null);

        if (listCalDAV != null)
            listCalDAV.setAdapter(null);
    }


    private static class AccountLoader extends AsyncTaskLoader<AccountInfo> implements DavService.RefreshingStatusListener, ServiceConnection {
        private final String accountName;
        private final OpenHelper dbHelper;
        private DavService.InfoBinder davService;

        public AccountLoader(Context context, String accountName) {
            super(context);
            this.accountName = accountName;
            dbHelper = new OpenHelper(context);
        }

        @Override
        protected void onStartLoading() {
            getContext().bindService(new Intent(getContext(), DavService.class), this, Context.BIND_AUTO_CREATE);
        }

        @Override
        protected void onStopLoading() {
            davService.removeRefreshingStatusListener(this);
            getContext().unbindService(this);
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            davService = (DavService.InfoBinder)service;
            davService.addRefreshingStatusListener(this, false);

            forceLoad();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            davService = null;
        }

        @Override
        public void onDavRefreshStatusChanged(long id, boolean refreshing) {
            forceLoad();
        }

        @Override
        public AccountInfo loadInBackground() {
            AccountInfo info = new AccountInfo();
            try {
                SQLiteDatabase db = dbHelper.getReadableDatabase();

                @Cleanup Cursor cursor = db.query(
                        Services._TABLE,
                        new String[] { Services.ID, Services.SERVICE },
                        Services.ACCOUNT_NAME + "=?", new String[] { accountName },
                        null, null, null);

                if (cursor.getCount() == 0)
                    // no services, account not useable
                    return null;

                while (cursor.moveToNext()) {
                    long id = cursor.getLong(0);
                    String service = cursor.getString(1);
                    if (Services.SERVICE_CARDDAV.equals(service)) {
                        info.carddav = new AccountInfo.ServiceInfo();
                        info.carddav.id = id;
                        info.carddav.refreshing = davService.isRefreshing(id);
                        info.carddav.hasHomeSets = hasHomeSets(db, id);
                        info.carddav.collections = readCollections(db, id);

                    } else if (Services.SERVICE_CALDAV.equals(service)) {
                        info.caldav = new AccountInfo.ServiceInfo();
                        info.caldav.id = id;
                        info.caldav.refreshing = davService.isRefreshing(id);
                        info.caldav.hasHomeSets = hasHomeSets(db, id);
                        info.caldav.collections = readCollections(db, id);
                    }
                }
            } finally {
                dbHelper.close();
            }
            return info;
        }

        private boolean hasHomeSets(@NonNull SQLiteDatabase db, long service) {
            @Cleanup Cursor cursor = db.query(ServiceDB.HomeSets._TABLE, null, ServiceDB.HomeSets.SERVICE_ID + "=?",
                    new String[] { String.valueOf(service) }, null, null, null);
            return cursor.getCount() > 0;
        }

        private List<CollectionInfo> readCollections(@NonNull SQLiteDatabase db, long service) {
            List<CollectionInfo> collections = new LinkedList<>();
            @Cleanup Cursor cursor = db.query(Collections._TABLE, null, Collections.SERVICE_ID + "=?",
                    new String[] { String.valueOf(service) }, null, null, Collections.SUPPORTS_VEVENT + " DESC," + Collections.DISPLAY_NAME);
            while (cursor.moveToNext()) {
                ContentValues values = new ContentValues();
                DatabaseUtils.cursorRowToContentValues(cursor, values);
                collections.add(CollectionInfo.fromDB(values));
            }
            return collections;
        }
    }


    /* LIST ADAPTERS */

    public static class AddressBookAdapter extends ArrayAdapter<CollectionInfo> {
        public AddressBookAdapter(Context context) {
            super(context, R.layout.account_carddav_item);
        }

        @Override
        public View getView(int position, View v, ViewGroup parent) {
            if (v == null)
                v = LayoutInflater.from(getContext()).inflate(R.layout.account_carddav_item, parent, false);

            final CollectionInfo info = getItem(position);

            RadioButton checked = (RadioButton)v.findViewById(R.id.checked);
            checked.setChecked(info.selected);

            TextView tv = (TextView)v.findViewById(R.id.title);
            tv.setText(TextUtils.isEmpty(info.displayName) ? info.url : info.displayName);

            tv = (TextView)v.findViewById(R.id.description);
            if (TextUtils.isEmpty(info.description))
                tv.setVisibility(View.GONE);
            else {
                tv.setVisibility(View.VISIBLE);
                tv.setText(info.description);
            }

            tv = (TextView)v.findViewById(R.id.read_only);
            tv.setVisibility(info.readOnly ? View.VISIBLE : View.GONE);

            return v;
        }
    }

    public static class CalendarAdapter extends ArrayAdapter<CollectionInfo> {
        public CalendarAdapter(Context context) {
            super(context, R.layout.account_caldav_item);
        }

        @Override
        public View getView(final int position, View v, ViewGroup parent) {
            if (v == null)
                v = LayoutInflater.from(getContext()).inflate(R.layout.account_caldav_item, parent, false);

            final CollectionInfo info = getItem(position);

            CheckBox checked = (CheckBox)v.findViewById(R.id.checked);
            checked.setChecked(info.selected);

            View vColor = v.findViewById(R.id.color);
            if (info.color != null) {
                vColor.setVisibility(View.VISIBLE);
                vColor.setBackgroundColor(info.color);
            } else
                vColor.setVisibility(View.GONE);

            TextView tv = (TextView)v.findViewById(R.id.title);
            tv.setText(TextUtils.isEmpty(info.displayName) ? info.url : info.displayName);

            tv = (TextView)v.findViewById(R.id.description);
            if (TextUtils.isEmpty(info.description))
                tv.setVisibility(View.GONE);
            else {
                tv.setVisibility(View.VISIBLE);
                tv.setText(info.description);
            }

            tv = (TextView)v.findViewById(R.id.read_only);
            tv.setVisibility(info.readOnly ? View.VISIBLE : View.GONE);

            tv = (TextView)v.findViewById(R.id.events);
            tv.setVisibility(BooleanUtils.isTrue(info.supportsVEVENT) ? View.VISIBLE : View.GONE);

            tv = (TextView)v.findViewById(R.id.tasks);
            tv.setVisibility(BooleanUtils.isTrue(info.supportsVTODO) ? View.VISIBLE : View.GONE);

            return v;
        }
    }


    /* USER ACTIONS */

    private void deleteAccount() {
        AccountManager accountManager = AccountManager.get(this);

        if (Build.VERSION.SDK_INT >= 22)
            accountManager.removeAccount(account, this, new AccountManagerCallback<Bundle>() {
                @Override
                public void run(AccountManagerFuture<Bundle> future) {
                    try {
                        if (future.getResult().getBoolean(AccountManager.KEY_BOOLEAN_RESULT))
                            finish();
                    } catch(OperationCanceledException|IOException|AuthenticatorException e) {
                        App.log.log(Level.SEVERE, "Couldn't remove account", e);
                    }
                }
            }, null);
        else
            accountManager.removeAccount(account, new AccountManagerCallback<Boolean>() {
                @Override
                public void run(AccountManagerFuture<Boolean> future) {
                    try {
                        if (future.getResult())
                            finish();
                    } catch (OperationCanceledException|IOException|AuthenticatorException e) {
                        App.log.log(Level.SEVERE, "Couldn't remove account", e);
                    }
                }
            }, null);
    }

    private void requestSync() {
        String authorities[] = {
                ContactsContract.AUTHORITY,
                CalendarContract.AUTHORITY,
                TaskProvider.ProviderName.OpenTasks.authority
        };

        for (String authority : authorities) {
            Bundle extras = new Bundle();
            extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);        // manual sync
            extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);     // run immediately (don't queue)
            ContentResolver.requestSync(account, authority, extras);
        }

        Snackbar.make(findViewById(R.id.parent), R.string.account_synchronizing_now, Snackbar.LENGTH_LONG).show();
    }

}
