/*
 * Copyright (c) 2009-2010 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.jme3.font;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.jme3.font.BitmapFont.Align;
import com.jme3.font.BitmapFont.VAlign;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.Node;

/**
 * @author YongHoon
 */
public class BitmapText extends Node {
    private BitmapFont font;
    private StringBlock block;
    private boolean needRefresh = true;
    private final BitmapTextPage[] textPages;
    private Letters letters;

    public BitmapText(BitmapFont font) {
        this(font, false, false);
    }

    public BitmapText(BitmapFont font, boolean rightToLeft) {
        this(font, rightToLeft, false);
    }

    public BitmapText(BitmapFont font, boolean rightToLeft, boolean arrayBased) {
        textPages = new BitmapTextPage[font.getPageSize()];
        for (int page = 0; page < textPages.length; page++) {
            textPages[page] = new BitmapTextPage(font, arrayBased, page);
            attachChild(textPages[page]);
        }

        this.font = font;
        this.block = new StringBlock();
        block.setSize(font.getPreferredSize());
        letters = new Letters(font, block, rightToLeft);
    }

    @Override
    public BitmapText clone() {
        BitmapText clone = (BitmapText) super.clone();
        for (int i = 0; i < textPages.length; i++) {
            clone.textPages[i] = textPages[i].clone();
        }
        clone.block = block.clone();
        clone.needRefresh = true;
        return clone;
    }

    public BitmapFont getFont() {
        return font;
    }
    
    /**
     * Changes text size
     * @param size text size
     */
    public void setSize(float size) {
        block.setSize(size);
        needRefresh = true;
        letters.invalidate();
    }

    /**
     *
     * @param text charsequence to change text to
     */
    public void setText(CharSequence text) {
        // note: text.toString() is free if text is already a java.lang.String.
        setText( text != null ? text.toString() : null );
    }

    /**
     *
     * @param text String to change text to
     */
    public void setText(String text) {
        text = text == null ? "" : text;
        if (text == block.getText() || block.getText().equals(text)) {
            return;
        }

        block.setText(text);
        letters.setText(text);
        needRefresh = true;
    }

    /**
     * @return returns text
     */
    public String getText() {
        return block.getText();
    }

    /**
     * @return color of the text
     */
    public ColorRGBA getColor() {
        return letters.getBaseColor();
    }

    /**
     * changes text color. all substring colors are deleted.
     * @param color new color of text
     */
    public void setColor(ColorRGBA color) {
        letters.setColor(color);
        letters.invalidate(); // TODO: Don't have to align.
        needRefresh = true;
    }

    /**
     * Define area where bitmaptext will be rendered
     * @param rect position and size box where text is rendered
     */
    public void setBox(Rectangle rect) {
        block.setTextBox(rect);
        letters.invalidate();
        needRefresh = true;
    }
    
    /**
     * @return height of the line
     */
    public float getLineHeight() {
        return font.getLineHeight(block);
    }
    
    /**
     * @return height of whole textblock
     */
    public float getHeight() {
        if (needRefresh) {
            assemble();
        }
        float height = getLineHeight()*block.getLineCount();
        Rectangle textBox = block.getTextBox();
        if (textBox != null) {
            return Math.max(height, textBox.height);
        }
        return height;
    }
    
    /**
     * @return width of line
     */
    public float getLineWidth() {
        if (needRefresh) {
            assemble();
        }
        Rectangle textBox = block.getTextBox();
        if (textBox != null) {
            return Math.max(letters.getTotalWidth(), textBox.width);
        }
        return letters.getTotalWidth();
    }
    
    /**
     * @return line count
     */
    public int getLineCount() {
        if (needRefresh) {
            assemble();
        }
        return block.getLineCount();
    }
    
    public LineWrapMode getLineWrapMode() {
        return block.getLineWrapMode();
    }
    
    /**
     * Set horizontal alignment. Applicable only when text bound is set.
     * @param align
     */
    public void setAlignment(BitmapFont.Align align) {
        if (block.getTextBox() == null && align != Align.Left) {
            throw new RuntimeException("Bound is not set");
        }
        block.setAlignment(align);
        letters.invalidate();
        needRefresh = true;
    }
    
    /**
     * Set vertical alignment. Applicable only when text bound is set.
     * @param align
     */
    public void setVerticalAlignment(BitmapFont.VAlign align) {
        if (block.getTextBox() == null && align != VAlign.Top) {
            throw new RuntimeException("Bound is not set");
        }
        block.setVerticalAlignment(align);
        letters.invalidate();
        needRefresh = true;
    }
    
    public BitmapFont.Align getAlignment() {
        return block.getAlignment();
    }
    
    public BitmapFont.VAlign getVerticalAlignment() {
        return block.getVerticalAlignment();
    }
    
    /**
     * Set the font style of substring. If font doesn't contain style, default style is used
     * @param start start index to set style. inclusive.
     * @param end   end index to set style. EXCLUSIVE.
     * @param style
     */
    public void setStyle(int start, int end, int style) {
        letters.setStyle(start, end, style);
    }
    
    /**
     * Set the font style of substring. If font doesn't contain style, default style is applied
     * @param regexp regular expression
     * @param style
     */
    public void setStyle(String regexp, int style) {
        Pattern p = Pattern.compile(regexp);
        Matcher m = p.matcher(block.getText());
        while (m.find()) {
            setStyle(m.start(), m.end(), style);
        }
    }
    
    /**
     * Set the color of substring.
     * @param start start index to set style. inclusive.
     * @param end   end index to set style. EXCLUSIVE.
     * @param color
     */
    public void setColor(int start, int end, ColorRGBA color) {
        letters.setColor(start, end, color);
        letters.invalidate();
        needRefresh = true;
    }
    
    /**
     * Set the color of substring.
     * @param regexp regular expression
     * @param color
     */
    public void setColor(String regexp, ColorRGBA color) {
        Pattern p = Pattern.compile(regexp);
        Matcher m = p.matcher(block.getText());
        while (m.find()) {
            letters.setColor(m.start(), m.end(), color);
        }
        letters.invalidate();
        needRefresh = true;
    }
    
    /**
     * @param tabs tab positions
     */
    public void setTabPosition(float... tabs) {
        block.setTabPosition(tabs);
        letters.invalidate();
        needRefresh = false;
    }
    
    /**
     * used for the tabs over the last tab position.
     * @param width tab size
     */
    public void setTabWidth(float width) {
        block.setTabWidth(width);
        letters.invalidate();
        needRefresh = false;
    }
    
    /**
     * for setLineWrapType(LineWrapType.NoWrap),
     * set the last character when the text exceeds the bound.
     * @param c
     */
    public void setEllipsisChar(char c) {
        block.setEllipsisChar(c);
        letters.invalidate();
        needRefresh = false;
    }

    /**
     * Available only when bounding is set. <code>setBox()</code> method call is needed in advance.
     * true when
     * @param wrap NoWrap   : Letters over the text bound is not shown. the last character is set to '...'(0x2026)
     *             Character: Character is split at the end of the line.
     *             Word     : Word is split at the end of the line.
     */
    public void setLineWrapMode(LineWrapMode wrap) {
        if (block.getLineWrapMode() != wrap) {
            block.setLineWrapMode(wrap);
            letters.invalidate();
            needRefresh = true;
        }
    }

    @Override
    public void updateLogicalState(float tpf) {
        super.updateLogicalState(tpf);
        if (needRefresh) {
            assemble();
        }
    }

    private void assemble() {
        // first generate quadlist
        letters.update();
        
        for (int i = 0; i < textPages.length; i++) {
            textPages[i].assemble(letters);
        }
        needRefresh = false;
    }
    
    public void render(RenderManager rm) {
        for (BitmapTextPage page : textPages) {
            Material mat = page.getMaterial();
            mat.setTexture("Texture", page.getTexture());
            mat.render(page, rm);
        }
    }
}
