package com.reactnativerangersapplogreactnativeplugin;

import android.app.Activity;
import android.util.Log;

import androidx.fragment.app.Fragment;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableMap;

import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;

public class RangersAppLogNavStack {

  private static RangersAppLogNavStack instance;
  private static final String TAG = "RangersAppLogNavStack";
  private Activity pausedActivity;
  private Map<Activity, Set<Fragment>> pausedFragmentMap = new HashMap();

  public static RangersAppLogNavStack getInstance() {
    if (instance == null) {
      instance = new RangersAppLogNavStack();
    }
    return instance;
  }

  private LinkedList<WeakReference> stack = new LinkedList();
  private WeakHashMap<Object, Map> pageInfoMap = new WeakHashMap();
  private Map<String, Object> lastPageInfo;
  private WeakReference lastPageRef;

  private RangersAppLogNavStack() {}

  private Map<String, Object> getReferPageInfo(Object page) {
    if (page == null) {
      return null;
    }

    Map info = pageInfoMap.get(page);
    if (info == null) {
      return null;
    }

    String pageKey = (String) info.get("page_key");
    String pageTitle = (String) info.get("page_title");
    String pageManualKey = (String) info.get("page_manual_key");
    String pagePath = (String) info.get("page_path");
    Integer rootTag = (Integer) info.get("rootTag");

    Map<String, Object> map = new HashMap();
    map.put("refer_page_key", pageKey != null ? pageKey : "");
    map.put("refer_page_title", pageTitle != null ? pageTitle : "");
    map.put("refer_page_manual_key", pageManualKey != null ? pageManualKey : "");
    map.put("referrer_page_path", pagePath != null ? pagePath : "");
    if (rootTag != null) {
      map.put("refer_react_tag", rootTag);
    }
    return map;
  }

  private void updateStack(Object page) {
    while (stack.size() > 0) {
      WeakReference<Object> weakPage = stack.getLast();
      if (weakPage == null) {
        stack.removeLast();
      } else if (weakPage.get() != page) {
        stack.removeLast();
        pageInfoMap.remove(weakPage.get());
      } else {
        break;
      }
    }
  }

  private Object checkPageHide(Activity activity) {
    Object referPage = null;
    if (pausedActivity != null) {
      IReactActivity pausedReactActivity = new ReactActivityDecorator(pausedActivity);
      triggerPageHide(pausedReactActivity.getActivity(), pausedReactActivity.getReactRootView());
      referPage = pausedActivity;
      pausedActivity = null;
    }

    Set<Fragment> pausedFragmentSet = pausedFragmentMap.get(activity);
    if (pausedFragmentSet != null && !pausedFragmentSet.isEmpty()) {
      for (Fragment pausedFragment: pausedFragmentSet) {
        IReactFragment pausedReactFragment = new ReactFragmentDecorator(pausedFragment);
        triggerPageHide(pausedReactFragment.getFragment(), pausedReactFragment.getReactRootView());
        referPage = pausedFragment;
      }
      pausedFragmentSet.clear();
    }

    return referPage;
  }

  private String getPageKey(Object page) {
    final String pkg = page.getClass().getPackage().getName();
    final String cls = page.getClass().getName();
    int packageLen = pkg.length();
    if (!cls.startsWith(pkg) || cls.length() <= packageLen
      || cls.charAt(packageLen) != '.') {
      return cls;
    }
    return cls.substring(packageLen+1);
  }

  private String getPageTitle(Object page) {
    if (page instanceof Activity) {
      return ((Activity) page).getTitle().toString();
    }

    return "";
  }

  private boolean isReactPage(Object page) {
    if (page instanceof Activity) {
      return new ReactActivityDecorator((Activity) page).isReactActivity();
    }

    if (page instanceof Fragment) {
      return new ReactFragmentDecorator((Fragment) page).isReactFragment();
    }

    return false;
  }

  public void onResumed(Activity activity) {
    try {
      // 排除 app 从后台切回的场景
      if (activity == pausedActivity) {
        return;
      }

      // 此时再触发上个页面的 hide
      Object referPage = checkPageHide(pausedActivity);

      IReactActivity reactActivity = new ReactActivityDecorator(activity);
      triggerPageShow(reactActivity.getActivity(), reactActivity.getReactRootView(), referPage);
    } catch (Exception e) {
      Log.e(TAG, "Activity Resumed", e);
    }
  }

  public void onPaused(Activity activity) {
    // 排除 app 切后台的场景
    pausedActivity = activity;
  }

  public void onResumed(Fragment fragment) {
    try {
      IReactFragment reactFragment = new ReactFragmentDecorator(fragment);
      if (!reactFragment.isReactFragment()) {
        return;
      }

      // 排除 app 从后台切回的场景
      Set<Fragment> pausedFragmentSet = pausedFragmentMap.get(fragment.getActivity());
      if (pausedFragmentSet != null && pausedFragmentSet.contains(fragment)) {
        return;
      }

      // 此时再触发上个页面的 hide
      Activity referFragmentActivity = pausedActivity;
      if (pausedFragmentSet != null && !pausedFragmentSet.isEmpty()) {
        referFragmentActivity = fragment.getActivity();
      }
      Object referPage = checkPageHide(referFragmentActivity);

      triggerPageShow(reactFragment.getFragment(), reactFragment.getReactRootView(), referPage);
    } catch (Exception e) {
      Log.e(TAG, "Fragment Resumed", e);
    }
  }

  public void onPaused(Fragment fragment) {
    try {
      IReactFragment reactFragment = new ReactFragmentDecorator(fragment);
      if (!reactFragment.isReactFragment()) {
        return;
      }

      Set<Fragment> pausedFragmentSet = pausedFragmentMap.get(fragment.getActivity());
      if (pausedFragmentSet == null) {
        pausedFragmentSet = new HashSet();
        pausedFragmentMap.put(fragment.getActivity(), pausedFragmentSet);
      }
      pausedFragmentSet.add(fragment);
    } catch (Exception e) {
      Log.e(TAG, "Fragment Paused", e);
    }
  }

  public void updateRNParams(int rootTag, ReadableMap params) {
    Iterator<WeakReference> it = stack.descendingIterator();
    while (it.hasNext()) {
      WeakReference<Object> weakActivity = it.next();
      if (weakActivity == null) {
        continue;
      }

      Object page = weakActivity.get();
      if (page == null) {
        continue;
      }

      Map info = pageInfoMap.get(page);
      if (info == null) {
        continue;
      }

      Integer value = (Integer) info.get("rootTag");
      if (value == null || rootTag != value.intValue()) {
        continue;
      }

      Iterator<Map.Entry<String, Object>> it2 = params.getEntryIterator();
      while (it2.hasNext()) {
        Map.Entry<String, Object> entry = it2.next();
        info.put(entry.getKey(), entry.getValue());
      }
    }
  }

  private void triggerPageShow(Object page, IReactRootView rootView, Object referPage) {
    try {
      //      System.out.println("onResumed");
      Map<String, Object> referPageInfo = getReferPageInfo(referPage);
      boolean isBack = false;
      if (pageInfoMap.containsKey(page)) {
        isBack = true;
        if (lastPageRef != null && lastPageRef.get() != page) {
          referPageInfo = lastPageInfo;
        } else {
          referPageInfo = null;
        }
        updateStack(page);
      }

      if (!isBack) {
        if (rootView == null) {
          Map<String, Object> info = new HashMap();
          info.put("page_key", getPageKey(page));
          info.put("page_title", getPageTitle(page));
          stack.add(new WeakReference(page));
          pageInfoMap.put(page, info);
        }
      }

      if (isReactPage(page)) {
        if (rootView != null) {
          HashMap params = new HashMap();
          params.put("is_back", Boolean.valueOf(isBack));
          String title = getPageTitle(page);
          if (title != null && title.length() > 0) {
            params.put("page_title", title);
          }
          if (referPageInfo != null) {
            params.putAll(referPageInfo);
          }
          rootView.findReactContext(context -> {
            ReactApplicationContext reactContext = (ReactApplicationContext) context;
            RangersAppLogEmitter emitter = reactContext.getNativeModule(RangersAppLogEmitter.class);
            rootView.findRootTag(rootTag -> {
              emitter.sendPageShowEvent(rootView, params);

              Map<String, Object> info = new HashMap();
              info.put("rootTag", rootTag);
              stack.add(new WeakReference(page));
              pageInfoMap.put(page, info);
            });
          });
        }
      }
    } catch (Exception e) {
      Log.e(TAG, "onResumed", e);
    }
  }

  private void triggerPageHide(Object page, IReactRootView rootView) {
    try {
//      System.out.println("onPaused");
      if (rootView != null && isReactPage(page)) {
        rootView.findReactContext(context -> {
          ReactApplicationContext reactContext = (ReactApplicationContext) context;
          RangersAppLogEmitter emitter = reactContext.getNativeModule(RangersAppLogEmitter.class);
          emitter.sendPageHideEvent(rootView, null);
        });
      }
      lastPageInfo = getReferPageInfo(page);
      lastPageRef = new WeakReference(page);
    } catch (Exception e) {
      Log.e(TAG, "onPaused", e);
    }
  }
}
