package com.reactnativenavigation.viewcontrollers.navigator;

import android.app.Activity;
import android.view.View;
import android.view.ViewGroup;

import com.facebook.react.ReactInstanceManager;
import com.reactnativenavigation.parse.Options;
import com.reactnativenavigation.presentation.OverlayManager;
import com.reactnativenavigation.presentation.Presenter;
import com.reactnativenavigation.presentation.RootPresenter;
import com.reactnativenavigation.react.events.EventEmitter;
import com.reactnativenavigation.utils.CommandListener;
import com.reactnativenavigation.utils.CommandListenerAdapter;
import com.reactnativenavigation.utils.CompatUtils;
import com.reactnativenavigation.utils.Functions.Func1;
import com.reactnativenavigation.viewcontrollers.ChildControllersRegistry;
import com.reactnativenavigation.viewcontrollers.ParentController;
import com.reactnativenavigation.viewcontrollers.ViewController;
import com.reactnativenavigation.viewcontrollers.modal.ModalStack;
import com.reactnativenavigation.viewcontrollers.stack.StackController;

import java.util.Collection;
import java.util.Collections;
import java.util.List;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.coordinatorlayout.widget.CoordinatorLayout;

public class Navigator extends ParentController {

    private final ModalStack modalStack;
    private final OverlayManager overlayManager;
    private final RootPresenter rootPresenter;
    private ViewController root;
    private ViewController previousRoot;
    private final CoordinatorLayout rootLayout;
    private final CoordinatorLayout modalsLayout;
    private final CoordinatorLayout overlaysLayout;
    private ViewGroup contentLayout;
    private Options defaultOptions = new Options();

    @Override
    public void setDefaultOptions(Options defaultOptions) {
        super.setDefaultOptions(defaultOptions);
        this.defaultOptions = defaultOptions;
        modalStack.setDefaultOptions(defaultOptions);
    }

    public Options getDefaultOptions() {
        return defaultOptions;
    }

    CoordinatorLayout getRootLayout() {
        return rootLayout;
    }

    public void setEventEmitter(EventEmitter eventEmitter) {
        modalStack.setEventEmitter(eventEmitter);
    }

    public void setContentLayout(ViewGroup contentLayout) {
        this.contentLayout = contentLayout;
        contentLayout.addView(rootLayout);
        modalsLayout.setVisibility(View.GONE); contentLayout.addView(modalsLayout);
        overlaysLayout.setVisibility(View.GONE); contentLayout.addView(overlaysLayout);
    }

    public Navigator(final Activity activity, ChildControllersRegistry childRegistry, ModalStack modalStack, OverlayManager overlayManager, RootPresenter rootPresenter) {
        super(activity, childRegistry,"navigator" + CompatUtils.generateViewId(), new Presenter(activity, new Options()), new Options());
        this.modalStack = modalStack;
        this.overlayManager = overlayManager;
        this.rootPresenter = rootPresenter;
        rootLayout = new CoordinatorLayout(getActivity());
        modalsLayout = new CoordinatorLayout(getActivity());
        overlaysLayout = new CoordinatorLayout(getActivity());
    }

    public void bindViews() {
        modalStack.setModalsLayout(modalsLayout);
        modalStack.setRootLayout(rootLayout);
        rootPresenter.setRootContainer(rootLayout);
    }

    @NonNull
    @Override
    protected ViewGroup createView() {
        return rootLayout;
    }

    @NonNull
    @Override
    public Collection<ViewController> getChildControllers() {
        return root == null ? Collections.emptyList() : Collections.singletonList(root);
    }

    @Override
    public boolean handleBack(CommandListener listener) {
        if (modalStack.isEmpty() && root == null) return false;
        if (modalStack.isEmpty()) return root.handleBack(listener);
        return modalStack.handleBack(listener, root);
    }

    @Override
    protected ViewController getCurrentChild() {
        return root;
    }

    @Override
    public void destroy() {
        destroyViews();
        super.destroy();
    }

    public void destroyViews() {
        modalStack.destroy();
        overlayManager.destroy(overlaysLayout);
        destroyRoot();
    }

    private void destroyRoot() {
        if (root != null) root.destroy();
        root = null;
    }

    private void destroyPreviousRoot() {
        if (previousRoot != null) previousRoot.destroy();
        previousRoot = null;
    }

    @Override
    public void sendOnNavigationButtonPressed(String buttonId) {

    }

    public void setRoot(final ViewController viewController, CommandListener commandListener, ReactInstanceManager reactInstanceManager) {
        previousRoot = root;
        modalStack.destroy();
        final boolean removeSplashView = isRootNotCreated();
        if (isRootNotCreated()) getView();
        root = viewController;
        rootPresenter.setRoot(root, defaultOptions, new CommandListenerAdapter(commandListener) {
            @Override
            public void onSuccess(String childId) {
                if (removeSplashView) contentLayout.removeViewAt(0);
                destroyPreviousRoot();
                super.onSuccess(childId);
            }
        }, reactInstanceManager);
    }

    public void mergeOptions(final String componentId, Options options) {
        ViewController target = findController(componentId);
        if (target != null) {
            target.mergeOptions(options);
        }
    }

    public void push(final String id, final ViewController viewController, CommandListener listener) {
        applyOnStack(id, listener, stack -> stack.push(viewController, listener));
    }

    public void setStackRoot(String id, List<ViewController> children, CommandListener listener) {
        applyOnStack(id, listener, stack -> stack.setRoot(children, listener));
    }

    public void pop(String id, Options mergeOptions, CommandListener listener) {
        applyOnStack(id, listener, stack -> stack.pop(mergeOptions, listener));
    }

    public void popToRoot(final String id, Options mergeOptions, CommandListener listener) {
        applyOnStack(id, listener, stack -> stack.popToRoot(mergeOptions, listener));
    }

    public void popTo(final String id, Options mergeOptions, CommandListener listener) {
        ViewController target = findController(id);
        if (target != null) {
            target.performOnParentStack(stack -> ((StackController) stack).popTo(target, mergeOptions, listener));
        } else {
            listener.onError("Failed to execute stack command. Stack by " + id + " not found.");
        }
    }

    public void showModal(final ViewController viewController, CommandListener listener) {
        modalStack.showModal(viewController, root, listener);
    }

    public void dismissModal(final String componentId, CommandListener listener) {
        if (isRootNotCreated() && modalStack.size() == 1) {
            listener.onError("Can not dismiss modal if root is not set and only one modal is displayed.");
            return;
        }
        modalStack.dismissModal(componentId, root, listener);
    }

    public void dismissAllModals(Options mergeOptions, CommandListener listener) {
        modalStack.dismissAllModals(root, mergeOptions, listener);
    }

    public void showOverlay(ViewController overlay, CommandListener listener) {
        overlayManager.show(overlaysLayout, overlay, listener);
    }

    public void dismissOverlay(final String componentId, CommandListener listener) {
        overlayManager.dismiss(overlaysLayout, componentId, listener);
    }

    @Nullable
    @Override
    public ViewController findController(String id) {
        ViewController controllerById = super.findController(id);
        if (controllerById == null) {
            controllerById = modalStack.findControllerById(id);
        }
        if (controllerById == null) {
            controllerById = overlayManager.findControllerById(id);
        }
        return controllerById;
    }

    private void applyOnStack(String fromId, CommandListener listener, Func1<StackController> task) {
        ViewController from = findController(fromId);
        if (from != null) {
            if (from instanceof StackController) {
                task.run((StackController) from);
            } else {
                from.performOnParentStack(stack -> task.run((StackController) stack) );
            }
        } else {
            listener.onError("Failed to execute stack command. Stack " + fromId + " not found.");
        }
    }

    private boolean isRootNotCreated() {
        return view == null;
    }

    @RestrictTo(RestrictTo.Scope.TESTS)
    CoordinatorLayout getModalsLayout() {
        return modalsLayout;
    }

    @RestrictTo(RestrictTo.Scope.TESTS)
    CoordinatorLayout getOverlaysLayout() {
        return overlaysLayout;
    }
}