/*
 * SVG Salamander
 * Copyright (c) 2004, Mark McKay
 * 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.
 *
 * 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 HOLDER 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. 
 * 
 * Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
 * projects can be found at http://www.kitfox.com
 *
 * Created on January 26, 2004, 1:56 AM
 */
package com.kitfox.svg;

import com.kitfox.svg.util.FontSystem;
import com.kitfox.svg.xml.StyleAttribute;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Mark McKay
 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
 */
public class Text extends ShapeElement
{
    public static final String TAG_NAME = "text";
    
    float x = 0;
    float y = 0;
    AffineTransform transform = null;
    String fontFamily;
    float fontSize;
    //List of strings and tspans containing the content of this node
    LinkedList content = new LinkedList();
    Shape textShape;
    public static final int TXAN_START = 0;
    public static final int TXAN_MIDDLE = 1;
    public static final int TXAN_END = 2;
    int textAnchor = TXAN_START;
    public static final int TXST_NORMAL = 0;
    public static final int TXST_ITALIC = 1;
    public static final int TXST_OBLIQUE = 2;
    int fontStyle;
    public static final int TXWE_NORMAL = 0;
    public static final int TXWE_BOLD = 1;
    public static final int TXWE_BOLDER = 2;
    public static final int TXWE_LIGHTER = 3;
    public static final int TXWE_100 = 4;
    public static final int TXWE_200 = 5;
    public static final int TXWE_300 = 6;
    public static final int TXWE_400 = 7;
    public static final int TXWE_500 = 8;
    public static final int TXWE_600 = 9;
    public static final int TXWE_700 = 10;
    public static final int TXWE_800 = 11;
    public static final int TXWE_900 = 12;
    int fontWeight;

    float textLength = -1;
    String lengthAdjust = "spacing";
    
    /**
     * Creates a new instance of Stop
     */
    public Text()
    {
    }

    public String getTagName()
    {
        return TAG_NAME;
    }

    public void appendText(String text)
    {
        content.addLast(text);
    }

    public void appendTspan(Tspan tspan) throws SVGElementException
    {
        super.loaderAddChild(null, tspan);
        content.addLast(tspan);
    }

    /**
     * Discard cached information
     */
    public void rebuild() throws SVGException
    {
        build();
    }

    public java.util.List getContent()
    {
        return content;
    }

    /**
     * Called after the start element but before the end element to indicate
     * each child tag that has been processed
     */
    public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException
    {
        super.loaderAddChild(helper, child);

        content.addLast(child);
    }

    /**
     * Called during load process to add text scanned within a tag
     */
    public void loaderAddText(SVGLoaderHelper helper, String text)
    {
        Matcher matchWs = Pattern.compile("\\s*").matcher(text);
        if (!matchWs.matches())
        {
            content.addLast(text);
        }
    }

    public void build() throws SVGException
    {
        super.build();

        StyleAttribute sty = new StyleAttribute();

        if (getPres(sty.setName("x")))
        {
            x = sty.getFloatValueWithUnits();
        }

        if (getPres(sty.setName("y")))
        {
            y = sty.getFloatValueWithUnits();
        }

        if (getStyle(sty.setName("font-family")))
        {
            fontFamily = sty.getStringValue();
        }
        else
        {
            fontFamily = "Sans Serif";
        }

        if (getStyle(sty.setName("font-size")))
        {
            fontSize = sty.getFloatValueWithUnits();
        }
        else
        {
            fontSize = 12f;
        }

        if (getStyle(sty.setName("textLength")))
        {
            textLength = sty.getFloatValueWithUnits();
        }
        else
        {
            textLength = -1;
        }

        if (getStyle(sty.setName("lengthAdjust")))
        {
            lengthAdjust = sty.getStringValue();
        }
        else
        {
            lengthAdjust = "spacing";
        }

        if (getStyle(sty.setName("font-style")))
        {
            String s = sty.getStringValue();
            if ("normal".equals(s))
            {
                fontStyle = TXST_NORMAL;
            } else if ("italic".equals(s))
            {
                fontStyle = TXST_ITALIC;
            } else if ("oblique".equals(s))
            {
                fontStyle = TXST_OBLIQUE;
            }
        } else
        {
            fontStyle = TXST_NORMAL;
        }

        if (getStyle(sty.setName("font-weight")))
        {
            String s = sty.getStringValue();
            if ("normal".equals(s))
            {
                fontWeight = TXWE_NORMAL;
            } else if ("bold".equals(s))
            {
                fontWeight = TXWE_BOLD;
            }
        } else
        {
            fontWeight = TXWE_NORMAL;
        }

        if (getStyle(sty.setName("text-anchor")))
        {
            String s = sty.getStringValue();
            if (s.equals("middle"))
            {
                textAnchor = TXAN_MIDDLE;
            } else if (s.equals("end"))
            {
                textAnchor = TXAN_END;
            } else
            {
                textAnchor = TXAN_START;
            }
        } else
        {
            textAnchor = TXAN_START;
        }

        //text anchor
        //text-decoration
        //text-rendering

        buildText();
    }

    protected void buildText() throws SVGException
    {
        //Get font
        String[] families = fontFamily.split(",");
        Font font = null;
        for (int i = 0; i < families.length; ++i)
        {
            font = diagram.getUniverse().getFont(fontFamily);
            if (font != null)
            {
                break;
            }
        }
        
        if (font == null)
        {
//            System.err.println("Could not load font");

            font = new FontSystem(fontFamily, fontStyle, fontWeight, (int)fontSize);
//            java.awt.Font sysFont = new java.awt.Font(fontFamily, style | weight, (int)fontSize);
//            buildSysFont(sysFont);
//            return;
        }

//        font = new java.awt.Font(font.getFamily(), style | weight, font.getSize());

//        Area textArea = new Area();
        GeneralPath textPath = new GeneralPath();
        textShape = textPath;

        float cursorX = x, cursorY = y;

        FontFace fontFace = font.getFontFace();
        //int unitsPerEm = fontFace.getUnitsPerEm();
//        int ascent = fontFace.getAscent();
//        float fontScale = fontSize / (float) ascent;

//        AffineTransform oldXform = g.getTransform();
//        TextBuilder builder = new TextBuilder();
//        
//        for (Iterator it = content.iterator(); it.hasNext();)
//        {
//            Object obj = it.next();
//
//            if (obj instanceof String)
//            {
//                String text = (String) obj;
//                if (text != null)
//                {
//                    text = text.trim();
//                }
//                
//                for (int i = 0; i < text.length(); i++)
//                {
//                    String unicode = text.substring(i, i + 1);
//                    MissingGlyph glyph = font.getGlyph(unicode);
//                    
//                    builder.appendGlyph(glyph);
//                }
//            }
//            else if (obj instanceof Tspan)
//            {
//                Tspan tspan = (Tspan)obj;
//                tspan.buildGlyphs(builder);
//            }
//        }
//
//        builder.formatGlyphs();
        
        

                
                
        
        
        
        AffineTransform xform = new AffineTransform();

        for (Iterator it = content.iterator(); it.hasNext();)
        {
            Object obj = it.next();

            if (obj instanceof String)
            {
                String text = (String) obj;
                if (text != null)
                {
                    text = text.trim();
                }

//                strokeWidthScalar = 1f / fontScale;

                for (int i = 0; i < text.length(); i++)
                {
                    xform.setToIdentity();
                    xform.setToTranslation(cursorX, cursorY);
//                    xform.scale(fontScale, fontScale);
//                    g.transform(xform);

                    String unicode = text.substring(i, i + 1);
                    MissingGlyph glyph = font.getGlyph(unicode);

                    Shape path = glyph.getPath();
                    if (path != null)
                    {
                        path = xform.createTransformedShape(path);
                        textPath.append(path, false);
                    }
//                    else glyph.render(g);

//                    cursorX += fontScale * glyph.getHorizAdvX();
                    cursorX += glyph.getHorizAdvX();

//                    g.setTransform(oldXform);
                }

                strokeWidthScalar = 1f;
            }
            else if (obj instanceof Tspan)
            {
//                Tspan tspan = (Tspan) obj;
//
//                xform.setToIdentity();
//                xform.setToTranslation(cursorX, cursorY);
//                xform.scale(fontScale, fontScale);
////                tspan.setCursorX(cursorX);
////                tspan.setCursorY(cursorY);
//
//                Shape tspanShape = tspan.getShape();
//                tspanShape = xform.createTransformedShape(tspanShape);
//                textPath.append(tspanShape, false);
////                tspan.render(g);
////                cursorX = tspan.getCursorX();
////                cursorY = tspan.getCursorY();
                
                
                Tspan tspan = (Tspan)obj;
                Point2D cursor = new Point2D.Float(cursorX, cursorY);
//                tspan.setCursorX(cursorX);
//                tspan.setCursorY(cursorY);
                tspan.appendToShape(textPath, cursor);
//                cursorX = tspan.getCursorX();
//                cursorY = tspan.getCursorY();
                cursorX = (float)cursor.getX();
                cursorY = (float)cursor.getY();
                
            }

        }

        switch (textAnchor)
        {
            case TXAN_MIDDLE:
            {
                AffineTransform at = new AffineTransform();
                at.translate(-textPath.getBounds().getWidth() / 2, 0);
                textPath.transform(at);
                break;
            }
            case TXAN_END:
            {
                AffineTransform at = new AffineTransform();
                at.translate(-textPath.getBounds().getWidth(), 0);
                textPath.transform(at);
                break;
            }
        }
    }

//    private void buildSysFont(java.awt.Font font) throws SVGException
//    {
//        GeneralPath textPath = new GeneralPath();
//        textShape = textPath;
//
//        float cursorX = x, cursorY = y;
//
////        FontMetrics fm = g.getFontMetrics(font);
//        FontRenderContext frc = new FontRenderContext(null, true, true);
//
////        FontFace fontFace = font.getFontFace();
//        //int unitsPerEm = fontFace.getUnitsPerEm();
////        int ascent = fm.getAscent();
////        float fontScale = fontSize / (float)ascent;
//
////        AffineTransform oldXform = g.getTransform();
//        AffineTransform xform = new AffineTransform();
//
//        for (Iterator it = content.iterator(); it.hasNext();)
//        {
//            Object obj = it.next();
//
//            if (obj instanceof String)
//            {
//                String text = (String)obj;
//                text = text.trim();
//
//                Shape textShape = font.createGlyphVector(frc, text).getOutline(cursorX, cursorY);
//                textPath.append(textShape, false);
////                renderShape(g, textShape);
////                g.drawString(text, cursorX, cursorY);
//
//                Rectangle2D rect = font.getStringBounds(text, frc);
//                cursorX += (float) rect.getWidth();
//            } else if (obj instanceof Tspan)
//            {
//                /*
//                 Tspan tspan = (Tspan)obj;
//                 
//                 xform.setToIdentity();
//                 xform.setToTranslation(cursorX, cursorY);
//                 
//                 Shape tspanShape = tspan.getShape();
//                 tspanShape = xform.createTransformedShape(tspanShape);
//                 textArea.add(new Area(tspanShape));
//                 
//                 cursorX += tspanShape.getBounds2D().getWidth();
//                 */
//
//
//                Tspan tspan = (Tspan)obj;
//                Point2D cursor = new Point2D.Float(cursorX, cursorY);
////                tspan.setCursorX(cursorX);
////                tspan.setCursorY(cursorY);
//                tspan.appendToShape(textPath, cursor);
////                cursorX = tspan.getCursorX();
////                cursorY = tspan.getCursorY();
//                cursorX = (float)cursor.getX();
//                cursorY = (float)cursor.getY();
//
//            }
//        }
//
//        switch (textAnchor)
//        {
//            case TXAN_MIDDLE:
//            {
//                AffineTransform at = new AffineTransform();
//                at.translate(-textPath.getBounds().getWidth() / 2, 0);
//                textPath.transform(at);
//                break;
//            }
//            case TXAN_END:
//            {
//                AffineTransform at = new AffineTransform();
//                at.translate(-Math.ceil(textPath.getBounds().getWidth()), 0);
//                textPath.transform(at);
//                break;
//            }
//        }
//    }

    public void render(Graphics2D g) throws SVGException
    {
        beginLayer(g);
        renderShape(g, textShape);
        finishLayer(g);
    }

    public Shape getShape()
    {
        return shapeToParent(textShape);
    }

    public Rectangle2D getBoundingBox() throws SVGException
    {
        return boundsToParent(includeStrokeInBounds(textShape.getBounds2D()));
    }

    /**
     * Updates all attributes in this diagram associated with a time event. Ie,
     * all attributes with track information.
     *
     * @return - true if this node has changed state as a result of the time
     * update
     */
    public boolean updateTime(double curTime) throws SVGException
    {
//        if (trackManager.getNumTracks() == 0) return false;
        boolean changeState = super.updateTime(curTime);

        //Get current values for parameters
        StyleAttribute sty = new StyleAttribute();
        boolean shapeChange = false;

        if (getPres(sty.setName("x")))
        {
            float newVal = sty.getFloatValueWithUnits();
            if (newVal != x)
            {
                x = newVal;
                shapeChange = true;
            }
        }

        if (getPres(sty.setName("y")))
        {
            float newVal = sty.getFloatValueWithUnits();
            if (newVal != y)
            {
                y = newVal;
                shapeChange = true;
            }
        }

        if (getStyle(sty.setName("textLength")))
        {
            textLength = sty.getFloatValueWithUnits();
        }
        else
        {
            textLength = -1;
        }

        if (getStyle(sty.setName("lengthAdjust")))
        {
            lengthAdjust = sty.getStringValue();
        }
        else
        {
            lengthAdjust = "spacing";
        }

        if (getPres(sty.setName("font-family")))
        {
            String newVal = sty.getStringValue();
            if (!newVal.equals(fontFamily))
            {
                fontFamily = newVal;
                shapeChange = true;
            }
        }

        if (getPres(sty.setName("font-size")))
        {
            float newVal = sty.getFloatValueWithUnits();
            if (newVal != fontSize)
            {
                fontSize = newVal;
                shapeChange = true;
            }
        }


        if (getStyle(sty.setName("font-style")))
        {
            String s = sty.getStringValue();
            int newVal = fontStyle;
            if ("normal".equals(s))
            {
                newVal = TXST_NORMAL;
            } else if ("italic".equals(s))
            {
                newVal = TXST_ITALIC;
            } else if ("oblique".equals(s))
            {
                newVal = TXST_OBLIQUE;
            }
            if (newVal != fontStyle)
            {
                fontStyle = newVal;
                shapeChange = true;
            }
        }

        if (getStyle(sty.setName("font-weight")))
        {
            String s = sty.getStringValue();
            int newVal = fontWeight;
            if ("normal".equals(s))
            {
                newVal = TXWE_NORMAL;
            } else if ("bold".equals(s))
            {
                newVal = TXWE_BOLD;
            }
            if (newVal != fontWeight)
            {
                fontWeight = newVal;
                shapeChange = true;
            }
        }

        if (shapeChange)
        {
            build();
//            buildFont();
//            return true;
        }

        return changeState || shapeChange;
    }
}
