/* Copyright (C) 1986-2024 Free Software Foundation, Inc.

  This file is part of GDB.

  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/>.  */

#include "cli/cli-cmds.h"
#include "regcache.h"
#include "gdbsupport/def-vector.h"
#include "valprint.h"
#include "remote.h"
#include "reggroups.h"
#include "target.h"
#include "gdbarch.h"
#include "inferior.h"

/* Dump registers from regcache, used for dumping raw registers and
  cooked registers.  */

class register_dump_regcache : public register_dump
{
public:
 register_dump_regcache (regcache *regcache, bool dump_pseudo)
   : register_dump (regcache->arch ()), m_regcache (regcache),
     m_dump_pseudo (dump_pseudo)
 {
 }

protected:

 int num_additional_headers () override
 { return 1; }

 void additional_headers (ui_out *out) override
 {
   out->table_header (0, ui_left, "value",
                      m_dump_pseudo ? "Cooked value" : "Raw value");
 }

 void dump_reg (ui_out *out, int regnum) override
 {
   if (regnum < gdbarch_num_regs (m_gdbarch) || m_dump_pseudo)
     {
       auto size = register_size (m_gdbarch, regnum);

       if (size == 0)
         return;

       gdb::byte_vector buf (size);
       auto status = m_regcache->cooked_read (regnum, buf.data ());

       if (status == REG_UNKNOWN)
         out->field_string ("value", "<invalid>");
       else if (status == REG_UNAVAILABLE)
         out->field_string ("value", "<unavailable>");
       else
         {
           string_file str;
           print_hex_chars (&str, buf.data (), size,
                            gdbarch_byte_order (m_gdbarch), true);
           out->field_stream ("value", str);
         }
     }
   else
     {
       /* Just print "<cooked>" for pseudo register when
          regcache_dump_raw.  */
       out->field_string ("value", "<cooked>");
     }
 }

private:
 regcache *m_regcache;

 /* Dump pseudo registers or not.  */
 const bool m_dump_pseudo;
};

/* Dump from reg_buffer, used when there is no thread or
  registers.  */

class register_dump_reg_buffer : public register_dump, reg_buffer
{
public:
 register_dump_reg_buffer (gdbarch *gdbarch, bool dump_pseudo)
   : register_dump (gdbarch), reg_buffer (gdbarch, dump_pseudo)
 {
 }

protected:

 int num_additional_headers () override
 { return 1; }

 void additional_headers (ui_out *out) override
 {
   out->table_header (0, ui_left, "value",
                      m_has_pseudo ? "Cooked value" : "Raw value");
 }

 void dump_reg (ui_out *out, int regnum) override
 {
   if (regnum < gdbarch_num_regs (m_gdbarch) || m_has_pseudo)
     {
       auto size = register_size (m_gdbarch, regnum);

       if (size == 0)
         return;

       auto status = get_register_status (regnum);

       gdb_assert (status != REG_VALID);

       if (status == REG_UNKNOWN)
         out->field_string ("value", "<invalid>");
       else
         out->field_string ("value", "<unavailable>");
     }
   else
     {
       /* Just print "<cooked>" for pseudo register when
          regcache_dump_raw.  */
       out->field_string ("value", "<cooked>");
     }
 }
};

/* For "maint print registers".  */

class register_dump_none : public register_dump
{
public:
 register_dump_none (gdbarch *arch)
   : register_dump (arch)
 {}

protected:

 int num_additional_headers () override
 { return 0; }

 void additional_headers (ui_out *out) override
 { }

 void dump_reg (ui_out *out, int regnum) override
 {}
};

/* For "maint print remote-registers".  */

class register_dump_remote : public register_dump
{
public:
 register_dump_remote (gdbarch *arch)
   : register_dump (arch)
 {}

protected:

 int num_additional_headers () override
 { return 3; }

 void additional_headers (ui_out *out) override
 {
   out->table_header (7, ui_left, "remnum", "Rmt Nr");
   out->table_header (11, ui_left, "goffset", "g/G Offset");
   out->table_header (3, ui_left, "expedited", "Expedited");
 }

 void dump_reg (ui_out *out, int regnum) override
 {
   int pnum, poffset;

   if (regnum < gdbarch_num_regs (m_gdbarch)
       && remote_register_number_and_offset (m_gdbarch, regnum,
                                             &pnum, &poffset))
     {
       out->field_signed ("remnum", pnum);
       out->field_signed ("goffset", poffset);

       if (remote_register_is_expedited (regnum))
         out->field_string ("expedited", "yes");
       else
         out->field_skip ("expedited");
     }
   else
     {
       out->field_skip ("remnum");
       out->field_skip ("goffset");
       out->field_skip ("expedited");
     }
 }
};

/* For "maint print register-groups".  */

class register_dump_groups : public register_dump
{
public:
 register_dump_groups (gdbarch *arch)
   : register_dump (arch)
 {}

protected:

 int num_additional_headers () override
 { return 1; }

 void additional_headers (ui_out *out) override
 {
   out->table_header (0, ui_left, "groups", "Groups");
 }

 void dump_reg (ui_out *out, int regnum) override
 {
   string_file file;
   const char *sep = "";
   for (const struct reggroup *group : gdbarch_reggroups (m_gdbarch))
     {
       if (gdbarch_register_reggroup_p (m_gdbarch, regnum, group))
         {
           gdb_printf (&file, "%s%s", sep, group->name ());
           sep = ",";
         }
     }
   out->field_stream ("groups", file);
 }
};

enum regcache_dump_what
{
 regcache_dump_none, regcache_dump_raw,
 regcache_dump_cooked, regcache_dump_groups,
 regcache_dump_remote
};

/* Helper for the various maint commands that print registers.  ARGS
  is the arguments passed to the command.  WHAT_TO_DUMP indicates
  exactly which registers to display.  COMMAND is the command name,
  used in error messages.  */

static void
regcache_print (const char *args, enum regcache_dump_what what_to_dump,
               const char *command)
{
 /* Where to send output.  */
 stdio_file file;
 std::optional<ui_out_redirect_pop> redirect;

 if (args != nullptr)
   {
     if (!file.open (args, "w"))
       perror_with_name (command);
     redirect.emplace (current_uiout, &file);
   }

 std::unique_ptr<register_dump> dump;
 std::unique_ptr<regcache> regs;
 gdbarch *gdbarch;

 if (target_has_registers ())
   gdbarch = get_thread_regcache (inferior_thread ())->arch ();
 else
   gdbarch = current_inferior ()->arch ();

 const char *name;
 switch (what_to_dump)
   {
   case regcache_dump_none:
     dump = std::make_unique<register_dump_none> (gdbarch);
     name = "Registers";
     break;
   case regcache_dump_remote:
     dump = std::make_unique<register_dump_remote> (gdbarch);
     name = "RegisterRemote";
     break;
   case regcache_dump_groups:
     dump = std::make_unique<register_dump_groups> (gdbarch);
     name = "RegisterGroups";
     break;
   case regcache_dump_raw:
   case regcache_dump_cooked:
     {
       name = "RegisterDump";
       auto dump_pseudo = (what_to_dump == regcache_dump_cooked);

       if (target_has_registers ())
         dump = (std::make_unique<register_dump_regcache>
                 (get_thread_regcache (inferior_thread ()), dump_pseudo));
       else
         {
           /* For the benefit of "maint print registers" & co when
              debugging an executable, allow dumping a regcache even when
              there is no thread selected / no registers.  */
           dump = std::make_unique<register_dump_reg_buffer> (gdbarch,
                                                              dump_pseudo);
         }
     }
     break;
   }

 dump->dump (current_uiout, name);
}

static void
maintenance_print_registers (const char *args, int from_tty)
{
 regcache_print (args, regcache_dump_none, "maintenance print registers");
}

static void
maintenance_print_raw_registers (const char *args, int from_tty)
{
 regcache_print (args, regcache_dump_raw, "maintenance print raw-registers");
}

static void
maintenance_print_cooked_registers (const char *args, int from_tty)
{
 regcache_print (args, regcache_dump_cooked,
                 "maintenance print cooked-registers");
}

static void
maintenance_print_register_groups (const char *args, int from_tty)
{
 regcache_print (args, regcache_dump_groups,
                 "maintenance print register-groups");
}

static void
maintenance_print_remote_registers (const char *args, int from_tty)
{
 regcache_print (args, regcache_dump_remote,
                 "maintenance print remote-registers");
}

void _initialize_regcache_dump ();
void
_initialize_regcache_dump ()
{
 add_cmd ("registers", class_maintenance, maintenance_print_registers,
          _("Print the internal register configuration.\n"
            "Takes an optional file parameter."), &maintenanceprintlist);
 add_cmd ("raw-registers", class_maintenance,
          maintenance_print_raw_registers,
          _("Print the internal register configuration "
            "including raw values.\n"
            "Takes an optional file parameter."), &maintenanceprintlist);
 add_cmd ("cooked-registers", class_maintenance,
          maintenance_print_cooked_registers,
          _("Print the internal register configuration "
            "including cooked values.\n"
            "Takes an optional file parameter."), &maintenanceprintlist);
 add_cmd ("register-groups", class_maintenance,
          maintenance_print_register_groups,
          _("Print the internal register configuration "
            "including each register's group.\n"
            "Takes an optional file parameter."),
          &maintenanceprintlist);
 add_cmd ("remote-registers", class_maintenance,
          maintenance_print_remote_registers, _("\
Print the internal register configuration.\n\
Usage: maintenance print remote-registers [FILE]\n\
The remote register number and g/G packets offset are included,\n\
as well as which registers were sent in the last stop reply packet\n\
(i.e., expedited)."),
          &maintenanceprintlist);
}