/* ide-diagnostic.c
 *
 * Copyright (C) 2015 Christian Hergert <christian@hergert.me>
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

#define G_LOG_DOMAIN "ide-diagnostic"

#include "egg-counter.h"

#include "ide-diagnostic.h"
#include "ide-internal.h"
#include "ide-source-location.h"
#include "ide-source-range.h"

G_DEFINE_BOXED_TYPE (IdeDiagnostic, ide_diagnostic, ide_diagnostic_ref, ide_diagnostic_unref)

EGG_DEFINE_COUNTER (instances, "IdeDiagnostic", "Instances", "Number of IdeDiagnostic")

struct _IdeDiagnostic
{
  volatile gint          ref_count;
  IdeDiagnosticSeverity  severity;
  gchar                 *text;
  IdeSourceLocation     *location;
  GPtrArray             *fixits;
  GPtrArray             *ranges;
};

IdeDiagnostic *
ide_diagnostic_ref (IdeDiagnostic *self)
{
  g_return_val_if_fail (self, NULL);
  g_return_val_if_fail (self->ref_count > 0, NULL);

  g_atomic_int_inc (&self->ref_count);

  return self;
}

void
ide_diagnostic_unref (IdeDiagnostic *self)
{
  g_return_if_fail (self);
  g_return_if_fail (self->ref_count > 0);

  if (g_atomic_int_dec_and_test (&self->ref_count))
    {
      g_clear_pointer (&self->location, ide_source_location_unref);
      g_clear_pointer (&self->text, g_free);
      g_clear_pointer (&self->ranges, g_ptr_array_unref);
      g_clear_pointer (&self->fixits, g_ptr_array_unref);
      g_free (self);

      EGG_COUNTER_DEC (instances);
    }
}

IdeDiagnosticSeverity
ide_diagnostic_get_severity (IdeDiagnostic *self)
{
  g_return_val_if_fail (self, IDE_DIAGNOSTIC_IGNORED);

  return self->severity;
}

const gchar *
ide_diagnostic_get_text (IdeDiagnostic *self)
{
  g_return_val_if_fail (self, NULL);

  return self->text;
}

/**
 * ide_diagnostic_get_text_for_display:
 *
 * This creates a new string that is formatted using the diagnostics line number, column, severity,
 * and message text in the format "line:column: severity: message".
 *
 * This can be convenient when wanting to quickly display a diagnostic such as in a tooltip.
 *
 * Returns: (transfer full): A string containing the text formatted for display.
 */
gchar *
ide_diagnostic_get_text_for_display (IdeDiagnostic *self)
{
  IdeSourceLocation *location;
  const gchar *severity;
  guint line = 0;
  guint column = 0;

  g_return_val_if_fail (self, NULL);

  severity = ide_diagnostic_severity_to_string (self->severity);
  location = ide_diagnostic_get_location (self);
  if (location)
    {
      line = ide_source_location_get_line (location) + 1;
      column = ide_source_location_get_line_offset (location) + 1;
    }

  return g_strdup_printf ("%u:%u: %s: %s", line, column, severity, self->text);
}

guint
ide_diagnostic_get_num_ranges (IdeDiagnostic *self)
{
  g_return_val_if_fail (self, 0);

  return self->ranges ? self->ranges->len : 0;
}

/**
 * ide_diagnostic_get_range:
 *
 * Retrieves the range found at @index. It is a programming error to call this
 * function with a value greater or equal to ide_diagnostic_get_num_ranges().
 *
 * Returns: (transfer none) (nullable): An #IdeSourceRange
 */
IdeSourceRange *
ide_diagnostic_get_range (IdeDiagnostic *self,
                          guint          index)
{
  g_return_val_if_fail (self, NULL);

  if (self->ranges)
    {
      if (index < self->ranges->len)
        return g_ptr_array_index (self->ranges, index);
    }

  return NULL;
}

/**
 * ide_diagnostic_get_location:
 * @self: An #IdeDiagnostic.
 *
 * Gets the location of a diagnostic.
 *
 * Returns: (transfer none): Gets the location of a diagnostic.
 */
IdeSourceLocation *
ide_diagnostic_get_location (IdeDiagnostic *self)
{
  g_return_val_if_fail (self, NULL);

  if (self->location)
    return self->location;

  if (self->ranges && self->ranges->len > 0)
    {
      IdeSourceRange *range;

      range = ide_diagnostic_get_range (self, 0);
      return ide_source_range_get_begin (range);
    }

  return NULL;
}

IdeDiagnostic *
_ide_diagnostic_new (IdeDiagnosticSeverity  severity,
                     const gchar           *text,
                     IdeSourceLocation     *location)
{
  IdeDiagnostic *ret;

  ret = g_new0 (IdeDiagnostic, 1);
  ret->ref_count = 1;
  ret->severity = severity;
  ret->text = g_strdup (text);
  ret->location = location ? ide_source_location_ref (location) : NULL;

  EGG_COUNTER_INC (instances);

  return ret;
}

void
_ide_diagnostic_take_fixit (IdeDiagnostic *self,
                            IdeFixit      *fixit)
{
  g_return_if_fail (self);
  g_return_if_fail (fixit);

  if (self->fixits == NULL)
    self->fixits = g_ptr_array_new_with_free_func ((GDestroyNotify)ide_fixit_unref);

  g_ptr_array_add (self->fixits, fixit);
}

void
_ide_diagnostic_take_range (IdeDiagnostic  *self,
                            IdeSourceRange *range)
{
  g_return_if_fail (self);
  g_return_if_fail (range);

  if (self->ranges == NULL)
    self->ranges = g_ptr_array_new_with_free_func ((GDestroyNotify)ide_source_range_unref);

  g_ptr_array_add (self->ranges, range);
}

void
_ide_diagnostic_add_range (IdeDiagnostic  *self,
                           IdeSourceRange *range)
{
  g_return_if_fail (self);
  g_return_if_fail (range);

  _ide_diagnostic_take_range (self, ide_source_range_ref (range));
}

const gchar *
ide_diagnostic_severity_to_string (IdeDiagnosticSeverity severity)
{
  switch (severity)
    {
    case IDE_DIAGNOSTIC_IGNORED:
      return "ignored";

    case IDE_DIAGNOSTIC_NOTE:
      return "note";

    case IDE_DIAGNOSTIC_DEPRECATED:
      return "deprecated";

    case IDE_DIAGNOSTIC_WARNING:
      return "warning";

    case IDE_DIAGNOSTIC_ERROR:
      return "error";

    case IDE_DIAGNOSTIC_FATAL:
      return "fatal";

    default:
      return "unknown";
    }
}

guint
ide_diagnostic_get_num_fixits (IdeDiagnostic *self)
{
  g_return_val_if_fail (self, 0);

  return (self->fixits != NULL) ? self->fixits->len : 0;
}

/**
 * ide_diagnostic_get_fixit:
 * @self: A #IdeDiagnostic.
 * @index: The index of the fixit.
 *
 * Gets the fixit denoted by @index. This value should be less than the value
 * returned from ide_diagnostic_get_num_fixits().
 *
 * Returns: (transfer none): An #IdeFixit.
 */
IdeFixit *
ide_diagnostic_get_fixit (IdeDiagnostic *self,
                          guint          index)
{
  g_return_val_if_fail (self, NULL);
  g_return_val_if_fail (self->fixits, NULL);
  g_return_val_if_fail (index < self->fixits->len, NULL);

  return g_ptr_array_index (self->fixits, index);
}
