/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */
#pragma once

#include <exception>
#include <mutex>
#include <jni.h>
#include <IO/WriteBufferFromString.h>
#include <boost/core/noncopyable.hpp>
#include <boost/stacktrace.hpp>
#include <jni/jni_common.h>
#include <Common/Exception.h>

#include <sstream>
namespace local_engine
{
class JniErrorsGlobalState : boost::noncopyable
{
protected:
    JniErrorsGlobalState() = default;

public:
    ~JniErrorsGlobalState() = default;

    static JniErrorsGlobalState & instance();
    static void throwException(JNIEnv * env, jclass exception_class, const std::string & message, const std::string & stack_trace = "");

    void initialize(JNIEnv * env_);
    void destroy(JNIEnv * env);

    inline jclass getIOExceptionClass() { return io_exception_class; }
    inline jclass getRuntimeExceptionClass() { return runtime_exception_class; }
    inline jclass getUnsupportedOperationExceptionClass() { return unsupportedoperation_exception_class; }
    inline jclass getIllegalAccessExceptionClass() { return illegal_access_exception_class; }
    inline jclass getIllegalArgumentExceptionClass() { return illegal_argument_exception_class; }

    void throwException(JNIEnv * env, const DB::Exception & e);
    void throwException(JNIEnv * env, const std::exception & e);
    void throwRuntimeException(JNIEnv * env, const std::string & message, const std::string & stack_trace = "");


private:
    jclass io_exception_class = nullptr;
    jclass runtime_exception_class = nullptr;
    jclass unsupportedoperation_exception_class = nullptr;
    jclass illegal_access_exception_class = nullptr;
    jclass illegal_argument_exception_class = nullptr;
};
//

#define LOCAL_ENGINE_JNI_METHOD_START \
    try \
    {
#define LOCAL_ENGINE_JNI_METHOD_END(env, ret) \
    } \
    catch (DB::Exception & e) \
    { \
        local_engine::JniErrorsGlobalState::instance().throwException(env, e); \
        return ret; \
    } \
    catch (std::exception & e) \
    { \
        local_engine::JniErrorsGlobalState::instance().throwException(env, e); \
        return ret; \
    } \
    catch (...) \
    { \
        DB::WriteBufferFromOwnString ostr; \
        auto trace = boost::stacktrace::stacktrace(); \
        boost::stacktrace::detail::to_string(&trace.as_vector()[0], trace.size()); \
        local_engine::JniErrorsGlobalState::instance().throwRuntimeException(env, "Unknown Exception", ostr.str().c_str()); \
        return ret; \
    }
}