/* 
 * Copyright 2014 OpenMarket Ltd
 * 
 * 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.matrix.androidsdk.rest.callback;

import android.util.Log;

import org.matrix.androidsdk.rest.model.MatrixError;
import org.matrix.androidsdk.util.UnsentEventsManager;

import retrofit.Callback;
import retrofit.RetrofitError;
import retrofit.client.Response;
import retrofit.mime.TypedByteArray;
import retrofit.mime.TypedInput;

public class RestAdapterCallback<T> implements Callback<T> {

    private static final String LOG_TAG = "RestAdapterCallback";

    /**
     * Callback when a request failed after a network error.
     * This callback should manage the request auto resent.
     */
    public interface RequestRetryCallBack {
        void onRetry();
    }

    // the event description
    private String mEventDescription;

    // the callback
    private final ApiCallback mApiCallback;

    // the retry callback
    private final RequestRetryCallBack mRequestRetryCallBack;

    // the unsent events manager
    private final UnsentEventsManager mUnsentEventsManager;

    // true to do not test if he event time line when sending again
    // the request when a data connection is retrieved.
    private final boolean mIgnoreEventTimeLifeInOffline;

    /**
     * Constructor with unsent events management
     * @param description the event description
     * @param unsentEventsManager the unsent events manager
     * @param apiCallback the callback
     * @param requestRetryCallBack the retry callback
     */
    public RestAdapterCallback(String description, UnsentEventsManager unsentEventsManager, ApiCallback apiCallback, RequestRetryCallBack requestRetryCallBack) {
        this(description, unsentEventsManager, false, apiCallback, requestRetryCallBack);
    }

    /**
     * Constructor with unsent events management
     * @param description the event description
     * @param ignoreEventTimeLifeOffline true to ignore the event time when resending the event.
     * @param unsentEventsManager the unsent events manager
     * @param apiCallback the callback
     * @param requestRetryCallBack the retry callback
     */
    public RestAdapterCallback(String description, UnsentEventsManager unsentEventsManager, boolean ignoreEventTimeLifeOffline, ApiCallback apiCallback, RequestRetryCallBack requestRetryCallBack)  {
        if (null != description) {
            Log.d(LOG_TAG, "Trigger the event [" + description + "]");
        }

        this.mEventDescription = description;
        this.mIgnoreEventTimeLifeInOffline = ignoreEventTimeLifeOffline;
        this.mApiCallback = apiCallback;
        this.mRequestRetryCallBack = requestRetryCallBack;
        this.mUnsentEventsManager = unsentEventsManager;
    }

    /**
     * Notify the {@link UnsentEventsManager} that the event has been successfully sent.
     * This method must be called each time a REST call succeed, in order to warn
     * the {@link UnsentEventsManager} to send the next unsent events.
     */
    protected void onEventSent() {
        if (null != mUnsentEventsManager) {
            try {
                // some users reported that their devices were connected
                // whereas this receiver was not called
                if (!mUnsentEventsManager.getNetworkConnectivityReceiver().isConnected()) {
                    Log.d(LOG_TAG, "## onEventSent(): request succeed, while network seen as disconnected => ask ConnectivityReceiver to dispatch info");
                    mUnsentEventsManager.getNetworkConnectivityReceiver().checkNetworkConnection(mUnsentEventsManager.getContext());
                }

                mUnsentEventsManager.onEventSent(mApiCallback);
            } catch (Exception e) {
                Log.d(LOG_TAG, "## onEventSent(): Exception " + e.getMessage());
            }
        }
    }

    @Override
    public void success(T t, Response response) {
        if (null != mEventDescription) {
            Log.d(LOG_TAG, "## Succeed() : [" + mEventDescription + "]");
        }

        // add try catch to prevent application crashes while managing destroyed object
        try {
            onEventSent();

            if (null != mApiCallback) {
                try {
                    mApiCallback.onSuccess(t);
                } catch (Exception e) {
                    Log.d(LOG_TAG, "## succeed() : onSuccess failed" + e.getMessage());
                }
            }
        } catch (Exception e) {
            // privacy
            Log.e(LOG_TAG, "## succeed(): Exception " + e.getMessage());
        }
    }

    /**
     * Default failure implementation that calls the right error handler
     * @param error the retrofit error
     */
    @Override
    public void failure(RetrofitError error) {
        if (null != mEventDescription) {
            Log.d(LOG_TAG, "## failure(): [" + mEventDescription + "]");
        }

        boolean retry = true;

        if (null != error.getResponse()) {
            retry = (error.getResponse().getStatus() < 400) || (error.getResponse().getStatus() >= 500);
        }

        if (retry && (null != mUnsentEventsManager)) {
            Log.d(LOG_TAG, "Add it to the UnsentEventsManager");
            mUnsentEventsManager.onEventSendingFailed(mEventDescription, mIgnoreEventTimeLifeInOffline, error, mApiCallback, mRequestRetryCallBack);
        } else {
            if (error.isNetworkError()) {
                try {
                    if (null != mApiCallback) {
                        try {
                            mApiCallback.onNetworkError(error);
                        } catch (Exception e) {
                            Log.e(LOG_TAG, "## failure(): onNetworkError " + error.getLocalizedMessage());
                        }
                    }
                } catch (Exception e) {
                    // privacy
                    //Log.e(LOG_TAG, "Exception NetworkError " + e.getMessage() + " while managing " + error.getUrl());
                    Log.e(LOG_TAG, "## failure():  NetworkError " + e.getLocalizedMessage());
                }
            }
            else {
                // Try to convert this into a Matrix error
                MatrixError mxError;
                try {
                    mxError = (MatrixError) error.getBodyAs(MatrixError.class);

                    mxError.mStatus = error.getResponse().getStatus();
                    mxError.mReason = error.getResponse().getReason();

                    TypedInput body = error.getResponse().getBody();

                    if (null != body) {
                        mxError.mErrorBodyMimeType = body.mimeType();
                        mxError.mErrorBody = body;

                        try {
                            if (body instanceof TypedByteArray) {
                                mxError.mErrorBodyAsString = new String(((TypedByteArray)body).getBytes());
                            } else {
                                mxError.mErrorBodyAsString = (String)error.getBodyAs(String.class);
                            }
                        } catch (Exception castException) {
                            Log.e(LOG_TAG, "## failure(): MatrixError cannot cast the response body" + castException.getMessage());
                        }
                    }
                }
                catch (Exception e) {
                    mxError = null;
                }
                if (mxError != null) {
                    if (MatrixError.LIMIT_EXCEEDED.equals(mxError.errcode) && (null != mUnsentEventsManager)) {
                        mUnsentEventsManager.onEventSendingFailed(mEventDescription, mIgnoreEventTimeLifeInOffline, error, mApiCallback, mRequestRetryCallBack);
                    } else {
                        try {
                            if (null != mApiCallback) {
                                mApiCallback.onMatrixError(mxError);
                            }
                        } catch (Exception e) {
                            // privacy
                            //Log.e(LOG_TAG, "Exception MatrixError " + e.getMessage() + " while managing " + error.getUrl());
                            Log.e(LOG_TAG, "## failure():  MatrixError " + e.getLocalizedMessage());
                        }
                    }
                }
                else {
                    try {
                        if (null != mApiCallback) {
                            mApiCallback.onUnexpectedError(error);
                        }
                    } catch (Exception e) {
                        // privacy
                        //Log.e(LOG_TAG, "Exception UnexpectedError " + e.getMessage() + " while managing " + error.getUrl());
                        Log.e(LOG_TAG, "## failure():  UnexpectedError " + e.getLocalizedMessage());
                    }
                }
            }
        }
    }
}

@@ -5382,32 +5382,70 @@
 escription + %22%5D%22
+ + %22 with error %22 + error.getMessage()
 );%0A        %7D%0A%0A  

Add the error description in rest error message.