/***************************************************************************
 *   crengine-ng                                                           *
 *   Copyright (C) 2007-2009,2011-2015 Vadim Lopatin <coolreader.org@gmail.com>
 *   Copyright (C) 2020 poire-z <poire-z@users.noreply.github.com>         *
 *   Copyright (C) 2020,2021 Aleksey Chernov <valexlin@gmail.com>          *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU General Public License           *
 *   as published by the Free Software Foundation; either version 2        *
 *   of the License, or (at your option) any later version.                *
 *                                                                         *
 *   This program 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 for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the Free Software           *
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,            *
 *   MA 02110-1301, USA.                                                   *
 ***************************************************************************/

/**
 * \file lvmemman.cpp
 * \brief memory manager implementation
 */

#include <lvmemman.h>
#include <lvref.h>
#include <lvstreamutils.h>
#include <crlog.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef _LINUX
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE
#endif
#include <signal.h>
#include <unistd.h>
#endif

#ifdef _DEBUG
#include <stdexcept>
#include <string>
#endif

static char file_to_remove_on_crash[2048] = "";

void crSetFileToRemoveOnFatalError(const char* filename) {
    strcpy(file_to_remove_on_crash, filename == NULL ? "" : filename); // NOLINT
}

#ifdef _LINUX
static struct sigaction old_sa[NSIG];

#ifdef ANDROID
//#define ANDROID_BACKTRACE
#endif

#ifdef ANDROID_BACKTRACE
#include <unwind.h>
#include <dlfcn.h>

namespace
{
struct BacktraceState
{
    void** current;
    void** end;
};

static _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) {
    BacktraceState* state = static_cast<BacktraceState*>(arg);
    uintptr_t pc = _Unwind_GetIP(context);
    if (pc) {
        if (state->current == state->end) {
            return _URC_END_OF_STACK;
        } else {
            *state->current++ = reinterpret_cast<void*>(pc);
        }
    }
    return _URC_NO_REASON;
}

} // namespace

size_t captureBacktrace(void** buffer, size_t max) {
    BacktraceState state = { buffer, buffer + max };
    _Unwind_Backtrace(unwindCallback, &state);

    return state.current - buffer;
}

void dumpBacktrace(void** addrs, size_t count) {
    for (size_t idx = 0; idx < count; ++idx) {
        const void* addr = addrs[idx];
        const char* symbol = "";

        Dl_info info;
        if (dladdr(addr, &info) && info.dli_sname) {
            symbol = info.dli_sname;
        }

        CRLog::trace("   # %02d : 0x%08x   %s", idx, addr, symbol);
    }
}

#endif // ANDROID_BACKTRACE

void cr_sigaction(int signal, siginfo_t* info, void* reserved) {
    CR_UNUSED2(info, reserved);
    if (file_to_remove_on_crash[0])
        unlink(file_to_remove_on_crash);
    CRLog::error("cr_sigaction(%d)", signal);

#ifdef ANDROID_BACKTRACE
    void* buffer[50];
    dumpBacktrace(buffer, captureBacktrace(buffer, 50));
#endif

    old_sa[signal].sa_handler(signal);
}
#endif

void crSetSignalHandler() {
#ifdef _LINUX
    static bool signals_are_set = false;
    if (signals_are_set)
        return;
    signals_are_set = true;
    struct sigaction handler = { 0 };
    handler.sa_sigaction = cr_sigaction;
    handler.sa_flags = SA_RESETHAND;
#define CATCHSIG(X) sigaction(X, &handler, &old_sa[X])
    CATCHSIG(SIGILL);
    CATCHSIG(SIGABRT);
    CATCHSIG(SIGBUS);
    CATCHSIG(SIGFPE);
    CATCHSIG(SIGSEGV);
    //CATCHSIG(SIGSTKFLT);
    CATCHSIG(SIGPIPE);
#endif
}

/// default fatal error handler: uses exit()
void lvDefFatalErrorHandler(int errorCode, const char* errorText) {
    char strbuff[10];
    sprintf(strbuff, "%d", errorCode);
    fprintf(stderr, "FATAL ERROR #%s: %s\n", strbuff, errorText);
#ifdef _DEBUG
    std::string errstr = std::string("FATAL ERROR #") + std::string(strbuff) + std::string(": ") + std::string(errorText);
    throw std::runtime_error(errstr);
#endif
    exit(errorCode);
}

lv_FatalErrorHandler_t* lvFatalErrorHandler = &lvDefFatalErrorHandler;

void crFatalError(int code, const char* errorText) {
    if (file_to_remove_on_crash[0])
        LVDeleteFile(Utf8ToUnicode(lString8(file_to_remove_on_crash)));
    lvFatalErrorHandler(code, errorText);
}

/// set fatal error handler
void crSetFatalErrorHandler(lv_FatalErrorHandler_t* handler) {
    lvFatalErrorHandler = handler;
}

ref_count_rec_t ref_count_rec_t::null_ref(NULL);
ref_count_rec_t ref_count_rec_t::protected_null_ref(NULL);

#if (LDOM_USE_OWN_MEM_MAN == 1)
ldomMemManStorage* pmsREF = NULL;

ldomMemManStorage* block_storages[LOCAL_STORAGE_COUNT] = {
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
};

inline int blockSizeToStorageIndex(size_t n) {
    return (n + ((1 << BLOCK_SIZE_GRANULARITY) - 1)) >> BLOCK_SIZE_GRANULARITY;
}

void* ldomAlloc(size_t n) {
    n = blockSizeToStorageIndex(n);
    if (n < LOCAL_STORAGE_COUNT) {
        if (block_storages[n] == NULL) {
            block_storages[n] = new ldomMemManStorage((n + 1) * BLOCK_SIZE_GRANULARITY);
        }
        return block_storages[n]->alloc();
    } else {
        return malloc(n);
    }
}

void ldomFree(void* p, size_t n) {
    n = blockSizeToStorageIndex(n);
    if (n < LOCAL_STORAGE_COUNT) {
        if (block_storages[n] == NULL) {
            crFatalError();
        }
        block_storages[n]->free((ldomMemBlock*)p);
    } else {
        free(p);
    }
}
#endif
