/*******************************************************************************
 * Copyright (c) 2012-2016 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 *******************************************************************************/
package org.eclipse.che.ide.rest;

import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.Response;
import com.google.gwt.user.client.Window;

import org.eclipse.che.ide.commons.exception.ServerDisconnectedException;
import org.eclipse.che.ide.commons.exception.ServerException;
import org.eclipse.che.ide.commons.exception.UnauthorizedException;

import static org.eclipse.che.ide.MimeType.APPLICATION_JSON;
import static org.eclipse.che.ide.rest.HTTPHeader.CONTENT_TYPE;

/**
 * Callback class for receiving the {@link Response}.
 *
 * @param <T>
 *         the return type of the response the callback expects.
 *         Use {@link Void} for methods returning {@code void}.
 *
 * @author Valeriy Svydenko
 */
public abstract class AsyncRequestCallback<T> implements RequestCallback {

    // HTTP code 207 is "Multi-Status"
    // IE misinterpreting HTTP status code 204 as 1223 (http://www.mail-archive.com/jquery-en@googlegroups.com/msg13093.html)
    private static final int[] DEFAULT_SUCCESS_CODES = {Response.SC_OK, Response.SC_CREATED, Response.SC_NO_CONTENT, 207, 1223};
    private final Unmarshallable<T>  unmarshaller;
    private       int[]              successCodes;
    private       AsyncRequestLoader loader;
    private       T                  payload;
    private       AsyncRequest       request;

    public AsyncRequestCallback() {
        this(null);
    }

    /**
     * Constructor retrieves unmarshaller with initialized (this is important!) object.
     * When response comes callback calls {@link Unmarshallable#unmarshal(com.google.gwt.http.client.Response)}
     * which populates the object.
     *
     * @param unmarshaller
     */
    public AsyncRequestCallback(Unmarshallable<T> unmarshaller) {
        this.successCodes = DEFAULT_SUCCESS_CODES;
        this.unmarshaller = unmarshaller;
    }

    /** @return the result */
    public T getPayload() {
        return payload;
    }

    /**
     * @param successCodes
     *         the successCodes to set
     */
    public void setSuccessCodes(int[] successCodes) {
        this.successCodes = successCodes;
    }

    public final void setLoader(AsyncRequestLoader loader) {
        this.loader = loader;
    }

    /** @see com.google.gwt.http.client.RequestCallback#onError(com.google.gwt.http.client.Request, java.lang.Throwable) */
    @Override
    public final void onError(Request request, Throwable exception) {
        if (loader != null) {
            loader.hide();
        }

        onFailure(exception);
    }

    /**
     * @see com.google.gwt.http.client.RequestCallback#onResponseReceived(com.google.gwt.http.client.Request,
     * com.google.gwt.http.client.Response)
     */
    @Override
    public final void onResponseReceived(Request request, Response response) {
        if (loader != null) {
            loader.hide();
        }

        // If there is no connection to the server then status equals 0 ( In Internet Explorer status is 12029 )
        if (response.getStatusCode() == 0 || response.getStatusCode() == 12029) {
            onServerDisconnected();
            return;
        }

        if (response.getStatusCode() == HTTPStatus.UNAUTHORIZED) {
            onUnauthorized(response);
            return;
        }

        if (isSuccessful(response)) {
            handleSuccess(response);
        } else {
            handleFailure(response);
        }
    }

    private void handleFailure(Response response) {
        Exception exception;
        String contentType = response.getHeader(CONTENT_TYPE);

        if (contentType != null && !contentType.contains(APPLICATION_JSON)) {
            String message = generateErrorMessage(response);
            exception = new Exception(message);
        } else {
            exception = new ServerException(response);
        }
        onFailure(exception);
    }

    private void handleSuccess(Response response) {
        try {
            if (unmarshaller != null) {
                //It's needed for handling a situation when response DTO object is NULL
                if (response.getStatusCode() != Response.SC_NO_CONTENT) {
                    unmarshaller.unmarshal(response);
                }
                payload = unmarshaller.getPayload();
            }

            onSuccess(payload);
        } catch (Exception e) {
            onFailure(e);
        }
    }


    private String generateErrorMessage(Response response) {
        StringBuilder message = new StringBuilder();
        String protocol = Window.Location.getProtocol();
        String host = Window.Location.getHost();
        String url = this.request.getRequestBuilder().getUrl();

        //deletes query params
        url = url.substring(0, url.indexOf('?'));

        message.append(response.getStatusCode())
               .append(" ")
               .append(response.getStatusText())
               .append(" ")
               .append(protocol)
               .append("//")
               .append(host)
               .append(url);

        return message.toString();
    }

    protected final boolean isSuccessful(Response response) {
        if (successCodes == null) {
            successCodes = DEFAULT_SUCCESS_CODES;
        }

        if ("Authentication-required".equals(response.getHeader(HTTPHeader.JAXRS_BODY_PROVIDED))) {
            return false;
        }

        for (int code : successCodes) {
            if (response.getStatusCode() == code) {
                return true;
            }
        }
        return false;
    }

    /**
     * If response is successfully received and response status code is in set of success codes.
     *
     * @param result
     *         the response returned from the request. Will be {@code null} if
     *         {@code unmarshaller} wasn't set
     */
    protected abstract void onSuccess(T result);

    /**
     * Called when an error received from the server or when request was failed.
     *
     * @param exception
     *         the exception thrown
     */
    protected abstract void onFailure(Throwable exception);

    /** If server disconnected. */
    protected void onServerDisconnected() {
        onFailure(new ServerDisconnectedException(request));
    }

    /**
     * If unauthorized.
     *
     * @param response
     */
    protected void onUnauthorized(Response response) {
        onFailure(new UnauthorizedException(response, request));
    }

    public AsyncRequest getRequest() {
        return request;
    }

    public void setRequest(AsyncRequest request) {
        this.request = request;
    }
}
