#usage "<b>Renumber the parts of a schematic</b>\n"
       "<p>"
       "<author>Author: support@cadsoft.de</author>"

// THIS PROGRAM IS PROVIDED AS IS AND WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED

string Info = "<b>ATTENTION<p>\n" +
       "Please verify that your corresponding layout file (if already existing) " +
       "has been loaded with the schematic file.<p>\n" +
       "Otherwise back-/forward-annotation will not work afterwards.</b><p>\n" +
       "This version renumbers only devices with package (no supply) " +
       "sorted by sheets and coordinates (vertical/descending, horizontal/ascending).<p>\n" +
       " You can change the following sorting parameters:<p>\n" +
       " descx = 0 (X ascending [left >> right])<p>\n" +
       " descx = 1 (X descending [right >> left])<p>\n" +
       " descy = 0 (Y ascending)<p>\n" +
       " descy = 1 (Y descending)<p>\n";

string Version = "4.2.0";
int descy = 1;         // set to 0 sorting ascending
int descx = 0;         // set to 1 sorting descending

numeric string OldNames[], NewNames[];
int x[], y[], i[], sh[];
int nrNames = 0;

numeric string SymNames[];     // Device-Name of Symbol
int symsh[];
int sx[], sy[];
int Snr = 0;
int Dnr = 0;

string error = "";
string SymPrefix[];
string DevPrefix[];
string DevName[];
string SymDevName[];

string cmd;
string c;

real Grid = 100; // in 100 Mil
string lbr[], dev[], sym[];

int GetNumberIndex(string Name)
{
  // Returns the index of the first digit of the numeric part of Name
  // -1 indicates there is no numeric part in Name
  int l = strlen(Name) - 1;
  for (int i = l; i >= 0; --i) {
      if (!isdigit(Name[i]))
         return i < l ? i + 1 : -1;
      }
  return 0;
}

string prefix(string name)      // Prefix of Device
{
  int num = GetNumberIndex(name);
  if (num < 1) return name;
  else {
     string pfx = name;
     pfx[num] = 0;
     return pfx;
     }
}

void DescendingY(void)
{
  for (int ny = 0;  ny < nrNames ; ny++) {
    y[ny] = 0 - y[ny];
    }
}

void DescendingX(void)
{
  for (int nx = 0;  nx < nrNames ; nx++) {
    x[nx] = 0 - x[nx];
    }
}

void SortElements(void)
{
  // Sorts the elements according to their location, first by ascending
  // x coordinates, then by ascending y coordinates.
  // If you prefer a different kind of sorting, you can implement this here.
  // As a result, the integer array i[] must contain the new sequence
  // in which to renumber the elements.
  if (descy) DescendingY();
  if (descx) DescendingX();

  sort(nrNames,i, sh, y, x);
  if (descy) DescendingY();
  if (descx) DescendingX();
  return;
}

void GenerateNames(void)
{
  // Generates new numeric parts to the element names in NewNames
  for (int n = 0; n < nrNames - 1; ++n) {
     int k = 0;
     string s = NewNames[i[n]];
     if (!isdigit(s[strlen(s) - 1])) {
        for (int j = n; j < nrNames; ++j) {
           if (NewNames[i[j]] == s) {
              sprintf(NewNames[i[j]], "%s%d", NewNames[i[j]], ++k);
              }
           }
        }
     }
  return;
}

void Rename(int x, int y, string New)
{
  // Generates the EAGLE command necessary to change element name Old to New
  sprintf(c, "Name '%s' (%d %d);\n", New, x, y);
  cmd += c;
  return;
}

void GenerateScript(void)
{
  // Generates an EAGLE script file that does the whole renumbering.
  // The tricky part here is that we cannot rename an element to a name
  // that already exists in the schematic (which, e.g. might be necessary if
  // we had to swap the names of two elements). Therefore we have to
  // use a ScratchName wherever this is necessary.

  // If there is nothing to do, the resulting script file will be empty.

  int ScratchIndex = 0;
  string ScratchName;
  int sch = 0;
  for (int n = 0; n < nrNames; ++n) {
     if (sh[i[n]] != sch) {
        sch = sh[i[n]];              // *** change sheet
        sprintf(c, "Edit .s%d;\n", sch);
        cmd += c;
        }
     if (OldNames[i[n]] != NewNames[i[n]]) {
        for (int k = n + 1; k < nrNames; ++k) {
           if (OldNames[i[k]] == NewNames[i[n]]) {
              sprintf(ScratchName, "$%0*d", ELEMENT_NAME_LENGTH - 1, ++ScratchIndex);

              if (sh[i[k]] != sch) {
                 sch = sh[i[k]];              // *** change sheet
                 sprintf(c, "Edit .s%d;\n", sch);
                 cmd += c;
                 }
              Rename(x[i[k]],y[i[k]], ScratchName);

              if (sh[i[n]] != sch) {
                 sch = sh[i[n]];              // *** change sheet
                 sprintf(c, "Edit .s%d;\n", sch);
                 cmd += c;
                 }

              OldNames[i[k]] = ScratchName;
              break;
              }
           }
        if (sh[i[n]] != sch) {
           sch = sh[i[n]];              // *** change sheet
           }
        Rename(x[i[n]],y[i[n]], NewNames[i[n]]);
        }
     }
  return;
}

// *** check collision befor rename ***
string CheckNames(void) {
   string new_name = ";";
   string h;

   for (int Dn = 0; Dn < Dnr; Dn++ ) {
      for (int Sn = 0; Sn < Snr; Sn++) {
         if (DevPrefix[Dn] == SymPrefix[Sn]) {
            sprintf(h, "# Do not use Prefix %s on Device with Package (%s) and Device without Package (%s)\n",
                                SymPrefix[Sn], DevName[Dn], SymDevName[Sn]);
            error += h;
            break;
            }
         }
      }
   for (int n = 0; n < nrNames - 1; ++n) {     // make a long string
      new_name += NewNames[n] + ";";
      }

   for (int xx = 0; xx < Snr - 1; xx++) {
      string sd = SymNames[xx];
      if(sd[0] == '$') {                       // if first character a $ on Symbolname
         error +=  "# Do not use $ character on first position in device name\n";
         sprintf(h, "# RENAME %s on (%.2f %.2f) - sheet %d befor run this ULP again' (%.2f %.2f)\n",
                                 SymNames[xx], sx[xx] / 1000.0, sy[xx] / 1000.0, symsh[xx], sx[xx] / 1000.0, sy[xx] / 1000.0);
         error += h;
         }

      int s;
      int pos = strrstr(new_name, ";" + SymNames[xx] + ";");
      if (pos > 0 ) {
         for (s = 0; s < nrNames - 1; s++) {
            if(NewNames[s] == SymNames[xx]) {
               break;
               }
            }
         error +=  "# Collision by symbol name and device name (eg. Frames, Supply ...)\n";
         sprintf(h, "# Rename PREFIX of Device %s on (%.2f %.2f) - sheet %d befor rename %s on (%.2f %.2f) - sheet %d';\n",
                                 SymNames[xx], sx[xx] / 1000.0, sy[xx] / 1000.0, symsh[xx],
                                 OldNames[s], x[s] / 1000.0, y[s] / 1000.0, sh[s] );
         error += h;
         }
      }
  return error;
}

void setgridmil (void)
{
  sprintf(c, "GRID MIL 100 OFF;\n");
  cmd += c;
  // ## only display layer 94 (symbol) if placed a text
  // ## on symbol origin.  15.06.2004 alf@cadsoft.de
  sprintf(c, "DISPLAY NONE 94 -95 -96;\n");
  cmd += c;
  return;
}

void visible(UL_SCHEMATIC S) {
  sprintf(c, "DISP NONE ");
  cmd += c;
  S.layers(L) {
     if (L.visible) {
        sprintf(c, "%d ", L.number);
        cmd += c;
        }
     }
  cmd += ";\n";
  return;
}

void menue(void) {
   int Result = dlgDialog("Renumber Schematic") {
      dlgLabel(Info);
      dlgHBoxLayout {
         dlgGroup("Sort X") {
            dlgRadioButton("&Ascending", descx);
            dlgRadioButton("&Descending", descx);
            }
         dlgGroup("Sort Y") {
            dlgRadioButton("A&scending", descy);
            dlgRadioButton("D&escending", descy);
            }
         }
      dlgHBoxLayout {
      dlgPushButton("+&OK") dlgAccept();
      dlgStretch(1);
      dlgPushButton("-&Cancel") dlgReject();
         }
      };
   if (!Result) exit (0);
   return ;
   }

if (schematic) {
 schematic(S) {
  menue();
  int l = 1;
  S.sheets(SH) {
    SH.parts(P) {
      int n = GetNumberIndex(P.name);
      if (n > 0) {

        if (P.device.package) {    // **** only Devices with Package
                                   // **** without Supply symbol Frames ect...
           DevPrefix[Dnr] = prefix(P.name);
           DevName[Dnr] = P.name;
           ++Dnr;
           P.instances(I) {

             int found = -1;
             for (int fn = 0; fn < nrNames; fn++) {
               if (OldNames[fn] == P.name) {
                  found = fn;
                  break;
                  }
               }
             if (found < 0) {

               x[nrNames] = u2mil(I.x);    // cannot use E.x/y directly because of
               y[nrNames] = u2mil(I.y);    // sort() problem with integers > 32767
               OldNames[nrNames] = P.name; // in version 3.50
               NewNames[nrNames] = strsub(P.name, 0, n);
               sh[nrNames] = I.sheet;
               ++nrNames;
               }

             else {
                if (sh[fn] == I.sheet) {
                  if ( u2mil(I.x) < x[fn] || u2mil(I.y) > y[fn] ) {
                      // tausche wenn x kleiner oder y groesser
                      x[fn] > u2mil(I.x);
                      y[fn] > u2mil(I.y);
                      }
                  }
               }
             }
           }

        // Only Symbol (Supply, Port, Frame...)
        else {  // *** check PartName on Symbols Supply, Port, Frame ... ***
           SymPrefix[Snr] = prefix(P.name);
           SymDevName[Snr] = P.name;
           P.instances(I) {
              SymNames[Snr] = P.name;     // Device-Name of Symbol
              sx[Snr] = u2mil(I.x);       // cannot use E.x/y directly because of
              sy[Snr] = u2mil(I.y);       // sort() problem with integers > 32767
              symsh[Snr] = I.sheet;
              ++Snr;
              break;
              }
           }
        }
      }
    }

  SortElements();
  GenerateNames();
  setgridmil ();
  GenerateScript();

  if (CheckNames()) {
    int select;
    dlgDialog("Symbol ref Device Names") {
    dlgVBoxLayout {
       dlgLabel("Warnings for renumber!");
       dlgTextView(error);
       }
    dlgHBoxLayout {
       dlgSpacing(450);
       }
    dlgHBoxLayout {
       dlgPushButton("+&OK") dlgAccept();
       dlgStretch(1);
         }
       };
    exit (-1);
    }

  sprintf(c, "GRID INCH 0.1;\n");
  cmd += c;
  sprintf(c, "EDIT .S1;\n");
  cmd += c;
  visible(S);
  exit (cmd);
 }
}

else {
   dlgMessageBox("\n    Start this ULP in a Schematic    \n");
   exit (0);
}
