/*

Copyright: All contributers to the Umple Project

This file is made available subject to the open source license found at:
http://umple.org/license

This file generates .gv files for use by Graphviz, representing the class diagram  in the Umple model. To generate for this format, use 
  generate GvClassDiagram;
in your umple file, or the command line option
  -g GvClassDiagram

DEBUG -- THIS IS A WIP. Details need to be modified to reflect it's intended goal


See also GvStateDiagram
 */

namespace cruise.umple.compiler;

class GvInstanceDiagramGenerator
{
  depend java.io.*;
  depend java.util.*;
  depend cruise.umple.util.*;
  depend cruise.umple.compiler.exceptions.*;
  isA CodeGeneratorWithSubptions;

    // Contains various aspects from an Umple file (.ump), such as classes, attributes, associations and methods.  Generated output is based
  // off of what's contained in here.
  UmpleModel model = null;
  String output = "";
  String generatorType = "in";

  boolean testBound = false;


  // Basic templates for GV elements to output
  
  // Template for what will appear at the start of each graph file
  graphStart(umpleVersion) <<!// Code generated by Umple <<=<<=umpleVersion>>>>

digraph "<<=filename>>" {
  rankdir="BT"
  node [ratio="auto" shape=record margin=0; href="javascript:Action.selectClass(&quot;\N&quot;);"];
!>>
  emit graphStart(String filename)(graphStart(UmpleModel.VERSION_NUMBER));


  // Returns what is being generated - called SuperGvGenerator
  // protected String generatorType()
  // {
  //   return "in";
  // }

  // Create the association between a class and its parent 
  // - called VisitClass in SuperGvGenerator
  // Note: I'm assuming this means umple compiles it after defining the one sided association on both ends??
  protected void parentClassAssoc(StringBuilder code, String className, String parentName)
  {
    code.append("  \""+className+"\" -> \""+parentName+"\""
        +" [arrowhead=\"empty\"; samehead=\"gen\"];\n\n");
  }

  // Creat an instance for a class
  // GV HTNL like file.. final details of algo
  protected void instanceCreation(UmpleInstance uInstance, StringBuilder code)
  {
    UmpleClass uClass = uInstance.getUmpleClass();
    String instanceName = uClass.getName() + "1";

    String instanceColor = uClass.getDisplayColor();
    if (!instanceColor.equals("")) instanceColor=" style=filled, fillcolor="+instanceColor+" ";
    code.append("\n  // Class: "+instanceName+"\n");

    code.append("  \""+instanceName+"\" ["+instanceColor+"shape=plaintext margin=0 label=");

    code.append("<<table border=\"1\" cellspacing=\"0\" cellborder=\"0\" cellpadding=\"10\">"
		   			    +"<tr><td>    <u>"+instanceName+"</u>    </td></tr>");

    code.append("</table>>"); 
    code.append("];\n");

  }

  // Create each class
  // Note: modify this.. says the max amount of classes is 200 maybe change to like 3 for now then add more 
  //        add random string and change the format of the box created by graphviz 
  //        would need to code append like it does bellow for class colour (??)
  protected void classCreation(UmpleClass uClass, StringBuilder code)
  {
    boolean manyClass = getModel().getUmpleClasses().size() >= 200 ? true : false;

    String className = uClass.getName();
    
    // End 0 closest to uClass 
    // End 1 furthest from uClass 

    String bound0 = uClass.getAssociation(1).getEnd(0).getMultiplicity().getRange();
    String bound1 = uClass.getAssociation(1).getEnd(1).getMultiplicity().getRange();
    // Setting the showmethods suboption to test removal of method creation in instance diagram
    //this.setSuboption("showmethods");
    // formatting class name in object diagrams
    String objectName = "Class: " + className + " bound0: " + bound0 + " bound1: " + bound1;

    String classColor = uClass.getDisplayColor();
    if (!classColor.equals("")) classColor=" style=filled, fillcolor="+classColor+" ";
    code.append("\n  // Class: "+objectName+"\n");
    
    if (manyClass) {
      code.append("  \""+className+"\" ["+classColor+"shape=record, label=\"{"+className);

      if(uClass.getIsAbstract()) {  // add abstract tag
        code.append("\n&laquo;abstract&raquo;");
      }
    } 
    else {
      code.append("  \""+objectName+"\" ["+classColor+"shape=plaintext margin=0 label=");
	    
      // Different cellpadding for if the class has nothing but the
      // name because of a glitch in graphviz's graphics
      // if ((uClass.getAttributes().size() == 0 || hasSuboption("hideattributes")) && 
      //     (uClass.getMethods().size() == 0 || !hasSuboption("showmethods")) 
      //     && !uClass.getIsAbstract()) {
	    // code.append("<<table border=\"1\" cellspacing=\"0\" cellborder=\"0\" cellpadding=\"10\">"
		  //  			    +"<tr><td>    <u>"+objectName+"</u>    </td></tr>");
      // }
      // else {
        code.append("<<table border=\"1\" cellspacing=\"0\" cellborder=\"0\" cellpadding=\"2\">"
                    +"<tr><td cellpadding=\"4\">    <u>"+objectName+"</u>    </td></tr>");
		  //}

      if(uClass.getIsAbstract()) {  // add abstract tag
        code.append("<tr><td cellpadding=\"4\">&laquo;abstract&raquo;"+"</td></tr>");
      }
    }
    
    attributeCreation(uClass, code, manyClass);

    // Terminate outputting the class attributes
    if(manyClass) code.append("}\"");
   	else code.append("</table>>"); 
    
    
    // Output the tooltip : Class name followed by any comment
    code.append(",\n   tooltip=\"class "+objectName+"\n");
    code.append(appendTooltipComment(uClass.getComments(), false));
    code.append("\"");

    // Terminate the class as a whole
    code.append("];\n");
	}

  // Create each attribute for each class - called by classCreation
  // Note: don't think attributes are relevant in object diagrams REVIEW this
  private void attributeCreation(UmpleClass uClass, StringBuilder code, boolean manyClass)
  {

    Random rand = new Random();
    int randIndex;

    // Set up random value for Integers type attributes
    int randomIntegerAttribute = rand.nextInt(10);

    // Set up random value for Boolean type attributes
    boolean randomBooleanAttribute = rand.nextBoolean();
    
    // Set up random value for Double type attributes
    double [] randomDoubles = new double[]{1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10};
    randIndex = rand.nextInt(10);
    double randomDoubleAttribute = randomDoubles[randIndex];

    // Set up random value for String type attributes
    String[] randomStrings = {"Lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit", "Duis", "porttitor", "quam", "sed", "felis", "auctor", "facilisis", "Nam", "semper", "dolor", "eget", "felis"};
    randIndex = rand.nextInt(20);
    String randomStringAttribute = randomStrings[randIndex];

    // Iterate through attributes of the class]
    boolean isFirst = true;

    //if (!hasSuboption("hideattributes")) { 
      for (Attribute uAttribute : uClass.getAttributes()) {
        if (!uAttribute.isConstant()) 
        {
          if (manyClass) {
            if (isFirst) code.append("|"); // attribute block starter
            else code.append("\\\n"); // separator between attributes
          }
          else {
            if (isFirst) code.append("<hr/>"); // attribute block starter
          }
          String isList = uAttribute.getIsList() ? "[]" : ""; //Hello
          String type = uAttribute.getType().replace("<","&lt;").replace(">","&gt;");
          
          if (manyClass) {
            code.append(""+uAttribute.getName()+"\\ :\\ "+type+isList+"\\l");
          }
          else {
            StringBuilder attrTooltip = new StringBuilder();
            attrTooltip.append(type+isList+" "+uAttribute.getName()+"&#13;");
            attrTooltip.append(appendTooltipComment(uAttribute.getComments(), true)); 
            if ((type.equals("Integer") || type.equals("int") || type.equals("Int")) && !uAttribute.getIsList()){
              code.append("<tr><td align=\"left\" href=\"\" title=\""+attrTooltip+"\">"
                  +"  "+uAttribute.getName() + " = " + randomIntegerAttribute + "  </td></tr>");
            } else if ((type.equals("Double") || type.equals("double")) && !uAttribute.getIsList()){
              code.append("<tr><td align=\"left\" href=\"\" title=\""+attrTooltip+"\">"
                  +"  "+uAttribute.getName() + " = " + randomDoubleAttribute + "  </td></tr>");
            } else if ((type.equals("Boolean") || type.equals("boolean")) && !uAttribute.getIsList()) {
              code.append("<tr><td align=\"left\" href=\"\" title=\""+attrTooltip+"\">"
                  +"  "+uAttribute.getName() + " = " + randomBooleanAttribute + "  </td></tr>");
            } else if ((type.equals("String")) && !uAttribute.getIsList()) {
              code.append("<tr><td align=\"left\" href=\"\" title=\""+attrTooltip+"\">"
                  +"  "+uAttribute.getName() + " = " + randomStringAttribute + "  </td></tr>");
            } else {
              code.append("<tr><td align=\"left\" href=\"\" title=\""+attrTooltip+"\">"
                  +"  "+uAttribute.getName()+" : "
                  +type+isList+"  </td></tr>");
            }      
            
          }
          
          isFirst = false;
        }
      }
   // }
  }

  // Create the associations in the class diagram
  // - called VisitClass in SuperGvGenerator
  // Note: will probably need to change how visit class works in SuperGvGenerator but not sure... figure that out
  protected void associationCreation(UmpleClass uClass, Association uAssoc, StringBuilder associations, String className)
  {
    AssociationEnd leftEnd = uAssoc.getEnd(0);
    AssociationEnd rightEnd = uAssoc.getEnd(1);
    String modifierOne = leftEnd.getModifier();
    String modifierTwo = rightEnd.getModifier();
    String visualLeftEnd, visualRightEnd, toolTip, label;

    String leftClass= leftEnd.getClassName();
    // Only output them when visiting one class
    if(leftClass.equals(className)) {
      String[] arrows = retArrows(uAssoc);
      String leftMult= leftEnd.toSimpleString();      
      String rightMult= rightEnd.toSimpleString();
      String rightClass= rightEnd.getClassName();
        
      if ("symmetricreflexive".equals(modifierOne) && "symmetricreflexive".equals(modifierTwo)) 
      {
        if ("undefined".equals(rightEnd.getDisplayRoleName()))
          label = leftMult+" "+leftEnd.getDisplayRoleName();
        else label = rightMult+" "+rightEnd.getDisplayRoleName();
          
        toolTip = leftClass + " " + label + " " + rightClass + "\n\nThere is a set of "+leftClass+" that have the "+label+" relationship with each other";
          
        associations.append(labelAssociation(leftClass,
          rightClass, arrows[0], label, toolTip));
      }
      else 
      {
        String nlDesc = "\n\n"; // initialize Natural Language String

        // Test the left multiplicity of the association
        String[] array = leftMult.split("\\.\\."); // Split the multiplicity of the left side 
        if (array.length == 1) {
          try {
            int leftMultInt = Integer.parseInt(leftMult);          
            if (leftMultInt > 0) {
              if ( leftMultInt == 1) {
                nlDesc += startWithVowel(rightClass) + getClassWithRoleName(rightEnd, "1") + " has " + startWithVowel(leftClass) + getClassWithRoleName(leftEnd, "1") + ". It must always have exactly, one, and can never have none. And there is an upper bound of at most one " + getClassWithRoleName(leftEnd, "1") + ".\n";
              }
              else {
                nlDesc += startWithVowel(rightClass) + getClassWithRoleName(rightEnd, "1") + " has " + leftMultInt + " " + getClassWithRoleName(leftEnd, leftMult) + "\n" + 
                " When " + startWithVowel(rightClass).toLowerCase() + getClassWithRoleName(rightEnd, "1") + " is created, its " + leftMultInt + " " + getClassWithRoleName(leftEnd, leftMult) + " must be assigned.\n";
              }
            }
          } catch (NumberFormatException ex) {
            if (leftMult.equals("*")) {
              // Text view when the multiplicity is *
              nlDesc += startWithVowel(rightClass) + getClassWithRoleName(rightEnd, "1") + " has some " + getClassWithRoleName(leftEnd, "*") + ". It can have none, and there is no upper bound defined.\n";
            } 
          }
        } else {
          String leftMult1 = array[0];
          String leftMult2 = array[1];
          try {
            int leftMultInt1 = Integer.parseInt(leftMult1);
              if ( leftMultInt1 == 0) {
                nlDesc += startWithVowel(rightClass) + getClassWithRoleName(rightEnd, "1") + " may have " + startWithVowel(leftClass).toLowerCase() + getClassWithRoleName(leftEnd, "0") + ". ";
              } else if (leftMultInt1 == 1) {
                nlDesc += startWithVowel(rightClass) + getClassWithRoleName(rightEnd, "1") + " has at least " + leftMultInt1 + " " + getClassWithRoleName(leftEnd, leftMult1) + " and can never have none. ";
              }
              else {
                nlDesc += startWithVowel(rightClass) + getClassWithRoleName(rightEnd, "1") + " has at least" + leftMultInt1 + " " + getClassWithRoleName(leftEnd, leftMult1) + " and can never have none. ";
              }

              int leftMultInt2 = Integer.parseInt(leftMult2);
              if (leftMultInt2 == 1) {
                nlDesc += "It can have none or just 1. \n";
              }
              else {
                nlDesc += "And there is an upper bound of at most " + leftMultInt2 + " " + getClassWithRoleName(leftEnd, leftMult2) + ".\n";
              }
          }
          catch (NumberFormatException ex) {
            if (leftMult2.equals("*")) {
              nlDesc += "And there is no upper bound defined.\n";
            }
          }
        }
        
        nlDesc +="\n"; // Newline to separate the two ends
        
        // Test the right multiplicity of the association
        String[] arrayR = rightMult.split("\\.\\."); // Split the multiplicity of the right side
        // Test the left multiplicity of the association
        if (arrayR.length == 1) {
          try {
            int rightMultInt = Integer.parseInt(rightMult);          
            if (rightMultInt > 0) {
              if ( rightMultInt == 1) {
                nlDesc += startWithVowel(leftClass) + getClassWithRoleName(leftEnd, "1") + " has " + startWithVowel(rightClass) + getClassWithRoleName(rightEnd, "1") + ". It must always have exactly, one, and can never have none. And there is an upper bound of at most one " + getClassWithRoleName(rightEnd, "1") + ".\n";
              }
              else {
                nlDesc += startWithVowel(leftClass) + getClassWithRoleName(leftEnd, "1") + " has " + rightMultInt + " " + getClassWithRoleName(rightEnd, rightMult) + ". \n" + 
                "When " + startWithVowel(leftClass).toLowerCase() + getClassWithRoleName(leftEnd, "1") + " is created, its " + rightMultInt + " " + getClassWithRoleName(rightEnd, rightMult) + " must be assigned.\n";
              }
            }
          } catch (NumberFormatException ex) {
            if (rightMult.equals("*")) {
              // Text view when the multiplicity is *
              nlDesc += startWithVowel(leftClass) + getClassWithRoleName(leftEnd, "1") + " has some " + getClassWithRoleName(rightEnd, "*") + ". It can have none, and there is no upper bound defined.\n";
            } 
          }
        } 
        else {
          String rightMult1 = arrayR[0];
          String rightMult2 = arrayR[1];
          try {
            int rightMultInt1 = Integer.parseInt(rightMult1);            
              if ( rightMultInt1 == 0) {
                nlDesc += startWithVowel(leftClass) + getClassWithRoleName(leftEnd, "1") + " may have " + startWithVowel(rightClass).toLowerCase() + getClassWithRoleName(rightEnd, "0") + ". ";
              }
              else if (rightMultInt1 == 1) {
                nlDesc += startWithVowel(leftClass) + getClassWithRoleName(leftEnd, "1") + " has at least " + rightMultInt1 + " " + getClassWithRoleName(rightEnd, rightMult1) + " and can never have none. ";
              }
              else {
                nlDesc += startWithVowel(leftClass) + getClassWithRoleName(leftEnd, "1") + " has at least" + rightMultInt1 + " " + getClassWithRoleName(rightEnd, rightMult1) + " and can never have none. ";
              }
              
              int rightMultInt2 = Integer.parseInt(rightMult2);
              if (rightMultInt2 == 1) {
                nlDesc += "It can have none or just 1. \n";
              }
              else {
                nlDesc += "And there is an upper bound of at most " + rightMultInt2 + " " + getClassWithRoleName(rightEnd, rightMult2) + ".\n";
              }
            
          } 
          catch (NumberFormatException ex) {
            if (rightMult2.equals("*")) {
              nlDesc += "And there is no upper bound defined.\n";
            }
          }
        }
        
        nlDesc +="\n"; // Newline to separate the following information

        // Test the multiplicity of the association
        if (leftMult.equals("1") && rightMult.equals("1")) {
          nlDesc += "There must always be an equal number of " + rightClass + " and " + leftClass + ", connected as Pairs.\n" + 
          "When " + startWithVowel(leftClass).toLowerCase() + leftClass + " is created, its " + rightClass + " must be created at the same time.\n" +
          "When " + startWithVowel(leftClass).toLowerCase() + leftClass + " is deleted, its " + rightClass + " is deleted at the same time.\n";
        }
        else if (arrows[1].equals("<@>-")) {
          // Part-whole
          nlDesc += "This is a composition. "+startWithVowel(rightClass)+ rightClass + " is part of "+ startWithVowel(leftClass).toLowerCase() + leftClass + ". When any "+ leftClass +" is destroyed its parts are destroyed too.\n";
        }
        else if (arrows[1].equals("-<@>")) {
          // Whole-Part
          nlDesc += "This is a composition. "+startWithVowel(leftClass)+ leftClass + " is part of "+ startWithVowel(rightClass).toLowerCase() + rightClass + ". When any "+ rightClass +" is destroyed its parts are destroyed too.\n";
        }
         
        else if (leftMult.equals("1") && rightMult.equals("*")) {
          nlDesc += "When " + startWithVowel(rightClass).toLowerCase() + rightClass + " is created, its " + leftClass + " must be assigned.\n" +
          "When " + startWithVowel(rightClass).toLowerCase() + rightClass + " is deleted, its " + leftClass + " is not deleted and its list of " + rightClass + "s is decreased.\n" +
          "When " + startWithVowel(leftClass).toLowerCase() + leftClass + " is deleted, its " + rightClass + "s are not deleted. They are assigned to another " + leftClass + ".\n";
        }
        else if (leftMult.equals("*") && rightMult.equals("1")) {
          nlDesc += "When " + startWithVowel(leftClass).toLowerCase() + leftClass + " is created, its " + rightClass + " must be assigned.\n" +
          "When " + startWithVowel(leftClass).toLowerCase() + leftClass + " is deleted, its " + rightClass + " is not deleted and its list of " + leftClass + "s is decreased.\n" +
          "When " + startWithVowel(rightClass).toLowerCase() + rightClass + " is deleted, its " + leftClass + "s are not deleted. They are assigned to another " + rightClass + ".\n";
        }
                
        visualLeftEnd = leftMult+" "+leftEnd.getDisplayRoleName();
        visualRightEnd = rightMult+" "+rightEnd.getDisplayRoleName();
        toolTip = leftClass + " " + visualLeftEnd +
            " " + arrows[1] + " " + visualRightEnd + " " + rightClass + nlDesc;
          
        associations.append(twoLabelAssociation(leftClass,
          rightClass, arrows[0], visualRightEnd, visualLeftEnd, toolTip));
      }
    }
  }

  // Check whether the classname starts with a vowel or not to see if we use A or An
  private String startWithVowel(String str) {
    char filter = str.charAt(0);
    if (filter == 'A' || filter == 'E' || filter == 'I' || filter == 'U' || filter == 'O' || filter == 'Y') {
      return "An ";
    }
    else {
      return "A ";
    }
  }

  private String getClassWithRoleName(AssociationEnd sideEnd1, String quantity) {
    String str = "";
    String roleName = sideEnd1.getRoleName().toLowerCase();
    String className = sideEnd1.getClassName().toLowerCase();
    String classNames = className + "s";
    try {
      int quantityInt = Integer.parseInt(quantity);
      if ( !(roleName.equals(classNames) || roleName.equals(className) ||
            className.equals(roleName.substring(0, roleName.length()-1))) ) {
        if (quantityInt!=0 && quantityInt!=1) {
          str += sideEnd1.getClassName() + "s called " + sideEnd1.getRoleName();
        } else if (quantityInt==0 || quantityInt==1) {
          str += sideEnd1.getClassName() + " called " + sideEnd1.getRoleName();
        }
      } else {
        if (quantityInt==0 || quantityInt==1) {
          str += sideEnd1.getClassName();
        } else {
          str += sideEnd1.getClassName() + "s";
        }
      }
    } catch (NumberFormatException ex) {
      if (quantity.equals("*")) {
        if ( !(roleName.equals(classNames) || roleName.equals(className) ||
            className.equals(roleName.substring(0, roleName.length()-1))) ) {
          str += sideEnd1.getClassName() + "s called " + sideEnd1.getRoleName();
        } else {
          str += sideEnd1.getClassName() + "s";
        }
      }
    }
    return str; 
  }

  // Used to indent code
  private void appendSpaces(StringBuilder code, int numSpaces) {
    for(int i=0; i<numSpaces; i++) {
      code.append(" ");
    }
  }

  public void generate()
  {
    StringBuilder code = new StringBuilder();
    StringBuilder associations = new StringBuilder();

    String className;

    // Output basic gv file header
    _graphStart(0,code,model.getUmpleFile().getSimpleFileName());

    // Missing Array List partiallyCreatedObjectsQueue
    // Used for objects that don't have all their associations yet
    ArrayList<UmpleInstance> partiallyCreatedObjectsQueue;
    
    // Set of classes we are visiting
    // We always visit from the top of the hierarchy first
    // This set ensures we track what we have visited
    HashSet visitedClasses = new HashSet();

    // Set of instances we are visiting
    // This is to ensure we don't revisit the same instance more than once
    HashSet visitedInstances = new HashSet();



    // Set of instances that have been instantiated
    // from each class
    // Key value pair being UmpleClass, ArrayList<UmpleInstance>
    HashMap instanceMap = new HashMap();

    // Iterate through each class. 
    // Add every class to instanceMap mapping with an initial
      // empty list of UmpleInstances
    for (UmpleClass uClass : model.getUmpleClasses())
    {
      visitClass(uClass, visitedClasses, code, associations);
      if(!instanceMap.containsKey(uClass)){
        ArrayList<UmpleInstance> umpleInstances = new ArrayList<UmpleInstance>();
        instanceMap.put(uClass, umpleInstances); // adding the ArrayList of UmpleInstances associated to a uClass in the instanceMap

      }
    } // End iteration through classes

    // get the list of associations
    // get the ends as a list of AssociationEnds
    // get multiplicities  +
    // if it's a 1 -- multiplicity mark that as the first class to instanciate with new UmpleInstance (I think)
      // then add to the arraylist of instances for that class 
      // in instanceMap
    HashSet visitedAssociations; // set of association lists for each class in the model

    for (UmpleClass uClass : model.getUmpleClasses())
    {
      if(instanceMap.containsKey(uClass)){
        visitedAssociations = new HashSet(); //hash set of visited associations for the class
        Association[] uAssociations = uClass.getAssociations();
        // get the association ends List
        if(!visitedAssociations.contains(uAssociations)){
          for (Association uAssoc : uAssociations)
          {
            List<AssociationEnd> uAssocEnds = uAssoc.getEnds();
            for (AssociationEnd uAssocEnd : uAssocEnds)
            {
              Multiplicity uMultip = uAssocEnd.getMultiplicity();
              if (uMultip.getLowerBound() == 1) {
                // go to randomization algo

                // Need to make sure to check if the class is in the ArrayList tracker and remove it once it's been created

              } else {
                // Add class to ArrayList keeping track of classes checked and not ready for instanciation
              }
            }
          }
        }
      }
      // skip checking associations if it's not in the map
        // smg went wrong earlier
    }

    terminateCode(code, associations);
    //writeModel();

  }

  // Outputs the instance in the GV file
      // sortedInstances Set used to keep track of when a singleton/priority object has been identitified
  protected void visitInstance(UmpleInstance uInstance, Set visitedInstances, Set sortedInstances, StringBuilder code, StringBuilder links)
  {

    // Ensure we only visit the instance once
    if(visitedInstances.contains(uInstance)) {
      return;
    }

    // Ensure we print out/handle singletons first
    UmpleClass relatedClass = uInstance.getUmpleClass();
    if (relatedClass != null) {
      if (relatedClass.isSingleton()) {
        // add to a sorted priority list of instances
          // this list keeps track of how many singletons have been found and if an instance/instances need(s) to be printed first
          // TO-DO: create sorted array that tracks these semi visited instances
            // not sure if this is what partiallyCreatedObjectsQueue is for tho.. 
      } 
    }
    // add instance to visitedInstances after generating it in the diagram
    visitedInstances.add(uInstance);
    
    // TO-DO: need to implement prioritization of instanciation:
      // Add weight attribute to UmpleInstance class that will track the associated UmpleClass' importance 
      // More important instances are attributed a smaller weight
          // If an instance belongs to a singleton class set its weight to 0
          // If an instnace belongs to a class with a 1 -- association set its weight to 1 
          // Other wise set it's weight to 2 (or set it to 2 by default)
        // Once all the instances have been looped through and assigned a weight, we sort the HashSet based off the weight
        // The loop through the sorted set and call the instanceCreation method to print the method to the GV file
    
    // instanceCreation method called at this point to add the instance to the graphviz file

    // Once all the instances have been created we add the links
    Set processedLinks = new HashSet();
    for(UmpleLink uLink : uInstance.getUmpleLinks()) {
      processedLinks.add(uLink);

      // call to linkCreation to display the related links in the graphviz file
          // TO-DO: create linkCreation method
    }
  

  }

    // Actually output the class contents  
  protected void visitClass(UmpleClass uClass, Set visitedClasses, StringBuilder code, StringBuilder associations)
  {
    
    // Ensure we only visit once
    if(visitedClasses.contains(uClass)) {
      return;
    }
    
    // Ensure  output parents in the hierarchy first
    UmpleClass parent = uClass.getExtendsClass();
    if(parent != null) {
      visitClass(parent, visitedClasses, code, associations);
    }
    visitedClasses.add(uClass);
          
    String className = uClass.getName();
    classCreation(uClass, code);

    // Add any generalization now to parents
    // We know that parents have been output first
    UmpleClass parentClass = uClass.getExtendsClass();
    if(parentClass!= null) {
      parentClassAssoc(code, className, parentClass.getName());
    }

    // Add any associations so they are output at the end
    Set processedAssociations = new HashSet(); // needed to prevent reflexive double count
    for(Association uAssoc : uClass.getAssociations()) {
    
      // Prevent double-counting of reflexive associations
      if(processedAssociations.contains(uAssoc)) continue;
      processedAssociations.add(uAssoc);
      
      associationCreation(uClass, uAssoc, associations, className);
    }
  }

  /* ----- Graphviz helper methods ----- */

    // Output the graphviz file to a file with the .gv suffix
  protected void writeModel()
  {
    try
    {
      String path = StringFormatter.addPathOrAbsolute(
        getModel().getUmpleFile().getPath(), 
        getOutput());
      File file = new File(path);
      file.mkdirs();
      String modelFilename = path + File.separator + model.getUmpleFile().getSimpleFileName() + ".gv";
      // String modelFilename = path + File.separator + model.getUmpleFile().getSimpleFileName()
      //     + getGeneratorType() + ".gv";
      BufferedWriter bw = new BufferedWriter(new FileWriter(modelFilename));
      bw.write(model.getCode());
      bw.flush();
      bw.close();
    }
    catch (Exception e)
    {
      throw new UmpleCompilerException("There was a problem with generating GraphViz Diagram code." + e, e);
    }
  } 

  protected void terminateCode(StringBuilder code, StringBuilder associations) {

    code.append("\n  // All associations\n");
    code.append(associations);

    code.append("}\n");

    model.setCode(code.toString());
    writeModel();
  } 
  
  // Add the comment to a tooltip
  public static String appendTooltipComment(List<Comment> comments, boolean title)
  {
    StringBuilder tooltip = new StringBuilder();;
    for (Comment uComment : comments) {
      String text;
      if (!title) {
        text = uComment.getText().replace("\"","\\\"");
      } else {
        text = uComment.getText().replace("\"","'");
      }
      text = text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;"); // Issue1584 and 1587 "Certain kinds of comments (<,>,&) result in failure to generate graphviz"
      if(text.startsWith(" *")) {
        tooltip.append(text.substring(2)+"&#13;");
      }
      else
      {
        tooltip.append(text+"&#13;");
      }
    }
    return tooltip.toString();
  }
  
  public static String[] retArrows(Association uAssoc)
  {
    String[] arrows = new String[2];

    if (uAssoc.getIsRightComposition()) {
      arrows[0] = "dir=\"forward\", arrowhead=\"diamond\"";
      arrows[1] = "-<@>";
    }
    else if (uAssoc.getIsLeftComposition()) {
      arrows[0] = "dir=\"back\", arrowtail=\"diamond\"";
      arrows[1] = "<@>-";
    }
    else {
      if(uAssoc.getIsLeftNavigable()) {
        if(uAssoc.getIsRightNavigable()) {
          arrows[0] = "dir=\"none\"";
          arrows[1] = "--";
        }
        else {
          arrows[0] = "dir=\"back\", arrowtail=\"open\"";
          arrows[1] = "<-";
        }
      }
      else if(uAssoc.getIsRightNavigable()) {
        arrows[0] = "dir=\"forward\", arrowhead=\"open\"";
        arrows[1] = "->";
      }
      else {
        arrows[0] = "dir=\"none\"";
        arrows[1] = "--";
      }
    }

    return arrows;
  }

  public static String labelAssociation(String left, String right, String arrow, String label, String tooltip)
  {
    String association;

    association = "  \""+left+"\" -> \""+right+"\" ["+arrow+
        ", label=\""+label+"\""+", tooltip=\""+tooltip+"\" labeltooltip=\""+tooltip+"\" headtooltip=\"" + tooltip + "\" tailtooltip=\"" + tooltip + "\" labelurl=\"javascript:void()\" tailurl=\"javascript:void()\" headurl=\"javascript:void()\" ];\n";

    return association;
  }

  public static String twoLabelAssociation(String left, String right, String arrow, String hLabel, String tLabel, String tooltip)
  {
    String association;

    association = "  \""+left+"\" -> \""+right+"\" ["+arrow+
        ", taillabel=\""+tLabel+"\""+", headlabel=\""+hLabel+"\""+
        ", tooltip=\""+tooltip+"\" headtooltip=\"" + tooltip + "\" tailtooltip=\"" + tooltip + "\" tailurl=\"javascript:void()\" headurl=\"javascript:void()\" ];\n";

    return association;
  }

  /* ----- Graphviz helper methods ----- */

}

class UmpleInstance
{
  depend java.io.*;
  depend java.util.*;
  depend cruise.umple.util.*;
  depend cruise.umple.compiler.exceptions.*;
  * -> 1 UmpleClass;
  1 -- * UmpleLink fromInstance;
  String [] attributeStings;
}

class UmpleLink
{
  depend java.io.*;
  depend java.util.*; 
  depend cruise.umple.util.*;
  depend cruise.umple.compiler.exceptions.*;
  * -> 1 Association;
  * -- 1 UmpleInstance toInstance;
}