/**
 * Copyright 2010-present Facebook.
 *
 * 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.facebook.internal;

import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.webkit.WebView;
import com.facebook.FacebookException;
import com.facebook.widget.FacebookDialog;
import com.facebook.widget.WebDialog;
import org.json.JSONException;
import org.json.JSONObject;

/**
 * com.facebook.internal is solely for the use of other packages within the Facebook SDK for Android. Use of
 * any of the classes in this package is unsupported, and they may be modified or removed without warning at
 * any time.
 *
 * This dialog is used as a fallback when a native FacebookDialog could not be displayed. The primary reason for
 * this separation is to keep this approach for internal use only until we stabilize the API.
 */
public class FacebookWebFallbackDialog extends WebDialog {
    private static final String TAG = FacebookWebFallbackDialog.class.getName();
    private static final int OS_BACK_BUTTON_RESPONSE_TIMEOUT_MILLISECONDS = 1500;

    private boolean waitingForDialogToClose;

    public static boolean presentWebFallback(final Context context,
                                             String dialogUrl,
                                             String applicationId,
                                             final FacebookDialog.PendingCall appCall,
                                             final FacebookDialog.Callback callback) {
        if (Utility.isNullOrEmpty(dialogUrl)) {
            return false;
        }

        String redirectUrl = String.format("fb%s://bridge/", applicationId);

        // Show the webdialog.
        FacebookWebFallbackDialog fallbackWebDialog = new FacebookWebFallbackDialog(
                context, dialogUrl, redirectUrl);
        fallbackWebDialog.setOnCompleteListener(new WebDialog.OnCompleteListener() {
            @Override
            public void onComplete(Bundle values, FacebookException error) {
                Intent dummyIntent = new Intent();
                dummyIntent.putExtras(values == null ? new Bundle() : values);
                FacebookDialog.handleActivityResult(
                        context,
                        appCall,
                        appCall.getRequestCode(),
                        dummyIntent,
                        callback);
            }
        });

        fallbackWebDialog.show();
        return true;
    }

    private FacebookWebFallbackDialog(Context context, String url, String expectedRedirectUrl) {
        super(context, url);

        setExpectedRedirectUrl(expectedRedirectUrl);
    }

    @Override
    protected Bundle parseResponseUri(String url) {
        Uri responseUri = Uri.parse(url);
        Bundle queryParams = Utility.parseUrlQueryString(responseUri.getQuery());

        // Convert Bridge args to the format that the Native dialog code understands.
        String bridgeArgsJSONString = queryParams.getString(ServerProtocol.FALLBACK_DIALOG_PARAM_BRIDGE_ARGS);
        queryParams.remove(ServerProtocol.FALLBACK_DIALOG_PARAM_BRIDGE_ARGS);

        if (!Utility.isNullOrEmpty(bridgeArgsJSONString)) {
            Bundle bridgeArgs;
            try {
                JSONObject bridgeArgsJSON = new JSONObject(bridgeArgsJSONString);
                bridgeArgs = BundleJSONConverter.convertToBundle(bridgeArgsJSON);
                queryParams.putBundle(NativeProtocol.EXTRA_PROTOCOL_BRIDGE_ARGS, bridgeArgs);
            } catch (JSONException je) {
                Utility.logd(TAG, "Unable to parse bridge_args JSON", je);
            }
        }

        // Convert Method results to the format that the Native dialog code understands.
        String methodResultsJSONString = queryParams.getString(ServerProtocol.FALLBACK_DIALOG_PARAM_METHOD_RESULTS);
        queryParams.remove(ServerProtocol.FALLBACK_DIALOG_PARAM_METHOD_RESULTS);

        if (!Utility.isNullOrEmpty(methodResultsJSONString)) {
            methodResultsJSONString = Utility.isNullOrEmpty(methodResultsJSONString) ? "{}" : methodResultsJSONString;
            Bundle methodResults;
            try {
                JSONObject methodArgsJSON = new JSONObject(methodResultsJSONString);
                methodResults = BundleJSONConverter.convertToBundle(methodArgsJSON);
                queryParams.putBundle(NativeProtocol.EXTRA_PROTOCOL_METHOD_RESULTS, methodResults);
            } catch (JSONException je) {
                Utility.logd(TAG, "Unable to parse bridge_args JSON", je);
            }
        }

        // The web host does not send a numeric version back. Put the latest known version in there so NativeProtocol
        // can continue parsing the response.
        queryParams.remove(ServerProtocol.FALLBACK_DIALOG_PARAM_VERSION);
        queryParams.putInt(NativeProtocol.EXTRA_PROTOCOL_VERSION, NativeProtocol.getLatestKnownVersion());

        return queryParams;
    }

    @Override
    public void dismiss() {
        WebView webView = getWebView();

        if (isListenerCalled() || webView == null || !webView.isShown()) {
            // If the listener has been called, or if the WebView isn't visible, we cannot give the dialog a chance
            // to respond. So defer to the parent implementation.
            super.dismiss();
            return;
        }

        // If we have already notified the dialog to close, then ignore this request to dismiss. The timer will
        // honor the request.
        if (waitingForDialogToClose) {
            return;
        }
        waitingForDialogToClose = true;

        // Now fire off the event that will tell the dialog to wind down.
        String eventJS =
                "(function() {" +
                "  var event = document.createEvent('Event');" +
                "  event.initEvent('fbPlatformDialogMustClose',true,true);" +
                "  document.dispatchEvent(event);" +
                "})();";
        webView.loadUrl("javascript:" + eventJS);

        // Set up a timeout for the dialog to respond. If the timer expires, we need to honor the user's desire to
        // dismiss the dialog.
        Handler handler = new Handler(Looper.getMainLooper());
        handler.postDelayed(
                new Runnable() {
                    @Override
                    public void run() {
                        if (!isListenerCalled()) {
                            // If we get here, then the dialog did not close quickly enough. So we need to honor the user's
                            // wish to cancel.
                            sendCancelToListener();
                        }
                    }
                },
                OS_BACK_BUTTON_RESPONSE_TIMEOUT_MILLISECONDS);
    }
}
