/*
 *                    BioJava development code
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  If you do not have a copy,
 * see:
 *
 *      http://www.gnu.org/copyleft/lesser.html
 *
 * Copyright for this code is held jointly by the individual
 * authors.  These should be listed in @author doc comments.
 *
 * For more information on the BioJava project and its aims,
 * or to join the biojava-l mailing list, visit the home page
 * at:
 *
 *      http://www.biojava.org/
 *
 */
package org.biojava.bio.seq.io;

import java.io.*;
import java.util.*;

import org.biojava.bio.*;
import org.biojava.bio.seq.*;
import org.biojava.bio.seq.impl.*;
import org.biojava.bio.seq.io.*;
import org.biojava.bio.seq.genomic.*;
import org.biojava.bio.symbol.*;
import org.biojava.utils.*;

/**
 * Translate Sequence object into sgv file
 *  @author Hanning Ni     Doubletwist Inc, 2001-7-22
 */
public class SVGGenerator
{
    /** source code version */
    public static final String VER_ID               = "$Header: $";
    // this part should not be hard coded. will change later ...
    public static final String CONTAMINATION = "contamination" ;
    public static final String REPEAT= "repeat" ;

    public static final int LABEL_LENGTH = 50 ;
    public static final int WINDOW_WIDTH = 800 + LABEL_LENGTH;
    public static final int WINDOW_HEIGHT = 600 ;

    /** The XML writer    */
    protected PrintWriter mOut;

    //define some global variable, some of them will be removed in the future

    /** the height of each row   */
    protected int row_height  = 0 ;
    /** ratio of sequence length to WINDOW_WIDTH  */
    protected float ratio = 0.0f ;

    /** contains label of sequence_map   -->  the maximum rows this label will occupy  */
    Hashtable table_forward   ;
    Hashtable table_complement  ;

    /** contain  label of sequence_map  --> the i-th of row in view  */
    Hashtable forward_row_loc  ;
    Hashtable complement_row_loc  ;

    /** contains all the components which will be draw in the final view  */
    Vector comp2svg  ;

    private int mId  ;
    private int total_length ;
    private int total_rows  ;

    /**
     * Constructor.
     * @param out Writer for the XML output.
     */
    public SVGGenerator(Writer out)
    {
        mOut = new PrintWriter(out);
        mId = 0 ;
        total_length = 0 ;
        total_rows = 0 ;
        table_forward = new Hashtable() ;
        table_complement  = new Hashtable();
        forward_row_loc = new Hashtable() ;
        complement_row_loc = new Hashtable();
        comp2svg = new Vector() ;
    }



    public void close()
    {
       mOut.close() ;
    }
    /**
     * Write XML version information and DOCTYPE tag for svg
     */
    private void
    writeDocumentType()
    {
        mOut.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>");
        mOut.println("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20000303 Stylable//EN\" \"http://www.w3.org/TR/2000/03/WD-SVG-20000303/DTD/svg-20000303-stylable.dtd\">");
    }

    /**
     * Write object.
     *
     */
    public void
    write(Sequence seq) throws IOException
    {
        try
        {
            init( seq ) ;
            writeDocumentType() ;
            writeHeader( );
            writeBody(  );
            writeFooter();
        }
        catch(Exception e)
        {
            throw new IOException(e.getMessage() );
        }finally{
            clear() ;
        }
    }

    private void init(Sequence sequence) throws Exception
    {
        //get the total dna length
        total_length = getLength( sequence ) ;
        //get the total rows which will show in the view
        total_rows = getRows( sequence ) ;
        row_height = WINDOW_HEIGHT / total_rows ;
        ratio = WINDOW_WIDTH / (float) total_length ;
   }

    private void clear()
    {
        mId = 0 ;
        total_length = 0 ;
        total_rows = 0 ;
        row_height = 0 ;
        ratio = 0.0f ;
        table_forward.clear();
        table_complement.clear();
        forward_row_loc.clear();
        complement_row_loc.clear();
        comp2svg.clear() ;
    }

    /** dump the svg header which defines how to draw each type of gene component  */
    private void
    writeHeader( ) throws Exception
    {

        //currently we use one basepair <-> one pixel
        // LABEL_LENGTH = (total_length / 5 > 50 ? total_length /5 : 50 ) ;
        int x =  WINDOW_WIDTH ; // total_length + LABEL_LENGTH ;
        //total_row does not include dna row yet
        int y = WINDOW_HEIGHT ; // ( total_rows + 1 ) * ROW_HEIGHT ;
        String head ="<svg onload=\"init(evt)\"  xml:space=\"preserve\" width=\"100%\" heigth=\"100%\" viewBox=\"0 0 " + x + " "  + y + "\">" ;
        mOut.println( head );
        //javascript here
        mOut.println("<script language=\"Javascript\">") ;
                      mOut.println("<![CDATA[") ;
        mOut.println("  var svgdoc; ") ;
                     mOut.println(" function init(event) {   ") ;
                     mOut.println("   svgdoc = event.getTarget().getOwnerDocument();  " ) ;
                    mOut.println("  } " ) ;


        mOut.println(" function showproperty( evt ) {  " ) ;
	  mOut.println("  var svgstyle;  " ) ;
        mOut.println(" var svgobj ;    " ) ;
        mOut.println(" svgobj = svgdoc.getElementById( evt.getTarget().getId() + \"t\"); " );
	  mOut.println("  svgstyle = svgobj.getStyle();  ") ;
	  mOut.println(" svgstyle.setProperty('visibility', 'visible'); ") ;
	  mOut.println( "}" ) ;
	  mOut.println(" function hideproperty( evt ) {  " ) ;
        mOut.println(" var svgobj;  " ) ;
	  mOut.println(" var svgstyle;   " ) ;
        mOut.println(" svgobj = svgdoc.getElementById(evt.getTarget().getId() + \"t\");  " ) ;
 	  mOut.println(" svgstyle = svgobj.getStyle();   ") ;
	  mOut.println("   svgstyle.setProperty('visibility', 'hidden');  ") ;
	  mOut.println( "}"  ) ;

        mOut.println(" ]]> </script> " ) ;

        mOut.println("<defs>") ;
        mOut.println("<g id=\"genebox\"> ") ;
        mOut.println("<rect x=\"0\" y=\"0\" width=\"10\" height=\"30\" style=\"fill: rgb(240,65,25);") ;
        mOut.println(" fill-opacity: 0.5; \" />") ;
        mOut.println("</g>") ;
        mOut.println("<g id=\"exonbox\">") ;
        mOut.println("<rect x=\"0\" y=\"0\" width=\"10\" height=\"20\" style=\"fill: rgb(240,240,25);") ;
        mOut.println(" fill-opacity: 0.5;\" />") ;
        mOut.println("</g>") ;
        mOut.println("<g id=\"intronbox\">") ;
        mOut.println("<rect x=\"0\" y=\"2.5\" width=\"10\" height=\"10\" style=\"fill: rgb(240,65,250);") ;
        mOut.println(" fill-opacity: 0.5;\" />") ;
        mOut.println("</g>") ;
        mOut.println("<g id=\"region\">") ;
        mOut.println("<rect x=\"0\" y=\"2.5\" width=\"10\" height=\"10\" style=\"fill: rgb(240,265,250);") ;
        mOut.println(" fill-opacity: 0.5;\" />") ;
        mOut.println("</g>") ;
        mOut.println("<g id=\"intronbox\">") ;
        mOut.println("<rect x=\"0\" y=\"2.5\" width=\"10\" height=\"10\" style=\"fill: rgb(240,65,250);") ;
        mOut.println(" fill-opacity: 0.5; \" />") ;
        mOut.println("</g>") ;
        mOut.println("<g id=\"forwardfragment\">") ;
        mOut.println("<rect x=\"0\" y=\"10\" width=\"10\" height=\"10\" style=\"fill: rgb(240,65,250);") ;
        mOut.println(" fill-opacity: 0.5; \" />") ;
        mOut.println(" <rect x=\"7.5\" y=\"10\" width=\"2.5\" height=\"10\"  style=\"fill: rgb(240,240,240); " ) ;
        mOut.println(" fill-opacity: 0.8;\" />") ;

        mOut.println("</g>") ;
        mOut.println("<g id=\"reversefragment\">") ;
        mOut.println("<rect x=\"0\" y=\"10\" width=\"10\" height=\"30\" style=\"fill: rgb(240,65,25);") ;
        mOut.println(" fill-opacity: 0.5; \" />") ;
        mOut.println(" <rect x=\"2.5\" y=\"10\" width=\"2.5\" height=\"10\"  style=\"fill: rgb(240,240,240); " ) ;
        mOut.println(" fill-opacity: 0.8;\" />") ;


        mOut.println("</g>") ;
        mOut.println("<g id=\"unorderedfragment\">") ;
        mOut.println("<rect x=\"0\" y=\"0\" width=\"10\" height=\"30\" style=\"fill: rgb(240,65,250);") ;
        mOut.println(" fill-opacity: 0.5; \" />") ;
        mOut.println("</g>") ;
        mOut.println("</defs>") ;
    }

    /**
     * first write all the labels of sequence_map
     * then write all the gene component
     * pre-condition :   after call write_header, which all ready store all the infomation into Vector
     */
    private void writeBody()
    {
        //write all the labels
        write_label() ;
        //write all the compoments
        for(int i = 0 ; i < comp2svg.size() ; i ++ )
        {
            //can be dna fragment, gene , exon and intron
            Component comp = (Component) comp2svg.elementAt(i) ;
            //add the length of label
            comp.start_x =  Math.round(   comp.start_x * ratio ) + LABEL_LENGTH ;
            //calculate the real start_y value , which need the information about algorihtm type and row
            if( comp.isForward  &&  forward_row_loc.get( comp.row_type ) != null )
               comp.start_y = ( (Integer) forward_row_loc.get( comp.row_type )).intValue() * row_height ;
            else if(( ! comp.isForward ) && complement_row_loc.get( comp.row_type ) != null )
               comp.start_y = ( (Integer) complement_row_loc.get( comp.row_type )).intValue() * row_height ;
            //now , dumping
           // System.out.println("writing : " + comp.start_y + " : " + comp.row_type );
            mOut.println( comp.toString() ) ;
        }
    }
    /**
     *  first calculate all the location of sequence_map in the final view
     *  then dumping
     */
    private void write_label()
    {
        // calculate all the location of sequence_map
        cal_row_loc() ;
        //dumping the labels in the forward strand, include DNA
        Enumeration nums = forward_row_loc.keys() ;
        while( nums.hasMoreElements() )
        {
            String this_label = (String) nums.nextElement()  ;
            int row  = ( (Integer) forward_row_loc.get( this_label )).intValue() ;
            print_text( this_label, 0 , row * row_height ) ;
        }
        //dumping  the labels in the complement strand
        nums = complement_row_loc.keys() ;
        while( nums.hasMoreElements() )
        {
            String this_label = (String) nums.nextElement()  ;
            int row  = ( (Integer) complement_row_loc.get( this_label )).intValue() ;
            print_text( this_label, 0 , row * row_height ) ;
        }
    }

/****
 *  dumping the svg text
 **/


    private void print_text(String label, int x, int y)
    {
               int id = mId ++ ;

                 mOut.println(  "<g font-family=\"Verdana\"> "
                 +  "<text  id=\"" + id + "t" + "\"  x=\"" + x + "\" y=\"" + y + "\" font-size=\"50\" fill = \"rgb(255,164,0)\"  textLength = \"" + LABEL_LENGTH  + "\">"
                 + label
                 +  "</text>"
                 + "</g>" );


    }

/**
 * calculate the real location of each sequence_map
 * must put nr pro into the out most level
 * and it is nice to put comtamination /repeat /genbank near dna sequence
 */
    private void cal_row_loc()
    {
        //write label first (dont care )
        int row = 0 ;
        Object o  ;
        String contamination ="";
        String repeat = "" ;
        Enumeration nums = table_forward.keys() ;
        while( nums.hasMoreElements() )
        {
            String this_label = (String) nums.nextElement()  ;
            if( this_label.indexOf( CONTAMINATION ) != -1 )
            {
                contamination = this_label ;
                continue ;
            }
            if( this_label.indexOf( REPEAT ) != -1  )
            {
                repeat = this_label ;
                continue ;
            }

            o =  table_forward.get( this_label ) ;
            forward_row_loc.put( this_label , new Integer( row ) ) ; //System.out.println(this_label + " : " + row ) ;
            row += ((Integer)o  ).intValue() ;
        }
        //put contamination , repeat   near dna
        o = table_forward.get( contamination ) ;
        if( o != null )
        {
             forward_row_loc.put( contamination , new Integer( row ) ) ;
             row += ((Integer)o).intValue() ;
        }
        o = table_forward.get( repeat ) ;
        if( o != null )
        {
             forward_row_loc.put( repeat , new Integer( row ) ) ; //System.out.println(REPEAT + " : " + row ) ;
             row += ((Integer)o).intValue() ;
        }

        //put dna into forward_row_loc for easy use.
        forward_row_loc.put( "DNA", new Integer( row ) ) ; //System.out.println("DNA" + " : " + row ) ;
        row ++ ;

        //put complement label in the reverse order
        o = table_complement.get( repeat ) ;
        if( o != null )
        {
             complement_row_loc.put( repeat , new Integer( row ) ) ; //System.out.println(REPEAT + " -: " + row ) ;
             row += ((Integer)o).intValue() ;
        }
        o = table_complement.get( contamination ) ;
        if( o != null )
        {
             complement_row_loc.put( contamination , new Integer( row ) ) ;
             row += ((Integer)o).intValue() ;
        }
        nums = table_complement.keys() ;
        while( nums.hasMoreElements() )
        {
            String this_label = (String) nums.nextElement()  ;
            if( this_label.indexOf( CONTAMINATION ) != -1 || this_label.indexOf( REPEAT ) != -1  )
            {
                continue ;
            }
            o =  table_complement.get( this_label ) ;
            complement_row_loc.put( this_label , new Integer( row ) ) ; //System.out.println(this_label + " -: " + row ) ;
            row += ((Integer)o  ).intValue() ;
        }

    }

    /**
     * Write &lt;/sciobj&gt;
     */
    private void
    writeFooter()
    {
        mOut.println("</svg>");
    }



    /**
     *  get the total length of dna sequence
     *
     */
    private int getLength(Sequence seq) throws Exception
    {
        return seq.length() ;
    }



    /**
     * actaully two function together, it is not good for design, just for performance
     * . (1) get the maximun rows each feature
     *   (2) put every gene component into list, for easy dumping
     */
    private int getRows(Sequence seq) throws Exception
    {
        int rows  = 0 ;
        int length = 0 ;
        /** store seq_map --> max rows **/

        if( seq instanceof SimpleAssembly)
           cal_sequence_row((SimpleAssembly) seq, length) ;
        else
           cal_sequence_row( seq , length) ;

         //sum up the total rows
        Enumeration nums = table_forward.keys() ;
        while( nums.hasMoreElements() )
        {
            rows += ((Integer)table_forward.get( nums.nextElement() ) ).intValue() ;
        }

        nums = table_complement.keys() ;
        while( nums.hasMoreElements() )
        {
            rows += ((Integer)table_complement.get( nums.nextElement() ) ).intValue() ;
        }

        return rows ;
    }

    private void cal_sequence_row(Sequence seq, int length)
    {
        String prop = annot2string( seq.getAnnotation() ) ;
        int x = 0 ;
        int y = -1 ;
        int len =   seq.length() ;
        String row_type = "DNA" ;
        comp2svg.addElement( new Component(x, y, len, Component.FORWARD, prop, row_type, true, mId++) );

        cal_feature_row( seq, length) ;
    }

    private void cal_sequence_row(SimpleAssembly seq , int length)
    {
        for(Iterator i = seq.features(); i.hasNext(); )
        {
            ComponentFeature child = (ComponentFeature) i.next();
            int type = (( child.getStrand() == StrandedFeature.NEGATIVE ) ? Component.REVERSE : Component.FORWARD );
            String prop = annot2string( child.getAnnotation() ) ;
            int x = child.getLocation().getMin() ;
            int y = -1 ;
            int len =   seq.length() ;
            String row_type = "DNA" ;
            comp2svg.addElement( new Component(x, y, len, type, prop, row_type, true, mId++) );

            cal_feature_row( child.getComponentSequence(), x ) ;
        }
     }
     /**
      * return :   feature source -> list of features
      */
     private HashMap clusterSubFeatures(FeatureHolder feature)
     {
         HashMap maps = new HashMap();
         for(Iterator i = feature.features(); i.hasNext(); )
         {
             Feature  child = (Feature) i.next();
             String source = child.getType() ;
             if( source == null )
                 source = child.getSource() ;
             if( source == null )
                 source = "UNKOWN" ;
               //  System.out.println( source ) ;
             Object o = maps.get( source ) ;
             if( o == null )
             {
                 List list = new ArrayList() ;
                 list.add (child ) ;
                 maps.put( source, list ) ;
             }else{
                 ((List)o).add( child ) ;
             }
          }
         // System.out.println( maps.size());
          return maps;
      }

    /**
     * (1) calculate the rows for contig
     * (2) put info of each genecomponent inot Component
     */
    private void cal_feature_row(Sequence seq, int length)
    {
        //decide strandness
        HashMap maps = clusterSubFeatures((FeatureHolder)seq ) ;
        for(Iterator it = maps.keySet().iterator(); it.hasNext();)
        {
            String label = (String) it.next() ;
            cal_map_row( (List) maps.get( label ), label , length) ;
        }
     }

    /**
     * (1) calculate the max row in this fragment
     * (2) store all the gene /exon of this map type in this fragment
     */
    private void cal_map_row(List list, String label , int length)
    {
        //first store sequence_map label at this level

        Vector regions_for = new Vector() ;
        Vector regions_com = new Vector() ;
        Iterator fs = list.iterator() ;
        while(fs.hasNext())
        {
           Feature f = (Feature) fs.next();
           cal_map_row(f, regions_for, regions_com, label, length) ;
        }

        // get the maximun rows in this fragment
        int rows =  cal_row_each_map_in_fragment( regions_for )   ;
        Object o = table_forward.get( label ) ;
        //store the max value for all BAC
        if( rows >=1 )
        {
          if( o != null )
          {
             if( ((Integer)o).intValue() < rows )
                 table_forward.put( label, new Integer( rows )) ;
          }
          else
             table_forward.put( label, new Integer( rows ) );
        }
         // get the maximun rows in this fragment
        rows =  cal_row_each_map_in_fragment( regions_com )   ;
         o = table_complement.get( label ) ;
        //store the max value for all BAC
        if( rows >=1 )
        {
          if( o != null )
          {
             if( ((Integer)o).intValue() < rows )
                 table_complement.put( label, new Integer( rows )) ;
          }
          else
             table_complement.put( label, new Integer( rows ) );
        }
    }

    private void cal_map_row(Feature f, Vector region_for, Vector region_com, String label,  int length)
    {
        Location loc = f.getLocation() ;

        boolean forward = true ;

        if( f instanceof StrandedFeature )
        {
            forward = (((StrandedFeature)f).getStrand() != StrandedFeature.NEGATIVE ) ;
        }
        if( forward )
            region_for.addElement( loc );
        else
            region_com.addElement( loc );

        dump_result( f, length, label , forward) ;

        for(Iterator it = f.features(); it.hasNext() ;)
        {
           cal_map_row( (Feature) it.next() , region_for, region_com,  label, length) ;
        }
     }
    /**
     * store the info of comp_result into Component
     * currently I only choose some info. for final show
     */
    private void dump_result(Feature f, int length, String row_type, boolean forward)
    {
        String prop = annot2string( f.getAnnotation() ) ;
        int start = f.getLocation().getMin() ;
        int end = f.getLocation().getMax() ;
        String type = f.getType() ;
        int x = length  +  start;
        int y = -1 ;
        int len =   end - start + 1 ;
        int comtype = Component.EXON ;  //default value
        if(  type.indexOf("gene") != -1 || f instanceof Gene )
            comtype = Component.GROUP ;
        else if( type.indexOf("exon") != -1  || f instanceof Exon)
            comtype = Component.EXON ;
        else if( type.indexOf("intron") != -1 )
            comtype = Component.INTRON ;
        comp2svg.addElement( new Component(x, y, len, comtype, prop, row_type, forward, mId++) );
    }


    /** cal the #rows this set of regions will occupy  */
    private int cal_row_each_map_in_fragment( Vector regions )
    {
        if( regions.size() <2 )
              return regions.size() ;
        return  cal_bins_for_one_vector( regions ).size() ;
    }

    /** regions must have at least two elements
     *  algorithm :
     *<p> init : create one bin, and put first region into it.
     *<p>  for each region aa in regions-A
     *<p>      for each bin bb in bins-B
     *<p>           check if aa can put into bb withou overlap any element
     *<p>      if can,  put aa into bb and break ;
     *<p>      if can not, create new bin bb-new ,and put bb-new into bins-B
     *<p>                                             put aa into bb-new
     *<p>  finally, the number of bins in bins-B is the number of rows
     *<p>  need.
     *<p>
     **/
    private Vector cal_bins_for_one_vector(Vector regions )
    {//System.out.println("entering bin : " + regions.size());
        //init
        Vector bins = new Vector() ;
        Vector one_bin = new Vector() ;
        one_bin.addElement( regions.elementAt(0) ) ;
        bins.addElement( one_bin ) ;
        //loop...
      try{
        for(int i = 1 ; i < regions.size() ; i++)
        {
            Location region = (Location) regions.elementAt(i) ;
            int start = region.getMin() ;
            int end = region.getMax()   ;
            boolean found_bin = false ;
            //check the already created bin
            for( int j = 0 ; j < bins.size() ; j++)
            {
                 Vector this_bin = (Vector) bins.elementAt( j ) ;
                 //check if region can be put into this bin
                 boolean overlap = false ;
                 for(int k = 0 ; k < this_bin.size() ; k++)
                 {
                    Location this_region = (Location) this_bin.elementAt( k ) ;
                    int ov = Math.min(end, this_region.getMax() ) - Math.max(start, this_region.getMin() );
                    if( ov > 0 ) //can not fit into this bin
                    {
                        overlap = true ;
                        break ;
                    }
                 }
                 if( ! overlap )  //can fit into this bin
                 {
                     this_bin.addElement( region ) ;
                     found_bin = true ;
                     break ;
                 }
             }
             if( ! found_bin ) //can not found a suitable bin, so create a new one..
             {
                 Vector new_bin = new Vector() ;
                 new_bin.addElement( region ) ;
                 bins.addElement( one_bin ) ;
             }
         }
        }catch(Exception e ){}
      //  System.out.println("leaving bin : " + bins.size());
        return bins ;
    }

    private String annot2string(Annotation  seqAn)
    {
        StringBuffer sb = new StringBuffer() ;
        for (Iterator i = seqAn.keys().iterator(); i.hasNext(); )
        {
            Object key = i.next();
            Object value = seqAn.getProperty(key);
            sb.append( key + " : " + filter( value.toString() ) + "\n") ;
        }
        return sb.toString();
    }
    private String filter( String target)
    {
        StringBuffer sb = new StringBuffer(   ) ;
        for(int i = 0 ; i < target.length() ; i++)
        {
            char chr = target.charAt(i) ;
            if( chr == '>' )
                sb.append("&gt;") ;
            else if ( chr == '<' )
                sb.append("&lt;") ;
            else if ( chr == '&' )
                sb.append("&amp;") ;
            else
                sb.append(chr);
         }
         return sb.toString() ;
    }

  /**
     * private class used for store location info for each gene components
     * for performance issue, I use all the public variables
     */
    class Component
    {
       //start point in view
       public int start_x ;
       //start point in view
       public int start_y ;
       //should be label of sequence_map
       public String row_type ;
       //should be type of genecomponent
       public int type ;
       public int id;

       public static final int GROUP = 0 ;
       public  static final int EXON = 1 ;
       public  static final int INTRON = 2 ;
       public  static final int FORWARD = 3;
       public  static final int REVERSE = 4 ;
       public  static final int UNORDER = 5 ;
       //should be the length of this component
       public int length ;
       //currently used for view to show the info
       public String property ;
       //which side
       public boolean isForward ;
       //construct..
       public Component(int x, int y, int len, int type, String prop , String row, boolean forward, int id){
           start_x = x;
           start_y = y ;
           length = len ;
           this.type = type ;
           property = prop ;
           row_type = row ;
           isForward = forward;
           this.id = id ;
      }
      //help method to get the type of svg element
      private String getSVGType(int type)
      {
        String s  = " ";
        switch(type)
        {
            case  GROUP : s = "genebox"; break;
            case  EXON : s = "exonbox"; break;
            case  INTRON : s = "intronbox"; break;
            case  FORWARD : s = "forwardfragment";  break;
            case  REVERSE  : s =  "reversefragment"; break;
            case  UNORDER : s = "unorderedfragment"; break;
        }
        return s;
      }


       public String toString()
       {
          String svgtype = getSVGType( type ) ;
          int len = ( length / 10 > 1 ? length/10 : 1 ) ;
          StringBuffer prop = new StringBuffer("") ;
          int index1 , index2;
          String breakline = System.getProperty( "line.separator" ) ;
          index1 = index2 = 0 ;
          if( property != null )
            if( property.indexOf("\n") == -1 )
                prop.append( property ) ;
            else
              while( (index1 = property.indexOf("\n", index2) ) != -1 )
              {
                prop.append( "<tspan x=\"" +  start_x + "\"   dy=\"1em\">"  + property.substring(index2, index1) + "</tspan>\n" ) ;
                                index2 = index1 + 1 ;
              }

          return  " <g  transform=\"translate(" + start_x + ", " + start_y + " ) scale(" + len + ",1)\">"
                 + "<use id=\"" + id + "\" xlink:href=\"#" + svgtype +
                    "\" onmouseover=\"showproperty(evt)\" onmouseout=\"hideproperty(evt)\" />\""
                 + "</g>"
                 +  "<g>"
                 +  "<text  id=\"" + id + "t" + "\"  x=\"" + start_x + "\" y=\"" + start_y + "\" font-size=\"45\" fill = \"blue\" visibility=\"hidden\">"
                 + prop.toString()
                 +  "</text>"
                 + "</g>" ;
       }
    }

}
