/*
    zdump.c - make core dump
    Copyright (C) 2012-2022 Ladislav Vaiz <ok1zia@nagano.cz>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    version 2 as published by the Free Software Foundation.

*/

#include <libziaint.h>

#include <zdump.h>

#include <zbfd.h>
#include <zdebug.h>
#include <zmsgbox.h>
#include <zpath.h>
#include <zselect.h>

#include <zglib.h>

#ifdef Z_HAVE_WINSOCK2_H
#include <winsock2.h>
#endif
#ifdef Z_HAVE_WINDOWS_H
#include <windows.h>
#endif

/*#include <zdebug.h>
#include <zmsgbox.h>
#include <zpath.h>
#include <zstr.h>
  */
/*#ifdef Z_HAVE_BFD_H
#include <bfd.h>
#endif

#ifdef Z_HAVE_EXECINFO_H
#include <execinfo.h>
#endif

#ifdef Z_HAVE_SIGNAL_H
#include <signal.h>
#endif

#include <string.h>
*/
#include <glib.h>

static char *zdump_filename = NULL;
void (*z_app_crash_handler)(GString *gs) = NULL;
static char *zdump_msg_title = NULL;
static char *zdump_appddir = NULL;

#ifdef Z_MSC_MINGW
static int zdumpset = 0;
#endif

#ifdef Z_HAVE_DBGHELP_H
#include "dbghelp.h"


BOOL CALLBACK z_minidumpcallback(PVOID pParam, const PMINIDUMP_CALLBACK_INPUT pInput, PMINIDUMP_CALLBACK_OUTPUT pOutput){
	if (!pInput) return FALSE;
	if (!pOutput) return FALSE;

	switch (pInput->CallbackType)
	{
		case IncludeModuleCallback: return TRUE;
		case IncludeThreadCallback: return TRUE;
		case ModuleCallback:
			if (!(pOutput->ModuleWriteFlags & ModuleReferencedByMemory))
				pOutput->ModuleWriteFlags &= (~ModuleWriteModule);
			return TRUE;
		case ThreadCallback: return TRUE;
		case ThreadExCallback: return TRUE;
		case MemoryCallback: return FALSE;
		default: return FALSE;
	}
}

int z_createminidump(char *filename, EXCEPTION_POINTERS *pep)
{
	MINIDUMP_CALLBACK_INFORMATION mci;
	MINIDUMP_EXCEPTION_INFORMATION mdei;
	MINIDUMP_TYPE mdt;
	HANDLE hFile;
	int ret = 0;
#ifndef Z_HAVE_DBGHELP_H
	HMODULE dll;
#endif

	if (!filename) return -1;
	hFile = CreateFile(filename, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE) return -1;

	mdei.ThreadId           = GetCurrentThreadId();
	mdei.ExceptionPointers  = pep;
	mdei.ClientPointers     = FALSE;
	mci.CallbackRoutine     = (MINIDUMP_CALLBACK_ROUTINE)z_minidumpcallback;
	mci.CallbackParam       = 0;
	mdt = (MINIDUMP_TYPE)(MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory);
#ifdef Z_HAVE_DBGHELP_H
	ret = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, mdt, (pep != 0) ? &mdei : 0, 0, &mci) == 0;
#else
	dll = LoadLibrary("DbgHelp.dll");
	if (dll){
		T_MiniDumpWriteDump P_MiniDumpWriteDump = (T_MiniDumpWriteDump)GetProcAddress(dll, "MiniDumpWriteDump");
		ret = P_MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, mdt, (pep != 0) ? &mdei : 0, 0, &mci) == 0;
		FreeLibrary(dll);
	}else{
		ret = -1;
	}
#endif
	CloseHandle(hFile);
	return ret;
}
#endif

//#include <unistd.h>

#ifdef Z_UNIX_ANDROID
void __attribute__((noinline)) z_sig_segv(void *arg, siginfo_t *siginfo, void *ctx){
    GString *gs;

    signal(SIGSEGV, SIG_DFL);
    dbg("z_sig_segv\n");
	gs = g_string_sized_new(2000);

	if (z_app_crash_handler) z_app_crash_handler(gs);

	g_string_append(gs, "\n\nBacktrace:\n");
    z_dump_backtrace(gs, NULL, ctx, 3);

  	z_msgbox_error(zdump_msg_title ? zdump_msg_title : "Libzia app", "%s", gs->str);
    raise(SIGSEGV);
}   
#endif

#ifdef Z_MSC_MINGW
static LONG WINAPI z_crashhandler(EXCEPTION_POINTERS *ex)
{
	GString *gs;
	int dump = 1;

	gs = g_string_sized_new(2000);
	if (z_app_crash_handler) z_app_crash_handler(gs);

#ifdef Z_MSC
	dump = z_createminidump(zdump_filename, ex);
	if (dump == 0) {
		if (gs->len > 0) g_string_append(gs, "\n\n");
		g_string_append_printf(gs, "Dump is saved to %s", zdump_filename);
	}
#endif

	g_string_append(gs, "\n\nBacktrace:\n");
	z_dump_backtrace(gs, ex, NULL, 0);
	z_msgbox_error(zdump_msg_title ? zdump_msg_title : "Libzia app", "%s", gs->str);
	exit(7303);
}

#endif



// can be called more times
void z_dump_init(char *filename, void (*handler)(GString *gs), char *msg_title, char *appddir){
	zg_free0(zdump_filename);
	zdump_filename = g_strdup(filename);
	z_app_crash_handler = handler;
	zg_free0(zdump_msg_title);
	zdump_msg_title = g_strdup(msg_title);
    zdump_appddir = appddir;
	z_wokna(zdump_filename);
    zselect_signal_init();
#ifdef Z_UNIX_ANDROID
    dbg("nastaven SIGSEGV\n");
	zselect_signal_set(SIGSEGV, z_sig_segv, NULL, 1);
#else
    dbg("Nejsme Z_UNIX_ANDROID\n");
#endif
#ifdef Z_MSC_MINGW
	if (!zdumpset){
		zdumpset = 1;
		SetUnhandledExceptionFilter(z_crashhandler);
	}
#endif
}

void z_dump_free(){
	if (zdump_filename) zg_free0(zdump_filename);
	if (zdump_msg_title) zg_free0(zdump_msg_title);
#ifdef Z_UNIX_ANDROID
	zselect_signal_set(SIGSEGV, NULL, NULL, 0);
#endif
}

void z_dump_create(void){
#ifdef Z_MSC
	z_createminidump(zdump_filename, NULL);
#else
	raise(SIGSEGV);
#endif
}


char *z_dump_backtrace(GString *gs, void *vex, void *ctx, int level){
    void *stack[30];
    int stacklen;
    struct zbfd *zbfd;
    int i;
    char *filename;


	zbfd = zbfd_init();

    stacklen = z_backtrace(stack, 30, vex, ctx, level);
 //   z_msgbox_info(__FUNCTION__, "stacklen=%d", stacklen);

    filename = z_binary_file_name();
    if (filename && zbfd_open(zbfd, filename, zdump_appddir) == 0){
        for (i = 0; i < stacklen; i++){
            //g_string_append_printf(gs, "#%-2d 0x%llx", i, (unsigned long long)stack[i]);
            g_string_append_printf(gs, "#%-2d %p", i, stack[i]);

            if (zbfd_lookup(zbfd, stack[i]) == 0){
//				g_string_append_printf(gs, " in %s() +0x%lX at %s:%d", zbfd->functionname, zbfd->offset, zbfd->filename, zbfd->line);
                if (zbfd->functionname) {
                    g_string_append_printf(gs, " in %s()", zbfd->functionname);
                    if (zbfd->offset > 0) g_string_append_printf(gs, " +0x%lX", zbfd->offset); 
                }
                if (zbfd->filename) {
                    g_string_append_printf(gs, " at %s", zbfd->filename);
                    if (zbfd->line) g_string_append_printf(gs, ":%d", zbfd->line);
                }
            }
            g_string_append(gs, "  \n");
        }
        zbfd_free(zbfd);
    }else{
		g_string_append_printf(gs, "%s\n", zbfd->errstr->str);
        for (i = 0; i < stacklen; i++){
            //g_string_append_printf(gs, "#%-2d 0x%llx", i, (unsigned long long)stack[i]);
            g_string_append_printf(gs, "#%-2d %p\n", i, stack[i]);
        }
    }
    if (filename) g_free(filename);
	return gs->str;
}
