text: /*
   *  nextpnr -- Next Generation Place and Route
   *
   *  Copyright (C) 2018  Miodrag Milanovic <miodrag@symbioticeda.com>
   *
   *  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.
   *
   */
  
  #include "jsonwrite.h"
  #include <assert.h>
  #include <fstream>
  #include <iostream>
  #include <iterator>
  #include <log.h>
  #include <map>
  #include <string>
  #include "nextpnr.h"
  #include "version.h"
  
  NEXTPNR_NAMESPACE_BEGIN
  
  namespace JsonWriter {
  
  std::string get_string(std::string str)
  {
      std::string newstr = "\"";
      for (char c : str) {
          if (c == '\\')
              newstr += c;
          newstr += c;
      }
      return newstr + "\"";
  }
  
  std::string get_name(IdString name, Context *ctx) { return get_string(name.c_str(ctx)); }
  
  void write_parameters(std::ostream &f, Context *ctx, const std::unordered_map<IdString, Property> &parameters,
                        bool for_module = false)
  {
      bool first = true;
      for (auto &param : parameters) {
          f << stringf("%s\n", first ? "" : ",");
          f << stringf("        %s%s: ", for_module ? "" : "    ", get_name(param.first, ctx).c_str());
          f << get_string(param.second.to_string());
          first = false;
      }
  }
  
  struct PortGroup
  {
      std::string name;
      std::vector<int> bits;
      PortType dir;
  };
  
  std::vector<PortGroup> group_ports(Context *ctx, const std::unordered_map<IdString, PortInfo> &ports,
                                     bool is_cell = false)
  {
      std::vector<PortGroup> groups;
      std::unordered_map<std::string, size_t> base_to_group;
      for (auto &pair : ports) {
          std::string name = pair.second.name.str(ctx);
          if ((name.back() != ']') || (name.find('[') == std::string::npos)) {
              groups.push_back({name,
                                {is_cell ? (pair.second.net ? pair.second.net->name.index : -1) : pair.first.index},
                                pair.second.type});
          } else {
              int off1 = int(name.find_last_of('['));
              std::string basename = name.substr(0, off1);
              int index = std::stoi(name.substr(off1 + 1, name.size() - (off1 + 2)));
  
              if (!base_to_group.count(basename)) {
                  base_to_group[basename] = groups.size();
                  groups.push_back({basename, std::vector<int>(index + 1, -1), pair.second.type});
              }
  
              auto &grp = groups.at(base_to_group[basename]);
              if (int(grp.bits.size()) <= index)
                  grp.bits.resize(index + 1, -1);
              NPNR_ASSERT(grp.bits.at(index) == -1);
              grp.bits.at(index) = pair.second.net ? pair.second.net->name.index : (is_cell ? -1 : pair.first.index);
          }
      }
      return groups;
  }
  
  std::string format_port_bits(const PortGroup &port, int &dummy_idx)
  {
      std::stringstream s;
      s << "[ ";
      bool first = true;
      if (port.bits.size() != 1 || port.bits.at(0) != -1) // skip single disconnected ports
          for (auto bit : port.bits) {
              if (!first)
                  s << ", ";
              if (bit == -1)
                  s << (++dummy_idx);
              else
                  s << bit;
              first = false;
          }
      s << " ]";
      return s.str();
  }
  
  void write_module(std::ostream &f, Context *ctx)
  {
      auto val = ctx->attrs.find(ctx->id("module"));
      int dummy_idx = int(ctx->idstring_idx_to_str->size()) + 1000;
      if (val != ctx->attrs.end())
          f << stringf("    %s: {\n", get_string(val->second.as_string()).c_str());
      else
          f << stringf("    %s: {\n", get_string("top").c_str());
      f << stringf("      \"settings\": {");
      write_parameters(f, ctx, ctx->settings, true);
      f << stringf("\n      },\n");
      f << stringf("      \"attributes\": {");
      write_parameters(f, ctx, ctx->attrs, true);
      f << stringf("\n      },\n");
      f << stringf("      \"ports\": {");
  
      auto ports = group_ports(ctx, ctx->ports);
      bool first = true;
      for (auto &port : ports) {
          f << stringf("%s\n", first ? "" : ",");
          f << stringf("        %s: {\n", get_string(port.name).c_str());
          f << stringf("          \"direction\": \"%s\",\n",
                       port.dir == PORT_IN ? "input" : port.dir == PORT_INOUT ? "inout" : "output");
          f << stringf("          \"bits\": %s\n", format_port_bits(port, dummy_idx).c_str());
          f << stringf("        }");
          first = false;
      }
      f << stringf("\n      },\n");
  
      f << stringf("      \"cells\": {");
      first = true;
      for (auto &pair : ctx->cells) {
          auto &c = pair.second;
          auto cell_ports = group_ports(ctx, c->ports, true);
          f << stringf("%s\n", first ? "" : ",");
          f << stringf("        %s: {\n", get_name(c->name, ctx).c_str());
          f << stringf("          \"hide_name\": %s,\n", c->name.c_str(ctx)[0] == '$' ? "1" : "0");
          f << stringf("          \"type\": %s,\n", get_name(c->type, ctx).c_str());
          f << stringf("          \"parameters\": {");
          write_parameters(f, ctx, c->params);
          f << stringf("\n          },\n");
          f << stringf("          \"attributes\": {");
          write_parameters(f, ctx, c->attrs);
          f << stringf("\n          },\n");
          f << stringf("          \"port_directions\": {");
          bool first2 = true;
          for (auto &pg : cell_ports) {
              std::string direction = (pg.dir == PORT_IN) ? "input" : (pg.dir == PORT_OUT) ? "output" : "inout";
              f << stringf("%s\n", first2 ? "" : ",");
              f << stringf("            %s: \"%s\"", get_string(pg.name).c_str(), direction.c_str());
              first2 = false;
          }
          f << stringf("\n          },\n");
          f << stringf("          \"connections\": {");
          first2 = true;
          for (auto &pg : cell_ports) {
              f << stringf("%s\n", first2 ? "" : ",");
              f << stringf("            %s: %s", get_string(pg.name).c_str(), format_port_bits(pg, dummy_idx).c_str());
              first2 = false;
          }
          f << stringf("\n          }\n");
  
          f << stringf("        }");
          first = false;
      }
  
      f << stringf("\n      },\n");
  
      f << stringf("      \"netnames\": {");
      first = true;
      for (auto &pair : ctx->nets) {
          auto &w = pair.second;
          f << stringf("%s\n", first ? "" : ",");
          f << stringf("        %s: {\n", get_name(w->name, ctx).c_str());
          f << stringf("          \"hide_name\": %s,\n", w->name.c_str(ctx)[0] == '$' ? "1" : "0");
          f << stringf("          \"bits\": [ %d ] ,\n", pair.first.index);
          f << stringf("          \"attributes\": {");
          write_parameters(f, ctx, w->attrs);
          f << stringf("\n          }\n");
          f << stringf("        }");
          first = false;
      }
  
      f << stringf("\n      }\n");
      f << stringf("    }");
  }
  
  void write_context(std::ostream &f, Context *ctx)
  {
      f << stringf("{\n");
      f << stringf("  \"creator\": %s,\n",
                   get_string("Next Generation Place and Route (Version " GIT_DESCRIBE_STR ")").c_str());
      f << stringf("  \"modules\": {\n");
      write_module(f, ctx);
      f << stringf("\n  }");
      f << stringf("\n}\n");
  }
  
  }; // End Namespace JsonWriter
  
  bool write_json_file(std::ostream &f, std::string &filename, Context *ctx)
  {
      try {
          using namespace JsonWriter;
          if (!f)
              log_error("failed to open JSON file.\n");
          write_context(f, ctx);
          log_break();
          return true;
      } catch (log_execution_error_exception) {
          return false;
      }
  }
  
  NEXTPNR_NAMESPACE_END
  