/*
 * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#include <memory>
#include "app.h"
#include "Log.h"
#include "SysInfo.h"
#include "ErrorHandling.h"


namespace {
const std::string* theLastErrorMsg = 0;

char nopLogAppenderMemory[sizeof(NopLogAppender)] = {};

class StandardLogAppender : public LogAppender {
public:
    virtual void append(const LogEvent& v) {
        std::cerr << "[" << v.logLevel << "] "
            << v.fileName
            << ":" << v.lineNum
            << ": " << v.message
            << std::endl;
    }
};

char standardLogAppenderMemory[sizeof(StandardLogAppender)] = {};

class LastErrorLogAppender : public LogAppender {
public:
    virtual void append(const LogEvent& v) {
        std::cerr << app::lastErrorMsg() << std::endl;
    }
} lastErrorLogAppender;


class ResetLastErrorMsgAtEndOfScope {
public:
    ResetLastErrorMsgAtEndOfScope() {
    }
    ~ResetLastErrorMsgAtEndOfScope() {
        JP_NO_THROW(theLastErrorMsg = 0);
    }
};

class SetLoggerAtEndOfScope {
public:
    SetLoggerAtEndOfScope(
            std::unique_ptr<WithExtraLogAppender>& withLogAppender,
            LogAppender* lastErrorLogAppender):
                withLogAppender(withLogAppender),
                lastErrorLogAppender(lastErrorLogAppender) {
    }

    ~SetLoggerAtEndOfScope() {
        JP_TRY;
        std::unique_ptr<WithExtraLogAppender> other(
                new WithExtraLogAppender(*lastErrorLogAppender));
        withLogAppender.swap(other);
        JP_CATCH_ALL;
    }

private:
    std::unique_ptr<WithExtraLogAppender>& withLogAppender;
    LogAppender* lastErrorLogAppender;
};

} // namespace


namespace app {
LogAppender& defaultLastErrorLogAppender() {
    return lastErrorLogAppender;
}


std::string lastErrorMsg() {
    if (theLastErrorMsg) {
        return *theLastErrorMsg;
    }
    return "";
}


bool isWithLogging() {
    // If JPACKAGE_DEBUG environment variable is set to "true"
    // logging is enabled.
    return SysInfo::getEnvVariable(
            std::nothrow, _T("JPACKAGE_DEBUG")) == _T("true");
}


int launch(const std::nothrow_t&,
        LauncherFunc func, LogAppender* lastErrorLogAppender) {
    // The log appender is set for the lifetime of the application.
    // Use in-place new to avoid accessing destroyed instance
    // when the shared object destructor logs something.
    if (isWithLogging()) {
        Logger::defaultLogger().setAppender(*new (standardLogAppenderMemory) StandardLogAppender());
    } else {
        Logger::defaultLogger().setAppender(*new (nopLogAppenderMemory) NopLogAppender());
    }

    LOG_TRACE_FUNCTION();

    if (!lastErrorLogAppender) {
        lastErrorLogAppender = &defaultLastErrorLogAppender();
    }
    std::unique_ptr<WithExtraLogAppender> withLogAppender;
    std::string errorMsg;
    const ResetLastErrorMsgAtEndOfScope resetLastErrorMsg;

    JP_TRY;

    // This will temporary change log appenders of the default logger
    // to save log messages in the default and additional log appenders.
    // Log appenders config of the default logger will be restored to
    // the original state at function exit automatically.
    const SetLoggerAtEndOfScope setLogger(withLogAppender, lastErrorLogAppender);
    func();
    return 0;

    // The point of all these redefines is to save the last raw error message in
    // 'theLastErrorMsg' variable.
    // By default error messages are saved in exception instances with the details
    // of error origin (source file, function name, line number).
    // We don't want these details in user error messages. However we still want to
    // save full information about the last error in the default log appender.
#undef JP_HANDLE_ERROR
#undef JP_HANDLE_UNKNOWN_ERROR
#undef JP_CATCH_EXCEPTIONS
#define JP_HANDLE_ERROR(e) \
    do { \
        errorMsg = (tstrings::any() << e.what()).str(); \
        theLastErrorMsg = &errorMsg; \
        reportError(JP_SOURCE_CODE_POS, e); \
    } while(0)
#define JP_HANDLE_UNKNOWN_ERROR \
    do { \
        errorMsg = "Unknown error"; \
        theLastErrorMsg = &errorMsg; \
        reportUnknownError(JP_SOURCE_CODE_POS); \
    } while(0)
#define JP_CATCH_EXCEPTIONS \
    catch (const JpErrorBase& e) { \
        errorMsg = (tstrings::any() << e.rawMessage()).str(); \
        theLastErrorMsg = &errorMsg; \
        try { \
            throw; \
        } catch (const std::runtime_error& e) { \
            reportError(JP_SOURCE_CODE_POS, e); \
        } \
    } catch (const std::runtime_error& e) { \
        errorMsg = lastCRTError(); \
        theLastErrorMsg = &errorMsg; \
        reportError(JP_SOURCE_CODE_POS, e); \
    } \
    JP_CATCH_UNKNOWN_EXCEPTION

    JP_CATCH_ALL;

#undef JP_HANDLE_ERROR
#undef JP_HANDLE_UNKNOWN_ERROR
#undef JP_CATCH_EXCEPTIONS
#define JP_HANDLE_ERROR(e)      JP_REPORT_ERROR(e)
#define JP_HANDLE_UNKNOWN_ERROR JP_REPORT_UNKNOWN_ERROR
#define JP_CATCH_EXCEPTIONS     JP_DEFAULT_CATCH_EXCEPTIONS

    return 1;
}
} // namespace app
