package de.c3nav.droid;

import android.Manifest;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.Bitmap;
import android.graphics.Typeface;
import android.graphics.drawable.Icon;
import android.media.MediaPlayer;
import android.net.ParseException;
import android.net.Uri;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.google.android.material.navigation.NavigationView;
import androidx.transition.AutoTransition;
import androidx.transition.Scene;
import androidx.transition.Slide;
import androidx.transition.Transition;
import androidx.transition.TransitionManager;
import androidx.core.app.ActivityCompat;
import androidx.core.app.ActivityOptionsCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.drawable.IconCompat;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.ViewManager;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.webkit.CookieManager;
import android.webkit.HttpAuthHandler;
import android.webkit.JavascriptInterface;
import android.webkit.JsResult;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.VideoView;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

public class MainActivity extends AppCompatActivity
        implements ActivityCompat.OnRequestPermissionsResultCallback {

    //Actions
    public static final String ACTION_CURRENT_LOCATION = "de.c3nav.droid.action.CURRENT_LOCATION";
    public static final String ACTION_CONTROL_PANEL = "de.c3nav.droid.action.CONTROL_PANEL";
    public static final String ACTION_EDITOR = "de.c3nav.droid.action.EDITOR";

    //Request Codes
    public static final int PERM_REQUEST = 1;
    public final static int SETTINGS_REQUEST = 2;

    //Activity State Keys
    public static final String STATE_SPLASHSCREEN = "splashScreenState";
    public static final String STATE_URI = "urlState";

    private WifiManager wifiManager;
    private PowerManager powerManager;
    private DrawerLayout mDrawerLayout;
    private NavigationView navigationView;
    private Menu navigationMenu;
    private Toolbar toolbar;
    private WebView webView;
    private MobileClient mobileClient;
    private Map<String, Integer> lastLevelValues = new HashMap<>();
    private boolean locationPermissionRequested;
    private Boolean locationPermissionCache = null;
    private WifiReceiver wifiReceiver;
    protected CustomSwipeToRefresh swipeLayout;

    private LinearLayout splashScreen;
    private VideoView logoAnimView;
    private LinearLayout logoScreen;
    private TextView logoScreenMessage;
    private EditText authUsername;
    private EditText authPassword;
    private TextView authMessage;
    private Button authLoginButton;

    private CachedUserPermissions cachedUserPermissions;
    private boolean loggedIn = false;
    private boolean inEditor = false;
    private boolean wifiMeasurementRunning = false;
    private boolean hasChangeSet = false;

    private TextView navHeaderTitle;
    private TextView navHeaderSubtitle;

    private MenuItem accountLink;
    private MenuItem editorChangesLink;
    private MenuItem editorDashboardLink;
    private MenuItem editorLink;
    private MenuItem controlPanelLink;

    private boolean logoAnimFinished = false;
    private boolean splashScreenStarted = false;
    private boolean splashScreenPaused = false;
    private boolean splashScreenDone = false;
    private boolean initialPageLoaded = false;
    private boolean splashScreenFadeoutStarted = false;

    private boolean httpAuthNeeded = false;
    private HttpAuthHandler lastAuthHandler = null;
    private boolean logoScreenIsVisible = false;
    private boolean loginScreenIsActive = false;

    private SharedPreferences sharedPrefs;
    private boolean settingKeepOnTop = true;
    private boolean settingKeepScreenOn = true;
    private boolean settingUseWifiLocating = true;
    private Integer settingWifiScanRate = 30;
    private boolean settingDeveloperModeEnabled = false;
    private String settingDeveloperInstanceUrl = "";
    private String settingDeveloperHttpUser = null;
    private String settingDeveloperHttpPassword = null;

    protected Uri instanceBaseUrl;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        setTheme(R.style.AppTheme);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (savedInstanceState != null) {
            splashScreenDone = savedInstanceState.getBoolean(STATE_SPLASHSCREEN, false);
        }

        instanceBaseUrl = Uri.parse(BuildConfig.WEB_URL);

        toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        ActionBar actionBar = getSupportActionBar();
        actionBar.setDisplayShowTitleEnabled(false);
        actionBar.setDisplayHomeAsUpEnabled(true);
        actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);


        Intent intent = getIntent();
        Set<String> intentCategories = intent.getCategories();
        boolean activityStartedFromLauncher = Intent.ACTION_MAIN.equals(intent.getAction())
                && intentCategories != null && intentCategories.contains(Intent.CATEGORY_LAUNCHER);
        boolean activityStartedFromURLHandler = Intent.ACTION_VIEW.equals(intent.getAction())
                && intentCategories != null && (
                        intentCategories.contains(Intent.CATEGORY_DEFAULT) ||
                        intentCategories.contains(Intent.CATEGORY_BROWSABLE) );

        sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
        locationPermissionRequested = sharedPrefs.getBoolean(getString(R.string.location_permission_requested_key), false);
        if (BuildConfig.DEBUG && sharedPrefs.getBoolean(getString(R.string.developer_mode_enabled_key), false)) {
            settingDeveloperInstanceUrl = sharedPrefs.getString(getString(R.string.developer_instance_url_key), "");

            if (!settingDeveloperInstanceUrl.isEmpty()) {
                try {
                    Uri devInstanceUri = Uri.parse(settingDeveloperInstanceUrl);
                    instanceBaseUrl = devInstanceUri;
                } catch (ParseException e) {
                    Log.d("developerSettings", "failed to parse developerInstanceUrl \"" + settingDeveloperInstanceUrl + "\", ignoring");
                }
            }
        }

        mDrawerLayout = findViewById(R.id.drawer_layout);

        navigationView = findViewById(R.id.nav_view);
        navigationView.setNavigationItemSelectedListener(
                new NavigationView.OnNavigationItemSelectedListener() {
                    @Override
                    public boolean onNavigationItemSelected(MenuItem item) {
                        Intent browserIntent;
                        Uri uri;
                        mDrawerLayout.closeDrawers();
                        switch (item.getItemId()) {
                            case R.id.accountLink:
                                if (loggedIn) {
                                    uri = MainActivity.this.instanceBaseUrl.buildUpon().encodedPath("/account/").build();
                                } else {
                                    uri = MainActivity.this.instanceBaseUrl.buildUpon().encodedPath("/login")
                                            .appendQueryParameter("next", Uri.parse(webView.getUrl()).getPath()).build();
                                }
                                MainActivity.this.evaluateJavascript("window.openInModal ? openInModal('" + uri.toString() + "') : window.location='" + uri.toString() + "';");
                                return true;
                            case R.id.editorChangesLink:
                                webView.loadUrl(MainActivity.this.instanceBaseUrl.buildUpon().encodedPath("/editor/changeset/").build().toString());
                                return true;
                            case R.id.editorDashboardLink:
                                webView.loadUrl(MainActivity.this.instanceBaseUrl.buildUpon().encodedPath("/editor/user/").build().toString());
                                return true;
                            case R.id.mapLink:
                                webView.loadUrl(instanceBaseUrl.toString());
                                return true;
                            case R.id.editorLink:
                                webView.loadUrl(MainActivity.this.instanceBaseUrl.buildUpon().encodedPath("/editor/").build().toString());
                                return true;
                            case R.id.controlPanelLink:
                                webView.loadUrl(MainActivity.this.instanceBaseUrl.buildUpon().encodedPath("/control/").build().toString());
                                return true;
                            case R.id.apiLink:
                                browserIntent = new Intent(Intent.ACTION_VIEW, MainActivity.this.instanceBaseUrl.buildUpon().encodedPath("/api/").build());
                                startActivity(browserIntent);
                                return true;
                            case R.id.twitterLink:
                                browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://twitter.com/c3nav/"));
                                startActivity(browserIntent);
                                return true;
                            case R.id.githubLink:
                                browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/c3nav/"));
                                startActivity(browserIntent);
                                return true;
                            case R.id.aboutLink:
                                uri = MainActivity.this.instanceBaseUrl.buildUpon().encodedPath("/about/").build();
                                MainActivity.this.evaluateJavascript("window.openInModal ? openInModal('" + uri.toString() + "') : window.location='" + uri.toString() + "';");
                                return true;
                            case R.id.settingsButton:
                                Intent settingsIntent = new Intent(MainActivity.this, SettingsActivity.class);
                                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                                    ActivityCompat.startActivityForResult(MainActivity.this, settingsIntent, SETTINGS_REQUEST, ActivityOptionsCompat.makeSceneTransitionAnimation(MainActivity.this).toBundle());
                                } else {
                                    startActivityForResult(settingsIntent, SETTINGS_REQUEST);
                                }
                            default:
                                return false;
                        }
                    }
                });

        mobileClient = new MobileClient();

        splashScreen = findViewById(R.id.splashScreen);
        logoAnimView = findViewById(R.id.logoAnimation);

        logoScreen = findViewById(R.id.logoScreen);
        logoScreenMessage = findViewById(R.id.logoScreenMessage);
        authUsername = findViewById(R.id.authUsername);
        authPassword = findViewById(R.id.authPassword);
        authMessage = findViewById(R.id.authMessage);
        authLoginButton = findViewById(R.id.authLoginButton);

        authLoginButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                MainActivity.this.handleLoginScreenSubmitt();
            }
        });

        authPassword.setOnEditorActionListener(new EditText.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
                if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_ACTION_SEND
                        || keyEvent != null && keyEvent.getAction() == KeyEvent.ACTION_DOWN && keyEvent.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
                    MainActivity.this.handleLoginScreenSubmitt();
                    return true;
                }
                return false;
            }
        });

        View headerLayout = navigationView.getHeaderView(0);
        navHeaderTitle = headerLayout.findViewById(R.id.title);
        navHeaderSubtitle = headerLayout.findViewById(R.id.subtitle);

        navigationMenu = navigationView.getMenu();
        accountLink = navigationMenu.findItem(R.id.accountLink);
        editorChangesLink = navigationMenu.findItem(R.id.editorChangesLink);
        editorDashboardLink = navigationMenu.findItem(R.id.editorDashboardLink);
        editorLink = navigationMenu.findItem(R.id.editorLink);
        controlPanelLink = navigationMenu.findItem(R.id.controlPanelLink);

        webView = findViewById(R.id.webView);
        swipeLayout = findViewById(R.id.swipe_container);

        wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
        wifiReceiver = new WifiReceiver();

        powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);

        if (!splashScreenDone && activityStartedFromLauncher) {
            mDrawerLayout.closeDrawers();
            mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
            showSplash();
        } else {
            skipSplash();
        }

        swipeLayout.setColorSchemeResources(R.color.colorPrimary);
        swipeLayout.setEnabled(true);
        swipeLayout.setOnRefreshListener(
            new SwipeRefreshLayout.OnRefreshListener() {
                @Override
                public void onRefresh() {
                    webView.reload();
                }
            }
        );

        webView.getSettings().setSupportZoom(false);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.getSettings().setUserAgentString(String.format(Locale.ENGLISH, "c3navClient/Android/%d/%d", BuildConfig.VERSION_CODE, Build.VERSION.SDK_INT));
        webView.addJavascriptInterface(mobileClient, "mobileclient");

        webView.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
                swipeLayout.setRefreshing(false);
                initialPageLoaded = true;
                Log.d("c3navWebView", "loading ended");
                maybeEndSplash();
                maybeHideLoginScreen();
            }

            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                super.onPageStarted(view, url, favicon);
                swipeLayout.setRefreshing(true);
                if (isWifiMeasurementRunning()) setWifiMeasurementRunning(false);
                Log.d("c3navWebView", "loading started");
            }

            private boolean shouldOverrideUrl(final Uri uri) {
                if (MainActivity.this.instanceBaseUrl.getHost().equals(uri.getHost())) {
                    List<String> pathSegments = uri.getPathSegments();
                    if (pathSegments.isEmpty() || !pathSegments.get(0).equals("api")) {
                        return false;
                    }
                }
                ExternalUrlDialog dialog = new ExternalUrlDialog();
                Bundle args = new Bundle();
                args.putString(ExternalUrlDialog.ARG_URL, uri.toString());
                dialog.setArguments(args);
                dialog.show(getSupportFragmentManager(), "externalUrl");
                return true;
            }

            @SuppressWarnings("deprecation")
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                return (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && shouldOverrideUrl(Uri.parse(url))) || super.shouldOverrideUrlLoading(view, url);
            }

            @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
                return shouldOverrideUrl(request.getUrl()) || super.shouldOverrideUrlLoading(view, request);
            }

            @Override
            public void onReceivedHttpAuthRequest(WebView view, final HttpAuthHandler handler, String host, String realm) {
                if (BuildConfig.DEBUG && settingDeveloperModeEnabled
                        && settingDeveloperHttpUser != null && !settingDeveloperHttpUser.isEmpty()
                        && settingDeveloperHttpPassword != null && !settingDeveloperHttpPassword.isEmpty()) {
                    handler.proceed(settingDeveloperHttpUser, settingDeveloperHttpPassword);
                } else {
                    httpAuthNeeded = true;
                    lastAuthHandler = handler;
                    maybeShowLoginScreen();
                }
            }
        });
        webView.setWebChromeClient(new WebChromeClient() {
            @Override
            public boolean onJsBeforeUnload(WebView view, String url, String message, final JsResult result) {
                AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(MainActivity.this);
                alertDialogBuilder.setMessage(message);
                alertDialogBuilder.setCancelable(false)
                        .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int id) {
                                result.confirm();
                                dialog.dismiss();
                            }
                        });

                alertDialogBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.cancel();
                        result.cancel();
                        swipeLayout.setRefreshing(false);
                    }
                });
                alertDialogBuilder.show();
                return true;
            }
        });

        updateSettings();
        hasLocationPermission(); //initialize locationPermissionCache
        cachedUserPermissions = new CachedUserPermissions();
        updateDynamicShortcuts();

        String url_to_call = instanceBaseUrl.toString();
        Uri data = intent.getData();

        if (intent.getAction().equals(ACTION_CONTROL_PANEL)) {
            url_to_call = instanceBaseUrl.buildUpon().appendPath("control").build().toString();
        } else if (intent.getAction().equals(ACTION_CURRENT_LOCATION)) {
            mobileClient.setCurrentLocationRequested(true);
        } else if (intent.getAction().equals(ACTION_EDITOR)) {
            url_to_call = instanceBaseUrl.buildUpon().appendPath("editor").build().toString();
        } else if (savedInstanceState != null && savedInstanceState.getString(STATE_URI) != null) {
            Uri savedUri = Uri.parse(savedInstanceState.getString(STATE_URI));
            if (savedUri.getHost().equals(instanceBaseUrl.getHost())) {
                url_to_call = savedUri.toString();
            }
        } else if (data != null) {
            Uri.Builder tmp_uri = data.buildUpon();
            tmp_uri.scheme("https");
            List<String> pathSegments = data.getPathSegments();
            if (!pathSegments.isEmpty() && pathSegments.get(0).equals("embed")) {
                tmp_uri.path("");
                for (String pathSegment : pathSegments) {
                    if (pathSegment.equals("embed")) continue;
                    tmp_uri.appendPath(pathSegment);
                }
                Log.d("c3navIntendUriBuilder", "converted embed URL to normal URL, orignal URL: " + data.toString());
            }
            url_to_call = tmp_uri.build().toString();
            Log.d("c3navIntendUriBuilder", "final url: " + url_to_call);
        }

        CookieManager.getInstance().setCookie(instanceBaseUrl.toString(), "c3nav_language=" + Locale.getDefault().getLanguage());
        webView.loadUrl(url_to_call);

    }

    protected void updateSettings() {
        settingKeepOnTop = sharedPrefs.getBoolean(getString(R.string.keep_on_top_key), true);
        settingKeepScreenOn = sharedPrefs.getBoolean(getString(R.string.keep_screen_on_key), true);
        if (settingUseWifiLocating != sharedPrefs.getBoolean(getString(R.string.use_wifi_locating_key), true)) {
            Log.d("c3nav-settings", "useWifiLocationSetting updated");
            settingUseWifiLocating = sharedPrefs.getBoolean(getString(R.string.use_wifi_locating_key), true);
            if (settingUseWifiLocating) {
                startScan();
            } else {
                mobileClient.setNearbyStations(null);
            }
            MainActivity.this.evaluateJavascript("nearby_stations_available();");
        }

        settingWifiScanRate = Integer.parseInt(sharedPrefs.getString(getString(R.string.wifi_scan_rate_key), "30"));

        setWindowFlags();

        if (BuildConfig.DEBUG) {
            settingDeveloperModeEnabled = sharedPrefs.getBoolean(getString(R.string.developer_mode_enabled_key), false);
            String newSettingDeveloperInstanceUrl = "";
            if (settingDeveloperModeEnabled) {
                newSettingDeveloperInstanceUrl = sharedPrefs.getString(getString(R.string.developer_instance_url_key), "");
                settingDeveloperHttpUser = sharedPrefs.getString(getString(R.string.developer_http_user_key), "");
                settingDeveloperHttpPassword = sharedPrefs.getString(getString(R.string.developer_http_password_key), "");
            } else {
                settingDeveloperHttpUser = null;
                settingDeveloperHttpPassword = null;
            }

            if (!settingDeveloperInstanceUrl.equals(newSettingDeveloperInstanceUrl)) {
                recreate();
            }
        }
    }

    protected void showSplash() {
        splashScreenStarted = true;
        logoAnimView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
            @Override
            public boolean onError(MediaPlayer mp, int what, int extra) {
                logoAnimFinished = true;
                if(!splashScreenDone) skipSplash();
                return true;
            }
        });
        logoAnimView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                logoAnimFinished = true;

                // keep the logo for 500 more ms before checking whether the webview is loaded (it's probably not)
                final Handler handler = new Handler();
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        maybeEndSplash();
                    }
                }, 500);
                maybeShowLoginScreen();
            }
        });
        playSplashVideo();
    }

    protected void playSplashVideo() {
        logoAnimView.setVideoURI(Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.logoanim));
        logoAnimView.start();
    }

    protected boolean maybeEndSplash() {
        if (splashScreenDone || !logoAnimFinished || !initialPageLoaded) {
            return false;
        }
        endSplash();
        return true;
    }

    protected void endSplash() {
        AutoTransition mySwapTransition = new AutoTransition();
        mySwapTransition.addListener(new Transition.TransitionListener() {
            private boolean transitionEnded = false;

            @Override
            public void onTransitionStart(Transition transition) { }

            @Override
            public void onTransitionEnd(Transition transition) {
                // remove animated logo because the stuff in the background is still visible
                fadeoutSplashScreen();
            }

            @Override
            public void onTransitionCancel(Transition transition) {}

            @Override
            public void onTransitionPause(Transition transition) { }

            @Override
            public void onTransitionResume(Transition transition) { }
        });

        TransitionManager.go(new Scene((ViewGroup) splashScreen.getParent()), mySwapTransition);
        splashScreen.setGravity(Gravity.TOP|Gravity.CENTER);
        logoAnimView.getLayoutParams().height = (int) toolbar.getHeight();
        logoAnimView.requestLayout();
    }

    protected void fadeoutSplashScreen() {
        if (splashScreenFadeoutStarted) return;
        splashScreenFadeoutStarted = true;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
            // center of the clipping circle
            int cx = (int) swipeLayout.getWidth()/2;
            int cy = (int) toolbar.getHeight()/2;

            // webview dimensions
            int width = swipeLayout.getWidth();
            int height = swipeLayout.getHeight();

            // get the final radius for the clipping circle
            float finalRadius = (float) Math.hypot(width - cx, height - cy);

            // create the animation
            Animator anim = ViewAnimationUtils.createCircularReveal(swipeLayout, cx, cy, 0f, finalRadius);
            anim.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    Log.d("c3nav", "splash animation done");
                    mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
                    splashScreenDone = true;
                    unloadSplashVideo();
                    checkLocationPermission();
                }
            });
            splashScreen.setVisibility(View.GONE);
            anim.start();
        } else {
            splashScreen.animate()
                    .alpha(0f)
                    .setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime))
                    .setListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            splashScreen.setVisibility(View.GONE);
                            splashScreen.setAlpha(1f);
                        }
                    });
        }
    }

    protected void unloadSplashVideo() {
        if (logoAnimView != null) {
            ((ViewManager) logoAnimView.getParent()).removeView(logoAnimView);
            logoAnimView = null;
        }
    }

    protected void skipSplash(boolean checkLogin) {
        mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
        splashScreenDone = true;
        splashScreen.setVisibility(View.GONE);
        unloadSplashVideo();
        if (checkLogin) maybeShowLoginScreen();
        checkLocationPermission();
    }

    protected void skipSplash() {
        skipSplash(true);
    }

    protected void showLogoScreen() {
        if (!splashScreenDone) {
            logoScreen.setVisibility(View.VISIBLE);
            skipSplash(false);
        } else if (logoScreenIsVisible || loginScreenIsActive) {
            TransitionManager.go(new Scene((ViewGroup) logoScreen.getParent()), new AutoTransition());
        } else {
            TransitionManager.go(new Scene((ViewGroup) logoScreen.getParent()), new Slide(Gravity.RIGHT));
        }
        logoScreen.setVisibility(View.VISIBLE);
        logoScreenMessage.setVisibility(View.GONE);
        authUsername.setVisibility(View.GONE);
        authPassword.setVisibility(View.GONE);
        authMessage.setVisibility(View.GONE);
        authLoginButton.setVisibility(View.GONE);
        logoScreenIsVisible = true;
    }

    protected void showLoginScreen(String message) {
        if (!logoScreenIsVisible) showLogoScreen();
        logoScreenMessage.setText(R.string.auth_title);
        authMessage.setText(message);
        authUsername.setEnabled(true);
        authPassword.setEnabled(true);
        authLoginButton.setEnabled(true);

        if (loginScreenIsActive) return;

        logoScreen.postDelayed(new Runnable() {
            @Override
            public void run() {
                TransitionManager.go(new Scene((ViewGroup) logoScreen.getParent()), new AutoTransition());
                logoScreenMessage.setVisibility(View.VISIBLE);
                authUsername.setVisibility(View.VISIBLE);
                authPassword.setVisibility(View.VISIBLE);
                authMessage.setVisibility(View.VISIBLE);
                authLoginButton.setVisibility(View.VISIBLE);
            }
        }, 500);
        loginScreenIsActive = true;
    }

    protected void showLoginScreen(int message) {
        showLoginScreen(getString(message));
    }

    protected void showLoginScreen() {
        showLoginScreen("");
    }

    protected void hideLoginScreen() {
        TransitionManager.go(new Scene((ViewGroup) logoScreen.getParent()), new Slide(Gravity.LEFT));
        logoScreen.setVisibility(View.GONE);
        logoScreenMessage.setVisibility(View.GONE);
        authUsername.setVisibility(View.GONE);
        authPassword.setVisibility(View.GONE);
        authMessage.setVisibility(View.GONE);
        authLoginButton.setVisibility(View.GONE);
        loginScreenIsActive = false;
        logoScreenIsVisible = false;
    }

    protected void maybeShowLoginScreen() {
        if (httpAuthNeeded && (splashScreenDone || logoAnimFinished)) {
            if (loginScreenIsActive) {
                showLoginScreen(R.string.auth_error);
            } else {
                showLoginScreen();
            }
        }
    }

    protected void maybeHideLoginScreen() {
        if (loginScreenIsActive && initialPageLoaded) {
            hideLoginScreen();
        }
    }

    protected void handleLoginScreenSubmitt() {
        if (lastAuthHandler != null) {
            lastAuthHandler.proceed(authUsername.getText().toString(), authPassword.getText().toString());
            lastAuthHandler = null;
            authLoginButton.setEnabled(false);
            authUsername.setEnabled(false);
            authPassword.setEnabled(false);
        } else {
            hideLoginScreen();
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle savedInstanceState) {
        savedInstanceState.putBoolean(STATE_SPLASHSCREEN, splashScreenDone);
        savedInstanceState.putString(STATE_URI, webView.getUrl());
        super.onSaveInstanceState(savedInstanceState);
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d("lifecycleEvents", "onResume called");
        evaluateJavascript("if (mobileclientOnResume) {mobileclientOnResume()};");
        registerReceiver(wifiReceiver, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
        if(checkLocationPermission(false, true)) startScan();
        if (splashScreenPaused && !splashScreenDone) {
            skipSplash();
        }
    }

    @Override
    protected void onPause() {
        Log.d("lifecycleEvents", "onPause called");
        unregisterReceiver(wifiReceiver);
        if (splashScreenStarted && !splashScreenDone) {
            splashScreenPaused = true;
        }
        evaluateJavascript("if (mobileclientOnPause) {mobileclientOnPause()};");
        super.onPause();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        Log.d("lifecycleEvents", "onActivityResult called");

        switch (requestCode) {
            case SETTINGS_REQUEST:
                Log.d("onActivityResult", "settings activity finished with result code " + resultCode);
                updateSettings();
                break;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case PERM_REQUEST:
                if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    locationPermissionCache = Boolean.TRUE;
                    if (!settingUseWifiLocating) {
                        SharedPreferences.Editor editor = sharedPrefs.edit();
                        editor.putBoolean(getString(R.string.use_wifi_locating_key), true);
                        editor.apply();
                        settingUseWifiLocating = true;
                    }
                    // let the js know we have location permission now and start a single scan
                    evaluateJavascript("nearby_stations_available();");
                    startScan();
                }
        }
    }

    protected boolean isLocationPermissionRequested() {
        return locationPermissionRequested;
    }

    private void setLocationPermissionRequested(boolean locationPermissionRequested) {
        this.locationPermissionRequested = locationPermissionRequested;
        SharedPreferences.Editor editor = sharedPrefs.edit();
        editor.putBoolean(getString(R.string.location_permission_requested_key), locationPermissionRequested);
        editor.apply();
    }

    protected boolean checkLocationPermission(boolean requestPermission, boolean ignoreCache) {
        if (!settingUseWifiLocating) {
            if (requestPermission) {
                new WifiLocationDisabledDialog().show(getSupportFragmentManager(), null);
            } else {
                return false;
            }
        }


        if (ignoreCache || locationPermissionCache == null) {
            int permissionCheck = ContextCompat.checkSelfPermission(MainActivity.this,
                    Manifest.permission.ACCESS_FINE_LOCATION);
            locationPermissionCache = new Boolean(permissionCheck == PackageManager.PERMISSION_GRANTED);
        }

        if (!locationPermissionCache.booleanValue()) {
            if (requestPermission) {
                ActivityCompat.requestPermissions(MainActivity.this,
                        new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                        PERM_REQUEST);
                this.setLocationPermissionRequested(true);
            }
            return false;
        }
        return settingUseWifiLocating;
    }

    protected boolean checkLocationPermission(boolean requestPermission) {
        return checkLocationPermission(requestPermission, false);
    }

    protected boolean checkLocationPermission() {
        return checkLocationPermission(!this.isLocationPermissionRequested() && splashScreenDone);
    }

    protected boolean hasLocationPermission() {
        return checkLocationPermission(false);
    }

    @SuppressWarnings("deprecation")
    protected void startScan() {
        if (!settingUseWifiLocating) return;
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            if (!powerManager.isScreenOn()) return;
        } else {
            if (!powerManager.isInteractive()) return;
        }
        if (!hasLocationPermission()) return;
        Log.d("c3navWifiScanner", "startScan triggered");
        wifiManager.startScan();
    }

    protected void setInEditor(boolean inEditor) {
        boolean inEditorOld = this.inEditor;
        this.inEditor = inEditor;

        if (inEditorOld != this.inEditor) {
            setWindowFlags();
        }
    }

    public boolean isInEditor() {
        return this.inEditor;
    }

    protected void setWifiMeasurementRunning(boolean wifiMeasurementRunning) {
        boolean wifiMeasurementRunningOld = this.wifiMeasurementRunning;
        this.wifiMeasurementRunning = wifiMeasurementRunning;

        if (wifiMeasurementRunningOld != this.wifiMeasurementRunning) {
            setWindowFlags();
        }
    }

    public boolean isWifiMeasurementRunning() {
        return this.wifiMeasurementRunning;
    }

    class MobileClient {
        private JSONArray nearbyStations;
        private boolean currentLocationRequested;

        @JavascriptInterface
        public String getNearbyStations() {
            if (this.nearbyStations != null) {
                return this.nearbyStations.toString();
            } else {
                return "[]";
            }
        }

        public void setNearbyStations(JSONArray nearbyStations) {
            this.nearbyStations = nearbyStations;
        }

        @JavascriptInterface
        public int getAppVersionCode() {
            return BuildConfig.VERSION_CODE;
        }

        @JavascriptInterface
        public void scanNow() {
            startScan();
        }

        @JavascriptInterface
        public boolean hasLocationPermission() {
            return MainActivity.this.hasLocationPermission();
        }

        @JavascriptInterface
        public boolean checkLocationPermission(boolean requestPermission) {
            return MainActivity.this.checkLocationPermission(requestPermission);
        }

        @JavascriptInterface
        public boolean checkLocationPermission() {
            return MainActivity.this.checkLocationPermission();
        }

        @JavascriptInterface
        public void shareUrl(String url) {
            Intent i = new Intent(Intent.ACTION_SEND);
            i.setType("text/plain");
            i.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.app_name));
            i.putExtra(Intent.EXTRA_TEXT, url);
            startActivity(Intent.createChooser(i, getString(R.string.share)));
        }

        @JavascriptInterface
        public void createShortcut(String url, String title) {
            Intent shortcutIntent = new Intent(getApplicationContext(),
                    MainActivity.class);
            shortcutIntent.setAction(Intent.ACTION_MAIN);
            shortcutIntent.setData(Uri.parse(url));

            if (!ShortcutManagerCompat.isRequestPinShortcutSupported(getApplicationContext())) {
                Toast.makeText(MainActivity.this, R.string.shortcut_not_supported, Toast.LENGTH_LONG).show();
            }

            final ShortcutInfoCompat shortcutInfo = new ShortcutInfoCompat.Builder(getApplicationContext(), url)
                    .setShortLabel(title)
                    .setLongLabel(title)
                    .setIcon(IconCompat.createWithResource(getApplicationContext(), R.mipmap.ic_launcher_36c3))
                    .setIntent(shortcutIntent)
                    .build();

            ShortcutManagerCompat.requestPinShortcut(getApplicationContext(), shortcutInfo, null);

            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
                Toast.makeText(MainActivity.this, R.string.shortcut_created, Toast.LENGTH_SHORT).show();
            }

        }

        @JavascriptInterface
        public void setUserData(String data) {
            Log.d("setUserData", data);

            final JSONObject user_data;
            try {
                user_data = new JSONObject(data);
            } catch (JSONException e) {
                Log.e("c3nav", "invalid JSON in setUserData: " + data, e);
                return;
            }

            cachedUserPermissions.updateFromUserData(user_data);

            runOnUiThread(new Runnable() {

                @Override
                public void run() {
                    try {
                        loggedIn = user_data.getBoolean("logged_in");
                    } catch (JSONException e) {
                        Log.e("c3navUserData", "missing required key logged_in in user data json object", e);
                        return;
                    }

                    if (loggedIn) {
                        navHeaderTitle.setText(user_data.optString("title"));
                        navHeaderTitle.setTypeface(null, Typeface.NORMAL);
                        navHeaderSubtitle.setText(user_data.isNull("subtitle") ? "" : user_data.optString("subtitle"));
                        accountLink.setTitle(R.string.your_account);
                    } else {
                        navHeaderTitle.setText(R.string.not_logged_in);
                        navHeaderTitle.setTypeface(null, Typeface.ITALIC);
                        navHeaderSubtitle.setText("");
                        accountLink.setTitle(R.string.login);
                    }

                    editorLink.setVisible(user_data.optBoolean("allow_editor"));
                    controlPanelLink.setVisible(user_data.optBoolean("allow_control_panel"));

                    hasChangeSet = user_data.optBoolean("has_changeset");
                    editorChangesLink.setEnabled(hasChangeSet);

                    boolean directEditing = user_data.optBoolean("direct_editing");
                    String changesCountDisplay = user_data.optString("changes_count_display");
                    SpannableString changesCountDisplaySpann = new SpannableString(user_data.optString("changes_count_display"));
                    if (directEditing)
                        changesCountDisplaySpann.setSpan(new ForegroundColorSpan(ContextCompat.getColor(getApplicationContext(), R.color.colorWarning)), 0, changesCountDisplay.length(), 0);
                    editorChangesLink.setTitle(changesCountDisplaySpann);
                    editorChangesLink.setIcon(directEditing ? R.drawable.ic_assignment_turned_in : R.drawable.ic_assignment);

                    editorDashboardLink.setVisible(loggedIn);
                    navigationMenu.setGroupVisible(R.id.editorNav, !changesCountDisplay.isEmpty());
                    MainActivity.this.setInEditor(!changesCountDisplay.isEmpty());


                }
            });

        }

        @JavascriptInterface
        public void wificollectorStart() {
            Log.d("c3nav", "wificollector started");
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    setWifiMeasurementRunning(true);
                }
            });
        }

        @JavascriptInterface
        public void wificollectorStop() {
            Log.d("c3nav", "wificollector stopped");
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    setWifiMeasurementRunning(false);
                }
            });
        }

        public void setCurrentLocationRequested(boolean currentLocationRequested) {
            this.currentLocationRequested = currentLocationRequested;
        }

        @JavascriptInterface
        public boolean isCurrentLocationRequested() {
            if (this.currentLocationRequested) {
                this.currentLocationRequested = false;
                return true;
            }
            return false;
        }

        @JavascriptInterface
        public void currentLocationRequesteFailed() {
            Toast.makeText(MainActivity.this, R.string.current_location_request_failed, Toast.LENGTH_SHORT).show();
        }

        @JavascriptInterface
        public int getWifiScanRate() {
            return Integer.parseInt(sharedPrefs.getString(getString(R.string.wifi_scan_rate_key), "30"));
        }
    }

    private void evaluateJavascript(String script, ValueCallback<String> resultCallback) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            webView.evaluateJavascript(script, resultCallback);
        } else {
            webView.loadUrl("javascript:"+script);
        }
    }

    private void evaluateJavascript(String script) {
        evaluateJavascript(script, null);
    }

    /*@Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.main, menu);
        return false;
    }*/

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                mDrawerLayout.openDrawer(GravityCompat.START);
                return true;
            case R.id.share:
                Intent i = new Intent(Intent.ACTION_SEND);
                i.setType("text/plain");
                i.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.app_name));
                i.putExtra(Intent.EXTRA_TEXT, webView.getUrl());
                startActivity(Intent.createChooser(i, getString(R.string.share)));
                return true;
            case R.id.refresh:
                webView.loadUrl(instanceBaseUrl.toString());
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    class WifiReceiver extends BroadcastReceiver {
        public void onReceive(Context c, Intent intent) {

            if (!checkLocationPermission()) return;

            List<ScanResult> wifiList = wifiManager.getScanResults();
            JSONArray ja = new JSONArray();
            Map<String, Integer> newLevelValues = new HashMap<String, Integer>();
            for (ScanResult result : wifiList) {
                JSONObject jo = new JSONObject();
                try {
                    jo.put("bssid", result.BSSID);
                    jo.put("ssid", result.SSID);
                    jo.put("level", result.level);
                    jo.put("frequency", result.frequency);

                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                        if (SystemClock.elapsedRealtime() - result.timestamp / 1000 > 1000) {
                            continue;
                        }
                        jo.put("last", SystemClock.elapsedRealtime() - result.timestamp / 1000);
                    } else {
                        // Workaround for older devices: If the signal level did not change
                        // at all since the last scan, we will assume that it is a cached
                        // value and should not be used.
                        newLevelValues.put(result.BSSID, result.level);
                        if (lastLevelValues.containsKey(result.BSSID) && lastLevelValues.get(result.BSSID) == result.level) {
                            Log.d("scan result", "Discard " + result.BSSID + " because level did not change");
                            continue;
                        }
                    }
                    ja.put(jo);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
            Log.d("scan result", ja.toString());
            mobileClient.setNearbyStations(ja);
            lastLevelValues = newLevelValues;

            webView.post(new Runnable() {
                @Override
                public void run() {
                    MainActivity.this.evaluateJavascript("nearby_stations_available();");
                }
            });
        }
    }

    @Override
    public void onBackPressed() {
        if (webView.canGoBack()) {
            webView.goBack();
        } else {
            finish();
        }
    }

    @Override
    public void onAttachedToWindow() {
        setWindowFlags();
    }

    @SuppressWarnings("deprecation")
    private void setWindowFlags() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1) {
            if ((settingKeepOnTop && !isInEditor()) || settingKeepScreenOn && isWifiMeasurementRunning()) {
                getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);

            } else {
                getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
            }
        } else {
            setShowWhenLocked((settingKeepOnTop && !isInEditor()) || settingKeepScreenOn && isWifiMeasurementRunning());
        }

        if (settingKeepScreenOn && isWifiMeasurementRunning()) {
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        } else {
            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        }
    }

    public Intent getShortcutIntet(@NonNull String action, Uri data) {
        Intent shortcutIntent = new Intent(getApplicationContext(), MainActivity.class);
        shortcutIntent.setAction(action);
        if (data != null) shortcutIntent.setData(data);
        return shortcutIntent;
    }

    @RequiresApi(Build.VERSION_CODES.N_MR1)
    public ShortcutInfo getShortcutInfo(@NonNull String id, @NonNull String shortLabel, String longLabel, @NonNull Icon icon, @NonNull String action, Uri data) {
        ShortcutInfo.Builder shortcutInfoBuilder = new ShortcutInfo.Builder(getApplicationContext(), action)
                .setShortLabel(shortLabel)
                .setIcon(icon)
                .setIntent(getShortcutIntet(action, null));
        if (longLabel != null) shortcutInfoBuilder.setLongLabel(longLabel);
        return shortcutInfoBuilder.build();
    }

    @RequiresApi(Build.VERSION_CODES.N_MR1)
    public ShortcutInfo getShortcutInfo(@NonNull String id, @NonNull int shortLabelRessource, int longLabelRessource, @NonNull int iconRessource, @NonNull String action, Uri data) {
        String shortLabel = getString(shortLabelRessource);
        String longLabel = (longLabelRessource != -1) ? getString(longLabelRessource) : null;
        Icon icon = Icon.createWithResource(getApplicationContext(), iconRessource);
        return getShortcutInfo(id,shortLabel, longLabel, icon, action, data);
    }

    public void updateDynamicShortcuts() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) return;

        final String CONTROL_PANEL_SHORTCUT_ID = "controlPanel";
        final String EDITOR_SHORTCUT_ID = "editor";
        final String SHORTCUT_PROGRAM_VERSION_KEY = "shortcutsLastUpdatedByVersion";

        boolean updateForced = false;

        ShortcutManager shortcutManager = getApplicationContext().getSystemService(ShortcutManager.class);

        List<ShortcutInfo> installedDynamicShortcuts = shortcutManager.getDynamicShortcuts();
        Set<String> installedDynamicShortcutsIDs = new HashSet<String>();
        Map<String,ShortcutInfo> currentDynamicShortcuts = new HashMap<String,ShortcutInfo>();

        for (ShortcutInfo shortcutInfo : installedDynamicShortcuts) {
            installedDynamicShortcutsIDs.add(shortcutInfo.getId());
        }

        // if shortcuts have been last updated by a different version recreate all, otherwise only changed.
        if (BuildConfig.VERSION_CODE != sharedPrefs.getInt(SHORTCUT_PROGRAM_VERSION_KEY, -1)) {
            Log.d("c3nav-shortcuts", "Program version changed, forcing update");
            updateForced = true;
        }

        if (cachedUserPermissions.hasControlPanelPermission()) {
            ShortcutInfo shortcutInfo = getShortcutInfo(CONTROL_PANEL_SHORTCUT_ID, R.string.shortcut_control_panel_short, R.string.shortcut_control_panel_long, R.drawable.ic_shortcut_build, ACTION_CONTROL_PANEL, null);
            currentDynamicShortcuts.put(shortcutInfo.getId(), shortcutInfo);
        }

        if (cachedUserPermissions.hasEditorPermission()) {
            ShortcutInfo shortcutInfo = getShortcutInfo(EDITOR_SHORTCUT_ID, R.string.shortcut_editor_short, R.string.shortcut_editor_long, R.drawable.ic_shortcut_edit, ACTION_EDITOR, null);
            currentDynamicShortcuts.put(shortcutInfo.getId(), shortcutInfo);
        }

        for (ShortcutInfo shortcutInfo : installedDynamicShortcuts) {
            if (currentDynamicShortcuts.containsKey(shortcutInfo.getId()) && !currentDynamicShortcuts.get(shortcutInfo.getId()).getIntent().getAction().equals(shortcutInfo.getIntent().getAction())) {
                Log.d("c3nav-shortcuts", "An Intend of an shortcut changed. forcing update");
                Log.d("c3nav-shortcuts", "new:" + currentDynamicShortcuts.get(shortcutInfo.getId()).getIntent().getAction() + " old:" +shortcutInfo.getIntent().getAction());
                updateForced = true;
                break;
            }
        }

        if (updateForced || !installedDynamicShortcutsIDs.equals(currentDynamicShortcuts.keySet())) {
            Log.d("c3nav-shortcuts", "DynamicShortcuts need update, updating...");
            if(currentDynamicShortcuts.isEmpty()) {
                shortcutManager.removeAllDynamicShortcuts();
            } else {
                shortcutManager.setDynamicShortcuts(new ArrayList<ShortcutInfo>(currentDynamicShortcuts.values()));
            }
            sharedPrefs.edit().putInt(SHORTCUT_PROGRAM_VERSION_KEY, BuildConfig.VERSION_CODE).apply();
        }

    }


    class CachedUserPermissions {
        private boolean allowControlPanel = false;
        private boolean allowEditor = false;
        private boolean loggedIn = false;

        public final static String KEY_ALLOW_CONTROL_PANEL = "cachedUserPermissionControlPanel";
        public final static String KEY_ALLOW_EDITOR = "cachedUserPermissionEditor";
        public final static String KEY_LOGGED_IN = "cachedUserPermissionLoggedIn";

        CachedUserPermissions() {
            super();
            allowControlPanel = sharedPrefs.getBoolean(KEY_ALLOW_CONTROL_PANEL, false);
            allowEditor = sharedPrefs.getBoolean(KEY_ALLOW_EDITOR, false);
            loggedIn = sharedPrefs.getBoolean(KEY_LOGGED_IN, false);
        }

        public void updateFromUserData(JSONObject userData) {
            boolean allowControlPanelOld = this.allowControlPanel;
            boolean allowEditorOld = this.allowEditor;
            boolean loggedInOld = this.loggedIn;

            try {
                allowControlPanel = userData.getBoolean("allow_control_panel");
                allowEditor = userData.getBoolean("allow_editor");
                loggedIn = userData.getBoolean("logged_in");
            } catch (JSONException e) {
                Log.e("c3navUserData", "failed to parse user data json object", e);
            }

            if (allowControlPanel != allowControlPanelOld || allowEditor != allowEditorOld || loggedIn != loggedInOld) {
                sharedPrefs.edit()
                        .putBoolean(KEY_ALLOW_CONTROL_PANEL, allowControlPanel)
                        .putBoolean(KEY_ALLOW_EDITOR, allowEditor)
                        .putBoolean(KEY_LOGGED_IN, loggedIn)
                        .apply();
                updateDynamicShortcuts();
            }
        }

        public boolean hasControlPanelPermission() {
            return allowControlPanel;
        }

        public boolean hasEditorPermission() {
            return allowEditor;
        }

        public boolean isLoggedIn() {
            return loggedIn;
        }
    }
}