/*
 * Copyright (C) 2014 PerfectCarl - https://github.com/PerfectCarl/vala-stacktrace
 *
 * Licensed 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.
 */

 /**
  * Provides services to display vala stacktraces
  */
namespace Ivy {

	internal enum Style {
		RESET = 0,
		BRIGHT = 1,
		DIM = 2,
		UNDERLINE = 3,
		BLINK = 4,
		REVERSE = 7,
		HIDDEN = 8
	}

	/**
	 * Defines how Unix signals are processed
	 */
	public enum CriticalHandler {
		/**
		 * Unix signals are ignored
		 */
		IGNORE,
		/**
		 * When a signal is intercepted, a stacktrace is displayed
		 * to ``stdout`` and the execution of the application is
		 * resumed
		 */
		PRINT_STACKTRACE,
		/**
		 * When a signal is intercepted, a stacktrace is displayed
		 * to ``stdout`` and  the application is stopped
		 */
		CRASH
	}

	/**
	 * Colors used for displaying stacktraces
	 */
	public enum Color {
		BLACK = 0,
		RED = 1,
		GREEN = 2,
		YELLOW = 3,
		BLUE = 4,
		MAGENTA = 5,
		CYAN = 6,
		WHITE = 7
	}

	/**
	 * A complete execution stacktrace
	 *
	 * Holds a collection of {@link Frame} and the basic methods to intercept Unix signals
	 * and prints the complete stacktrace to ``stdout`` in colors.
	 *
	 * For more information, refer to the [[https://github.com/I-hate-farms/stacktrace|official website]].
	 *
	 * Here's a sample of a printed stacktrace:
	 * {{{
	 * An error occured (SIGSEGV) in samples/vala_file.vala, line 21 in 'this_will_crash_harder'
	 * The reason is likely a null reference being used.
	 *
	 *    #1  <unknown>                                   in 'strlen'
	 *        at /lib/x86_64-linux-gnu/libc.so.6
	 * *  #2  samples/vala_file.vala             line  21 in 'this_will_crash_harder'
	 *        at /home/cran/Documents/Projects/i-hate-farms/stacktrace/samples/vala_file.vala:21
	 *    #3  samples/module/OtherModule.vala    line  11 in 'other_module_do_it'
	 *        at /home/cran/Documents/Projects/i-hate-farms/stacktrace/samples/module/OtherModule.vala:11
	 *    #4  samples/error_sigsegv.vala         line  19 in 'namespace_someclass_exec'
	 *        at /home/cran/Documents/Projects/i-hate-farms/stacktrace/samples/error_sigsegv.vala:19
	 *    #5  samples/error_sigsegv.vala         line  29 in 'this_will_crash'
	 *        at /home/cran/Documents/Projects/i-hate-farms/stacktrace/samples/error_sigsegv.vala:29
	 *    #6  samples/error_sigsegv.vala         line  39 in '_vala_main'
	 *        at /home/cran/Documents/Projects/i-hate-farms/stacktrace/samples/error_sigsegv.vala:39
	 *    #7  error_sigsegv.vala.c               line 421 in 'main'
	 *        at /home/cran/Documents/Projects/i-hate-farms/stacktrace/error_sigsegv.vala.c:421
	 *    #8  <unknown>                                   in '__libc_start_main'
	 *        at /lib/x86_64-linux-gnu/libc.so.6
	 * }}}
	 */
	public class Stacktrace {

		internal Frame first_vala = null;

		internal int max_file_name_length = 0;

		internal int max_line_number_length = 0;

		internal bool is_all_function_name_blank = true;

		internal bool is_all_file_name_blank = true;

		private Gee.List<Frame> _frames = new Gee.ArrayList<Frame>();

		/**
		 * Unix signal being intercepted
		 *
		 */
		public ProcessSignal sig;

		/**
		 * Enables the Unix signals interception
		 *
		 * Setting it to false and preventing signals interception and the collection
		 * of the complete stacktrace (that might a significant effect on performance)
		 * is useful when the application uses a library that emits a ``SIGTRAP``
		 * signal for unknown reasons.
		 *
		 * Such a case would cripple the application performance and clutter the
		 * ``stdout`` ouput for no benefit.
		 *
		 * Default is ``true``
		 */
		public static bool enabled { get;set;default = true;}

		/**
		 * Hides frames located in external system libraries (like ``libgc``) without
		 * code information
		 *
		 * * Default is ``true``
		 *
		 * Before
		 * {{{
		 *    An error occured (SIGTRAP) in ../src/Database/Core/QueryResult.vala, line 26 in 'app_center_core_query_result_finalize'
		 *   The reason is likely an uncaught error.
		 *
		 *    #1  <unknown>                                          in 'g_signal_handlers_disconnect_matched'
		 *        at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
		 * *  #2  ../src/Database/Core/QueryResult.vala    line  26 in 'app_center_core_query_result_finalize'
		 *        at /home/cran/Documents/Projects/i-hate-farms/app/exocron/src/Database/Core/QueryResult.vala:26
		 *    #3  <unknown>                                          in 'g_object_unref'
		 *        at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
		 *    #4  ../src/Database/Core/Dao.vala            line  88 in 'app_center_core_dao_insert'
		 *        at /home/cran/Documents/Projects/i-hate-farms/app/exocron/src/Database/Core/Dao.vala:88
		 *    #5  ../src/Database/PackageKitSource.vala    line  18 in 'app_center_core_package_kit_source_fetch'
		 *        at /home/cran/Documents/Projects/i-hate-farms/app/exocron/src/Database/PackageKitSource.vala:18
		 *    #6  ../src/MainPanel.vala                    line  68 in '__lambda19_'
		 *        at /home/cran/Documents/Projects/i-hate-farms/app/exocron/src/MainPanel.vala:68
		 *    #7  src/MainPanel.c                          line 297 in '___lambda19__app_center_views_browse_view_show_app_info'
		 *        at /home/cran/Documents/Projects/i-hate-farms/app/exocron/build/src/MainPanel.c:297
		 *    #8  <unknown>                                          in 'g_cclosure_marshal_VOID__STRINGv'
		 *        at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
		 *    #9  <unknown>                                          in 'g_signal_emit_valist'
		 *        at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
		 *    #10  <unknown>                                          in 'g_signal_emit_by_name'
		 *        at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
		 *    #11  src/BrowseView.c                         line 871 in '__lambda16_'
		 *        at /home/cran/Documents/Projects/i-hate-farms/app/exocron/build/src/BrowseView.c:871
		 *    #12  src/BrowseView.c                         line 878 in '___lambda16__gtk_button_clicked'
		 *        at /home/cran/Documents/Projects/i-hate-farms/app/exocron/build/src/BrowseView.c:878
		 *    #13  <unknown>                                          in 'g_signal_emit_valist'
		 *        at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
		 *    #14  <unknown>                                          in 'g_signal_emit'
		 *        at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
		 *    #15  <unknown>                                          in 'g_closure_invoke'
		 *        at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
		 *    #16  <unknown>                                          in 'g_signal_emit_valist'
		 *        at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
		 *    #17  <unknown>                                          in 'g_signal_emit'
		 *        at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
		 *    #18  <unknown>                                          in 'ffi_call_unix64'
		 *        at /usr/lib/x86_64-linux-gnu/libffi.so.6
		 *    #19  <unknown>                                          in 'ffi_call'
		 *        at /usr/lib/x86_64-linux-gnu/libffi.so.6
		 *    #20  <unknown>                                          in 'g_cclosure_marshal_generic_va'
		 *        at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
		 *    #21  <unknown>                                          in 'g_signal_emit_valist'
		 *        at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
		 *    #22  <unknown>                                          in 'g_signal_emit'
		 *        at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
		 *    #23  <unknown>                                          in 'g_cclosure_marshal_VOID__BOXEDv'
		 *        at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
		 *    #24  <unknown>                                          in 'g_signal_emit_valist'
		 *        at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
		 *    #25  <unknown>                                          in 'g_signal_emit'
		 *        at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
		 *    #26  <unknown>                                          in 'gtk_event_controller_handle_event'
		 *        at /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
		 *    #27  <unknown>                                          in 'g_signal_emit_valist'
		 *        at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
		 *    #28  <unknown>                                          in 'g_signal_emit'
		 *        at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
		 *    #29  <unknown>                                          in 'gtk_main_do_event'
		 *        at /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
		 *    #30  <unknown>                                          in 'g_main_context_dispatch'
		 *        at /lib/x86_64-linux-gnu/libglib-2.0.so.0
		 *    #31  <unknown>                                          in 'g_main_context_iteration'
		 *        at /lib/x86_64-linux-gnu/libglib-2.0.so.0
		 *    #32  <unknown>                                          in 'g_application_run'
		 *        at /usr/lib/x86_64-linux-gnu/libgio-2.0.so.0
		 *    #33  <unknown>                                          in 'granite_application_run'
		 *        at /usr/lib/x86_64-linux-gnu/libgranite.so.2
		 *    #34  ../src/Application.vala                  line  54 in 'app_center_main'
		 *        at /home/cran/Documents/Projects/i-hate-farms/app/exocron/src/Application.vala:54
		 *    #35  src/Application.c                        line 296 in 'main'
		 *        at /home/cran/Documents/Projects/i-hate-farms/app/exocron/build/src/Application.c:296
		 *    #36  <unknown>                                          in '__libc_start_main'
		 *        at /lib/x86_64-linux-gnu/libc.so.6
		 *
		 * }}}
		 *
		 * After :
		 * {{{
		 * An error occured (SIGTRAP) in ../src/Database/Core/QueryResult.vala, line 26 in 'app_center_core_query_result_finalize'
		 *    The reason is likely an uncaught error.
		 *
		 *     #1  <unknown>                                         in 'g_signal_handlers_disconnect_matched'
		 *         at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
		 *  *  #2  ../src/Database/Core/QueryResult.vala    line  26 in 'app_center_core_query_result_finalize'
		 *         at /home/cran/Documents/Projects/i-hate-farms/app/exocron/src/Database/Core/QueryResult.vala:26
		 *     #3  ../src/Database/Core/Dao.vala            line  88 in 'app_center_core_dao_insert'
		 *         at /home/cran/Documents/Projects/i-hate-farms/app/exocron/src/Database/Core/Dao.vala:88
		 *     #4  ../src/Database/PackageKitSource.vala    line  18 in 'app_center_core_package_kit_source_fetch'
		 *         at /home/cran/Documents/Projects/i-hate-farms/app/exocron/src/Database/PackageKitSource.vala:18
		 *     #5  ../src/MainPanel.vala                    line  68 in '__lambda19_'
		 *         at /home/cran/Documents/Projects/i-hate-farms/app/exocron/src/MainPanel.vala:68
		 *     #6  src/MainPanel.c                          line 297 in '___lambda19__app_center_views_browse_view_show_app_info'
		 *         at /home/cran/Documents/Projects/i-hate-farms/app/exocron/build/src/MainPanel.c:297
		 *     #7  src/BrowseView.c                         line 871 in '__lambda16_'
		 *         at /home/cran/Documents/Projects/i-hate-farms/app/exocron/build/src/BrowseView.c:871
		 *     #8  src/BrowseView.c                         line 878 in '___lambda16__gtk_button_clicked'
		 *         at /home/cran/Documents/Projects/i-hate-farms/app/exocron/build/src/BrowseView.c:878
		 *     #9  ../src/Application.vala                  line  55 in 'app_center_main'
		 *         at /home/cran/Documents/Projects/i-hate-farms/app/exocron/src/Application.vala:55
		 *     #10  src/Application.c                       line 304 in 'main'
		 *         at /home/cran/Documents/Projects/i-hate-farms/app/exocron/build/src/Application.c:304
		 * }}}
		 */
		public static bool hide_installed_libraries { get;set;default = true;}

		/**
		 * Sets the default higlighted text color for stacktrace that are created
		 * via Unix signals interception
		 *
		 * Default is ``Color.WHITE``
		 */
		public static Color default_highlight_color { get;set;default = Color.WHITE;}

		/**
		 * Sets the default background color for stacktrace that are created
		 * via Unix signals interception
		 *
		 * Default is ``Color.RED``
		 */
		 public static Color default_error_background { get;set;default = Color.RED;}

		/**
		 * Sets the stacktrace higlighted text color when printed on ``stdout``
		 *
		 * Default is ``Color.WHITE``
		 */
		 public Color highlight_color { get;set;default = Color.WHITE;}

		/**
		 * Sets the stacktrace background color when printed on ``stdout``
		 *
		 * Default is ``Color.RED``
		 */
		public Color error_background { get;set;default = Color.RED;}

		/**
		 * Collection of frames
		 *
		 */
		public Gee.List<Frame> frames {
			get {
				return _frames;
			}
		}

		private Printer printer = new Printer ();
		private Extractor extractor = new Extractor ();

		public Stacktrace (GLib.ProcessSignal sig = GLib.ProcessSignal.TTOU) {
			this.sig = sig;
			// The stacktrace is used likely to understand the inner
			// working of the app so we displays everything.
			if (is_custom) {
				hide_installed_libraries = false;
				error_background = Color.BLUE;
			} else {
				error_background = default_error_background;
				highlight_color = default_highlight_color;
			}
			extractor.create_stacktrace (this);
		}

		/**
		 * Returns true if the stacktrace is "custom"
		 *
		 * A custom stacktrace has been created via code as
		 * opposed to created via Unix signal interception.
		 *
		 * Custom stacktrace are displayed with a different color scheme (default ``Color.GREEN``) that
		 * can be set via {@link highlight_color} and {@link error_background}
		 *
		 * Here's how to create a custom stacktrace:
		 * {{{
		 *
		 *   int my_function (string arg) {
		 *      var custom_stracktrace = new Stacktrace ();
		 *      custom_stracktrace.print ();
		 *      return 0;
		 *   }
		 * }}}
		 */
		public bool is_custom {
			get {
				return sig == ProcessSignal.TTOU;
			}
		}

		/**
		 * Prints the stacktrace to ``stdout`` with colors
		 *
		 */
		public void print () {
		   printer.print (this);
		}

		/**
		 * Registers handlers to intercept Unix signals
		 *
		 * Calling ``register_handlers`` is required for the
		 * library to display a stacktrace when the application encounters an error (ie raises a ``SIGABRT``,
		 * a ``SIGSEV`` or a ``SIGTRAP`` signal).
		 *
		 * ''Note:'' calling ``register_handlers`` is not needed to be able to display custom stacktraces. <<BR>>
		 * (See {@link is_custom} for more information about custom stacktraces).
		 *
		 * How to initialize the library so it can intercept Unix signals:
		 * {{{
		 *
		 *   static int main (string[] arg) {
		 *      Stacktrace.register_handlers ();
		 *      // Start your application
		 *      ...
		 *      return 0;
		 *   }
		 * }}}
		 *
		 */
		public static void register_handlers () {
			//Log.set_always_fatal (LogLevelFlags.LEVEL_CRITICAL);
			Log.set_always_fatal (LogLevelFlags.LEVEL_ERROR);

			Process.@signal (ProcessSignal.SEGV, handler);
			Process.@signal (ProcessSignal.TRAP, handler);
			if (critical_handling != CriticalHandler.IGNORE)
				Process.@signal (ProcessSignal.ABRT, handler);
		}

		/**
		 * Defines how Unix signals are processed
		 *
		 * Default is ``CriticalHandler.PRINT_STACKTRACE``.
		 */
		public static CriticalHandler critical_handling  { get;set;default = CriticalHandler.PRINT_STACKTRACE;}

		/*{
			set {
				_critical_handling = value;
				if( value == CriticalHandler.CRASH )
				//var variables = Environ.get ();
				//Environ.set_variable (variables, "G_DEBUG", "fatal-criticals" );
				Log.set_always_fatal (LogLevelFlags.LEVEL_CRITICAL);
			}
			get {
			}

		   }*/

		private static void handler (int sig) {
			if( !enabled)
				return;
			Stacktrace stack = new Stacktrace ((ProcessSignal) sig);
			stack.print ();
			if (sig != ProcessSignal.TRAP ||
				(sig == ProcessSignal.TRAP && critical_handling == CriticalHandler.CRASH))
				Process.exit (1);
		}

	}
}
