package cnp.ew.displayer;
import java.awt.*;
import java.awt.image.*;
import java.util.*;
import cnp.ew.util.*;
import cnp.ew.lightweight.*;

/**
 * Displays a string within a given rectangle, using various style
 * information to determine its formatting.
 *
 * @version        $Version$
 * @author         $Author: Ken $
 */
final public class CpGeneralStringDisplayer extends CpAbstractSelectableTextDisplayer
    implements CpAlignable, CpFocusDisplayable
{

    public static final int SELECT_NONE = 0;
    public static final int SELECT_TEXT = 1;
    public static final int SELECT_CELL = 2;


    public Color foreColor = Color.black;
    public Color backColor;
    public Color selectionForeColor = Color.white;
    public Color selectionBackColor= new Color(0, 0, 128);

    public int selectionDisplayStyle = SELECT_TEXT;

    public int horizontalAlignment = ALIGN_LEFT;
    public int verticalAlignment = ALIGN_TOP;

    public boolean shouldDisplayDisabled = false;
    public boolean showFocus=false;

    // Metrics used in calculating a reasonable preferred size for
    // wrapped text. Public so they can be settable.
    public int charsPerLineToNumLinesRatio = 15;
    public int minCharsPerLine = 25;

    // These are ALL public to get around a problem with IE 3.0
    public Vector stringList;
    public boolean shouldWrapText = false;

    public Vector wrappedStringList;

    public int pixelWrapWidth = -1;

    // if it's null, no margins
    public Insets margin=null;

    // if it's null, no selection expansion
    public Insets selectionExpansion=null;

        // This is public to get around a problem with IE 3.0
        // This is calculated *alot*; cache it.
    public Dimension cachedPreferredSize=null;


    public CpGeneralStringDisplayer()
    {
        super();
        setText("M");
    }

    public CpGeneralStringDisplayer(String s)
    {
        super();
        setText(s);
    }

    public void setFont(Font newFont)
    {
        super.setFont(newFont);
        cachedPreferredSize = null;
    }

    public void setMargin(int newMargin)
    {
        setMargin(new Insets(newMargin, newMargin, newMargin, newMargin));
    }

    public void setMargin(int hMargin, int vMargin)
    {
        setMargin(new Insets(vMargin, hMargin, vMargin, hMargin));
    }

    public void setMargin(Insets newMargin)
    {
        margin = newMargin;
        cachedPreferredSize = null;
    }

    public void setSelectionExpansion(int newExpansion)
    {
        setSelectionExpansion(new Insets(newExpansion, newExpansion, newExpansion, newExpansion));
    }

    public void setSelectionExpansion(int hExpansion, int vExpansion)
    {
        setSelectionExpansion(new Insets(vExpansion, hExpansion, vExpansion, vExpansion));
    }

    public void setSelectionExpansion(Insets newSelectionExpansion)
    {
        selectionExpansion = newSelectionExpansion;
        cachedPreferredSize = null;
    }

    public void setShowFocus(boolean showFocus)
    {
        this.showFocus = showFocus;
    }

    public boolean getShowFocus()
    {
        return showFocus;
    }

    /**
     * Answer the preferred size for this displayable object.
     */
    public Dimension preferredSize(CpLightweightComponent c)
    {
        if (cachedPreferredSize != null) {
            return cachedPreferredSize;
        }

        if (shouldWrapText) {
            cachedPreferredSize = wrappedPreferredSize(c);
        } else {
            cachedPreferredSize = basicPreferredSize(c);
        }

        if (shouldDisplayDisabled) {
            cachedPreferredSize.width++;
            cachedPreferredSize.height++;
        }

        if (selectionExpansion != null) {
            cachedPreferredSize.width += selectionExpansion.left + selectionExpansion.right;
            cachedPreferredSize.height += selectionExpansion.top + selectionExpansion.bottom;
        }

        if (margin != null) {
            cachedPreferredSize.width += margin.left + margin.right;
            cachedPreferredSize.height += margin.top + margin.bottom;
        }

        return cachedPreferredSize;
    }


    Dimension wrappedPreferredSize(CpLightweightComponent c)
    {
        Enumeration e;
        int maxChars=0, maxWidth=0;
        FontMetrics fontMetrics = getFontMetrics(c);


        if (wrappedStringList == null) {
            e = stringList.elements();
            while (e.hasMoreElements()) {
                String string = (String)e.nextElement();
                maxChars = Math.max(maxChars, string.length());
                maxWidth = Math.max(maxWidth, fontMetrics.stringWidth(string));
            }
            int charsPerLine = Math.max((int)Math.sqrt(maxChars * charsPerLineToNumLinesRatio), minCharsPerLine);
            int goodWidth = Math.min(fontMetrics.charWidth('a') * charsPerLine, maxWidth);

            wrappedStringList = CpTextManipulator.wrapTextToWrapWidth(stringList, goodWidth, fontMetrics);
            // Now that we've come up with a good general wrap width, make it exact to the width of the
            // current wrapping.
            maxWidth = 0;
            e = wrappedStringList.elements();
            while (e.hasMoreElements()) {
                String string = (String)e.nextElement();
                maxWidth = Math.max(maxWidth, fontMetrics.stringWidth(string));
            }
            pixelWrapWidth = maxWidth;
        } else {
        // if we've already wrapped, don't try this technique, but instead assume the wrap width they set earlier
        // was what they wanted.
            maxWidth = pixelWrapWidth;
        }
        return new Dimension(maxWidth, fontMetrics.getHeight() * wrappedStringList.size());
    }

    public int wrappedPreferredHeightFor(CpLightweightComponent c, int width)
    {
        FontMetrics fontMetrics = getFontMetrics(c);

        pixelWrapWidth = width;
        wrappedStringList = CpTextManipulator.wrapTextToWrapWidth(stringList, width, fontMetrics);
        int height = fontMetrics.getHeight() * wrappedStringList.size();

        if (shouldDisplayDisabled) {
            height++;
        }

        if (selectionExpansion != null) {
            height += selectionExpansion.top + selectionExpansion.bottom;
        }

        if (margin != null) {
            height += margin.top + margin.bottom;
        }

        return height;
    }

    Dimension basicPreferredSize(CpLightweightComponent c)
    {
        Enumeration e=stringList.elements();
        int maxWidth=0;
        FontMetrics fontMetrics = getFontMetrics(c);

        while (e.hasMoreElements()) {
            maxWidth = Math.max(maxWidth, fontMetrics.stringWidth((String)e.nextElement()));
        }
        return new Dimension(maxWidth, fontMetrics.getHeight() * stringList.size());
    }

    public void setVerticalAlignment(int newVerticalAlignment)
    {
        verticalAlignment = newVerticalAlignment;
        cachedPreferredSize = null;
    }

    public int getVerticalAlignment()
    {
        return verticalAlignment;
    }

    public void setHorizontalAlignment(int newHorizontalAlignment)
    {
        horizontalAlignment = newHorizontalAlignment;
        cachedPreferredSize = null;
    }

    public int getHorizontalAlignment()
    {
        return horizontalAlignment;
    }

    public void setBackColor(Color color)
    {
        backColor = color;
    }

    public Color getBackColor()
    {
        return backColor;
    }

    public void setShouldDisplayDisabled(boolean newshouldDisplayDisabled)
    {
        shouldDisplayDisabled = newshouldDisplayDisabled;
        cachedPreferredSize = null;
    }

    public boolean getShouldDisplayDisabled()
    {
        return shouldDisplayDisabled;
    }

    public void setText(String newText)
    {
        // Test thrown in by Ted, 8/13/96
        if (newText == null) {
            newText = "";
        }
        StringTokenizer tokenizer = new StringTokenizer(newText, "\n\r");

        stringList = new Vector(1);
        while (tokenizer.hasMoreTokens()) {
            stringList.addElement(tokenizer.nextToken());
        }
        cachedPreferredSize = null;
        wrappedStringList = null;
    }

    public String getText()
    {
        String s = "";
        for (int i = 0; i < stringList.size(); i++ ) {
            s += (String)stringList.elementAt(i);
            if (i != stringList.size() - 1) {
                s += "\n\r";
            }
        }
        return s;
    }

    public void setTextList(Vector newStringList)
    {
        stringList = newStringList;
        cachedPreferredSize = null;
    }

    public void paintIn(CpLightweightComponent c, Graphics g, Rectangle rect)
    {
        paintIn(c, g, rect.x, rect.y, rect.width, rect.height);
    }

    public void setWrapWidth(int newPixelWrapWidth)
    {
        if (newPixelWrapWidth != pixelWrapWidth) {
            pixelWrapWidth = newPixelWrapWidth;
            wrappedStringList = null;
            cachedPreferredSize = null;
        }
    }

    public void setShouldWrap(boolean newShouldWrap)
    {
        shouldWrapText = newShouldWrap;
        cachedPreferredSize = null;
    }

    public boolean getShouldWrap()
    {
        return shouldWrapText;
    }


    public void wrapText(CpLightweightComponent c)
    {
        wrappedStringList = CpTextManipulator.wrapTextToWrapWidth(stringList, pixelWrapWidth, getFontMetrics(c));
    }


    Vector getStringListToDisplay(CpLightweightComponent c)
    {
        if (shouldWrapText) {
            if (wrappedStringList == null) {
                wrapText(c);
            }
            return wrappedStringList;
        } else {
            return stringList;
        }
    }

    public void paintIn(CpLightweightComponent c, Graphics g, int x, int y, int w, int h)
    {
        // Ted added 10/18
        setWrapWidth(w);

        if (font != null) {
	        g.setFont(font);
	    }
        if (shouldDisplayDisabled) {
            Color savedForeColor = foreColor;
            foreColor = Color.white;
            basicPaintIn(c, g, x + 1, y + 1, w, h);
            Color savedBackColor = backColor;
            backColor = null;
            foreColor = Color.gray;
            basicPaintIn(c, g, x, y, w, h);
            foreColor = savedForeColor;
            backColor = savedBackColor;
        } else {
            basicPaintIn(c, g, x, y, w, h);
        }
    }


    /**
     * Display the text using g.
     */
	public void basicPaintIn(CpLightweightComponent c, Graphics g, int x, int y, int w, int h)
	{

	    FontMetrics metrics;
	    int baselineY, deltaY, fontHeight, fontAscent, numStrings;
	    Vector list;

        fillCellBackground(g, x, y, w, h);
        list = getStringListToDisplay(c);
        if (list == null) { return; }

	    metrics = getFontMetrics(c);
	    fontAscent = metrics.getAscent();
        fontHeight = metrics.getHeight();
        deltaY = fontHeight;
        numStrings = list.size();

        int marginTop = 0;
        int marginBottom = 0;
        int marginLeft = 0;
        int marginRight = 0;
        if (margin != null) {
            marginTop += margin.top;
            marginBottom += margin.bottom;
            marginLeft += margin.left;
            marginRight += margin.right;
        }

        if (selectionExpansion != null) {
            marginTop += selectionExpansion.top;
            marginBottom += selectionExpansion.bottom;
            marginLeft += selectionExpansion.left;
            marginRight += selectionExpansion.right;
        }

	    switch (verticalAlignment) {
	    case ALIGN_TOP:
	        baselineY = y + marginTop + fontAscent;
	        break;
	    case ALIGN_JUSTIFIED:
            deltaY = ((h - marginTop - marginBottom - (numStrings * fontHeight)) / numStrings);
	        baselineY = y + marginTop + deltaY / 2 + fontAscent;
	        deltaY += fontHeight;
	        break;
	    case ALIGN_BOTTOM:
	        baselineY = y + h - marginBottom - (fontHeight * numStrings) + fontAscent;
	        break;
	    case ALIGN_CENTER:
            baselineY = y + ((h - marginTop - marginBottom - (fontHeight * numStrings) ) / 2) + fontAscent + marginTop;
            break;
        default:
            throw new IllegalArgumentException("Invalid vertical alignment");
	    }

        Enumeration e = list.elements();
        int savedBaselineY = baselineY;
        int focusRectLeft = 10000;
        int focusRectWidth = 0;
        while (e.hasMoreElements()) {
            int [] leftAndWidth = paintCurrentLine(g, metrics, (String)e.nextElement(), x + marginLeft, baselineY, w - marginLeft - marginRight, fontAscent, fontHeight);
            baselineY += deltaY;
            focusRectLeft = Math.min(focusRectLeft, leftAndWidth[0]);
            focusRectWidth = Math.max(focusRectWidth, leftAndWidth[1]);
        }

	    if (showFocus && selectionDisplayStyle == SELECT_TEXT) {
            if (selectionExpansion != null) {
                CpFocusRect.drawFocusRect(
                    g,
                    focusRectLeft - selectionExpansion.left,
                    savedBaselineY - fontAscent - selectionExpansion.top,
                    focusRectWidth + selectionExpansion.left + selectionExpansion.right,
                    (deltaY * numStrings) + selectionExpansion.top + selectionExpansion.bottom,
                    isSelected ? CpFocusRect.yellowFocusColor : CpFocusRect.clearFocusColor);
            } else {
                CpFocusRect.drawFocusRect(
                    g,
                    focusRectLeft,
                    savedBaselineY - fontAscent,
                    focusRectWidth,
                    (deltaY * numStrings),
                    isSelected ? CpFocusRect.yellowFocusColor : CpFocusRect.clearFocusColor);
            }
        }
    }

    int [] paintCurrentLine(Graphics g, FontMetrics metrics, String text, int x, int baselineY, int w, int ascent, int fontHeight)
    {
        int startX;
        int textWidth;
        textWidth = metrics.stringWidth(text);

        // Adds ... to end if the text won't fit within the bounding box.  TBD: Should this
        // be a switch?
        if (textWidth > w) {
            int dotsWidth = metrics.stringWidth("...");
            int i = 0;
            int curWidth = 0;
            while (curWidth < w - dotsWidth) {
                curWidth += metrics.charWidth(text.charAt(i));
                i++;
            }
            text = text.substring(0, i) + "...";
            textWidth = metrics.stringWidth(text);
        }

        switch (horizontalAlignment) {
        case ALIGN_LEFT:
            startX = x;
            break;
        case ALIGN_RIGHT:
            startX = x + w - textWidth;
            break;
        case ALIGN_CENTER:
            startX = x + ((w - textWidth) / 2);
            break;
        case ALIGN_JUSTIFIED:
            startX = x;
            textWidth = w;
            break;
        default:
            throw new IllegalArgumentException("Invalid horizontal alignment");
        }

	    if (isSelected && selectionDisplayStyle != SELECT_NONE) {
	        if (selectionDisplayStyle == SELECT_TEXT && selectionBackColor != null) {
                g.setColor(selectionBackColor);
                if (selectionExpansion != null) {
                    g.fillRect(
                        startX - selectionExpansion.left,
                        baselineY - ascent - selectionExpansion.top,
                        textWidth + selectionExpansion.left + selectionExpansion.right,
                        fontHeight + selectionExpansion.top + selectionExpansion.bottom);
                } else {
                    g.fillRect(
                        startX,
                        baselineY - ascent,
                        textWidth,
                        fontHeight);
                }
            }
            g.setColor(selectionForeColor);
	    } else {
	        g.setColor(foreColor);
	    }


	    if (horizontalAlignment == ALIGN_JUSTIFIED) {
            paintCurrentLineJustified(g, metrics, text, x, baselineY, w, ascent, fontHeight);
        } else {
            g.drawString(text, startX, baselineY);
	    }

	    int [] leftAndWidth = new int[2];
	    leftAndWidth[0] = startX;
	    leftAndWidth[1] = textWidth;
	    return leftAndWidth;
	}

    void paintCurrentLineJustified(Graphics g, FontMetrics metrics, String text, int x, int baselineY, int w, int ascent, int fontHeight)
    {
        int currentStringWidth, totalStringWidth=0, startX, index, deltaX;
        Vector substrings = CpTextManipulator.convertStringToWhitespaceSeparatedSubstrings(text);
        int stringWidths[] = new int[substrings.size()];

        for (index = 0; index < substrings.size(); index++) {
            currentStringWidth = metrics.stringWidth((String)substrings.elementAt(index));
            totalStringWidth += currentStringWidth;
            stringWidths[index] = currentStringWidth;
        }

        startX = x;

        switch (substrings.size()) {
        case 0:
            deltaX = 0;
            break;
        case 1:
            deltaX = (w - stringWidths[0]) / 2;
            startX = x + deltaX;
            break;
        default:
            deltaX = (w - totalStringWidth) / (substrings.size() - 1);
            break;
        }

        for (index = 0; index < substrings.size(); index++) {
            g.drawString((String)substrings.elementAt(index), startX, baselineY);
            startX += stringWidths[index] + deltaX;
        }
    }

    /**
     * Fill the background of the displayer.  If they are using a cell
     * selection style, and this displayer is selected, fill in the selection
     * background color.
     * If the appropriate background color is null, don't fill at all.
     */
    void fillCellBackground(Graphics g, int x, int y, int w, int h)
    {
        Color color;

        if (isSelected && selectionDisplayStyle == SELECT_CELL) {
            color = selectionBackColor;
        } else {
            color = backColor;
        }

        if (color != null) {
	        g.setColor(color);
	        g.fillRect(x, y, w, h);
	    }

	    if (showFocus && selectionDisplayStyle == SELECT_CELL) {
	        CpFocusRect.drawFocusRect(g,
	            x, y, w, h,
	            isSelected ? CpFocusRect.yellowFocusColor : CpFocusRect.clearFocusColor);
	    }

	}

	public int getFontAscent(CpLightweightComponent c)
	{
	    return getFontMetrics(c).getAscent();
	}



}
