/*
 * Copyright 2015 Niek Haarman
 *
 * 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 com.nhaarman.triad;

import android.app.Activity;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
import com.nhaarman.triad.Triad.Callback;
import java.util.Iterator;

import static com.nhaarman.triad.Preconditions.checkNotNull;
import static com.nhaarman.triad.Preconditions.checkState;
import static com.nhaarman.triad.TriadIntents.createBackstack;
import static com.nhaarman.triad.TriadIntents.createScreen;

/**
 * This class represents a delegate which can be used to use Triad in any
 * {@link Activity}.
 * <p>
 * When using the {@code TriadDelegate}, you must proxy the following Activity
 * lifecycle methods to it:
 * <ul>
 * <li>{@link #onCreate(Intent)}</li>
 * <li>{@link #onResume()}</li>
 * <li>{@link #onDestroy()}</li>
 * <li>{@link #onBackPressed()}</li>
 * <li>{@link #onActivityResult(int, int, Intent)}</li>
 * </ul>
 *
 * @param <ApplicationComponent> The {@code ApplicationComponent} to use for {@code BasePresenter} creation.
 */
public class TriadDelegate<ApplicationComponent> {

    /**
     * The {@link Activity} instance this {@code TriadDelegate} is bound to.
     */
    @NonNull
    private final Activity activity;

    @NonNull
    private final TransitionAnimator defaultTransitionAnimator;

    @Nullable
    private ApplicationComponent applicationComponent;

    /**
     * The {@link Triad} instance that is used to navigate between {@link Screen}s.
     */
    @Nullable
    private Triad triad;

    @Nullable
    private ViewGroup rootView;

    @Nullable
    private Screen<ApplicationComponent> currentScreen;

    /**
     * An optional {@link OnScreenChangedListener} that is notified of screen changes.
     */
    @Nullable
    private OnScreenChangedListener<ApplicationComponent> onScreenChangedListener;

    private TriadDelegate(
          @NonNull final Activity activity,
          @NonNull final TransitionAnimator transitionAnimator
    ) {
        this.activity = activity;
        defaultTransitionAnimator = transitionAnimator;
    }

    @NonNull
    public Screen<ApplicationComponent> getCurrentScreen() {
        return checkNotNull(currentScreen, "Current screen is null.");
    }

    public void onCreate(@Nullable final Intent intent) {
        checkState(activity.getApplication() instanceof TriadProvider, "Make sure your Application class implements TriadProvider.");
        checkState(activity.getApplication() instanceof ApplicationComponentProvider, "Make sure your Application class implements ApplicationComponentProvider.");

        applicationComponent = ((ApplicationComponentProvider<ApplicationComponent>) activity.getApplication()).getApplicationComponent();
        rootView = (ViewGroup) activity.findViewById(android.R.id.content);

        triad = ((TriadProvider) activity.getApplication()).getTriad();
        triad.setActivity(activity);
        triad.setListener(new MyTriadListener());

        if (triad.getBackstack().size() > 0 || triad.isTransitioning()) {
            triad.showCurrent();
        } else if (intent != null) {
            Screen<?> screen = createScreen(intent);
            if (screen != null) {
                triad.startWith(screen);
            } else {
                Backstack backstack = createBackstack(intent);
                if (backstack != null) {
                    triad.startWith(backstack);
                }
            }
        }
    }

    public void onResume() {
        if (currentScreen != null) {
            currentScreen.attach(rootView);
        }
    }

    public boolean onBackPressed() {
        checkState(triad != null, "Triad is null. Make sure to call TriadDelegate.onCreate().");

        boolean handledByScreen = currentScreen != null && currentScreen.onBackPressed();
        if (handledByScreen) return true;

        boolean handledByTriad = triad.goBack();
        if (handledByTriad) return true;

        boolean backstackIsEmpty = triad.getBackstack().size() == 0;
        return !backstackIsEmpty;
    }

    public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
        checkState(triad != null, "Triad is null. Make sure to call TriadDelegate.onCreate()");

        triad.onActivityResult(requestCode, resultCode, data);
    }

    public void onPause() {
        if (currentScreen != null) {
            currentScreen.detach();
        }
    }

    public void onDestroy() {
        checkState(triad != null, "Triad is null. Make sure to call TriadDelegate.onCreate()");

        if (!activity.isFinishing()) return;

        for (Iterator<Screen<?>> iterator = triad.getBackstack().reverseIterator(); iterator.hasNext(); ) {
            Screen<?> screen = iterator.next();
            screen.onDestroy();
        }
    }

    /**
     * Returns the {@link Triad} instance to be used to navigate between {@link Screen}s.
     */
    @NonNull
    public Triad getTriad() {
        checkState(triad != null, "Triad is null. Make sure to call TriadDelegate.onCreate().");

        return triad;
    }

    /**
     * Sets an {@link OnScreenChangedListener} to be notified of screen changes.
     */
    public void setOnScreenChangedListener(@Nullable final OnScreenChangedListener<ApplicationComponent> onScreenChangedListener) {
        this.onScreenChangedListener = onScreenChangedListener;
    }

    private void onScreenChanged(@NonNull final Screen<ApplicationComponent> screen) {
        if (onScreenChangedListener != null) {
            onScreenChangedListener.onScreenChanged(screen);
        }
    }

    @NonNull
    public static <T> TriadDelegate<T> createFor(@NonNull final Activity activity) {
        return new TriadDelegate(activity, DefaultTransitionAnimator.INSTANCE);
    }

    @NonNull
    public static <T> TriadDelegate<T> createFor(@NonNull final Activity activity, @NonNull final TransitionAnimator defaultTransitionAnimator) {
        return new TriadDelegate(activity, defaultTransitionAnimator);
    }

    private class MyTriadListener implements Triad.Listener<ApplicationComponent> {

        @Override
        public void screenPushed(@NonNull final Screen<ApplicationComponent> pushedScreen) {
            checkState(applicationComponent != null, "ApplicationComponent is null. Make sure to call TriadDelegate.onCreate().");

            pushedScreen.setApplicationComponent(applicationComponent);
            pushedScreen.onCreate();
        }

        @Override
        public void screenPopped(@NonNull final Screen<ApplicationComponent> poppedScreen) {
            poppedScreen.onDestroy();
        }

        @Override
        public void forward(@NonNull final Screen<ApplicationComponent> newScreen, @Nullable final TransitionAnimator animator, @NonNull final Triad.Callback callback) {
            checkState(rootView != null, "Root view is null. Make sure to call TriadDelegate.onCreate().");

            final View oldView = rootView.getChildAt(0);
            final Screen<ApplicationComponent> oldScreen = currentScreen;
            if (oldView != null && oldScreen != null) {
                oldScreen.saveState(oldView);
            }

            currentScreen = newScreen;
            final View newView = newScreen.createView(rootView);

            boolean handled = false;
            if (animator != null) {
                handled = animator.forward(oldView, newView, rootView, new Callback() {
                    @Override
                    public void onComplete() {
                        newScreen.attach(rootView);

                        if (oldScreen != null) {
                            oldScreen.detach();
                        }

                        callback.onComplete();
                    }
                });
            }

            if (!handled) {
                defaultTransitionAnimator.forward(oldView, newView, rootView, new Callback() {
                    @Override
                    public void onComplete() {
                        newScreen.attach(rootView);

                        if (oldScreen != null) {
                            oldScreen.detach();
                        }

                        callback.onComplete();
                    }
                });
            }

            onScreenChanged(newScreen);
        }

        @Override
        public void backward(@NonNull final Screen<ApplicationComponent> newScreen, @Nullable final TransitionAnimator animator, @NonNull final Triad.Callback callback) {
            checkState(rootView != null, "Root view is null. Make sure to call TriadDelegate.onCreate().");

            final Screen<ApplicationComponent> oldScreen = currentScreen;
            currentScreen = newScreen;

            final View oldView = rootView.getChildAt(0);
            final View newView = newScreen.createView(rootView);
            newScreen.restoreState(newView);

            boolean handled = false;
            if (animator != null) {
                handled = animator.backward(oldView, newView, rootView, new Callback() {
                    @Override
                    public void onComplete() {
                        newScreen.attach(rootView);

                        if (oldScreen != null) {
                            oldScreen.detach();
                        }

                        callback.onComplete();
                    }
                });
            }

            if (!handled) {
                defaultTransitionAnimator.backward(oldView, newView, rootView, new Callback() {
                    @Override
                    public void onComplete() {
                        newScreen.attach(rootView);

                        if (oldScreen != null) {
                            oldScreen.detach();
                        }

                        callback.onComplete();
                    }
                });
            }

            onScreenChanged(newScreen);
        }

        @Override
        public void replace(@NonNull final Screen<ApplicationComponent> newScreen, @Nullable final TransitionAnimator animator, @NonNull final Triad.Callback callback) {
            checkState(rootView != null, "Root view is null. Make sure to call TriadDelegate.onCreate().");

            final Screen<ApplicationComponent> oldScreen = currentScreen;
            currentScreen = newScreen;

            final View oldView = rootView.getChildAt(0);
            final View newView = newScreen.createView(rootView);

            boolean handled = false;
            if (animator != null) {
                handled = animator.forward(oldView, newView, rootView, new Callback() {
                    @Override
                    public void onComplete() {
                        newScreen.attach(rootView);

                        if (oldScreen != null) {
                            oldScreen.detach();
                        }

                        callback.onComplete();
                    }
                });
            }

            if (!handled) {
                defaultTransitionAnimator.forward(oldView, newView, rootView, new Callback() {
                    @Override
                    public void onComplete() {
                        newScreen.attach(rootView);

                        if (oldScreen != null) {
                            oldScreen.detach();
                        }

                        callback.onComplete();
                    }
                });
            }

            onScreenChanged(newScreen);
        }
    }
}