// TextViewHTMLContainer.java
// By Ned Etcode
// Copyright 1996 Netscape Communications Corp.  All rights reserved.

package netscape.application;
import netscape.util.*;



/** This class should be private and is used to parse some HTML in the textview.
 *  It is public to allow getClass() to work
 *  @private
 */
public class TextViewHTMLContainer implements HTMLElement, TextViewHTMLElement {
    String marker;
    String begin = null;
    String end   = null;
    String attributes;
    Object children[];
    /** Add a CR if necessary + another CR */
    static String markersStartingWithDoubleCarriageReturn[] =
        {"H1","H2","H3","H4","H5","H6","BLOCKQUOTE","DL"};
    static String markersEndingWithDoubleCarriageReturn[]   =
        {"H1","H2","H3","H4","H5","H6","BLOCKQUOTE","DL"};

    /** Add a CR */
    static String markersStartingWithCarriageReturn[] =
         { "CENTER","PRE","OL","UL","MENU","DIR","ADDRESS"};
    static String markersEndingWithCarriageReturn[]   =
        { "CENTER","PRE","ADDRESS","LI" };

    static String markersStartingWithCarriageReturnOptionaly[] = { "DT","DD" };

    static final String LIST_CONTEXT = "listctxt";

    private String currentListMarker(Hashtable context) {
        Vector v = (Vector) context.get(LIST_CONTEXT);
        Vector m;

        if( v == null )
            return null;

        m = (Vector) v.lastElement();
        if( m == null || m.count() != 2)
            return null;

        return (String)m.elementAt(0);
    }

    private int levelOfCurrentListMarker(Hashtable context) {
        Vector v = (Vector) context.get(LIST_CONTEXT);
        Vector m;

        if( v == null )
            return 1;
        return v.count();
    }

    private void addListInContext(String aMarker,Hashtable context) {
        Vector v = (Vector) context.get(LIST_CONTEXT);
        Vector m = new Vector();
        m.addElement(aMarker);
        m.addElement("0");
        if( v != null )
            v.addElement(m);
        else {
            v = new Vector();
            v.addElement(m);
            context.put(LIST_CONTEXT,v);
        }
    }

    private void removeLastListFromContext(Hashtable context) {
        Vector v = (Vector) context.get(LIST_CONTEXT);
        Vector m;
        if( v != null && (m = (Vector)v.lastElement()) != null ) {
            v.removeLastElement();
        }
    }

    private void bumpNumberOfListItemProcessed(Hashtable context) {
        Vector v = (Vector) context.get(LIST_CONTEXT);
        Vector m;
        if( v != null && (m = (Vector)v.lastElement()) != null ) {
            String n;
            n = (String) m.elementAt(1);
            n = "" + (Integer.parseInt((String)m.elementAt(1)) + 1);
            m.removeLastElement();
            m.addElement(n);
        }
    }

    private int numberOfListItemProcessed(Hashtable context) {
        Vector v = (Vector) context.get(LIST_CONTEXT);
        Vector m;
        if( v != null && (m = (Vector)v.lastElement()) != null ) {
            return Integer.parseInt((String) m.elementAt(1));
        }
        return 0;
    }



    private String beginForMarker( String aMarker ,Hashtable context,FastStringBuffer fb) {
        int i,c;
        char lastChar = 0;

        if( fb.length() > 0 )
          lastChar = fb.charAt(fb.length()-1);

        if (aMarker.equals("LI")) {
            String markerType = currentListMarker(context);
            int level;
            if( markerType == null ) /* LI without list ignore */
                return null;
            else {
                FastStringBuffer lfb = new FastStringBuffer();
                level = levelOfCurrentListMarker(context);

                for(i=0;i<level;i++)
                    lfb.append("\t");
                if(markerType.equals("OL"))
                    return lfb.toString()  +
                        (numberOfListItemProcessed(context) + 1) + ". ";
                else
                    return lfb.toString() + "\u00b7 ";
            }
        }
        for(i=0,c=markersStartingWithDoubleCarriageReturn.length ; i < c ; i++)
            if( markersStartingWithDoubleCarriageReturn[i].equals(aMarker)) {
                if(lastChar != '\n')
                    return "\n\n";
                else
                    return "\n";
            }

        for(i=0,c=markersStartingWithCarriageReturn.length ; i < c ; i++)
            if( markersStartingWithCarriageReturn[i].equals(aMarker))
              return "\n";

        if( lastChar != '\n') {
          for(i=0,c=markersStartingWithCarriageReturnOptionaly.length ; i < c ; i++ )
            if( markersStartingWithCarriageReturnOptionaly[i].equals(aMarker))
              return "\n";
        }

        return "";
    }

    private String endForMarker( String aMarker, Hashtable context,FastStringBuffer fb ) {
        int i,c;
        char lastChar = 0;

        if( fb.length() > 0 )
          lastChar = fb.charAt(fb.length() - 1);

        for(i=0,c=markersEndingWithDoubleCarriageReturn.length ; i < c ; i++)
            if( markersEndingWithDoubleCarriageReturn[i].equals(aMarker)){
                if( lastChar != '\n' )
                    return "\n\n";
                else
                    return "\n";
            }

        for(i=0,c=markersEndingWithCarriageReturn.length ; i < c ; i++)
            if( markersEndingWithCarriageReturn[i].equals(aMarker)) {
                    return "\n";
            }

        return "";
    }


    Hashtable changeAttributesForMarker(Hashtable initialAttr, String marker,Hashtable tvDefAttr,
                                        Hashtable context) {
        Hashtable newAttr;
        Font defaultFont = Font.defaultFont();

        if( initialAttr != null && initialAttr.count() > 0) {
          Enumeration enumeration;
          Object key;

          newAttr = (Hashtable) TextView.hashtablePool.allocateObject();
          enumeration = initialAttr.keys();
          while(enumeration.hasMoreElements() ) {
            key = enumeration.nextElement();
            newAttr.put(key,initialAttr.get(key));
          }
        } else
            newAttr = (Hashtable)TextView.hashtablePool.allocateObject();

        if( marker.equals("H1"))
            newAttr.put(TextView.FONT_KEY, Font.fontNamed(defaultFont.name(),
                                                          Font.BOLD, 24));
        else if( marker.equals("H2"))
            newAttr.put(TextView.FONT_KEY, Font.fontNamed(defaultFont.name(),
                                                          Font.BOLD, 18));
        else if( marker.equals("H3"))
            newAttr.put(TextView.FONT_KEY, Font.fontNamed(defaultFont.name(),
                                                          Font.BOLD, 16));
        else if( marker.equals("H4"))
            newAttr.put(TextView.FONT_KEY, Font.fontNamed(defaultFont.name(),
                                                          Font.BOLD, 12));
        else if( marker.equals("H5"))
            newAttr.put(TextView.FONT_KEY, Font.fontNamed(defaultFont.name(),
                                                          Font.BOLD,  10));
        else if( marker.equals("H6"))
            newAttr.put(TextView.FONT_KEY, Font.fontNamed(defaultFont.name(),
                                                          Font.BOLD,  8));
        else if( marker.equals("B") || marker.equals("STRONG")) {
            Font f = (Font) newAttr.get(TextView.FONT_KEY);
            if( f != null ) {
                if(!f.isBold())
                    newAttr.put(TextView.FONT_KEY, Font.fontNamed( f.name(),
                                   f.style() | Font.BOLD,f.size()));
            } else {
                newAttr.put(TextView.FONT_KEY, Font.fontNamed(defaultFont.name(),Font.BOLD,
                                                        defaultFont.size()));
            }
        } else if( marker.equals("CENTER")) {
            TextParagraphFormat pf = (TextParagraphFormat)
                newAttr.get(TextView.PARAGRAPH_FORMAT_KEY);
            TextParagraphFormat centerFormat;
            if( pf != null )
                centerFormat = (TextParagraphFormat)pf.clone();
             else
                centerFormat = (TextParagraphFormat)
                    ((TextParagraphFormat)tvDefAttr.get(TextView.PARAGRAPH_FORMAT_KEY)).clone();
            centerFormat.setJustification(Graphics.CENTERED);
            newAttr.put( TextView.PARAGRAPH_FORMAT_KEY, centerFormat );
        } else if( marker.equals("BLOCKQUOTE") || marker.equals("DD")) {
            TextParagraphFormat pf = (TextParagraphFormat)
            newAttr.get(TextView.PARAGRAPH_FORMAT_KEY);
            TextParagraphFormat indentFormat;
            if( pf != null )
                indentFormat = (TextParagraphFormat)pf.clone();
             else
                indentFormat = (TextParagraphFormat)
                    ((TextParagraphFormat)tvDefAttr.get(TextView.PARAGRAPH_FORMAT_KEY)).clone();
            indentFormat.setLeftMargin(50);
            newAttr.put( TextView.PARAGRAPH_FORMAT_KEY, indentFormat );
        } else if( marker.equals("EM") || marker.equals("I") || marker.equals("ADDRESS") ||
                   marker.equals("VAR") || marker.equals("CITE")) {
            Font f = (Font) newAttr.get(TextView.FONT_KEY);
            if( f != null ) {
                if(!f.isItalic())
                    newAttr.put(TextView.FONT_KEY, Font.fontNamed( f.name(), f.style() | Font.ITALIC,
                                                             f.size()));
            } else {
                newAttr.put(TextView.FONT_KEY, Font.fontNamed(defaultFont.name(),Font.ITALIC,
                                                        defaultFont.size()));
            }
        } else if( marker.equals("PRE")) {
            Font f = (Font) newAttr.get(TextView.FONT_KEY);
            TextParagraphFormat defaultFormat = (TextParagraphFormat)
                ((TextParagraphFormat)tvDefAttr.get(TextView.PARAGRAPH_FORMAT_KEY)).clone();
            if( f == null )
                f = Font.defaultFont();
            newAttr.put(TextView.FONT_KEY, Font.fontNamed( "Courrier", f.style(), f.size()));
            newAttr.put(TextView.PARAGRAPH_FORMAT_KEY, defaultFormat);
        } else if(marker.equals("TT") || marker.equals("CODE") ||
                  marker.equals("SAMP") || marker.equals("KBD")) {
            Font f = (Font) newAttr.get(TextView.FONT_KEY);
            if( f == null )
                f = Font.defaultFont();
            newAttr.put(TextView.FONT_KEY, Font.fontNamed( "Courrier", f.style(), f.size()));
        } else if( marker.equals("A")) {
          Hashtable attr;
          String url;
          String name;

          try {
            attr = HTMLParser.hashtableForAttributeString(attributes);
          } catch(HTMLParsingException e) {
            attr = null;
          }
          if( attr != null ) {
            if( (url = (String)attr.get("HREF")) != null) {
              newAttr.put( TextView.LINK_KEY, url);
            }

            if((name = (String) attr.get("NAME")) != null ) {
              newAttr.put( TextView.LINK_DESTINATION_KEY, name);
            }
          }
        } else if( marker.equals("LI")) {
            TextParagraphFormat pf =(TextParagraphFormat) newAttr.get(TextView.PARAGRAPH_FORMAT_KEY);
            if( pf == null )
                pf = (TextParagraphFormat)
                    ((TextParagraphFormat)tvDefAttr.get(TextView.PARAGRAPH_FORMAT_KEY)).clone();
            pf.setWrapsUnderFirstCharacter(true);
            newAttr.put(TextView.PARAGRAPH_FORMAT_KEY, pf);
        }
        return newAttr;
    }



    public void appendString(Hashtable context,FastStringBuffer fb) {
      fb.append((begin=beginForMarker(marker,context,fb)));
      if( children != null ) {
        int i,c;
        setupContext(context);
        for(i = 0 , c = children.length ; i < c ; i++ )
          ((TextViewHTMLElement)children[i]).appendString(context,fb);
        cleanupContext(context);
      }
      fb.append((end=endForMarker(marker,context,fb)));
    }

    /** Return the length in number of character of the component */
    public int length() {
        int result = 0;
        int i,c;

        if( begin != null )
            result += begin.length();
        if( children != null )
                for(i = 0 , c = children.length ; i < c ; i++ )
                    result += ((TextViewHTMLElement)children[i]).length();
        result += end.length();
        return result;
    }

    /** Set the attributes for the string starting at index index */
    public void setAttributesStartingAt(int index, Hashtable initialAttributes,TextView target,
                                        Hashtable context) {
        int i,c,offset = 0;
        Hashtable newAttributes =
            changeAttributesForMarker( initialAttributes, marker, target.defaultAttributes(),
                                       context);

        if( begin != null )
            offset += begin.length();
        if( children != null ) {
            for(i=0,c=children.length ; i < c ; i++ ) {
                ((TextViewHTMLElement)children[i]).setAttributesStartingAt( index + offset,
                                    newAttributes, target,context);
                offset += ((TextViewHTMLElement)children[i]).length();
            }
        } else if( newAttributes != null ) { /** Apply attributes when Length is 0
                                              *  this can happen for a link destination
                                              *  attribute.
                                              */
            Range r = TextView.allocateRange(index,0);
            target.addAttributesForRange(newAttributes,r);
            TextView.recycleRange( r );
        }


        newAttributes.clear();
        TextView.hashtablePool.recycleObject((Object)newAttributes);
    }

    public void setMarker(String aString) {
        marker = aString;
    }

    public void setAttributes(String attr) {
        attributes = attr;
    }

    public void setChildren(Object child[]) {
        children = child;
    }

    public void setString(String aString) {
    }

    private void setupContext(Hashtable context) {
        if( marker.equals("OL") || marker.equals("UL") || marker.equals("DIR") || marker.equals("MENU"))
            addListInContext(marker,context);
        else if( marker.equals("LI") && currentListMarker(context) != null ) {
            bumpNumberOfListItemProcessed(context);
        }
    }

    private void cleanupContext(Hashtable context) {
        if( marker.equals("OL") || marker.equals("UL") || marker.equals("DIR") || marker.equals("MENU"))
            removeLastListFromContext(context);
    }
}

