package com.takwolf.android.fragmentswitcher;

import android.content.Context;
import android.os.Build;
import android.support.annotation.AttrRes;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.annotation.StyleRes;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.widget.FrameLayout;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;

public class FragmentSwitcher extends FrameLayout {

    public static final int NO_KEY = -1;

    @IntRange(from = 0, to = Integer.MAX_VALUE)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Key {}

    private final SparseArray<Fragment> fragments = new SparseArray<>();

    private Adapter adapter;
    private Adapter adapterNotUse;
    private int currentKey = NO_KEY;
    private Integer currentKeyNotUse;

    private boolean firstLayout = true;

    private List<OnFragmentSelectedListener> onFragmentSelectedListenerList;

    public FragmentSwitcher(@NonNull Context context) {
        super(context);
    }

    public FragmentSwitcher(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public FragmentSwitcher(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public FragmentSwitcher(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (firstLayout) {
            firstLayout = false;
            if (adapterNotUse != null) {
                setAdapter(adapterNotUse);
                adapterNotUse = null;
            }
            if (currentKeyNotUse != null) {
                setCurrentFragment(currentKeyNotUse);
                currentKeyNotUse = null;
            }
        }
    }

    public void setAdapter(Adapter adapter) {
        if (getId() == NO_ID) {
            throw new RuntimeException("Must set id before call this function.");
        }
        if (firstLayout) {
            adapterNotUse = adapter;
        } else {
            if (this.adapter != null) {
                setCurrentFragment(NO_KEY);
                for (int i = 0; i < fragments.size(); i++) {
                    int key = fragments.keyAt(i);
                    Fragment fragment = fragments.valueAt(i);
                    this.adapter.getFragmentManager()
                            .beginTransaction()
                            .remove(fragment)
                            .commitNowAllowingStateLoss();
                    this.adapter.onFragmentDetached(key, fragment);
                }
            }
            fragments.clear();
            this.adapter = adapter;
        }
    }

    public int getCurrentFragment() {
        if (firstLayout) {
            return currentKeyNotUse == null ? NO_KEY : currentKeyNotUse;
        } else {
            return currentKey;
        }
    }

    public void setCurrentFragment(int key) {
        if (key < -1) {
            throw new IllegalArgumentException("Key must >= 0 or = -1.");
        }
        if (firstLayout) {
            if (adapterNotUse == null) {
                throw new RuntimeException("Must set adapter before call this function.");
            }
            currentKeyNotUse = key;
        } else {
            if (adapter == null) {
                throw new RuntimeException("Must set adapter before call this function.");
            }
            if (currentKey != key) {
                if (currentKey >= 0) {
                    Fragment fragment = fragments.get(currentKey);
                    adapter.getFragmentManager()
                            .beginTransaction()
                            .hide(fragment)
                            .commitNowAllowingStateLoss();
                    fragment.setUserVisibleHint(false);
                    dispatchOnFragmentSelected(currentKey, fragment, false);
                }
                currentKey = key;
                if (key >= 0) {
                    Fragment fragment = fragments.get(key);
                    if (fragment == null) {
                        fragment = adapter.getFragment(key);
                        fragments.put(key, fragment);
                        adapter.getFragmentManager()
                                .beginTransaction()
                                .add(getId(), fragment)
                                .commitNowAllowingStateLoss();
                        adapter.onFragmentAttached(key, fragment);
                    } else {
                        adapter.getFragmentManager()
                                .beginTransaction()
                                .show(fragment)
                                .commitNowAllowingStateLoss();
                    }
                    fragment.setUserVisibleHint(true);
                    dispatchOnFragmentSelected(key, fragment, true);
                }
            }
        }
    }

    private void dispatchOnFragmentSelected(@Key int key, @NonNull Fragment fragment, boolean selected) {
        if (onFragmentSelectedListenerList != null && !onFragmentSelectedListenerList.isEmpty()) {
            for (OnFragmentSelectedListener onFragmentSelectedListener : onFragmentSelectedListenerList) {
                onFragmentSelectedListener.onFragmentSelected(key, fragment, selected);
            }
        }
    }

    public void addOnFragmentSelectedListener(@NonNull OnFragmentSelectedListener listener) {
        if (onFragmentSelectedListenerList == null) {
            onFragmentSelectedListenerList = new ArrayList<>();
        }
        onFragmentSelectedListenerList.add(listener);
    }

    public void removeOnFragmentSelectedListener(@NonNull OnFragmentSelectedListener listener) {
        if (onFragmentSelectedListenerList != null) {
            onFragmentSelectedListenerList.remove(listener);
        }
    }

    public void clearOnFragmentSelectedListener() {
        if (onFragmentSelectedListenerList != null) {
            onFragmentSelectedListenerList.clear();
        }
    }

    public static abstract class Adapter {

        private final FragmentManager fragmentManager;

        public Adapter(@NonNull FragmentManager fragmentManager) {
            this.fragmentManager = fragmentManager;
        }

        @NonNull
        private FragmentManager getFragmentManager() {
            return fragmentManager;
        }

        @NonNull
        public abstract Fragment getFragment(@Key int key);

        public void onFragmentAttached(@Key int key, @NonNull Fragment fragment) {}

        public void onFragmentDetached(@Key int key, @NonNull Fragment fragment) {}

    }

    public interface OnFragmentSelectedListener {

        void onFragmentSelected(@Key int key, @NonNull Fragment fragment, boolean selected);

    }

}
