// Core of the FPGA assembly (FASM) output code.
//
// The netlist walker implements the core walking of the netlist and routing
// graph with the intention of emitting the complete implemented design to the
// output FASM file.  Once output, the FASM file is expected to completely
// describe the features required to implement the design in a hardware
// bitstream.
#ifndef FASM_H
#define FASM_H

#include <iostream>
#include <list>
#include <map>
#include <ostream>
#include <set>
#include <sstream>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>

#include "netlist_walker.h"
#include "netlist_writer.h"
#include "lut.h"
#include "parameters.h"
#include "route_tree_fwd.h"

namespace fasm {

// FASM netlist visitor.
//
// This netlist visitor emits FASM features to the provided ostream using
// FASM metadata tags.  See the FASM documentation in docs/utils/fasm.rst
// for a complete list of tags.
//
// High level methodology:
//  - At each top level pb_type, root tile FASM prefix is checked.
//  - At each pb_type, the CLB prefix is build by walking from the child to
//    the root pb_type.
//  - All LUTs are visited and emitted via the fasm_type/fasm_lut tags.
//  - All route through wires are emitted via the same mechanism as LUTs.
//  - All atom's (e.g. leaf pb_types) are visited and have their parameters
//    emitted via fasm_params tags.
//  - All pb_type interconnect's are visited and are emitted via fasm_mux tags.
//  - All pb_type and pb_type mode's are checked for static feature emission
//    via the fasm_features tag.
//
// Once the netlist visitor is done, in finish_impl, the routing graph is
// walked and static features are emitted via the fasm_features tag.
//
// Note that FASM features are incrementally written the ostream as they are
// seen, so the order of FASM output is arbitrary based on the walk order of
// the netlist and routing graph.
class FasmWriterVisitor : public NetlistVisitor {

  public:
      FasmWriterVisitor(vtr::string_internment *strings, std::ostream& f, bool is_flat);

  private:
      void visit_top_impl(const char* top_level_name) override;
      void visit_route_through_impl(const t_pb* atom) override;
      void visit_atom_impl(const t_pb* atom) override;
      // clb in visit_clb_impl stands for complex logic block.
      // visit_clb_impl is called on each top-level pb_type used in the design.
      void visit_clb_impl(ClusterBlockId blk_id, const t_pb* clb) override;
      void visit_all_impl(const t_pb_routes &top_pb_route, const t_pb* pb) override;
      void finish_impl() override;

  private:
      void output_fasm_features(const std::string& features) const;
      void output_fasm_features(const std::string& features, std::string_view clb_prefix, std::string_view blk_prefix) const;
      void check_features(const t_metadata_dict *meta) const;
      void check_interconnect(const t_pb_routes &pb_route, int inode);
      void check_for_lut(const t_pb* atom);
      void output_fasm_mux(std::string_view fasm_mux, t_interconnect *interconnect, const t_pb_graph_pin *mux_input_pin);
      void walk_routing();
      void walk_route_tree(const RRGraphBuilder& rr_graph_builder, const RouteTreeNode& root);
      std::string build_clb_prefix(const t_pb *pb, const t_pb_graph_node* pb_graph_node, bool* is_parent_pb_null) const;
      const LutOutputDefinition* find_lut(const t_pb_graph_node* pb_graph_node);
      void check_for_param(const t_pb *atom);

      // Walk from node to parents and search for a CLB prefix.
      void find_clb_prefix(const t_pb_graph_node *node,
        bool *have_prefix, std::string *clb_prefix) const;
      std::string handle_fasm_prefix(const t_metadata_dict *meta,
        const t_pb_graph_node *pb_graph_node, const t_pb_type *pb_type) const;
      const t_metadata_dict *get_fasm_type(const t_pb_graph_node* pb_graph_node, std::string_view target_type) const;

      vtr::string_internment *strings_;
      std::ostream& os_;

      t_pb_graph_node *root_clb_ = nullptr;
      bool current_blk_has_prefix_ = false;
      t_physical_tile_type_ptr physical_tile_ = nullptr;
      t_logical_block_type_ptr logical_block_ = nullptr;
      std::string blk_prefix_;
      std::string clb_prefix_;
      std::map<const t_pb_graph_node *, std::string> clb_prefix_map_;
      ClusterBlockId current_blk_id_;
      IntraLbPbPinLookup pb_graph_pin_lookup_from_index_by_type_;
      std::map<const t_pb_type*, std::vector<std::pair<std::string, LutOutputDefinition>>> lut_definitions_;
      std::map<const t_pb_type*, Parameters> parameters_;

      std::map<const std::string, std::string> tags_;

      vtr::interned_string fasm_lut;
      vtr::interned_string fasm_features;
      vtr::interned_string fasm_params;
      vtr::interned_string fasm_prefix;
      vtr::interned_string fasm_placeholders;
      vtr::interned_string fasm_type;
      vtr::interned_string fasm_mux;
      bool is_flat_;
};

} // namespace fasm

#endif  // FASM_H
