/**
 * Copyright 2017-2019 European Union, interactive instruments GmbH
 * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by
 * the European Commission - subsequent versions of the EUPL (the "Licence");
 * You may not use this work except in compliance with the Licence.
 * You may obtain a copy of the Licence at:
 *
 * https://joinup.ec.europa.eu/software/page/eupl
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the Licence is distributed on an "AS IS" basis,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Licence for the specific language governing permissions and
 * limitations under the Licence.
 *
 * This work was supported by the EU Interoperability Solutions for
 * European Public Administrations Programme (http://ec.europa.eu/isa)
 * through Action 1.17: A Reusable INSPIRE Reference Platform (ARE3NA).
 */
package de.interactive_instruments.etf.webapp.controller;

import java.io.IOException;
import java.util.Objects;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;

import de.interactive_instruments.etf.webapp.helpers.View;

/**
 * Handles all raised, uncaught exceptions.
 */
@ControllerAdvice(annotations = Controller.class)
class WebExceptionHandler {

    @Autowired
    private StatusController statusController;

    public static final String DEFAULT_ERROR_VIEW = "error";

    private final org.slf4j.Logger logger = LoggerFactory.getLogger(WebExceptionHandler.class);

    private static byte[] reserve = new byte[30 * 1024 * 1024]; // Reserve 30 MB

    private final static String contactAdminText = " This is a critical error and the system "
            + "will try to prevent data loss by " + " switching into maintenance mode.";

    private ModelAndView createError(final Exception e, final String hint, final String url, boolean submitReport)
            throws Exception {
        if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
            // Not associated with a view
            throw e;
        }

        final ModelAndView mav = new ModelAndView();
        mav.addObject("ex", e);
        if (hint != null) {
            mav.addObject("hint", hint);
        }
        mav.addObject("url", url);
        final UUID exceptionId = UUID.randomUUID();
        mav.addObject("exid", "EXID-" + exceptionId.toString());
        logger.error(
                "EXID-" + exceptionId.toString() + ": An exception occurred while trying to access \"" +
                        url + "\"",
                e);
        mav.addObject("submitReport", submitReport && View.getSubmitAnalysisData().equals("true"));
        mav.setViewName(DEFAULT_ERROR_VIEW);
        return mav;
    }

    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = Exception.class)
    public ModelAndView defaultErrorHandler(final HttpServletRequest request, final Exception e) throws Exception {

        if (e != null && e.getCause() != null && e.getCause() instanceof OutOfMemoryError) {
            // Recover the reserved memory
            reserve = null;
            System.gc();
        }

        final String hint;
        final boolean submitReport;
        if (e.getCause() != null && e.getCause() instanceof OutOfMemoryError) {
            hint = "The Java Virtual Machine is out of memory and"
                    + " no more memory could be made available. "
                    + " Ask your System Administrator to increase the Java heap space by "
                    + " adjusting the '-Xmx' parameter!" + contactAdminText;
            submitReport = false;
            statusController.triggerMaintenance();
        } else if (Objects.equals(e.getMessage(), "No space left on device")
                || e.getCause() != null && e.getCause() instanceof IOException &&
                        Objects.equals(e.getCause().getMessage(), "No space left on device")) {
            hint = "Disk space critical. Contact your system Administrator. " + contactAdminText;
            statusController.triggerMaintenance();
            submitReport = false;
        } else if (e.getCause() != null && e.getCause() instanceof StackOverflowError || e
                .getCause() instanceof StackOverflowError) {
            hint = "This is most likely a fatal bug in the application." + " Please report it!";
            submitReport = true;
        } else {
            hint = null;
            submitReport = true;
        }
        return createError(e, hint, request.getRequestURL().toString(), submitReport);
    }
}