package de.sebse.fuplanner.services.KVV;

import android.content.Context;
import android.util.Pair;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

import androidx.annotation.NonNull;
import de.sebse.fuplanner.services.KVV.types.LoginToken;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.logging.Logger;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;

/**
 * Created by sebastian on 29.10.17.
 */

public class KVV {
    private final Context context;
    private LoginToken lastToken;
    private boolean isLoginPending = true;
    private final ArrayList<LastTokenCallback> updatingList;
    private final HashMap<String, Object> addons = new HashMap<>();
    private final MainActivityListener mListener;
    private Logger log = new Logger(this);

    public KVV(Context context) {
        mListener = (MainActivityListener) context;
        this.context = context;
        this.updatingList = new ArrayList<>();
    }

    public boolean isLoggedIn() {
        log.d(this.lastToken, new KVVLogin(this.context).easyLogin());
        return this.lastToken != null;
    }

    public LoginToken easyLogin() {
        KVVLogin login = new KVVLogin(this.context);
        lastToken = login.easyLogin();
        this.endUpdate();
        return lastToken;
    }

    public void login(@NonNull String username, @NonNull String password, final NetworkCallback<LoginToken> callback, NetworkErrorCallback error) {
        KVVLogin login = new KVVLogin(this.context);
        login.login(username, password, success -> {
            lastToken = success;
            this.endUpdate();
            try {
                login.saveOffline();
            } catch (IOException e) {
                e.printStackTrace();
            }
            callback.onResponse(success);
        }, error1 -> {
            this.endUpdate();
            error.onError(error1);
        });
    }

    public void logout() {
        KVVModuleList modules = (KVVModuleList) addons.get("modules");
        if (modules != null) {
            modules.deleteModulesOffline(this.context);
        }
        invalidate();
    }

    public void invalidate() {
        if (lastToken != null) {
            new KVVLogin(context).deleteOffline();
            lastToken = null;
        }
        addons.clear();
        this.isLoginPending = true;
    }

    public void getModule(String id, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error) {
        getModule(id, callback, error, false);
    }

    public void getModule(String id, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error, boolean forceRefresh) {
        getModulePart(modules -> modules.getModule(id, saveOnCallback(modules, callback, forceRefresh), errorOnCallback(error), forceRefresh));
    }

    public void getModuleList(final NetworkCallback<Modules> callback, final NetworkErrorCallback error) {
        getModuleList(callback, error, false);
    }

    public void getModuleList(final NetworkCallback<Modules> callback, final NetworkErrorCallback error, boolean forceRefresh) {
        getModulePart(modules -> modules.getModuleList(saveOnCallback(modules, callback, forceRefresh), errorOnCallback(error), forceRefresh));
    }

    public void getModuleDetails(Modules.Module module, final NetworkCallback<Pair<Modules.Module, Boolean>> callback, final NetworkErrorCallback error) {
        getModuleDetails(module, callback, error, false);
    }

    public void getModuleDetails(Modules.Module module, final NetworkCallback<Pair<Modules.Module, Boolean>> callback, final NetworkErrorCallback error, boolean forceRefresh) {
        getModulePart(modules -> modules.getModuleDetails(module, saveOnCallback(modules, callback, forceRefresh), errorOnCallback(error), forceRefresh));
    }

    public void getModuleAnnouncements(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error) {
        getModuleAnnouncements(module, callback, error, false);
    }

    public void getModuleAnnouncements(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error, boolean forceRefresh) {
        getModulePart(modules -> modules.getAnnouncements(module, saveOnCallback(modules, callback, forceRefresh), errorOnCallback(error), forceRefresh));
    }

    public void getModuleAssignments(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error) {
        getModuleAssignments(module, callback, error, false);
    }

    public void getModuleAssignments(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error, boolean forceRefresh) {
        getModulePart(modules -> modules.getAssignments(module, saveOnCallback(modules, callback, forceRefresh), errorOnCallback(error), forceRefresh));
    }

    public void getModuleEvents(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error) {
        getModuleEvents(module, callback, error, false);
    }

    public void getModuleEvents(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error, boolean forceRefresh) {
        getModulePart(modules -> modules.getEvents(module, saveOnCallback(modules, callback, forceRefresh), errorOnCallback(error), forceRefresh));
    }

    public void getModuleGradebook(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error) {
        getModuleGradebook(module, callback, error, false);
    }

    public void getModuleGradebook(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error, boolean forceRefresh) {
        getModulePart(modules -> modules.getGradebook(module, saveOnCallback(modules, callback, forceRefresh), errorOnCallback(error), forceRefresh));
    }

    public void getModuleResources(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error) {
        getModuleResources(module, callback, error, false);
    }

    public void getModuleResources(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error, boolean forceRefresh) {
        getModulePart(modules -> modules.getResources(module, saveOnCallback(modules, callback, forceRefresh), errorOnCallback(error), forceRefresh));
    }

    public void getResourceFile(final NetworkCallback<String> callback, final NetworkErrorCallback error, String filename, String url, String moduleName, boolean downloadNew){
        getModulePart(modules -> modules.getResourceFile(callback, errorOnCallback(error),filename, url, moduleName, downloadNew));
    }

    public void testLogin(BooleanFunction callback) {
        getLastToken(lastToken -> {
            TestLogin.testLogin(context, lastToken, success -> callback.apply(true), error -> callback.apply(false));
        });
    }





    private void getModulePart(ModListFunction func) {
        this.getLastToken(token -> {
            KVVModuleList modules = (KVVModuleList) addons.get("modules");
            if (modules == null) {
                modules = new KVVModuleList(this.context, token);
                addons.put("modules", modules);
            }
            func.apply(modules);
        });
    }

    private<T> NetworkCallback<T> saveOnCallback(KVVModuleList modules, NetworkCallback<T> callback, boolean forceRefresh){
        return (success -> {
            try {
                modules.saveModulesOffline(this.context);
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (forceRefresh)
                mListener.onRefreshCompleted(false);
            callback.onResponse(success);
        });
    }

    private NetworkErrorCallback errorOnCallback(NetworkErrorCallback errorCallback){
        return (error -> {
            if (error.getHttpStatus() == 401 || error.getHttpStatus() == 403)
                mListener.loginTokenInvalid(true);
            else
                mListener.onRefreshCompleted(true);
            errorCallback.onError(error);
        });
    }

    @FunctionalInterface
    interface ModListFunction {
        void apply(KVVModuleList mod);
    }

    @FunctionalInterface
    public interface BooleanFunction {
        void apply(boolean isSuccess);
    }

    private void getLastToken(LastTokenCallback lastTokenCallback) {
        if (this.isLoginPending) {
            this.updatingList.add(lastTokenCallback);
        } else {
            lastTokenCallback.onReceived(this.lastToken);
        }
    }

    private void endUpdate() {
        this.isLoginPending = false;
        for (LastTokenCallback s: this.updatingList) {
            s.onReceived(this.lastToken);
        }
    }
}
