package de.sebse.fuplanner.services.kvv.sync;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.SyncResult;
import android.os.Bundle;

import java.util.ArrayList;
import java.util.Iterator;

import androidx.annotation.StringRes;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.kvv.KVV;
import de.sebse.fuplanner.services.kvv.KVVListener;
import de.sebse.fuplanner.services.kvv.types.Announcement;
import de.sebse.fuplanner.services.kvv.types.Assignment;
import de.sebse.fuplanner.services.kvv.types.AssignmentList;
import de.sebse.fuplanner.services.kvv.types.Event;
import de.sebse.fuplanner.services.kvv.types.EventList;
import de.sebse.fuplanner.services.kvv.types.Grade;
import de.sebse.fuplanner.services.kvv.types.Modules;
import de.sebse.fuplanner.services.kvv.types.Resource;
import de.sebse.fuplanner.tools.CustomAccountManager;
import de.sebse.fuplanner.tools.CustomNotificationManager;
import de.sebse.fuplanner.tools.NewAsyncQueue;
import de.sebse.fuplanner.tools.UtilsDate;
import de.sebse.fuplanner.tools.logging.Logger;

public class KVVSyncAdapter extends AbstractThreadedSyncAdapter {
    private KVV mKVV;
    private Logger log = new Logger(this);
    private NewAsyncQueue mQueue = new NewAsyncQueue();

    /**
     * Set up the sync adapter
     */
    public KVVSyncAdapter(Context context, boolean autoInitialize) {
        super(context, autoInitialize);
        init(context);
    }
    /**
     * Set up the sync adapter. This form of the
     * constructor maintains compatibility with Android 3.0
     * and later platform versions
     */
    public KVVSyncAdapter(
            Context context,
            boolean autoInitialize,
            boolean allowParallelSyncs) {
        super(context, autoInitialize, allowParallelSyncs);
        init(context);
    }

    private void init(Context context) {
        mKVV = new KVV(new KVVListener() {

            CustomAccountManager accountManager = null;
            @Override
            public CustomAccountManager getAccountManager() {
                if (accountManager == null)
                    accountManager = new CustomAccountManager(AccountManager.get(context), () -> null);
                return accountManager;
            }
        }, context);
        mQueue.add(() -> {
            mKVV.account().restoreOnlineLogin(bool -> {
                mQueue.next();
            });
        });
    }

    /*
     * Specify the code you want to run in the sync adapter. The entire
     * sync adapter runs in a background thread, so you don't have to set
     * up your own background processing.
     */
    @Override
    public void onPerformSync(
            Account account,
            Bundle extras,
            String authority,
            ContentProviderClient provider,
            SyncResult syncResult) {
        mQueue.add(() -> {
            mKVV.modules().list().recv(success -> {
                Iterator<Modules.Module> iterator = success.latestSemesterIterator();
                while (iterator.hasNext()) {
                    Modules.Module module = iterator.next();
                    final ArrayList<Announcement> announcements = module.announcements;
                    final AssignmentList assignments = module.assignments;
                    final EventList events = module.events;
                    final ArrayList<Grade> gradebook = module.gradebook;
                    final ArrayList<Resource> resources = module.resources;
                    mKVV.modules().details().recv(module, success1 -> {
                        if (success1.second) {
                            sendNotifications(announcements, module.announcements, module.title, Announcement::getTitle, Announcement::getId,
                                    R.string.announcement_updated, R.string.announcement_added, R.string.announcement_removed);
                            sendNotifications(assignments, module.assignments, module.title, Assignment::getTitle, Assignment::getId,
                                    R.string.assignment_updated, R.string.assignment_added, R.string.assignment_removed);
                            sendNotifications(events, module.events, module.title, evt -> evt.getTitle()+" - "+UtilsDate.getModifiedDate(evt.getStartDate()), Event::getId,
                                    R.string.event_updated, R.string.event_added, R.string.event_removed);
                            sendNotifications(gradebook, module.gradebook, module.title, Grade::getItemName, Grade::getItemName,
                                    R.string.gradebook_updated, R.string.gradebook_added, R.string.gradebook_removed);
                            sendNotifications(resources, module.resources, module.title, Resource::getTitle, Resource::getUrl,
                                    R.string.resource_updated, R.string.resource_added, R.string.resource_removed);
                            mQueue.next();
                        }
                    }, msg -> {
                        log.e(msg);
                        mQueue.next();
                    }, true);
                }
            }, msg -> {
                log.e(msg);
                mQueue.next();
            }, true);
        });
    }

    private <T> void sendNotifications(Iterable<T> oldList, Iterable<T> newList, String title, StringInterface<T> titleInterface, StringInterface<T> idInterface, @StringRes int updateRes, @StringRes int addRes, @StringRes int removeRes) {
        if (oldList == null || newList == null) {
            return;
        }
        ArrayList<T> obsoletes = new ArrayList<T>();
        for (T old: oldList) {
            obsoletes.add(old);
        }
        for (T newEntry: newList) {
            boolean found = false;
            for (T oldEntry: oldList) {
                if (idInterface.get(newEntry).equals(idInterface.get(oldEntry))) {
                    found = true;
                    if (newEntry.hashCode() != oldEntry.hashCode()) {
                        CustomNotificationManager.sendNotification(getContext(), getContext().getString(updateRes, title), titleInterface.get(newEntry));
                    }
                    obsoletes.remove(oldEntry);
                    break;
                }
            }
            if (!found) {
                CustomNotificationManager.sendNotification(getContext(), getContext().getString(addRes, title), titleInterface.get(newEntry));
            }
        }
        for (T oldEntry: obsoletes) {
            CustomNotificationManager.sendNotification(getContext(), getContext().getString(removeRes, title), titleInterface.get(oldEntry));
        }
    }

    @FunctionalInterface
    interface StringInterface<T> {
        String get(T element);
    }
}
