// Copyright Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT

#pragma once

#include <functional>
#include <variant>

#include <rocRoller/KernelGraph/ControlGraph/ControlGraph_fwd.hpp>

#include <rocRoller/Graph/Hypergraph.hpp>
#include <rocRoller/Graph/Hypergraph_fwd.hpp>
#include <rocRoller/KernelGraph/ControlGraph/ControlEdge.hpp>
#include <rocRoller/KernelGraph/ControlGraph/ControlGraph_fwd.hpp>
#include <rocRoller/KernelGraph/ControlGraph/Operation.hpp>
#include <rocRoller/KernelGraph/Policy.hpp>
#include <rocRoller/Utilities/Comparison.hpp>

namespace rocRoller
{
    /**
     * Control flow routines.
     *
     * Control flow is represented as a graph.  Nodes in the control flow graph represent
     * operations (like load/store or a for loop).  Edges in the graph encode dependencies
     * between nodes.
     *
     * The graph answers the question:
     * What are the series of operations needed to solve the problem?
     *
     * Each node of the graph, when traversed, will help generate assembly code.
     * It relies on coordinate transform graph expressions.
     *
     * The control graph should begin with a single Kernel node.
     *
     * There are two main categories of edges: Sequence edges and Body-like edges.
     *
     * From a given node, each kind of Body-like edge denodes a separate body
     * of that node.
     * For example, a Conditional node may have Body edges and Else edges.
     * The nodes downstream of the Body edges represent the true case, and the
     * nodes downstream of the Else edges represent the false case.
     */
    namespace KernelGraph::ControlGraph
    {
        enum class NodeOrdering
        {
            LeftFirst = 0,
            LeftInBodyOfRight,
            Undefined,
            RightInBodyOfLeft,
            RightFirst,
            Count
        };

        enum class CacheStatus
        {
            Invalid = 0, //< Cache is empty
            Partial, //< Cache does not have all the orders between nodes
            Valid, //< Cache has all orders of nodes
            Count
        };

        /**
         * Return a full representation of 'n'
         */
        std::string   toString(NodeOrdering n);
        std::ostream& operator<<(std::ostream& stream, NodeOrdering n);

        /**
         * Return a full representation of 'c'
         */
        std::string   toString(CacheStatus c);
        std::ostream& operator<<(std::ostream& stream, CacheStatus c);

        /**
         * Return a 3-character representation of 'n'.
         */
        std::string abbrev(NodeOrdering n);

        /**
         * If ordering `order` applies to (a, b), return the ordering that applies to (b, a).
         */
        NodeOrdering opposite(NodeOrdering order);

        struct NodeOrders
        {
            std::vector<int> after;
            std::vector<int> before;
            std::vector<int> inBody;
            std::vector<int> containing;
            void             clear()
            {
                after.clear();
                before.clear();
                inBody.clear();
                containing.clear();
            }
        };

        /**
         * Control flow graph.
         *
         * Nodes in the graph represent operations.  Edges describe
         * dependencies.
         */
        class ControlGraph : public Graph::Hypergraph<Operation, ControlEdge, false>
        {
        public:
            using Base = Graph::Hypergraph<Operation, ControlEdge, false>;

            ControlGraph() = default;

            /**
             * @brief Get a node/edge from the control graph.
             *
             * If the element specified by tag cannot be converted to
             * T, the return value is empty.
             *
             * @param tag Graph tag/index.
             */
            template <typename T>
            requires(std::constructible_from<ControlGraph::Element, T>) std::optional<T> get(
                int tag)
            const;

            /**
             * Returns the relative ordering of nodeA and nodeB according to the graph rules.
             *
             * From node X,
             *
             *  - Nodes connected via a Sequence edge (and their descendents) are after X.
             *  - Nodes connected via other kinds of edges (Initialize, Body, ForLoopIncrement)
             *    are in the body of X.
             *  - The descendents of X are ordered by the first kind of edge, in this order:
             *    Initialize -> Body -> ForLoopIncrement -> Sequence.
             *
             * Nodes whose relationship is not determined by the above rules could be
             * scheduled concurrently.
             */
            NodeOrdering compareNodes(UpdateCachePolicy const, int nodeA, int nodeB) const;
            NodeOrdering compareNodes(CacheOnlyPolicy const, int nodeA, int nodeB) const;
            NodeOrdering compareNodes(UseCacheIfAvailablePolicy const, int nodeA, int nodeB) const;
            NodeOrdering compareNodes(IgnoreCachePolicy const, int nodeA, int nodeB) const;

            /**
             * Yields (in no particular order) all nodes that are definitely after `node`.
             */
            Generator<int> nodesAfter(int node) const;

            /**
             * Yields (in no particular order) all nodes that are definitely before `node`.
             */
            Generator<int> nodesBefore(int node) const;

            /**
             * Yields (in no particular order) all nodes that are inside the body of `node`.
             * Note
             */
            Generator<int> nodesInBody(int node) const;

            /**
             * Yields (in no particular order) all nodes that contain `node` in their body.
             * Note
             */
            Generator<int> nodesContaining(int node) const;

            /**
             * Returns a string containing a text table describing the relationship between
             * all nodes in the graph.
             */
            std::string nodeOrderTableString() const;

            /**
             * Returns a string containing a text table describing the relationship between
             * the listed nodes in the graph.
             */
            std::string nodeOrderTableString(std::set<int> const& nodes) const;

            /**
             * Contains a map of every definite ordering between two nodes.
             * Note that node pairs whose ordering is undefined will be missing from the cache.
             * Also note that the entries are not duplicated for (nodeA, nodeB) and
             * (nodeB, nodeA). Only the entry where the first node ID is the lower number
             * will be present. The other entry can be obtained from opposite(nodeB, nodeA).
             *
             * Also, if a reference to the returned value is maintained through any changes
             * to the graph, the returned map will be cleared.
             */
            std::unordered_map<int, std::unordered_map<int, NodeOrdering>> nodeOrderTable() const;

            template <typename T>
            requires(std::constructible_from<Operation, T>)
                std::set<std::pair<int, int>> ambiguousNodes()
            const;

            /**
             * @brief Given two control stacks, add the necessary sequence edge such that the final nodes in the stack are relatively ordered.
             *
             * The final nodes in the control stacks are ordered by adding a sequence edge between the first nodes in the stacks that differ.
             *
             * If ordered is true, the order imposed is a -> b.
             * Otherwise, the order is inferred based on the following rules:
             * 1. If the differing nodes are translate time evaluatable setcoords, order the smaller one first.
             * 2. Otherwise, order the smaller numbered node first.
             *
             *
             * @tparam Range
             * @param aControlStack Path in the body-parent tree (computed from the control graph) from root to a.
             * @param bControlStack Path in the body-parent tree from root to b.
             * @param ordered Inputs are passed in order.
             */
            template <CForwardRangeOf<int> Range>
            void orderMemoryNodes(Range const& aControlStack,
                                  Range const& bControlStack,
                                  bool         ordered = true);

            template <typename T>
            inline std::predicate<int> auto isElemType() const
            {
                return [this](int x) -> bool { return get<T>(x).has_value(); };
            }

            /**
             * Connects each argument in order with a separate edge of type `Edge`. Accepts
             * any number of arguments.  The edges will be default-constructed.
             *
             * e.g. chain<Sequence>(a, b, c) will create two sequence edges, one from a to b,
             * and one from b to c.
             */
            template <CControlEdge Edge, std::convertible_to<int>... Nodes>
            void chain(int a, int b, Nodes... remaining);

            /**
             *  Check if modifying an element (index) is allowed or not. This
             *  only comes into effect when the graph is in restricted mode.
             */
            virtual bool isModificationAllowed(int index) const override;

            /**
             *  Set the graph to be in restricted mode. Some operations would
             *  be disallowed when in restricted mode.
             */
            void setRestricted()
            {
                m_changesRestricted = true;
            }

        private:
            /**
             * Populates m_orderCache for startingNodes relative to their descendents, and the
             * descendents of each relative to each other. Returns the descendents of
             * startingNodes.
             */
            template <CForwardRangeOf<int> Range>
            std::vector<int> populateOrderCache(Range const& startingNodes) const;

            /**
             * Populates m_orderCache for startingNode relative to its descendents, and the
             * descendents relative to each other. Returns the descendents of startingNode.
             */
            std::vector<int> populateOrderCache(int startingNode) const;

            virtual void clearCache(Graph::GraphModification modification) override;
            void         populateOrderCache() const;
            void         sortOrderCache() const;

            NodeOrdering lookupOrder(CacheOnlyPolicy const, int nodeA, int nodeB) const;
            NodeOrdering lookupOrder(IgnoreCachePolicy const, int nodeA, int nodeB) const;

            void writeOrderCache(int nodeA, int nodeB, NodeOrdering order) const;

            template <CForwardRangeOf<int> ARange = std::initializer_list<int>,
                      CForwardRangeOf<int> BRange = std::initializer_list<int>>
            void writeOrderCache(ARange const& nodesA,
                                 BRange const& nodesB,
                                 NodeOrdering  order) const;

            mutable std::unordered_map<int, NodeOrders> m_orderCache;
            /**
             * If an entry is present, the value will be the IDs of every descendent from the key,
             * following every kind of edge.
             */
            mutable std::unordered_map<int, std::vector<int>> m_descendentCache;

            mutable CacheStatus m_cacheStatus = CacheStatus::Invalid;

            mutable bool m_changesRestricted = false;
        };

        std::string name(ControlGraph::Element const& el);

        /**
         * @brief Determine if x holds an Operation of type T.
         */
        template <typename T>
        bool isOperation(auto const& x);

        /**
         * @brief Determine if x holds a ControlEdge of type T.
         */
        template <typename T>
        bool isEdge(auto const& x);
    }
}

#include <rocRoller/KernelGraph/ControlGraph/ControlGraph_impl.hpp>
