public errordomain StacktraceThrowError {
    CAPTURED
}

public class StacktraceError : Object {
    private const string STACKTRACE_MARKER = "Stacktrace at throw-time:";

    public string original_message { get; private set; default = ""; }

    public Stacktrace stacktrace { get; private set; }

    public string stacktrace_text { get; private set; default = ""; }

    public StacktraceError (string message, int extra_skipped_frames_count = 0) {
        original_message = message;
        stacktrace = new Stacktrace (ProcessSignal.TTOU, extra_skipped_frames_count + 1);
        stacktrace_text = format_stacktrace (stacktrace);
    }

    public string to_error_message () {
        if (stacktrace_text == "")
            return original_message;

        return "%s\n\n%s\n%s".printf (
            original_message,
            STACKTRACE_MARKER,
            stacktrace_text
        );
    }

    public static void @throw (string message) throws StacktraceThrowError {
        var captured = new StacktraceError (message, 2);
        throw new StacktraceThrowError.CAPTURED ("%s", captured.to_error_message ());
    }

    public static void throw_error (Error error) throws StacktraceThrowError {
        var captured = new StacktraceError (error.message, 2);
        throw new StacktraceThrowError.CAPTURED ("%s", captured.to_error_message ());
    }

    public static bool has_captured_stacktrace (Error error) {
        return error.message.index_of (STACKTRACE_MARKER) >= 0;
    }

    public static string? get_captured_stacktrace (Error error) {
        int marker_pos = error.message.index_of (STACKTRACE_MARKER);
        if (marker_pos < 0)
            return null;

        int stacktrace_pos = marker_pos + STACKTRACE_MARKER.length;
        return error.message.substring (stacktrace_pos).strip ();
    }

    public static string format_stacktrace (Stacktrace stacktrace) {
        var builder = new StringBuilder ();
        int index = 1;

        foreach (var frame in stacktrace.frames) {
            bool show_frame = frame.function != "" ||
                frame.file_path.has_suffix (".vala") ||
                frame.file_path.has_suffix (".c");

            if (!show_frame)
                continue;

            builder.append ("#%d ".printf (index));

            if (frame.function != "")
                builder.append ("in '%s'".printf (frame.function));
            else
                builder.append ("in <unknown>");

            if (frame.file_path != "") {
                builder.append (" at %s".printf (frame.file_path));
                if (frame.line_number != "")
                    builder.append (":%s".printf (frame.line_number));
            } else if (frame.elf_path != "") {
                builder.append (" in %s".printf (frame.elf_path));
            } else if (frame.address != "") {
                builder.append (" at %s".printf (frame.address));
            }

            builder.append_c ('\n');
            if (frame.elf_path != "" && frame.elf_path != frame.file_path)
                builder.append ("    in %s\n".printf (frame.elf_path));
            index++;
        }

        return builder.str.strip ();
    }
}
