/*
 * Copyright (c) 2006 Wisconsin Court System
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 */
package org.xhtmlrenderer.render;

import org.w3c.dom.Element;
import org.w3c.dom.Text;
import org.xhtmlrenderer.css.constants.IdentValue;
import org.xhtmlrenderer.css.extend.ContentFunction;
import org.xhtmlrenderer.css.parser.FSFunction;
import org.xhtmlrenderer.css.style.CalculatedStyle;
import org.xhtmlrenderer.layout.LayoutContext;
import org.xhtmlrenderer.layout.Styleable;
import org.xhtmlrenderer.layout.TextUtil;
import org.xhtmlrenderer.layout.WhitespaceStripper;

/**
 * A class which reprsents a portion of an inline element. If an inline element
 * does not contain any nested elements, then a single <code>InlineBox</code>
 * object will contain the content for the entire element. Otherwise multiple
 * <code>InlineBox</code> objects will be created corresponding to each
 * discrete chunk of text appearing in the elment. It is not rendered directly
 * (and hence does not extend from {@link Box}), but does play an important
 * role in layout (for example, when calculating min/max widths). Note that it
 * does not contain children. Inline content is stored as a flat list in the
 * layout tree. However, <code>InlineBox</code> does contain enough
 * information to reconstruct the original element nesting and this is, in fact,
 * done during inline layout.
 * 
 * @see InlineLayoutBox
 */
public class InlineBox implements Styleable {
    private Element _element;
    
    private String _originalText;
    private String _text;
    private boolean _removableWhitespace;
    private boolean _startsHere;
    private boolean _endsHere;
    
    private CalculatedStyle _style;
    
    private ContentFunction _contentFunction;
    private FSFunction _function;
    
    private boolean _minMaxCalculated;
    private int _maxWidth;
    private int _minWidth;
    
    private int _firstLineWidth;
    
    private String _pseudoElementOrClass;

    private final Text _textNode;
    
    public InlineBox(String text, Text textNode) {
        _text = text;
        _originalText = text;
        _textNode = textNode;
    }
    
    public String getText() {
        return _text;
    }
    
    public void setText(String text) {
        _text = text;
        _originalText = text;
    }
    
    public void applyTextTransform() {
        _text = _originalText;
        _text = TextUtil.transformText(_text, getStyle());
    }

    public boolean isRemovableWhitespace() {
        return _removableWhitespace;
    }

    public void setRemovableWhitespace(boolean removeableWhitespace) {
        _removableWhitespace = removeableWhitespace;
    }

    public boolean isEndsHere() {
        return _endsHere;
    }

    public void setEndsHere(boolean endsHere) {
        _endsHere = endsHere;
    }

    public boolean isStartsHere() {
        return _startsHere;
    }

    public void setStartsHere(boolean startsHere) {
        _startsHere = startsHere;
    }

    public CalculatedStyle getStyle() {
        return _style;
    }

    public void setStyle(CalculatedStyle style) {
        _style = style;
    }

    public Element getElement() {
        return _element;
    }

    public void setElement(Element element) {
        _element = element;
    }

    public ContentFunction getContentFunction() {
        return _contentFunction;
    }

    public void setContentFunction(ContentFunction contentFunction) {
        _contentFunction = contentFunction;
    }

    public boolean isDynamicFunction() {
        return _contentFunction != null;
    }
    
    private int getTextWidth(LayoutContext c, String s) {
        return c.getTextRenderer().getWidth(
                c.getFontContext(), 
                c.getFont(getStyle().getFont(c)), 
                s);
    }
    
    private void calcMaxWidthFromLineLength(LayoutContext c, int cbWidth, boolean trim) {
        int last = 0;
        int current = 0;
        
        while ( (current = _text.indexOf(WhitespaceStripper.EOL, last)) != -1) {
            String target = _text.substring(last, current);
            if (trim) {
                target = target.trim();
            }
            int length = getTextWidth(c, target);
            if (last == 0) {
                length += getStyle().getMarginBorderPadding(c, cbWidth, CalculatedStyle.LEFT);
            }
            if (length > _maxWidth) {
                _maxWidth = length;
            }
            if (last == 0) {
                _firstLineWidth = length;
            }
            last = current + 1;
        }
        
        String target = _text.substring(last);
        if (trim) {
            target = target.trim();
        }
        int length = getTextWidth(c, target);
        length += getStyle().getMarginBorderPadding(c, cbWidth, CalculatedStyle.RIGHT);
        if (length > _maxWidth) {
            _maxWidth = length;
        }
        if (last == 0) {
            _firstLineWidth = length;
        }        
    }
    
    public int getSpaceWidth(LayoutContext c) {
        return c.getTextRenderer().getWidth(
                c.getFontContext(), 
                getStyle().getFSFont(c), 
                WhitespaceStripper.SPACE);
        
    }
    
    public int getTrailingSpaceWidth(LayoutContext c) {
        if (_text.length() > 0 && _text.charAt(_text.length()-1) == ' ') {
            return getSpaceWidth(c);
        } else {
            return 0;
        }
    }
    
    private int calcMinWidthFromWordLength(
            LayoutContext c, int cbWidth, boolean trimLeadingSpace, boolean includeWS) {
        int spaceWidth = getSpaceWidth(c);
        
        int last = 0;
        int current = 0;
        int maxWidth = 0;
        int spaceCount = 0;
        
        boolean haveFirstWord = false;
        int firstWord = 0;
        int lastWord = 0;
        
        String text = getText(trimLeadingSpace);
        
        while ( (current = text.indexOf(WhitespaceStripper.SPACE, last)) != -1) {
            int length = getTextWidth(c, text.substring(last, current));
            if (spaceCount > 0) {
                if (includeWS) {
                    for (int i = 0; i < spaceCount; i++) {
                        length += spaceWidth;
                    }
                }
                spaceCount = 0;
            }
            if (length > 0) {
                if (! haveFirstWord) {
                    firstWord = length;
                }
                lastWord = length;
            }
            
            if (length > _minWidth) {
                _minWidth = length;
            }
            maxWidth += length;
            if (! includeWS) {
                maxWidth += spaceWidth;
            }
            
            last = current;
            for (int i = current; i < text.length(); i++) {
                if (text.charAt(i) == ' ') {
                    spaceCount++;
                    last++;
                } else {
                    break;
                }
            }
        }
        
        int length = getTextWidth(c, text.substring(last));
        if (spaceCount > 0) {
            if (includeWS) {
                for (int i = 0; i < spaceCount; i++) {
                    length += spaceWidth;
                }
            }
            spaceCount = 0;
        }
        if (length > 0) {
            if (! haveFirstWord) {
                firstWord = length;
            }
            lastWord = length;
        }        
        if (length > _minWidth) {
            _minWidth = length;
        }
        maxWidth += length;
        
        if (isStartsHere()) {
            int leftMBP = getStyle().getMarginBorderPadding(c, cbWidth, CalculatedStyle.LEFT);
            if (firstWord + leftMBP > _minWidth) {
                _minWidth = firstWord + leftMBP;
            }
            maxWidth += leftMBP;
        } 
        
        if (isEndsHere()) {
            int rightMBP = getStyle().getMarginBorderPadding(c, cbWidth, CalculatedStyle.RIGHT);
            if (lastWord + rightMBP > _minWidth) {
                _minWidth = lastWord + rightMBP;
            }
            maxWidth += rightMBP;
        }
        
        return maxWidth;
    }
    
    private String getText(boolean trimLeadingSpace) {
        if (! trimLeadingSpace) {
            return getText();
        } else {
            if (_text.length() > 0 && _text.charAt(0) == ' ') {
                return _text.substring(1);
            } else {
                return _text;
            }
        }
    }
    
    private int getInlineMBP(LayoutContext c, int cbWidth) {
        return getStyle().getMarginBorderPadding(c, cbWidth, CalculatedStyle.LEFT) +
            getStyle().getMarginBorderPadding(c, cbWidth, CalculatedStyle.RIGHT);
    }
    
    public void calcMinMaxWidth(LayoutContext c, int cbWidth, boolean trimLeadingSpace) {
        if (! _minMaxCalculated) {
            IdentValue whitespace = getStyle().getWhitespace();
            if (whitespace == IdentValue.NOWRAP) {
                _minWidth = _maxWidth = getInlineMBP(c, cbWidth) + getTextWidth(c, getText(trimLeadingSpace));
            } else if (whitespace == IdentValue.PRE) {
                calcMaxWidthFromLineLength(c, cbWidth, false);
                _minWidth = _maxWidth;
            } else if (whitespace == IdentValue.PRE_WRAP) {
                calcMinWidthFromWordLength(c, cbWidth, false, true);
                calcMaxWidthFromLineLength(c, cbWidth, false);
            } else if (whitespace == IdentValue.PRE_LINE) {
                calcMinWidthFromWordLength(c, cbWidth, trimLeadingSpace, false);
                calcMaxWidthFromLineLength(c, cbWidth, true);
            } else /* if (whitespace == IdentValue.NORMAL) */ {
                _maxWidth = calcMinWidthFromWordLength(c, cbWidth, trimLeadingSpace, false);
            }
            
            _minMaxCalculated = true;
        }
    }
    
    public int getMaxWidth() {
        return _maxWidth;
    }
    
    public int getMinWidth() {
        return _minWidth;
    }
    
    public int getFirstLineWidth() {
        return _firstLineWidth;
    }

    public String getPseudoElementOrClass() {
        return _pseudoElementOrClass;
    }

    public void setPseudoElementOrClass(String pseudoElementOrClass) {
        _pseudoElementOrClass = pseudoElementOrClass;
    }
    
    public String toString() {
        StringBuffer result = new StringBuffer();
        result.append("InlineBox: ");
        if (getElement() != null) {
            result.append("<");
            result.append(getElement().getNodeName());
            result.append("> ");
        } else {
            result.append("(anonymous) ");
        } 
        if (getPseudoElementOrClass() != null) {
            result.append(':');
            result.append(getPseudoElementOrClass());
            result.append(' ');
        }
        if (isStartsHere() || isEndsHere()) {
            result.append("(");
            if (isStartsHere()) {
                result.append("S");
            }
            if (isEndsHere()) {
                result.append("E");
            }
            result.append(") ");
        }
        
        appendPositioningInfo(result);
        
        result.append("(");
        result.append(shortText());
        result.append(") ");
        return result.toString();
    }
    
    protected void appendPositioningInfo(StringBuffer result) {
        if (getStyle().isRelative()) {
            result.append("(relative) ");
        }
        if (getStyle().isFixed()) {
            result.append("(fixed) ");
        }
        if (getStyle().isAbsolute()) {
            result.append("(absolute) ");
        }
        if (getStyle().isFloated()) {
            result.append("(floated) ");
        }
    } 
    
    private String shortText() {
        if (_text == null) {
            return null;
        } else {
            StringBuffer result = new StringBuffer();
            for (int i = 0; i < _text.length() && i < 40; i++) {
                char c = _text.charAt(i);
                if (c == '\n') {
                    result.append(' ');
                } else {
                    result.append(c);
                }
            }
            if (result.length() == 40) {
                result.append("...");
            }
            return result.toString();
        }
    }

    public FSFunction getFunction() {
        return _function;
    }

    public void setFunction(FSFunction function) {
        _function = function;
    }
    
    public void truncateText() {
        _text = "";
        _originalText = "";
    }

    public Text getTextNode() {
        return this._textNode;
    }
}
