/*
 * Copyright (c) 2018, Chris Johns <chrisj@rtems.org>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
/**
 * @file
 *
 * @ingroup rtems-ld
 *
 * @brief RTEMS Linker DWARF module manages the libdwarf interface.
 *
 */

#if !defined (_RLD_DWARF_H_)
#define _RLD_DWARF_H_

#include <iostream>

#include <rld.h>
#include <rld-dwarf-types.h>

namespace rld
{
  namespace dwarf
  {
    /**
     * Forward decls
     */
    class sources;
    class debug_info_entry;
    class file;

    /**
     * Address.
     */
    class address
    {
    public:
      address (const sources& source, dwarf_line& line);
      address (const address& orig, const sources& source);
      address (const address& orig, dwarf_address addr);
      address (const address& orig);
      address ();
      ~address ();

      /**
       * Is this address valid?
       */
      bool valid () const;

      /**
       * The location in the address space.
       */
      dwarf_address location () const;

      /**
       * Source file path. This is a full path.
       */
      std::string path () const;

      /**
       * Line number.
       */
      int line () const;

      /**
       * Is a begin statement?
       */
      bool is_a_begin_statement () const;

      /**
       * Is in a block?
       */
      bool is_in_a_block () const;

      /**
       * Is an end sequence?
       */
      bool is_an_end_sequence () const;

      /**
       * Assigment operator.
       */
      address& operator = (const address& rhs);

      /**
       * Less than operator to allow sorting.
       */
      bool operator < (const address& rhs) const;

    private:

      dwarf_address  addr;
      sources const* source;
      dwarf_unsigned source_index;
      dwarf_signed   source_line;
      bool           begin_statement;
      bool           block;
      bool           end_sequence;
    };

    /**
     * The addresses table is a vector sorted from low to high addresses..
     */
    typedef std::vector < address > addresses;

    /**
     * Range, one entry in an address range container.
     */
    class range
    {
    public:
      range (const dwarf_ranges* range);
      range (const range& orig);
      ~range ();

      /**
       * Address 1 in the range.
       */
      dwarf_unsigned addr1 () const;
      dwarf_unsigned addr2 () const;

      /**
       * Get the type of range.
       */
      dwarf_ranges_type type () const;

      /**
       * Is the range the end?
       */
      bool end () const;

      /**
       * Is the range empty? See DWARF 2.17.3.
       */
      bool empty () const;

      /**
       * Assigment operator.
       */
      range& operator = (const range& rhs);

      /**
       * Dump the range.
       */
      void dump (std::ostream& out) const;

    private:

      const dwarf_ranges* range_;
    };

    typedef std::vector < range > ranges;

    /**
     * Address ranges, is a range of addresses.
     */
    class address_ranges
    {
    public:
      address_ranges (file& debug);
      address_ranges (debug_info_entry& die);
      address_ranges (file& debug, dwarf_offset offset);
      address_ranges (const address_ranges& orig);
      ~address_ranges ();

      /**
       * Load the ranges from the DIE.
       */
      bool load (debug_info_entry& die, bool error = true);

      /**
       * Load the ranges from the debug info.
       */
      bool load (dwarf_offset offset, bool error = true);

      /**
       * Get the container.
       */
      const ranges& get () const;

      /**
       * Address range empty?
       */
      bool empty () const;

      /**
       * Assigment operator.
       */
      address_ranges& operator = (const address_ranges& rhs);

      /**
       * Dump the address ranges.
       */
      void dump (std::ostream& out) const;

    private:

      file&         debug;
      dwarf_offset  offset;
      dwarf_ranges* dranges;
      dwarf_signed  dranges_count;
      ranges        ranges_;
    };

    /**
     * Line addresses.
     */
    class line_addresses
    {
    public:
      line_addresses (file& debug, debug_info_entry& die);
      ~line_addresses ();

      /**
       * Count of lines.
       */
      size_t count () const;

      /**
       * Index operator.
       */
      dwarf_line& operator [] (const int index);

    private:

      file&        debug;
      dwarf_line*  lines;
      dwarf_signed count_;
    };

    /**
     * Sources.
     *
     * This is a CU table of sources. The address's contain an index to a
     * string in this table.
     */
    class sources
    {
    public:
      sources (file& debug, dwarf_offset die_offset);
      sources (const sources& orig);
      ~sources ();

      /**
       * Index operator.
       */
      std::string operator [] (const int index) const;

      /**
       * Deallocate.
       */
      void dealloc ();

      /**
       * Move assignment operator.
       */
      sources& operator = (sources&& rhs);

    private:

      file&        debug;
      char**       source;
      dwarf_signed count;
      dwarf_offset die_offset;
    };

    /**
     * Function.
     */
    class function
    {
    public:

      /**
       * The various inline states. See Table 3.4 DWARF 5 standard.
       */
      enum inlined {
        inl_not_inlined = 0,          /**< Not declared inline nore inlined. */
        inl_inline = 1,               /**< Not declared inline but inlined. */
        inl_declared_not_inlined = 2, /**< Declared inline but not inlined. */
        inl_declared_inlined = 3      /**< Declared inline and inlined */
      };

      function (file& debug, debug_info_entry& die);
      function (const function& orig);
      ~function ();

      /**
       * Get the name of the function.
       */
      std::string name () const;

      /**
       * Get the linkage name of the function.
       */
      std::string linkage_name () const;

      /**
       * Get the ranges for the funcion, if empty the PC low and PC high values
       * will be valid.
       */
      const address_ranges& get_ranges () const;

      /**
       * Get the PC low address, valid if ranges is empty.
       */
      dwarf_unsigned pc_low () const;

      /**
       * Get the PC high address, valid if ranges is empty.
       */
      dwarf_unsigned pc_high () const;

      /**
       * Does the function have machine code in the image?
       */
      bool has_machine_code () const;

      /**
       * Is the function external?
       */
      bool is_external () const;

      /**
       * Is this just a declaration?
       */
      bool is_declaration () const;

      /**
       * Is the function inlined?
       */
      bool is_inlined () const;

      /**
       * Get the inlined state.
       */
      inlined get_inlined () const;

      /**
       * Get the call file of the inlined function.
       */
      std::string call_file () const;

      /**
       * Is the address inside the function.
       */
      bool inside (dwarf_address addr) const;

      /**
       * Size of the function.
       */
      size_t size () const;

      /**
       * Assigment operator.
       */
      function& operator = (const function& rhs);

      /**
       * Dump the function.
       */
      void dump (std::ostream& out) const;

    private:

      file&          debug;
      bool           machine_code_;
      bool           external_;
      bool           declaration_;
      bool           prototyped_;
      dwarf_unsigned inline_;
      dwarf_unsigned entry_pc_;
      bool           has_entry_pc_;
      dwarf_unsigned pc_low_;
      dwarf_unsigned pc_high_;
      address_ranges ranges_;
      std::string    name_;
      std::string    linkage_name_;
      std::string    decl_file_;
      dwarf_unsigned decl_line_;
      std::string    call_file_;
      dwarf_unsigned call_line_;
    };

    typedef std::vector < function > functions;

    /**
     * Worker to sort the functions.
     */
    struct function_compare
    {
      enum sort_by
      {
        fc_by_name,
        fc_by_size,
        fc_by_address
      };

      const sort_by by;

      bool operator () (const function& a, const function& b) const;

      function_compare (sort_by by = fc_by_name);
    };

    /**
     * Debug Information Element (DIE).
     *
     * This class clean up and deallocations a DIE when it desctructs.
     */
    class debug_info_entry
    {
    public:
      /**
       * Construct the DIE, we need to be careful not to share the DIE pointer.
       */
      debug_info_entry (file& debug);
      debug_info_entry (file& debug, dwarf_die& die);
      debug_info_entry (file& debug, dwarf_offset offset);
      debug_info_entry (const debug_info_entry& orig);

      /**
       * Destruct and clean up.
       */
      ~debug_info_entry ();

      /**
       * Is the DIE valid?
       */
      bool valid (bool fatal = true) const;

      /**
       * Get the DIE.
       */
      dwarf_die get () const;

      /**
       * Casting operators to get the DIE.
       */
      operator dwarf_die& ();
      operator dwarf_die* ();

      /**
       * Assignment operators.
       */
      debug_info_entry& operator = (debug_info_entry& rhs);
      debug_info_entry& operator = (dwarf_offset offset);

      /**
       * Compare operators.
       */
      bool operator == (debug_info_entry& rhs) const;
      bool operator == (const dwarf_die rhs) const;

      /**
       * Get the tag.
       */
      dwarf_tag tag ();

      /**
       * Get the offset.
       */
      dwarf_offset offset ();

      /**
       * Get the low PC.
       */
      bool get_lowpc (dwarf_address& addr, bool error = false) const;

      /**
       * Get the high PC.
       */
      bool get_highpc (dwarf_address& addr,
                       bool&          is_address,
                       bool           error = false) const;

      /**
       * Get an attribute.
       */
      bool attribute (dwarf_attr       attr,
                      dwarf_attribute& value,
                      bool             error = true) const;

      /**
       * Get a flag.
       */
      bool attribute (dwarf_attr  attr,
                      dwarf_bool& value,
                      bool        error = true) const;

      /**
       * Get an unsigned attribute.
       */
      bool attribute (dwarf_attr      attr,
                      dwarf_unsigned& value,
                      bool            error = true) const;

      /**
       * Get a string attribute.
       */
      bool attribute (dwarf_attr   attr,
                      std::string& value,
                      bool         error = true) const;

      /**
       * Get source lines. Returns the CU line table with all columns.
       *
       * You need to clean this up.
       */
      bool source_lines (dwarf_line*&  lines,
                         dwarf_signed& linecount) const;

      /**
       * Get the source files. This is a table of source files in a CU
       */
      void source_files (char**&       source,
                         dwarf_signed& sourcecount) const;

      /**
       * Get the ranges.
       */
      bool ranges (dwarf_ranges*& ranges, dwarf_signed& rangescount) const;

      /**
       * Get the child.
       */
      bool get_child (debug_info_entry& child_die);

      /**
       * Has a child?
       */
      bool has_child () const;

      /**
       * Get the silbing
       */
      bool get_sibling (debug_info_entry& sibling_die);

      /**
       * Has a silbing?
       */
      bool has_sibling () const;

      /**
       * Get the debug info for this DIE.
       */
      file& get_debug () const;

      /**
       * deallocate the DIE.
       */
      void dealloc ();

      /**
       * Dump this DIE.
       */
      void dump (std::ostream& out,
                 std::string   prefix,
                 bool          newline = true);

    private:

      /**
       * Update the internal DIE and offset values.
       */
      void update ();

      file&        debug;
      dwarf_die    die;
      dwarf_tag    tag_;
      dwarf_offset offset_;

    };

    /**
     * Dump the DIE and all it's children and siblings.
     */
    void die_dump_children (debug_info_entry& die,
                            std::ostream&     out,
                            std::string       prefix,
                            int               depth = -1,
                            int               nesting = 0);

    /**
     * Dump the DIE and all it's children and siblings.
     */
    void die_dump (debug_info_entry& die,
                   std::ostream&     out,
                   std::string       prefix,
                   int               depth = -1,
                   int               nesting = 0);

    /**
     * Compilation Unit.
     */
    class compilation_unit
    {
    public:
      compilation_unit (file&             debug,
                        debug_info_entry& die,
                        dwarf_offset      offset);
      compilation_unit (const compilation_unit& orig);
      ~compilation_unit ();

      /**
       * Load the types.
       */
      void load_types ();

      /**
       * Load the functions.
       */
      void load_functions ();

      /**
       * Name of the CU.
       */
      std::string name () const;

      /**
       * Producer of the CL, the tool that compiled it.
       */
      std::string producer () const;

      /**
       * The low PC value, 0 if there is no attribute.
       */
      unsigned int pc_low () const;

      /**
       * The high PC value, ~0 if there is no attribute.
       */
      unsigned int pc_high () const;

      /**
       * Get the source and line for an address. If the address does not match
       * false is returned the file is set to 'unknown' and the line is set to
       * 0.
       */
      bool get_source (const dwarf_address addr,
                       address&            addr_line);

      /**
       * Get the functions.
       */
      functions& get_functions ();

      /**
       * Is the address inside the CU? If the PC low and high attributes are
       * valid they are used or the lines are checked.
       */
      bool inside (dwarf_unsigned addr) const;

      /**
       * Copy assignment operator.
       */
      compilation_unit& operator = (const compilation_unit& rhs);

      /**
       * Output the DIE tree.
       */
      void dump_die (std::ostream&     out,
                     const std::string prefix = " ",
                     int               depth = -1);

    private:

      void load_functions (debug_info_entry& die);

      file&          debug;       ///< The DWARF debug handle.
      dwarf_unsigned offset_;     ///< The CU offset in .debug_info
      std::string    name_;       ///< The name of the CU.
      std::string    producer_;   ///< The producer of the CU.
      dwarf_unsigned pc_low_;     ///< The PC low address
      dwarf_unsigned pc_high_;    ///< The PC high address.
      address_ranges ranges_;     ///< Non-continous address range.

      dwarf_offset   die_offset;  ///< The offset of the DIE in the image.

      sources        source_;     ///< Sources table for this CU.
      addresses      addr_lines_; ///< Address table.

      functions      functions_;  ///< The functions in the CU.
    };

    typedef std::list < compilation_unit > compilation_units;

    /**
     * A source and flags.
     */
    struct source_flags
    {
      std::string  source;  ///< The source file.
      rld::strings flags;   ///< the flags used to build the code.

      source_flags (const std::string& source);
    };

    typedef std::vector < source_flags > sources_flags;

    /**
     * Worker to sort the sources.
     */
    struct source_flags_compare
    {
      const bool by_basename;

      bool operator () (const source_flags& a, const source_flags& b) const;

      source_flags_compare (bool by_basename = true);
    };

    /**
     * A container of producers and the source they build.
     */
    struct producer_source
    {
      std::string   producer; ///< The producer
      sources_flags sources;  ///< The sources built by the producer with
                              ///  flags.

      producer_source (const std::string& producer);
      producer_source ();
    };

    typedef std::list < producer_source > producer_sources;

    /**
     * A DWARF file.
     */
    class file
    {
    public:
     /**
       * Construct an DWARF file.
       */
      file ();

      /**
       * Destruct the DWARF file object.
       */
      ~file ();

      /**
       * Begin using the DWARF information in an ELF file.
       *
       * @param elf The ELF file.
       */
      void begin (rld::elf::file& elf);

      /**
       * End using the DWARF file.
       */
      void end ();

      /**
       * Load the DWARF debug information.
       */
      void load_debug ();

      /**
       * Load the DWARF type information.
       */
      void load_types ();

      /**
       * Load the DWARF functions information.
       */
      void load_functions ();

      /**
       * Get the source location given an address.
       */
      bool get_source (const unsigned int address,
                       std::string&       source_file,
                       int&               source_line);

      /**
       * Get the producer sources from the compilation units.
       */
      void get_producer_sources (producer_sources& producers);

      /**
       * Does the function exist.
       */
      bool function_valid (std::string&name);

      /**
       * Get the function given a name. Raises an exception if not found.
       */
      function& get_function (std::string& name);

      /**
       * Get the function given an address.
       */
      bool get_function (const unsigned int address,
                         std::string&       name);

      /**
       * Get the DWARF debug information reference.
       */
      dwarf& get_debug ();

      /**
       * Get the compilation units.
       */
      compilation_units& get_cus ();

      /*
       * The DWARF debug conversion operator.
       */
      operator dwarf () { return get_debug (); }

      /**
       * Get the name of the file.
       */
      const std::string& name () const;

      /**
       * Dump the DWARF data.
       */
      void dump (std::ostream&     out,
                 const std::string prefix = " ",
                 int               depth = -1);

    private:

      /**
       * Check if the file is usable. Throw an exception if not.
       *
       * @param where Where the check is performed.
       */
      void check (const char* where) const;

      dwarf           debug;   ///< The libdwarf debug data
      rld::elf::file* elf_;    ///< The libelf reference used to access the
                               ///  DWARF data.

      compilation_units cus;   ///< Image's compilation units
    };

  }
}

#endif
