public interface EventListener {

    /**
     * Called when a new message has been received.
     *
     * @param context      The application context.
     * @param providerName The name of the provider which has received the event.
     * @param extras       The data associated with the message.
     */
    void onMessage(@NonNull Context context, @NonNull String providerName, @Nullable Bundle extras);

    /**
     * Called when a notification about deleted messages has been received.
     * Some providers don't send the count of deleted messages. In this case the message count will equal to
     * {@link org.onepf.opfpush.OPFConstants#MESSAGES_COUNT_UNKNOWN}.
     *
     * @param context       The application context.
     * @param providerName  The name of the provider which has received the event.
     * @param messagesCount The count of messages. The {@link org.onepf.opfpush.OPFConstants#MESSAGES_COUNT_UNKNOWN} value
     *                      if there is no information about the count.
     */
    void onDeletedMessages(@NonNull Context context, @NonNull String providerName, int messagesCount);

    /**
     * Called when a message about successful registration has been received.
     *
     * @param context        The application context.
     * @param providerName   The name of the provider which has received the event.
     * @param registrationId The registration ID for push notifications.
     */
    void onRegistered(@NonNull Context context, @NonNull String providerName, @NonNull String registrationId);

    /**
     * Called when the {@link org.onepf.opfpush.OPFPushHelper} becomes {@code UNREGISTERED}.
     * Calling this method doesn't mean that the provider is already unregistered. The unregistering
     * is performed in the background and is retried if an unregistration error is occurred.
     *
     * @param context        The application context.
     * @param providerName   The name of the provider which has received the event.
     * @param registrationId The old registration ID for push notification.
     */
    void onUnregistered(@NonNull Context context, @NonNull String providerName, @Nullable String registrationId);

    /**
     * The {@code OpenPushHelper} can't find any available provider for register push.
     * You should notify the user that push notifications will not be received.
     * <p/>
     * A push provider can be unavailable in two common reasons:
     * <ol>
     * <li>The {@link org.onepf.opfpush.pushprovider.PushProvider#getAvailabilityResult()} method has returned false value.</li>
     * <li>An unrecoverable registration error has occurred.</li>
     * </ol>
     * <p/>
     * In the first case an {@link org.onepf.opfpush.model.UnrecoverablePushError} object will be put
     * to the {@code Map} if {@link org.onepf.opfpush.model.AvailabilityResult#getErrorCode()} is not null.
     * In other words if a provider returned specific error code while an availability check,
     * you will get the {@link org.onepf.opfpush.model.UnrecoverablePushError.Type#AVAILABILITY_ERROR}
     * and the {@link org.onepf.opfpush.model.UnrecoverablePushError#getAvailabilityErrorCode()} will return
     * not null value.
     * If a provider is no available because the host app (store) of the provider isn't installed,
     * you will not get an error for the provider in the {@code Map}.
     * <p/>
     * In the second case you can get the {@link org.onepf.opfpush.model.UnrecoverablePushError} object
     * from the {@code pushErrors} map.
     * You can notify the user about the occurred error if the error can be resolved by user.
     * For example if you get the {@link org.onepf.opfpush.model.UnrecoverablePushError.Type#AUTHENTICATION_FAILED}
     * for the GCM push provider, you can ask the user whether he wants to add the google account.
     *
     * @param context            The application context.
     * @param pushErrors The map in which registration errors are stored with a push providers' name as a key
     *                           If there is no registration errors, the map is empty.
     */
    void onNoAvailableProvider(@NonNull Context context, @NonNull Map<String, UnrecoverablePushError> pushErrors);
} public final class UnrecoverablePushError extends PushError<UnrecoverablePushError.Type> {

    @Nullable
    private Integer availabilityErrorCode;

    public UnrecoverablePushError(@NonNull final Type type,
                                  @NonNull final String providerName) {
        this(type, providerName, type.name(), null);
    }

    public UnrecoverablePushError(@NonNull final Type type,
                                  @NonNull final String providerName,
                                  @NonNull final String errorId) {
        this(type, providerName, errorId, null);
    }

    public UnrecoverablePushError(@NonNull final Type type,
                                  @NonNull final String providerName,
                                  @NonNull final Integer availabilityErrorCode) {
        this(type, providerName, type.name(), availabilityErrorCode);
    }

    public UnrecoverablePushError(@NonNull final Type type,
                                  @NonNull final String providerName,
                                  @NonNull final String errorId,
                                  @Nullable final Integer availabilityErrorCode) {
        super(type, providerName, errorId);
        this.availabilityErrorCode = availabilityErrorCode;
    }

    /**
     * Returns an availability error code, or {@code null} if a provider hasn't returned a specific availability error.
     * For GCM it corresponds to the {@code ConnectionResult} codes.
     *
     * @return The error code indicates the specific provider availability error. Can be {@code null}.
     */
    @Nullable
    public Integer getAvailabilityErrorCode() {
        return availabilityErrorCode;
    }

    @Override
    public boolean isRecoverable() {
        return false;
    }

    @Override
    public String toString() {
        return String.format(
                Locale.US,
                "UnrecoverablePushError : \n"
                        + "{\"providerName\" : \"%1$s\",\n"
                        + "\"type\" : \"%2$s\",\n"
                        + "\"originalError\":\"%3$s\",\n"
                        + "\"availabilityErrorCode\":\"%4$s\"}",
                providerName,
                type,
                originalError,
                availabilityErrorCode);
    }

    public enum Type implements ErrorType {

        /**
         * Invalid parameters have been sent to register provider.
         */
        INVALID_PARAMETERS,

        /**
         * An invalid sender ID has been used for the registration.
         */
        INVALID_SENDER,

        /**
         * The authentication failure.
         */
        AUTHENTICATION_FAILED,

        /**
         * A provider specific error has occurred while a registration.
         * Look {@link #getOriginalError()} for more information.
         */
        PROVIDER_SPECIFIC_ERROR,

        /**
         * Occurred when a provider is unavailable by provider specific error.
         * Use {@link #getAvailabilityErrorCode()} to know about the reason of a provider unavailability.
         */
        AVAILABILITY_ERROR
    }
} public static final String ACTION_NO_AVAILABLE_PROVIDER = "org.onepf.opfpush.intent.NO_AVAILABLE_PROVIDER"; public static final String ACTION_RECEIVE = "org.onepf.opfpush.intent.RECEIVE"; public static final String ACTION_REGISTRATION = "org.onepf.opfpush.intent.REGISTRATION"; public static final String ACTION_UNREGISTRATION = "org.onepf.opfpush.intent.UNREGISTRATION"; public static final String EXTRA_MESSAGE_COUNT = "org.onepf.opfpush.intent.EXTRA_MESSAGE_COUNT"; public static final String EXTRA_MESSAGE_TYPE = "org.onepf.opfpush.intent.EXTRA_MESSAGE_TYPE"; public static final String EXTRA_PROVIDER_NAME = "org.onepf.opfpush.intent.EXTRA_PROVIDER_NAME"; public static final String EXTRA_REGISTRATION_ERRORS = "org.onepf.opfpush.intent.EXTRA_REGISTRATION_ERRORS"; public static final String EXTRA_REGISTRATION_ID = "org.onepf.opfpush.intent.EXTRA_REGISTRATION_ID";
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.onepf.opfpush.listener.EventListener;
import org.onepf.opfpush.model.UnrecoverablePushError;
import org.onepf.opfutils.OPFLog;
import org.onepf.opfutils.OPFUtils;
import java.io.Serializable;
import java.util.Map;
import static org.onepf.opfpush.OPFConstants.ACTION_NO_AVAILABLE_PROVIDER;
import static org.onepf.opfpush.OPFConstants.ACTION_RECEIVE;
import static org.onepf.opfpush.OPFConstants.ACTION_REGISTRATION;
import static org.onepf.opfpush.OPFConstants.ACTION_UNREGISTRATION;
import static org.onepf.opfpush.OPFConstants.EXTRA_MESSAGE_COUNT;
import static org.onepf.opfpush.OPFConstants.EXTRA_MESSAGE_TYPE;
import static org.onepf.opfpush.OPFConstants.EXTRA_PROVIDER_NAME;
import static org.onepf.opfpush.OPFConstants.EXTRA_REGISTRATION_ERRORS;
import static org.onepf.opfpush.OPFConstants.EXTRA_REGISTRATION_ID;
import static org.onepf.opfpush.model.MessageType.MESSAGE_TYPE_DELETED;
import static org.onepf.opfpush.model.MessageType.MESSAGE_TYPE_MESSAGE;
/*
 * Copyright 2012-2015 One Platform Foundation
 *
 * 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 org.onepf.opfpush;





/**
 * @author Roman Savin
 * @since 26.12.14
 */
@SuppressWarnings({"PMD.StdCyclomaticComplexity", "PMD.ModifiedCyclomaticComplexity"})
final class EventListenerWrapperCreator {

    private EventListenerWrapperCreator() {
        throw new UnsupportedOperationException();
    }

    @NonNull
    static EventListener getEventListenerWrapper(
            @Nullable final EventListener eventListener
    ) {
        OPFLog.logMethod(eventListener);

        if (eventListener != null) {
            return createMainLooperWrapper(eventListener);
        } else {
            return createBroadcastSender();
        }
    }

    @NonNull
    private static EventListener createMainLooperWrapper(@NonNull final EventListener eventListener) {
        OPFLog.logMethod(eventListener);
        return new EventListener() {

            private final Handler handler = new Handler(Looper.getMainLooper());

            @Override
            public void onMessage(
                    @NonNull final Context context,
                    @NonNull final String providerName,
                    @Nullable final Bundle extras
            ) {
                post(new Runnable() {
                    @Override
                    public void run() {
                        OPFLog.d("Post onMessage(%1$s, %2$s)", providerName, extras);
                        eventListener.onMessage(context, providerName, extras);
                    }
                });
            }

            @Override
            public void onDeletedMessages(
                    @NonNull final Context context,
                    @NonNull final String providerName,
                    final int messagesCount
            ) {
                post(new Runnable() {
                    @Override
                    public void run() {
                        OPFLog.d("Post onDeletedMessages(%1$s, %2$s)", providerName, messagesCount);
                        eventListener.onDeletedMessages(context, providerName, messagesCount);
                    }
                });
            }

            @Override
            public void onRegistered(
                    @NonNull final Context context,
                    @NonNull final String providerName,
                    @NonNull final String registrationId
            ) {
                post(new Runnable() {
                    @Override
                    public void run() {
                        OPFLog.d("Post onRegistered(%1$s, %2$s)", providerName, registrationId);
                        eventListener.onRegistered(context, providerName, registrationId);
                    }
                });
            }

            @Override
            public void onUnregistered(
                    @NonNull final Context context,
                    @NonNull final String providerName,
                    @Nullable final String registrationId
            ) {
                post(new Runnable() {
                    @Override
                    public void run() {
                        OPFLog.d("Post onUnregistered(%1$s, %2$s)", providerName, registrationId);
                        eventListener.onUnregistered(context, providerName, registrationId);
                    }
                });
            }

            @Override
            public void onNoAvailableProvider(
                    @NonNull final Context context,
                    @NonNull final Map<String, UnrecoverablePushError> pushErrors) {
                post(new Runnable() {
                    @Override
                    public void run() {
                        OPFLog.d("Post onNoAvailableProvider()");
                        eventListener.onNoAvailableProvider(context, pushErrors);
                    }
                });
            }

            private void post(@NonNull final Runnable runnable) {
                if (OPFUtils.isMainThread()) {
                    runnable.run();
                } else {
                    handler.post(runnable);
                }
            }
        };
    }

    @NonNull
    private static EventListener createBroadcastSender() {
        OPFLog.logMethod();
        return new EventListener() {
            @Override
            public void onMessage(
                    @NonNull final Context context,
                    @NonNull final String providerName,
                    @Nullable final Bundle extras
            ) {
                OPFLog.d("SendBroadcast onMessage(%1$s, %2$s)", providerName, extras);

                final Intent intent = new Intent(ACTION_RECEIVE);
                if (extras != null) {
                    intent.putExtras(extras);
                }
                intent.putExtra(EXTRA_MESSAGE_TYPE, MESSAGE_TYPE_MESSAGE);