/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * 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.android.hotspot2.osulogin;

import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.http.SslError;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.webkit.SslErrorHandler;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;
import android.widget.Toast;

import androidx.annotation.Nullable;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;

import java.net.MalformedURLException;
import java.net.URL;

/**
 * Online Sign Up Login Web View launched during Provision Process of Hotspot 2.0 rel2.
 */
public class OsuLoginActivity extends Activity {
    private static final String TAG = "OsuLogin";
    private static final boolean DBG = true;

    private String mUrl;
    private String mHostName;
    private Network mNetwork;
    private ConnectivityManager mCm;
    private ConnectivityManager.NetworkCallback mNetworkCallback;
    private WifiManager mWifiManager;
    private WebView mWebView;
    private SwipeRefreshLayout mSwipeRefreshLayout;
    private ProgressBar mProgressBar;
    private boolean mForceDisconnect = true;
    boolean mRedirectResponseReceived = false;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (DBG) {
            Log.d(TAG, "onCreate: Opening OSU Web View");
        }

        mWifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
        if (mWifiManager == null) {
            Log.e(TAG, "Cannot get wifi service");
            finishAndRemoveTask();
            return;
        }

        if (getIntent() == null) {
            Log.e(TAG, "Intent is null");
            finishAndRemoveTask();
            return;
        }

        mNetwork = getIntent().getParcelableExtra(WifiManager.EXTRA_OSU_NETWORK);
        if (mNetwork == null) {
            Log.e(TAG, "Cannot get the network instance for OSU from intent");
            finishAndRemoveTask();
            return;
        }

        mUrl = getIntent().getStringExtra(WifiManager.EXTRA_URL);
        if (mUrl == null) {
            Log.e(TAG, "Cannot get OSU server url from intent");
            finishAndRemoveTask();
            return;
        }

        mHostName = getHost(mUrl);
        if (mHostName == null) {
            Log.e(TAG, "Cannot get host from the url");
            finishAndRemoveTask();
            return;
        }

        mCm = (ConnectivityManager) getApplicationContext().getSystemService(
                Context.CONNECTIVITY_SERVICE);
        if (mCm == null) {
            Log.e(TAG, "Cannot get connectivity service");
            finishAndRemoveTask();
            return;
        }

        if (!mCm.bindProcessToNetwork(mNetwork)) {
            Log.e(TAG, "Network is no longer valid");
            finishAndRemoveTask();
            return;
        }

        final NetworkCapabilities networkCapabilities = mCm.getNetworkCapabilities(mNetwork);
        if (networkCapabilities == null || !networkCapabilities.hasTransport(
                NetworkCapabilities.TRANSPORT_WIFI)) {
            Log.e(TAG, "WiFi is not supported for the Network");
            finishAndRemoveTask();
            return;
        }

        getActionBar().setDisplayShowHomeEnabled(false);
        getActionBar().setElevation(0); // remove shadow
        getActionBar().setTitle(getString(R.string.action_bar_label));
        getActionBar().setSubtitle("");
        setContentView(R.layout.osu_web_view);

        // Exit this app if network disappeared.
        mNetworkCallback = new ConnectivityManager.NetworkCallback() {
            @Override
            public void onLost(Network network) {
                if (DBG) {
                    Log.d(TAG, "Lost for the current Network, close the browser");
                }
                mForceDisconnect = false; // It is already disconnected.
                if (!mRedirectResponseReceived) {
                    showSignUpFailedToast();
                }
                if (mNetwork.equals(network)) {
                    finishAndRemoveTask();
                }
            }
        };

        mCm.registerNetworkCallback(
                new NetworkRequest.Builder().addTransportType(
                        NetworkCapabilities.TRANSPORT_WIFI).removeCapability(
                        NET_CAPABILITY_TRUSTED).build(),
                mNetworkCallback);

        mWebView = findViewById(R.id.webview);
        mWebView.clearCache(true);
        WebSettings webSettings = mWebView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
        webSettings.setUseWideViewPort(true);
        webSettings.setLoadWithOverviewMode(true);
        webSettings.setSupportZoom(true);
        webSettings.setBuiltInZoomControls(true);
        webSettings.setDisplayZoomControls(false);
        mProgressBar = findViewById(R.id.progress_bar);
        mWebView.setWebViewClient(new OsuWebViewClient());
        mWebView.setWebChromeClient(new WebChromeClient() {
            @Override
            public void onProgressChanged(WebView view, int newProgress) {
                mProgressBar.setProgress(newProgress);
            }
        });

        if (DBG) {
            Log.d(TAG, "OSU Web View to " + mUrl);
        }

        mWebView.loadUrl(mUrl);
        mSwipeRefreshLayout = findViewById(R.id.swipe_refresh);
        mSwipeRefreshLayout.setOnRefreshListener(() -> {
            mWebView.reload();
            mSwipeRefreshLayout.setRefreshing(true);
        });
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        // Check if the key event was the Back button.
        if ((keyCode == KeyEvent.KEYCODE_BACK)) {
            // If there is a history to move back
            if (mWebView.canGoBack()) {
                mWebView.goBack();
                return true;
            }
        }
        return super.onKeyDown(keyCode, event);
    }

    @Override
    protected void onDestroy() {
        if (mNetworkCallback != null) {
            mCm.unregisterNetworkCallback(mNetworkCallback);
            mNetworkCallback = null;
        }
        if (mWifiManager != null && mForceDisconnect) {
            mWifiManager.disconnect();
            mWifiManager = null;
        }
        super.onDestroy();
    }

    private String getHost(String url) {
        try {
            return new URL(url).getHost();
        } catch (MalformedURLException e) {
            Log.e(TAG, "Invalid URL " + url);
        }
        return null;
    }

    private String getHeaderSubtitle(String urlString) {
        try {
            URL url = new URL(urlString);
            return url.getProtocol() + "://" +  url.getHost();
        } catch (MalformedURLException e) {
            Log.e(TAG, "Invalid URL " + urlString);
        }
        return "";
    }

    private void showSignUpFailedToast() {
        Toast.makeText(getApplicationContext(), R.string.sign_up_failed,
                Toast.LENGTH_SHORT).show();
    }

    private class OsuWebViewClient extends WebViewClient {
        boolean mPageError = false;

        @Override
        public void onPageStarted(WebView view, String urlString, Bitmap favicon) {
            String subtitle = getHeaderSubtitle(urlString);
            getActionBar().setSubtitle(subtitle);
            mProgressBar.setVisibility(View.VISIBLE);
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            mProgressBar.setVisibility(View.INVISIBLE);
            mSwipeRefreshLayout.setRefreshing(false);

            // Do not show the page error on UI.
            if (mPageError) {
                if (mRedirectResponseReceived) {
                    // Do not disconnect current connection while provisioning is in progress.
                    mForceDisconnect = false;
                }
                finishAndRemoveTask();
            }
        }

        @Override
        public void onReceivedError(WebView view, WebResourceRequest request,
                WebResourceError error) {
            if (request.getUrl().toString().startsWith("http://127.0.0.1")) {
                mRedirectResponseReceived = true;
                view.stopLoading();
                Log.d(TAG, "Redirect received");
            }

            if (request.isForMainFrame()) {
                // This happens right after getting HTTP redirect response from an OSU server
                // since no more Http request is allowed to send to the OSU server.
                mPageError = true;
                Log.e(TAG, "onReceived Error for MainFrame: " + error.getErrorCode());
            }
        }

        @Override
        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
            Log.e(TAG, String.format("SSL error: %d, url: %s, certificate: %s",
                    error.getPrimaryError(), error.getUrl(), error.getCertificate()));
            mPageError = true;
            handler.cancel();
        }
    }
}
