/* File: node_hash.c
 *
 * This file is part of XSCHEM,
 * a schematic capture and Spice/Vhdl/Verilog netlisting tool for circuit
 * simulation.
 * Copyright (C) 1998-2024 Stefan Frederik Schippers
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "xschem.h"
static Node_hashentry *node_hash_lookup(const char *token, const char *dir,int what,int port,
       char *sig_type, char *verilog_type, char *value, char *class, const char *orig_tok)
/*    token        dir et all      what           ... action ...
 * --------------------------------------------------------------------------
 * "whatever"     "in"/"out"    0,XINSERT insert in hash table if not in and return NULL
 *                                        if already present just return entry address
 *                                        and update in/out fields sum up port field
 *                                        return NULL otherwise
 *
 * "whatever"     whatever      2,XDELETE  delete entry if found return NULL
 * "whatever"     whatever      1,XLOOKUP  only look up element, dont insert */
{
 unsigned int hashcode, index;
 Node_hashentry **preventry;
 Drivers d;

 if(token==NULL || token[0]==0 ) return NULL;
 dbg(3, "node_hash_lookup(): called with: %s dir=%s what=%d port=%d\n",
        token, dir, what, port);
 d.in=d.out=d.inout=0;
 if(dir && dir[0]) {
   if(!strcmp(dir,"in") )  d.in=1;
   else if(!strcmp(dir,"out") ) d.out=1;
   else if(!strcmp(dir,"inout") ) d.inout=1;
 }
 d.port=port;
 hashcode=str_hash(token);
 index=hashcode % HASHSIZE;
 preventry=&xctx->node_table[index];
 while(1)
 {
  if( !(*preventry) )                  /* empty slot */
  {
   if( what==XINSERT )          /* insert data */
   {
    Node_hashentry *entry = (Node_hashentry *)my_malloc(_ALLOC_ID_, sizeof( Node_hashentry ));
    entry->next = NULL;
    entry->token = entry->sig_type = entry->verilog_type =
                   entry->value = entry->class = entry->orig_tok = NULL;
    my_strdup(_ALLOC_ID_, &(entry->token),token);
    if(sig_type &&sig_type[0]) my_strdup(_ALLOC_ID_,  &(entry->sig_type), sig_type);
    if(verilog_type &&verilog_type[0]) my_strdup(_ALLOC_ID_,  &(entry->verilog_type), verilog_type);
    if(class && class[0]) my_strdup(_ALLOC_ID_,  &(entry->class), class);
    if(orig_tok && orig_tok[0]) my_strdup(_ALLOC_ID_,  &(entry->orig_tok), orig_tok);
    if(value && value[0]) my_strdup(_ALLOC_ID_,  &(entry->value), value);
    entry->d.port=d.port;
    entry->d.in=d.in;
    entry->d.out=d.out;
    entry->d.inout=d.inout;
    entry->hash=hashcode;
    *preventry=entry;
    dbg(3, "node_hash_lookup(): hashing %s : value=%s\n\n",
           entry->token, entry->value? entry->value:"<NULL>");
    dbg(3, "node_hash_lookup(): hashing %s in=%d out=%d inout=%d port=%d\n",
                token, d.in, d.out, d.inout, d.port);
   }
   return NULL; /* whether inserted or not return NULL since it was not in */
  }
  if( (*preventry) -> hash==hashcode && strcmp(token,(*preventry)->token)==0 ) /* found matching tok */
  {
   if(what==XDELETE)                /* remove token from the hash table ... */
   {
    Node_hashentry *saveptr=(*preventry)->next, *entry = *preventry;
    my_free(_ALLOC_ID_, &entry->token);
    my_free(_ALLOC_ID_, &entry->verilog_type);
    my_free(_ALLOC_ID_, &entry->sig_type);
    my_free(_ALLOC_ID_, &entry->class);
    my_free(_ALLOC_ID_, &entry->orig_tok);
    my_free(_ALLOC_ID_, &entry->value);
    my_free(_ALLOC_ID_, &entry);
    *preventry=saveptr;
    return NULL;
   }
   else /* found matching entry, return the address and update in/out count */
   {
    Node_hashentry *entry = *preventry;
    entry->d.port+=port;
    entry->d.in+=d.in;
    entry->d.out+=d.out;
    entry->d.inout+=d.inout;
    if(sig_type && sig_type[0] !='\0')
      my_strdup(_ALLOC_ID_,  &(entry->sig_type), sig_type);
    if(verilog_type && verilog_type[0] !='\0')
      my_strdup(_ALLOC_ID_,  &(entry->verilog_type), verilog_type);
    if(value && value[0] !='\0')
      my_strdup(_ALLOC_ID_,  &(entry->value), value);
    if(class && class[0] !='\0')
      my_strdup(_ALLOC_ID_,  &(entry->class), class);
    dbg(3, "node_hash_lookup(): hashing %s : value=%s\n\n",
           entry->token, entry->value? entry->value:"<NULL>");
    return entry;
   }
  }
  preventry=&(*preventry)->next; /* descend into the list. */
 }
}

/* wrapper to node_hash_lookup that handles buses */
/* warning, in case of buses return only pointer to first bus element */
Node_hashentry *bus_node_hash_lookup(const char *token, const char *dir, int what, int port,
       char *sig_type,char *verilog_type, char *value, char *class)
{
 char *start, *string_ptr, c;
 int mult;
 char *string=NULL;
 Node_hashentry *ptr1=NULL, *ptr2=NULL;

 if(token==NULL || token[0] == 0) return NULL;
 if( token[0] == '#')
 {
   my_strdup(_ALLOC_ID_, &string, token);
 }
 else
 {
   dbg(3, "bus_node_hash_lookup(): expanding node: %s\n", token);
   my_strdup(_ALLOC_ID_, &string, expandlabel(token,&mult));
   dbg(3, "bus_node_hash_lookup(): done expanding node: %s\n", token);
 }
 if(string==NULL) return NULL;
 string_ptr = start = string;
 while(1)
 {
  c=(*string_ptr);
  if(c==','|| c=='\0')
  {
    *string_ptr='\0';  /* set end string at comma position.... */
    /* insert one bus element at a time in hash table */
    ptr1=node_hash_lookup(start, dir, what,port, sig_type, verilog_type, value, class, token);
    if(!ptr2) ptr2=ptr1;
    dbg(3, "bus_node_hash_lookup(): processing node: %s\n", start);
    *string_ptr=c;     /* ....restore original char */
    start=string_ptr+1;
  }
  if(c==0) break;
  string_ptr++;
 }
 /* if something found return first pointer */
 my_free(_ALLOC_ID_, &string);
 return ptr2;
}

static void node_hash_free_entry(Node_hashentry *entry)
{
  Node_hashentry *tmp;

  while(entry) {
    tmp = entry->next;
    my_free(_ALLOC_ID_, &entry->token);
    my_free(_ALLOC_ID_, &entry->verilog_type);
    my_free(_ALLOC_ID_, &entry->sig_type);
    my_free(_ALLOC_ID_, &entry->class);
    my_free(_ALLOC_ID_, &entry->orig_tok);
    my_free(_ALLOC_ID_, &entry->value);
    my_free(_ALLOC_ID_, &entry);
    entry = tmp;
  }
}

void node_hash_free(void) /* remove the whole hash table  */
{
 int i;

 dbg(2, "node_hash_free(): removing hash table\n");
 for(i=0;i<HASHSIZE; ++i)
 {
  node_hash_free_entry( xctx->node_table[i] );
  xctx->node_table[i] = NULL;
 }
}

int traverse_node_hash()
{
 int i;
 int err = 0;
 Node_hashentry *entry;
 char str[2048]; /* 20161122 overflow safe */
 int incr_hi;

 incr_hi = tclgetboolvar("incr_hilight");
 for(i=0;i<HASHSIZE; ++i)
 {
  entry = xctx->node_table[i];
  while(entry)
  {
   if( !record_global_node(3, NULL, entry->token)) {
     if(entry->d.out ==0  && entry->d.inout == 0)
     {
       my_snprintf(str, S(str), "Error: undriven node: %s", entry->token);
       if(!xctx->netlist_count) bus_hilight_hash_lookup(entry->token, xctx->hilight_color, XINSERT_NOREPLACE);
       if(incr_hi) incr_hilight_color();
       statusmsg(str, 2);
       err |= 1;
     }
     else if(entry->d.out + entry->d.inout + entry->d.in == 1)
     {
       my_snprintf(str, S(str), "Warning: open net: %s", entry->token);
       if(!xctx->netlist_count) bus_hilight_hash_lookup(entry->token, xctx->hilight_color, XINSERT_NOREPLACE);
       if(incr_hi) incr_hilight_color();
       statusmsg(str,2);
       if(tclgetboolvar("erc_open_net_is_error")) err |= 1;
     }
     else if(entry->d.out >=2 && entry->d.port>=0)  /*  era d.port>=2   03102001 */
     {
       my_snprintf(str, S(str), "Warning: shorted output node: %s", entry->token);
       if(!xctx->netlist_count) bus_hilight_hash_lookup(entry->token, xctx->hilight_color, XINSERT_NOREPLACE);
       if(incr_hi) incr_hilight_color();
       statusmsg(str,2);
       if(tclgetboolvar("erc_shorted_output_is_error")) err |= 1;
     }
     else if(entry->d.in ==0 && entry->d.inout == 0)
     {
       my_snprintf(str, S(str), "Warning: node: %s goes nowhere", entry->token);
       if(!xctx->netlist_count) bus_hilight_hash_lookup(entry->token, xctx->hilight_color, XINSERT_NOREPLACE);
       if(incr_hi) incr_hilight_color();
       statusmsg(str,2);
     }
     else if(entry->d.out >=2 && entry->d.inout == 0 && entry->d.port>=0)  /*  era d.port>=2   03102001 */
     {
       my_snprintf(str, S(str), "Warning: shorted output node: %s", entry->token);
       if(!xctx->netlist_count) bus_hilight_hash_lookup(entry->token, xctx->hilight_color, XINSERT_NOREPLACE);
       if(incr_hi) incr_hilight_color();
       statusmsg(str,2);
       if(tclgetboolvar("erc_shorted_output_is_error")) err |= 1;
     }
   }
   dbg(1, "traverse_node_hash(): node: %s in=%d out=%d inout=%d port=%d\n",
        entry->token, entry->d.in, entry->d.out, entry->d.inout, entry->d.port);

   entry = entry->next;
  }
 }
 return err;
}

void print_vhdl_signals(FILE *fd)
{
  Node_hashentry *ptr;
  int i, found;
  int mult,j;
  char *class=NULL;

  found=0;
  for(i=0;i<HASHSIZE; ++i) {
    ptr = xctx->node_table[i];
    while(ptr) {
      if(strstr(ptr->token, ".")) {
        dbg(2, "print_vhdl_signals(): record field, skipping: %s\n", ptr->token);
        ptr = ptr->next;
        continue; /* signal is a record field, no declaration */
      }
      if(ptr->d.port == 0 ) {
        found = 1;
        if(ptr->token[0]=='#') {
          mult=get_unnamed_node(3, 0,  atoi((ptr->token)+4) );
        }
        else {
          mult=1;
        }
        dbg(2, "print_vhdl_signals(): node: %s mult: %d value=%s \n\n",
              ptr->token,mult, ptr->value?ptr->value:"<NULL>");
        if( ptr->class && ptr->class[0] )
          my_strdup(_ALLOC_ID_, &class, ptr->class);
        else
          my_strdup(_ALLOC_ID_, &class, "signal");
        if(mult>1) {
          for(j=mult-1;j>=0;j--) {
            fprintf(fd, "%s %s[%d] : ", class, ptr->token[0]=='#' ? ptr->token+1 : ptr->token,j);
            if(ptr->sig_type && ptr->sig_type[0]) {
              fprintf(fd, "%s", ptr->sig_type);
            }
            else {
             fprintf(fd, "std_logic");
            }
            if(ptr->value && ptr->value[0]) fprintf(fd, " := %s ", ptr->value);
            fprintf(fd, " ; %s\n", ptr->orig_tok);
          }
        }
        else {
          fprintf(fd, "%s %s : ", class, ptr->token[0]=='#' ? ptr->token+1 : ptr->token);
          if(ptr->sig_type && ptr->sig_type[0]) {
            fprintf(fd, "%s", ptr->sig_type);
          }
          else {
            fprintf(fd, "std_logic");
          }
          if(ptr->value && ptr->value[0]) fprintf(fd, " := %s ", ptr->value);
          fprintf(fd, " ; %s\n", ptr->orig_tok);
        }
      }
      ptr = ptr->next;
    }
  }
  if(found) fprintf(fd, "\n" );
  my_free(_ALLOC_ID_, &class);
}



void print_verilog_signals(FILE *fd)
{
  Node_hashentry *ptr;
  int i, found;
  int mult,j;

  dbg(2, " print_verilog_signals(): entering routine\n");
  found=0;
  for(i=0;i<HASHSIZE; ++i) {
    ptr = xctx->node_table[i];
    while(ptr) {
      if(ptr->d.port == 0 ) {
        found = 1;
        if(ptr->token[0]=='#') {
         mult=get_unnamed_node(3, 0,  atoi((ptr->token)+4) );
        }
        else {
         mult=1;
        }
        dbg(2, " print_verilog_signals(): node: %s mult: %d value=%s \n\n",
               ptr->token,mult, ptr->value?ptr->value:"<NULL>");
        if(mult>1) {
          for(j=mult-1;j>=0;j--) {
            if(ptr->verilog_type && ptr->verilog_type[0]) {
              fprintf(fd, "%s ", ptr->verilog_type);
            }
            else {
              fprintf(fd, "wire ");
            }
            fprintf(fd, "%s[%d] ", ptr->token[0]=='#' ? ptr->token+1 : ptr->token,j);
            if(ptr->value && ptr->value[0]) fprintf(fd, "= %s ", ptr->value);
            fprintf(fd, "; // %s\n", ptr->orig_tok);
          }
        }
        else {
          if(ptr->verilog_type && ptr->verilog_type[0]) {
            fprintf(fd, "%s ", ptr->verilog_type);
          } else {
            fprintf(fd, "wire ");
          }
          fprintf(fd, "%s ", ptr->token[0]=='#' ? ptr->token+1 : ptr->token);
          if(ptr->value && ptr->value[0]) fprintf(fd, "= %s ", ptr->value);
          fprintf(fd, "; // %s\n", ptr->orig_tok);
        }
      }
      ptr = ptr->next;
    }
  }
  if(found) fprintf(fd, "\n" );
}

void list_nets(char **result)
{
  Node_hashentry *ptr;
  int i;
  int netlist_lvs_ignore=tclgetboolvar("lvs_ignore");
  char *type = NULL;
  int mult, k;
  char *pin_node = NULL;
  char *p_n_s1, *p_n_s2, *lab;

  prepare_netlist_structs(1);
  for(i = 0; i < xctx->instances; i++) {
    if(skip_instance(i, 0, netlist_lvs_ignore)) continue;
    my_strdup(_ALLOC_ID_, &type,(xctx->inst[i].ptr+ xctx->sym)->type);
    if(type && xctx->inst[i].node && IS_PIN(type)) {
      my_strdup2(_ALLOC_ID_, &pin_node, expandlabel(xctx->inst[i].lab, &mult));
      p_n_s1 = pin_node;
      for(k = 1; k <= mult; ++k) {
        lab = my_strtok_r(p_n_s1, ",", "", 0, &p_n_s2);
        p_n_s1 = NULL;
        my_mstrcat(_ALLOC_ID_, result, "{", lab, " ", type, "}\n", NULL);
      }
    }
    if(pin_node) my_free(_ALLOC_ID_, &pin_node);
  }
  if(type) my_free(_ALLOC_ID_, &type);

  for(i=0;i<HASHSIZE; ++i) {
    ptr = xctx->node_table[i];
    while(ptr) {
      if(!ptr->d.port) {
        my_mstrcat(_ALLOC_ID_, result,
          "{", ptr->token, " ", "net", "}\n", NULL);
      }
      ptr = ptr->next;
    }
  }
}

