package com.iorga.ivif.ja;

import com.google.common.base.Throwables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ejb.EJBException;
import javax.inject.Inject;
import javax.persistence.OptimisticLockException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.util.List;
import java.util.UUID;

@Provider
public class GlobalExceptionMapper implements ExceptionMapper<Throwable> {
    private static final Logger LOG = LoggerFactory.getLogger(GlobalExceptionMapper.class);

    public static final String HEADER_PREFIX = HeaderUtil.HEADER_PREFIX + "Exception";

    @Inject
    private ClientMessages clientMessages;

    @Inject
    private HeaderUtil headerUtil;

    @Override
    public Response toResponse(Throwable throwable) {
        String uuid = UUID.randomUUID().toString();

        swallowClientMessages(throwable, uuid);

        final Response response = selectHandleMethod(throwable, uuid);
        if (response == null) {
            return handleThrowable(throwable, uuid);
        } else {
            return response;
        }
    }

    private void swallowClientMessages(Throwable exception, String uuid) {

        final List<Message> messages = clientMessages.getMessages();
        if (!messages.isEmpty()) {
            final String endMessage = " for exception #" + uuid + " (" + exception.getClass().getName() + ": " + exception.getMessage() + ").";
            try {
                LOG.warn("Swallowing client messages " + headerUtil.toJsonString(messages) + endMessage);
            } catch (IOException e) {
                LOG.warn("Swallowing client messages " + endMessage);
                LOG.error("Problem while rendering client messages to json string", e);
            }
        }
    }

    private Response selectHandleMethod(Throwable throwable, String uuid) {
        if (throwable instanceof EJBException) {
            return selectHandleMethod(throwable.getCause(), uuid);
        } else if (throwable instanceof FunctionalException) {
            return handleFunctionalException((FunctionalException) throwable, uuid);
        } else if (throwable instanceof OptimisticLockException) {
            return handleOptimisticLockException((OptimisticLockException)throwable, uuid);
        } else {
            return null;
        }
    }

    private Response handleFunctionalException(FunctionalException exception, String uuid) {
        String base64ExceptionTemplate;
        try {
            base64ExceptionTemplate = headerUtil.toBase64JsonString(exception.getFunctionalMessage());
        } catch (IOException e) {
            LOG.error("Problem while rendering the exception to base64 (#" + uuid + ")", e);
            base64ExceptionTemplate = "";
        }
        return Response.status(Response.Status.BAD_REQUEST)
                .header(MessageUtils.MESSAGES_HEADER, base64ExceptionTemplate)
                .build();
    }

    private Response handleOptimisticLockException(OptimisticLockException exception, String uuid) {
        final Object entity = exception.getEntity();
        final String message;
        if (entity instanceof IEntity) {
            message = ((IEntity) entity).displayName() + " has been saved by another user since you have clicked on 'Edit' button. Please click on 'Cancel' in order to refresh all the data (be careful, your current modifications will be lost).";
        } else {
            message = exception.getMessage();
        }
        return handleFunctionalException(new FunctionalException(message), uuid);
    }

    private static class ExceptionTemplate {
        private final Throwable exception;
        private final String uuid;

        public ExceptionTemplate(Throwable exception, String uuid) {
            this.exception = exception;
            this.uuid = uuid;
        }

        public String getMessage() {
            return exception.getMessage();
        }

        public String getClassName() {
            return exception.getClass().getName();
        }

        public String getUuid() {
            return uuid;
        }
    }
    private Response handleThrowable(Throwable throwable, String uuid) {
        LOG.error("Handling unexpected throwable #" + uuid, throwable);
        final ExceptionTemplate exceptionTemplate = new ExceptionTemplate(throwable, uuid);
        String base64ExceptionTemplate;
        try {
            base64ExceptionTemplate = headerUtil.toBase64JsonString(exceptionTemplate);
        } catch (IOException e) {
            LOG.error("Problem while rendering the throwable to base64", e);
            base64ExceptionTemplate = "";
        }
        return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                .header(HEADER_PREFIX, base64ExceptionTemplate)
                .entity(Throwables.getStackTraceAsString(throwable))
                .build();
    }
}

@@ -253,21 +253,115 @@
 .rs.
-core.Response
+WebApplicationException;%0Aimport javax.ws.rs.core.Response;%0Aimport javax.ws.rs.core.Response.ResponseBuilder
 ;%0Aim
@@ -1191,32 +1191,38 @@
 (throwable, uuid
+, null
 );%0A        %7D els
@@ -2437,32 +2437,205 @@
 rowable, uuid);%0A
+        %7D else if (throwable instanceof WebApplicationException) %7B%0A            return handleThrowable(throwable, uuid, ((WebApplicationException) throwable).getResponse());%0A
         %7D else %7B
@@ -4513,32 +4513,59 @@
 ble, String uuid
+, Response responseTemplate
 ) %7B%0A        LOG.
@@ -5033,201 +5033,431 @@
     
-return Response.status(Response.Status.INTERNAL_SERVER_ERROR)%0A                .header(HEADER_PREFIX, base64ExceptionTemplate)%0A                .entity(Throwables.getStackTraceAsString(throwable)
+ResponseBuilder responseBuilder;%0A        if (responseTemplate != null) %7B%0A            responseBuilder = Response.fromResponse(responseTemplate);%0A        %7D else %7B%0A            responseBuilder = Response.status(Response.Status.INTERNAL_SERVER_ERROR)%0A                    .entity(Throwables.getStackTraceAsString(throwable));%0A        %7D%0A        return responseBuilder%0A                .header(HEADER_PREFIX, base64ExceptionTemplate
 )%0A  

Handle WebApplicationException which already contains response