import java.util.*;
import org.w3c.dom.*;
import com.sun.xml.tree.*;

/**
 * RIPE class.
 *
 * @author ottrey@ripe.net
 * @version $Version$
 *
 */
public class ClassDef {

  public static final int EXTRA_BIT=11;

  private String    name;
  private String    code;
  private String    status;
  private String    description;
  private Vector    attributes;
  private int       dbaseCode;

  private int       width;        // The longest attribute name.
  private Hashtable foreignAttrs;

  // -----------------oOo-----------------
  //              Constructors
  // -----------------oOo-----------------
  /**
   * Creates a RIPE class.
   *               
   * @author ottrey@ripe.net
   * @version $Version$
   *               
   * @param obj The node from which a RIPE class is made.
   * 
   * NOTE: No-one has implemented a Node.getNodeValue() method yet.  ;-(
   *       description = tw.getNextElement("description").getNodeValue();
   *       So I wrote a crappy one here.
   * @see getNodeValue
   */
  public ClassDef(Node obj, Hashtable ripeAttributes) {
    width = 0;
    name      = obj.getAttributes().getNamedItem("name").getNodeValue();
    code      = obj.getAttributes().getNamedItem("code").getNodeValue();
    status    = obj.getAttributes().getNamedItem("status").getNodeValue();

    // Prepare to walk the tree.
    TreeWalker tw = new TreeWalker(obj);

    // Extract the "description".
    description = getNodeValue(tw.getNextElement("description"));

    // Create the attributes.
    attributes = createAttributes(tw.getNextElement("attributes"), ripeAttributes);
    
    // Get the dbase_code.
    dbaseCode = -1;
    Node dcn = tw.getNextElement("dbase_code");
    if (dcn != null) {
      String dbaseCodeStr = dcn.getAttributes().getNamedItem("value").getNodeValue();
      try {
        dbaseCode = Integer.parseInt(dbaseCodeStr);
      }
      catch (NumberFormatException e) {
        System.err.println("Bad dbaseCode: " + dbaseCodeStr); 
        System.err.println("\tencounterd in Object: " + obj); 
        System.exit(-1);
      }
    }

    // Create the foreignAttrs.
    foreignAttrs = createForeignAttrs();

  } // ClassDef()

  
  /**
   * Aaaargh I shouldn't have to write this. :-(
   *
   * @param        node
   * @return       The value of the node.
   * @see          ClassDef
   *
   */
  private String getNodeValue(Node node) {
    String nodeStr = node.toString();
    int startIndex = nodeStr.indexOf('>') + 1;
    int endIndex = nodeStr.lastIndexOf('<') - 1;
    
    return nodeStr.substring(startIndex, endIndex);
  } // getNodeValue()
  
  /**
   * Create the attributes for this class from the attributes already created.
   *
   * @param  attrs          The head node of the attributes tree from the classes DOM.
   * @param  ripeAttributes The attributes created from the attributes DOM.
   * @return       (methods only)
   *
   */
  private Vector createAttributes(Node attrs, Hashtable ripeAttributes) {
    // Create a set of attributes for this class.
    Vector result = new Vector();

    Node attrNode;
    AttributeDef adTmp;
    String attrCode=null;
    String status;

    // Prepare to walk the tree.
    TreeWalker tw = new TreeWalker(attrs);
    try {
      // Get the attribute node from the tree.
      while ((attrNode = tw.getNext()) != null) {
        // Get the attribute code from the node.
        attrCode = new String(attrNode.getNodeName());

        // Get the attribute from the ripeAttributes.
        adTmp = (AttributeDef)ripeAttributes.get(attrCode);

        // Keep track of the longest attribute name.
        if ( adTmp.getName().length() > width ) {
          width = adTmp.getName().length();
        }

        // If the attribute is "valid"
        if ((status=adTmp.getStatus()).equals("valid")) {

          // Create a clone of the attribute.
          AttributeDef ad = null;
          try {
            ad = (AttributeDef)adTmp.clone();
          }
          catch (CloneNotSupportedException e) {
            System.err.println("Doh!");
          }

          // Set the min attribute.
          ad.setChoice(attrNode.getAttributes().getNamedItem("choice").getNodeValue());

          // Set the max attribute.
          ad.setNumber(attrNode.getAttributes().getNamedItem("number").getNodeValue());

          // Add the attribute to this class.
          result.addElement(ad);
        }
        else {
          System.err.println("Warning: Class " + name + " contains a " +
                             status + " attribute: " + attrCode);
        }
      }
    }
    catch (NullPointerException e) {
      // We tried to do something with a null - No worries, we caught it!
      System.err.println("Trouble creating attributes: " + e);
      System.err.println("attrCode=" + attrCode);
      System.err.println("attrs=" + attrs);
    }
    
    return result;
  } // createAttributes()

  private Hashtable createForeignAttrs() {
    // Create a set of foreign attributes for this class.
    Hashtable result = new Hashtable();

    Enumeration e = attributes.elements();
    while (e.hasMoreElements()) {
      AttributeDef ad = (AttributeDef)e.nextElement();
      String code = ad.getCode();
      String foreign = ad.getForeign();
      if (foreign.length() > 1 ) {
        result.put(code, foreign);
      }
    }
    return result;
  } // createAttributes()

  private String toCString(String str) {
    String result = new String();
    char c;
    
    result += "\"";
    for(int i=0; i < str.length(); i++) {
      c = str.charAt(i);
      switch (c) {
        case '\n':
          result += "\\n\"\n\"";
        break;
        
        case '"':
          result += "\\\"";
        break;
        
        case '&':
          if(str.regionMatches(true, i, "&lt;", 0, 4)) {
            result += "<";
            i += 3;
          } else if(str.regionMatches(true, i, "&gt;", 0, 4)) {
            result += ">";
            i += 3;
          }
          else {
            result += c;
          }
        break;

        default:
          result += c;
      }
    }
    result += "\"";

    return result;
  } // toCString

  public String getName() {
    return name;
  } // getCode()

  public String getCode() {
    return code;
  } // getCode()

  public String getStatus() {
    return status;
  } // getStatus()

  public String getDescription(boolean CStyle) {
    String result = description;

    if (CStyle) {
      result = toCString(description);
    }

    return result;
  } // getDescription()

  public int getDbaseCode() {
    return dbaseCode;
  } // getDbaseCode()

  public int getWidth() {
    return width + EXTRA_BIT;
  } // getWidth()

  public Vector getAttributes() {
    return attributes;
  } // getAttributes()

  public Hashtable getForeignAttrs() {
    return foreignAttrs;
  } // getForeignAttrs()

  public String getEnum() {
    return new String("C_" + code).toUpperCase();
  } // getEnum()

 /**
  * Returns a template for the class in the form:
  *
  * boolean CStyle Returns in C style eg, with "\n\" at the end of each line.
  *
  * person:      [mandatory]  [single]     [primary/look-up key]    
  * address:     [mandatory]  [multiple]   [ ]                      
  * phone:       [mandatory]  [multiple]   [ ]                      
  * fax-no:      [optional]   [multiple]   [ ]                      
  * e-mail:      [optional]   [multiple]   [look-up key]            
  * nic-hdl:     [mandatory]  [single]     [primary/look-up key]    
  * remarks:     [optional]   [multiple]   [ ]                      
  * notify:      [optional]   [multiple]   [inverse key]            
  * mnt-by:      [optional]   [multiple]   [inverse key]            
  * changed:     [mandatory]  [multiple]   [ ]                      
  * source:      [mandatory]  [single]     [ ]                      
  *
  * @return String the template.
  */
  public String getTemplate(boolean CStyle) {
    String result = new String();

    String pad1 = "              "; /* XXX It is a crappy! assumption of the longest attribute name. */
    String pad2 = "           ";
    String pad3 = "           ";

    String sofl = new String();
    String eofl = new String();
    if(CStyle) {
      sofl = "\"";
      eofl = "\\n\"\n";
    }
    else {
      sofl = "";
      eofl = "\n";
    }

    AttributeDef ad;
    String name, choice, number, keytype;
    Enumeration e = attributes.elements();
    while (e.hasMoreElements()) {
      ad = (AttributeDef)e.nextElement();
      name = ad.getName();
      choice = ad.getChoice();
      number = ad.getNumber();
      keytype = ad.getKeytype2();
      result += (sofl + name + ":"                                 +
                 pad1.substring(0, pad1.length()-name.length())    +
                 "[" + choice + "]"                                +
                 pad2.substring(0, pad2.length()-choice.length())  +
                 "[" + number + "]"                                +
                 pad3.substring(0, pad3.length()-number.length())  +
                 "[" + keytype + "]"                               +
                 eofl
                );
    }

    return result;
  } // getTemplate()

  public String getTemplateV(boolean CStyle) {
    String result=new String();
    String templ = getTemplate(CStyle);

    String sofl = new String();
    String eofl = new String();
    if(CStyle) {
      sofl = "\"";
      eofl = "\\n\"\n";
    }
    else {
      sofl = "";
      eofl = "\n";
    }

    result += (sofl + "The " + name + " class:" + eofl          +
               toCString(description)                           +
               sofl + eofl                                      +
               templ                                            +
               sofl + eofl                                      +
               sofl + "The content of the attributes of the "   +
               name + " class are defined below:" + eofl        +
               sofl + eofl
              );

    AttributeDef ad;
    String name, description, format;
    Enumeration e = attributes.elements();
    while (e.hasMoreElements()) {
      ad = (AttributeDef)e.nextElement();
      name = ad.getName();
      if (CStyle) {
        description = toCString(ad.getDescription());
        format = toCString(ad.getFormat());
      }
      else {
        description = ad.getDescription();
        format = ad.getFormat();
      }

      result += (sofl + name + eofl +
                 description        +
                 format             +
                 sofl + eofl     
                );
    }

    result += (sofl + eofl                                               +
               sofl + "Further information may be found at:" + eofl      +
               sofl + eofl                                               +
               sofl + "http://www.ripe.net/docs/ripe-157.html" + eofl    +
               sofl + "ftp://ftp.ripe.net/ripe/docs/ripe-157.ps" + eofl  +
               sofl + "ftp://ftp.ripe.net/ripe/docs/ripe-157.txt" + eofl +
               sofl + eofl
              );

    return result;
  } // getTemplateV()

  public String getDiagram(int maxWidth, Hashtable foreigns) {
    ClassDiagram om = new ClassDiagram(this, maxWidth, foreigns);

    return om.toString();
  } // getDiagram()

  /*
  public boolean equals(ClassDef ro) {
    return code.equals(ro.getCode());
  } // equals()
  */
    
  public String toString() {
    return new String("ripe class={" +
                         "\n\tname="         + name          +
                         "\n\tcode="         + code          +
                         "\n\tstatus="       + status        +
                         "\n\tdescription="  + description   +
                         "\n\tattributes="   + attributes    +
                         "\n\tdbaseCode="    + dbaseCode     +
                         "\n\twidth="        + width         +
                         "\n\tforeignAttrs=" + foreignAttrs  +
                         "\n}");
  } // toString()

  /**
   * RIPE class diagram.
   *
   * @author ottrey@ripe.net
   * @version $Version$
   *
   */
  public class ClassDiagram {
    private Vector    diagram;

    // -----------------oOo-----------------
    //              Constructors
    // -----------------oOo-----------------
    /**
     * Creates a RIPE class diagram.
     *               
     * @author ottrey@ripe.net
     * @version $Version$
     *               
     * @param od Definition to make diagram from.
     */
    public ClassDiagram(ClassDef od, int maxWidth, Hashtable foreigns) {
      diagram = new Vector();
      String line = new String();

      String border = new String();
      border = "+-"; 
      for (int i=0; i < getWidth(); i++) {
        border += ("-");
      }
      border += "-+   "; 
      for (int i=od.getWidth(); i < maxWidth; i++) {
        border += (" ");
      }
      Enumeration e1 = foreigns.keys();
      while (e1.hasMoreElements()) {
        String foreign = (String)e1.nextElement();
        if (e1.hasMoreElements()) {
          border += ("|--");
        }
      }
      border += ("|\n");
      diagram.addElement(border);

      line = ("| " + od.getCode() + ": " + od.getName());
      for (int i=od.getName().length()+4; i < getWidth(); i++) {
        line += (" ");
      }
      line += (" |   ");
      for (int i=od.getWidth(); i < maxWidth; i++) {
        line += (" ");
      }
      Enumeration e3 = foreigns.keys();
      while (e3.hasMoreElements()) {
        String foreign = (String)e3.nextElement();
        line += (foreign + " ");
      }
      line += ("\n");
      diagram.addElement(line);

      diagram.addElement(border);

      AttributeDef ad;
      Enumeration e = od.getAttributes().elements();
      while (e.hasMoreElements()) {
        ad = (AttributeDef)e.nextElement();
        String name = ad.getName();
        String keytype = ad.getKeytype3();
        line = ("| " + ad.getCode() + ": " + name + " ");
        for (int i=name.length(); i < width; i++) {
          line += (" ");
        }
        line += (keytype + " |");

        // The horizontal line.
        // Is a foreign key.
        boolean f = (ad.getForeign().length() > 1);
        // Is a referenced key.
        boolean p = (foreigns.contains(ad.getCode()));
        if (f) {
          line += ("->-");
        }
        else if (p) {
          line += ("-|-");
        } else {
          line += ("   ");
        }
        for (int i=od.getWidth(); i < maxWidth; i++) {
          if (f || p) {
            line += ("-");
          }
          else {
            line += (" ");
          }
        }

        // Add the xrefs.
        Enumeration e2 = foreigns.keys();
        while (e2.hasMoreElements()) {
          String foreign = (String)e2.nextElement();
          String code = ad.getCode();
          if (foreign.equals(code)) {
            line += ("X");
          }
          else if (foreigns.get(foreign).equals(code)) {
            line += ("o");
          }
          else {
            line += ("|");
          }
          if (e2.hasMoreElements()) {
            line += ("--");
          }
        }
        line += ("\n");
        diagram.addElement(line);
      }

      diagram.addElement(border);
    } // ClassDiagram()

    public String toString() {
      String result = new String();

      Enumeration e = diagram.elements();
      while (e.hasMoreElements()) {
        String line = (String)e.nextElement();
        result += line;
      }
      
      return result;
    } // toString()

  } // ClassDiagram

} // ClassDef
