/*
 * Copyright (c) 2013-2014 Freescale Semiconductor, Inc.
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */
#if !defined(_StExecutableImage_h_)
#define _StExecutableImage_h_

#include "blfwk/stdafx.h"
#include <list>

/*!
 * \brief Used to build a representation of memory regions.
 *
 * An intermediate representation of the memory regions and segments loaded
 * from an executable file. Also used to find contiguous segments that are
 * specified separately in the source file.
 *
 * When regions are added, an attempt is made to coalesce contiguous regions.
 * In order for this to succeed, the touching regions must be of the same
 * type and have the same permissions. Regions are also kept sorted by their
 * address range as they are added.
 *
 * \todo Implement alignment support.
 */
class StExecutableImage
{
public:
    //! Possible types of memory regions.
    typedef enum
    {
        TEXT_REGION, //!< A region containing data or instructions.
        FILL_REGION  //!< Region to be initialized with zero bytes.
    } MemoryRegionType;

    //! Memory region flag constants.
    enum
    {
        REGION_READ_FLAG = 1,  //!< Region is readable.
        REGION_WRITE_FLAG = 2, //!< Region is writable.
        REGION_EXEC_FLAG = 4,  //!< Region may contain executable code.

        REGION_RW_FLAG = REGION_READ_FLAG | REGION_WRITE_FLAG, //!< Region is read-write.

        //! Mask to access only permissions flags for a region.
        REGION_PERM_FLAG_MASK = 0x7
    };

    /*!
     * Representation of a contiguous region of memory.
     *
     * \todo Add comparison operators so we can use the STL sort algorithm.
     */
    struct MemoryRegion
    {
        MemoryRegionType m_type; //!< Memory region type.
        uint32_t m_address;      //!< The 32-bit start address of this region.
        uint32_t m_length;       //!< Number of bytes in this region.
        uint8_t *m_data;         //!< Pointer to data. Will be NULL for FILL_REGION type.
        unsigned m_flags;        //!< Flags for the region.

        //! \brief Calculates the address of the last byte occupied by this region.
        inline uint32_t endAddress() const { return m_address + m_length - 1; }
        //! \brief Equality operator.
        bool operator==(const MemoryRegion &other) const;
    };

    //! A list of #StExecutableImage::MemoryRegion objects.
    typedef std::list<MemoryRegion> MemoryRegionList;

    //! The iterator type used to access #StExecutableImage::MemoryRegion objects. This type
    //! is used by the methods #getRegionBegin() and #getRegionEnd().
    typedef MemoryRegionList::const_iterator const_iterator;

    //! The possible actions for regions matching an address filter range.
    typedef enum
    {
        ADDR_FILTER_NONE,    //!< Do nothing.
        ADDR_FILTER_ERROR,   //!< Raise an error exception.
        ADDR_FILTER_WARNING, //!< Raise a warning exception.
        ADDR_FILTER_CROP     //!< Don't include the matching address range in the executable image.
    } AddressFilterAction;

    /*!
     * An address filter consists of a single address range and an action. If a
     * memory region overlaps the filter's range then the action will be performed.
     * The possible filter actions are defined by the #AddressFilterAction enumeration.
     */
    struct AddressFilter
    {
        AddressFilterAction m_action; //!< Action to be performed when the filter is matched.
        uint32_t m_fromAddress;       //!< Start address of the filter. Should be lower than or equal to #m_toAddress.
        uint32_t m_toAddress;         //!< End address of the filter. Should be higher than or equal to #m_fromAddress.
        unsigned m_priority;          //!< Priority for this filter. Zero is the lowest priority.

        //! \brief Constructor.
        AddressFilter(AddressFilterAction action, uint32_t from, uint32_t to, unsigned priority = 0)
            : m_action(action)
            , m_fromAddress(from)
            , m_toAddress(to)
            , m_priority(priority)
        {
        }

        //! \brief Test routine.
        bool matchesMemoryRegion(const MemoryRegion &region) const;

        //! \brief Compares two address filter objects.
        int compare(const AddressFilter &other) const;

        //! \name Comparison operators
        //@{
        inline bool operator<(const AddressFilter &other) const { return compare(other) == -1; }
        inline bool operator>(const AddressFilter &other) const { return compare(other) == 1; }
        inline bool operator==(const AddressFilter &other) const { return compare(other) == 0; }
        inline bool operator<=(const AddressFilter &other) const { return compare(other) != 1; }
        inline bool operator>=(const AddressFilter &other) const { return compare(other) != -1; }
        //@}
    };

    //! List of #StExecutableImage::AddressFilter objects.
    typedef std::list<AddressFilter> AddressFilterList;

    //! The exception class raised for the #ADDR_FILTER_ERROR and #ADDR_FILTER_WARNING
    //! filter actions.
    class address_filter_exception
    {
    public:
        //! \brief Constructor.
        //!
        //! A local copy of \a matchingFilter is made, in case the image and/or filter
        //! are on the stack and would be disposed of when the exception is raised.
        address_filter_exception(bool isError, std::string &imageName, const AddressFilter &matchingFilter)
            : m_isError(isError)
            , m_imageName(imageName)
            , m_filter(matchingFilter)
        {
        }

        //! \brief Returns true if the exception is an error. Otherwise the exception
        //!     is for a warning.
        inline bool isError() const { return m_isError; }
        //! \brief
        inline std::string getImageName() const { return m_imageName; }
        //! \brief
        inline const AddressFilter &getMatchingFilter() const { return m_filter; }
    protected:
        bool m_isError;
        std::string m_imageName;
        AddressFilter m_filter;
    };

public:
    //! \brief Constructor.
    StExecutableImage(int inAlignment = 256);

    //! \brief Copy constructor.
    StExecutableImage(const StExecutableImage &inOther);

    //! \brief Destructor.
    virtual ~StExecutableImage();

    //! \name Image name
    //! Methods for getting and setting the image name.
    //@{
    //! \brief Sets the image's name to \a inName.
    virtual void setName(const std::string &inName);

    //! \brief Returns a copy of the image's name.
    virtual std::string getName() const;
    //@}

    //! \name Regions
    //! Methods to add and access memory regions.
    //@{
    //! \brief Add a region to be filled with zeroes.
    virtual void addFillRegion(uint32_t inAddress, unsigned inLength);

    //! \brief Add a region containing data to be loaded.
    virtual void addTextRegion(uint32_t inAddress, const uint8_t *inData, unsigned inLength);

    //! \brief Returns the total number of regions.
    //!
    //! Note that this count may not be the same as the number of calls to
    //! addFillRegion() and addTextRegion() due to region coalescing.
    inline unsigned getRegionCount() const { return static_cast<unsigned>(m_image.size()); }
    //! \brief Returns a reference to the region specified by \a inIndex.
    const MemoryRegion &getRegionAtIndex(unsigned inIndex) const;

    //! \brief Return an iterator to the first region.
    inline const_iterator getRegionBegin() const { return m_image.begin(); }
    //! \brief Return an iterator to the next-after-last region.
    inline const_iterator getRegionEnd() const { return m_image.end(); }
    //@}

    //! \name Entry point
    //@{
    //! \brief Sets the entry point address.
    inline void setEntryPoint(uint32_t inEntryAddress)
    {
        m_entry = inEntryAddress;
        m_hasEntry = true;
    }

    //! \brief Returns true if an entry point has been set.
    inline bool hasEntryPoint() const { return m_hasEntry; }
    //! \brief Returns the entry point address.
    inline uint32_t getEntryPoint() const { return hasEntryPoint() ? m_entry : 0; }
    //@}

    //! \name Address filter
    //@{
    //! \brief Add a new address filter.
    virtual void addAddressFilter(const AddressFilter &filter);

    //! \brief Add multiple address filters at once.
    //!
    //! The template argument \a _T must be an iterator or const iterator that
    //! dereferences to an StExecutableImage::AddressFilter reference. All filters
    //! from \a from to \a to will be added to the address filter list.
    template <typename _T>
    void addAddressFilters(_T from, _T to)
    {
        _T it = from;
        for (; it != to; ++it)
        {
            addAddressFilter(*it);
        }
    }

    //! \brief Remove all active filters.
    virtual void clearAddressFilters();

    //! \brief Process all active filters and perform associated actions.
    virtual void applyAddressFilters();
    //@}

protected:
    std::string m_name;          //!< The name of the image (can be a file name, for instance).
    int m_alignment;             //!< The required address alignment for each memory region.
    bool m_hasEntry;             //!< True if an entry point has been set.
    uint32_t m_entry;            //!< Entry point address.
    MemoryRegionList m_image;    //!< The memory regions.
    AddressFilterList m_filters; //!< List of active address filters.

    //! \brief Deletes the portion \a region that overlaps \a filter.
    void cropRegionToFilter(MemoryRegion &region, const AddressFilter &filter);

    //! \brief Inserts the region in sorted order or merges with one already in the image.
    void insertOrMergeRegion(MemoryRegion &inRegion);

    //! \brief Merges two memory regions into one.
    void mergeRegions(MemoryRegion &inOldRegion, MemoryRegion &inNewRegion);
};

#endif // _StExecutableImage_h_
