package higbie;

import higbie.OrderedList;
import higbie.StaticStuff;

import java.util.Vector;
import java.io.*;
import java.util.*;
public class Debug implements Serializable {
   /**   Debugging setup and output class    Serializable version
   *     
   *           © Lee Higbie, 2002 - 2005                    lee at seki dot com
   *           This program may be copied and modified provided that this
   *           copyright notice is preserved and a reasonable effort is made
   *           to send a copy of any modified program to Lee Higbie
   */
   //   Last backup = zrchive/Debug.java.9  on 1 Feb 2006
   /**
   *     Debug is a class for determining which debug statements to print 
    *       and how to print them.   It uses no files and is Serializable.
   *     Constructor takes a sequence of strings which it checks against
   *     argument to debug.
   *<p>     Typical usage:  args[] from main
   *<p>     this.deb = new Debug (args);  // using args for the debugging flags
    *          Output only to the Java console.
   *        ...
   *<p>     deb.print(debug_flag, message);
    *<p>    which prints classNm lineNo [methnm] message  if debug_flag is set.
    *<p>    deb.finalize()    // at end of debugging session
    *<p>    which  prints a list of all debug flags encountered, the number 
    *       of times each was seen and whether or not it has been set.
   */

   /** arguments to constructor */
   Vector <String> args = new Vector <String> (3);
   /** debug flags found in the code and count of occurrences*/
   OrderedList <String> codeFlagsFound = new OrderedList <String> ();
   int argCt = 0;
   static int trcbStrt = 2;
   boolean consoleOut = true, errorMessage = false, intervalOut = false;
   final String noPrintCommand = "noprint", counterCommand = "counter";
   final int noPrintCmdLen = noPrintCommand.length()+1, 
             counterCmdLen = counterCommand.length()+1;
   final String blankStr = " ", nullStr = "", nullWordStr = "nullWord";
   final String argFileName = "DebugArgFile";
   char intervalChar = ' ';
   int limitLow = 0, limitHigh = Integer.MAX_VALUE;
   /** Length of portion of class name to print in debug stmnts*/
   int   clsNmLn           =  10, classNameLength   = 12, 
   /** Length of portion of methodnames to print in brackets in debug output. */
         mthNmLn           =  5,  methodNameLength  = 10;
   //    first two are for short usages, last two are for detailed use

//**      *************** ***************          print: String, int, String
/**     prints  diagnostic information
*       @param dbf   the debug flag, tested against ones enabled in constr
*       @param cntr  the int counter to be checked for print range 
*       @param diag  the diagnostic to be printed
*/
   public void print(String dbf, int cntr, String diag) {
      codeFlagsFound.searchAddElement(dbf);
      if (cntr < limitLow || cntr > limitHigh) { return; }
      if ( !strMatch(dbf) ) { return; }  // end if
      trcbStrt = 3;
      print ("(" + cntr + ") " + diag);
   }           // end 3-arg print method

//*      *************** ***************                   print: String, String
/**     prints  diagnostic information
*       @param dbf   The debug flag, tested against ones enabled in constr
*       @param diag  The diagnostic String to be printed
*/
   public void print(String dbf, String diag) {
      codeFlagsFound.searchAddElement(dbf);
      if ( !strMatch(dbf) ) { return; }  // end if
      if (consoleOut) { System.out.println(getPrefix(clsNmLn, mthNmLn) + diag); }  
      trcbStrt = 2;
   }        // end print (String, String)

//**      *************** ***************             print: String, String, int 
/**     prints  diagnostic information--for user debugging
*       @param dbf   the debug flag, enabled? and printed in output line
*       @param diag  the diagnostic to be printed
*/
   public void print(String dbf, String diag, int whatZis) {
     codeFlagsFound.searchAddElement(dbf);
     if ( !strMatch(dbf) ) { return; }  // end if
     String otp = "(" + whatZis + "): " + diag;
     if (consoleOut) { System.out.println(otp); }  // end if print output 
   }        // end method print (String, String, ????)

//**      *************** ***************                 print:  String
/**     prints  diagnostic information--for user debugging
*       @param sln the line to be printed
*/
  private static void print(String sln) {
     System.out.println(getPrefix(98, 6) + sln);     
   }        // end print (String )

//**      *************** ***************                ******* trace
/**     prints  diagnostic information
*       @param cntr  tracing occurs if cntr is in range
*       @param dpth  tracback of dpth lines printed
*       @param diag  Additional diagnostic to be printed
*/
   public void trace(int cntr, int dpth, String diag) {
      if (cntr < limitLow || cntr > limitHigh) { return; }
      //print (ln, mn, dbf, cl, " ** Trace** " + diag);
      StackTraceElement[] here = (new Throwable()).getStackTrace();
      int depth = Math.min(dpth+1, here.length);
      StringBuffer otp = new StringBuffer("**Trace>> " + 
                     getPrefix(classNameLength, methodNameLength) + "  " + diag);
      for (int i = 1; i < depth; i++) { 
         String mnm = here[i].getMethodName();
         int st = (mnm.charAt(0) == '<') ? 1 : 0;
         otp.append("\n  " + i +". "+ mnm.substring(st, mnm.length()-1) 
               + " @ " + here[i].getClassName() 
               + " # " + here[i].getLineNumber());
      }     // end for i loop: 
      if (consoleOut) { System.out.println(otp); }  // end if print output 
   }           // end trace print method

//**************    ***********************   ******  traceSetLim
/**   traceSetLim sets the upper and lower limits for cntr in trace
*     @param      low   (default 0) traces if cntr is greater than low
*     @param      high  (default MAX_VALUE) traces if cntr is less than high
*/
public void traceSetLim (int low, int high) {
   limitLow    = low;
   limitHigh   = high;
}     // end method traceSetLim

//**      *************** ***************                ******* Constructors
   public Debug(String[] args){
      this.setSwitches(args);
   }          // end constructor Debug (String[])

//**      *************** ***************           ******* Special Constructors
/**     @param arg      String[] of the array of debug strings
*       @param argCnt   the number of strings to use.    
*       NOTE:  this constructor resets the debugging parameters
*/
   public Debug(String[] arg, int argCnt){
     argCt = argCnt;
     args.removeAllElements();
     if (argCt>0) {
        String ending = "s ";
        if (argCt == 1) {
           ending = " ";
        }
        System.out.print("\nDebug starting now with "+argCt+" option"+ ending);
        for (int i=0; i<argCt-1; i++) {
           args.addElement(((String)arg[i]));
           System.out.print(args.elementAt(i)+", ");
        }
        args.addElement(((String)arg[argCt-1]));
        System.out.println(args.elementAt(argCt-1)+".");
     }
   }                    // end constructor Debug (String, int)

//**      *************** ***************           ******* Special Constructors
/**     @param arg   String[] for the array of debug strings
*       @param cnln  the number of the class names to show
*       @param mnln  the number of the method names to show.   
*       NOTE:  this constructor resets the debugging parameters
*/
   public Debug(String[] arg, int cnln, int mnln){
     clsNmLn = cnln;
     mthNmLn = mnln;
     classNameLength = 2 * clsNmLn;
     methodNameLength = 2 * mthNmLn;
     this.setSwitches(arg);
   }                    // end constructor Debug (String, int, int)
   
//**      *************** ***************           ******* no-arg Constructors
   public Debug() {
      System.out.println("Debug #176:  No no-arg constructor");
      BufferedReader debIn;
      StringBuffer argLine = new StringBuffer();
      try {          //  
         debIn = new BufferedReader(new FileReader(argFileName));
         } catch (IOException ioe) {
            System.out.println(argFileName +" not found.");
            return;
         }        // end catch block
      
      while (true) {
         String inpLine;
         try {
            inpLine = debIn.readLine();
            if (inpLine == null) { break; }         // end if
            argLine.append(inpLine+" ");
            } catch (IOException ioe) {
               break;
            }        // end I/O Exception ==> last line of data read
      }     // end while more lines in file to read
      //System.out.println("Debug 207: "+ argCt +" "+ argLine);
      setSwitches (StaticStuff.bdTokens(argLine.toString() ));
   }       // end no-arg constructor

//**      *************** ***************             ******* strMatch 
   private boolean strMatch(String s) {
     boolean startup = false;
     if (argCt == 0) return false;
     if ((argCt == 1) && args.elementAt(0).equals("all")) return true;
     // string = s.toLowerCase();  debugging stmnts must use lower case.
     for (int i=0; i<argCt; i++) {
        //System.out.println("Debug: 187"+startup+" "+s+" "+i+" "+argCt+" "+
         //    args.elementAt(i));
       if (s.equals(args.elementAt(i))) {
          return true;
       }
       if (args.elementAt(i).equals("options") || args.elementAt(i).
               equals("setup")) {
          startup = true;
       }
     }
     if (s.equals("ANY") && !startup) { return true; }  // end if
     return false;
   }

//**      *************** ***************                ******* setSwitches
/*   @param   arg   an array of strings for which debug statement printing
*/
   private void setSwitches(String[] arg) {
     Date now = new Date();
     if (arg == null) { return; }
     if (arg.length>0) {
       int argdeduct = 0;
       StringBuffer opl = new StringBuffer("");
       for (int i=0; i<arg.length; i++) {
         String thisarg = ((String)arg[i]);
         if (thisarg == null) { break; }
         char frstchar = thisarg.charAt(0);
         //System.out.println("Debug: 214"+i+" "+arg.length +" "+ thisarg+" "
           //                 + frstchar +" "+ thisarg.length());
         if (frstchar == '/' || frstchar == '-') {
           argdeduct++;
           if (thisarg.substring(1,noPrintCmdLen).
                        equals(noPrintCommand)) {    // /noprint
              System.out.println("Debug #226: No diag info, period. ");
              consoleOut = false;
           }  // end if this argument begins with /noprint or -noprint
           else if (thisarg.substring(1,counterCmdLen).
                        equals(counterCommand)) {    // /counter
             intervalChar = thisarg.charAt(counterCmdLen);
             String str2 = "b", str3 = "c";
             String str1 = thisarg.substring(counterCmdLen+1);
             System.out.println("Debug 228"+intervalChar+" "+str1);
             switch (intervalChar) {
               case '=':                // interval says = one number
                   if (NumberFormatter.isInteger(str1)) {
                      limitLow  = Integer.parseInt(str1);
                      limitHigh = Integer.parseInt(str1);
                   }         // end if
                   else {
                      System.out.println("Debug 246 @ set56: Non-integer: "
                                          + str1 +".");
                   }         // end else clause
                break;             // end equals case 
                case '<':
                   if (NumberFormatter.isInteger(str1)) {
                      limitLow = Integer.parseInt(str1);
                   }         // end if
                   else {
                       System.out.println("Debug 255 @ set65: Non-integer: "+
                                          str1+".");
                   }         // end else clause
                break;             // end less than case 
                case '>':
                   if (NumberFormatter.isInteger(str1)) {
                      limitHigh = Integer.parseInt(str1);
                   }         // end if
                   else {
                       System.out.println("Debug 264 @ set74: Non-integer: "+
                                          str1+".");
                   }         // end else clause
                break;             // end greater than case 
             case '(':
                int cp = str1.indexOf(",");
                int cqp = str1.indexOf(")");
                
                   if (cp > 0 || cqp > cp) {
                      str2 = str1.substring(0,cp);
                      str3 = str1.substring(cp+1, cqp);
                      if (NumberFormatter.isInteger(str2) && 
                          NumberFormatter.isInteger(str3)) {
                         limitLow  = Integer.parseInt(str2);
                         limitHigh = Integer.parseInt(str3);
                      }         // end if
                      else {
                          System.out.println("Debug 281 @ set91: Non-integer: "+
                                             str2+", "+ str3+".");
                      }         // end else clause
                   }         // end if
                   else {
                       System.out.println("Debug 286 @ set96: Non-integer: "+
                                          str1+", "+str2+", "+str3+".");
                   }         // end else clause
                break;             // end interval case 
                default:
                   System.out.println("Debug 291 @ set101: Non-integer: "+
                                      str1+".");
             }                     // end switch on 
             intervalOut = true;
           }  // end if this argument begins with /counter or -counter
           //System.out.println("Debug 286"+limitLow+" "+limitHigh);
         }  // end if this argument begins with / or -
         else {                   // begin clause for debug argument
           if (thisarg.equals(nullWordStr)) {
             break;
           }  // end if
           // System.out.print(" Running with debugging switches: ");
           args.addElement(thisarg);
           opl.append(thisarg+", ");
           //System.out.print(" "+thisarg);
           argCt++;
         }              // end else clause over debug arguments
       }                      // end for i over Strings in args[]
       int argcnt = arg.length-argdeduct;
       String ending = (argcnt == 1) ? ": ": "s: ";
       opl.insert(0, now +" debugging setup with "+ argcnt +" parameter"
                  + ending + "\n");
       opl.setCharAt(opl.length() - 2,'.');
       System.out.println("Debug: " + opl.toString());
     }       // end if number of args > 0
   }     // end setSwitches

//**      *************** ***************                ******* getSwitches
/**    String not array of strings with the list of switches that are set
*   @return a String with a comma-space separated list of switches
*/
   public String getSwitches() {
      String so = new String((String)(args.elementAt(0)));
      for (int i=1; i< argCt; i++) {
         so = so+", "+ args.elementAt(i);
      }
      return so;
   }
   
   /**   getClassName   for output statements
   *     @param   cl  the object whose name is needed 
   *     @return  the name of the object unless ... 
   */
   private static String getClassName (Object cl) {
      int upbd, beg;
      String className = " ", clnm = (cl.getClass()).toString();
      //System.out.println("Debug 327"+clnm.length()+" "+clnm);
      if ((cl.getClass()).equals(className.getClass())) {
         upbd = Math.min(10+clnm.lastIndexOf("."), clnm.length());
         // question about value 10 above  for static objects only
         //System.out.println("Debug 331"+upbd+" <"+clnm+"> "+junk);
         className = clnm.substring(Math.max(6, clnm.lastIndexOf(".")+1),upbd);
         //className = cl.toString();
      }  // end if
      else {
        beg = Math.max(clnm.lastIndexOf("."), clnm.lastIndexOf(" "));
        upbd = Math.min(beg+6, clnm.length());
        className = clnm.substring(Math.max(6, clnm.lastIndexOf(".")+1),upbd);
      }   // end else = not a string clause
      return className; 
   }     // end method getClassName
   
   //**      *************** ***************                ******* getPrefix
   /**   @param   cnln   the length of the desired class name substring
   *     @param   mthln  the length of the desired method name substring
   *     @return  "ClassName ClassLineNo [MethodName MethodLineNo] " 
   */
   private static String getPrefix (int cnln, int mthln) {
      StackTraceElement[] here = (new Throwable()).getStackTrace();
      String clsNm = here[trcbStrt].getClassName();
      String ocln  = here[trcbStrt-1].getClassName();
      if (ocln.indexOf("Debug") < 0) { 
         trcbStrt--; 
         //System.out.println ("Debug 418: "+ trcbStrt +" old class = "+ clsNm);
         clsNm = ocln;
      }        // end test for correct stack traceback position
      int dotPosn = Math.max(0, clsNm.lastIndexOf('.') + 1);
      String mthNm = here[trcbStrt].getMethodName();
      int cl = Math.min (cnln + dotPosn, clsNm.length()), 
         ml = Math.min (mthln, mthNm.length());
      //System.out.println ("Debug 399: "+clsNm+" "+cl+" "+here.length
        //           + " " + dotPosn);
      /*for (int i = 0; i < here.length; i++) { 
         System.out.println (here[i]);
      } */    // end for i loop: 
      return clsNm.substring(dotPosn, cl) + " " + here[trcbStrt].getLineNumber() 
                   + " [" + mthNm.substring(0, ml) + "] "; 
   }     // end method getPrefix
   
   //**      *************** ***************              ******* finalizedebug
   /**   Lists debug flags found encountered code.
   */
   public void finalizeDebug() {
      int i;
      StringBuffer sb = new StringBuffer();
      for (i = 0; i < codeFlagsFound.size(); i++) {
         String fl  =  (String)codeFlagsFound.elementAt(i); 
         String flp =  StaticStuff.padRight(fl, 12, 3);
         sb.append( flp + (strMatch(fl)?"E":"I") + 
               StaticStuff.padLeft(
                  ((Integer)codeFlagsFound.counts.elementAt(i)).toString(),6) 
                    +((i%4==3)?"\n":";  ") );
      }     // end for i loop: 
      if (sb.length() > 3) {
         print (codeFlagsFound.size()+" debug flags encountered  (Flag  " 
                  + "Enabled/Ignored  number of endounters):\n" 
                  + sb.substring(0, sb.length()-((i%4==0)?1:3))); 
      // 0 in test above because i is incremented after last append
      } else {          // end if sb is good
         print ("No debug flags encountered.");
      }                 // end else clause
   }          // end finalizeDebug method
   
   //**      *************** ***************              ******* finalizedebug
   /**   Optionally lists debug flags found in code.
    *    @param   output tells if the line of output listing debug flags is 
    *             printed when this method is called
   */
   public void finalizeDebug(boolean output) {
      int i;
      if ( !output) { return; }
      StringBuffer sb = new StringBuffer();
      for (i = 0; i < codeFlagsFound.size(); i++) {
         String fl  =  (String)codeFlagsFound.elementAt(i); 
         String flp =  StaticStuff.padRight(fl, 12, 3);
         sb.append( flp + (strMatch(fl)?" Enb":" Ign") + 
                    StaticStuff.padLeft(
                       ((Integer)codeFlagsFound.counts.elementAt(i)).toString(),6) 
                    +((i%3==2)?"\n":";") );
      }     // end for i loop: 
      print (codeFlagsFound.size()+" debug flags encountered  (Flag  " 
                  + "Enabled/Ignored  number of encounters):\n" 
                   + sb.substring(0, sb.length()-((i%4==0)?1:3))); 
       // 0 in test above because i is incremented after last append
   }          // end finalizeDebug method

}                  // end of Debug class
