/*****************************************************************************
 * Copyright (C) The Apache Software Foundation. All rights reserved.        *
 * ------------------------------------------------------------------------- *
 * This software is published under the terms of the Apache Software License *
 * version 1.1, a copy of which has been included with this distribution in  *
 * the LICENSE file.                                                         *
 *****************************************************************************/

package org.apache.batik.bridge;

import java.awt.Paint;
import java.awt.Stroke;
import java.awt.font.TextAttribute;
import java.text.AttributedCharacterIterator;

import org.apache.batik.dom.svg.SVGOMDocument;
import org.apache.batik.dom.svg.XMLBaseSupport;
import org.apache.batik.dom.util.XLinkSupport;
import org.apache.batik.gvt.font.Glyph;
import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;


/**
 * Bridge class for the &lt;altGlyph> element.
 *
 * @author <a href="mailto:bella.robinson@cmis.csiro.au">Bella Robinson</a>
 * @version $Id: SVGAltGlyphElementBridge.java,v 1.11 2003/04/11 13:54:44 vhardy Exp $
 */
public class SVGAltGlyphElementBridge extends AbstractSVGBridge
                                      implements ErrorConstants {

    /**
     * Constructs a new bridge for the &lt;altGlyph> element.
     */
    public SVGAltGlyphElementBridge() {
    }

    /**
     * Returns 'altGlyph'.
     */
    public String getLocalName() {
        return SVG_ALT_GLYPH_TAG;
    }

    /**
     * Constructs an array of Glyphs that represents the specified
     * &lt;altGlyph> element at the requested size.
     *
     * @param ctx The current bridge context.
     * @param altGlyphElement The altGlyph element to base the SVGGVTGlyphVector
     * construction on.
     * @param fontSize The font size of the Glyphs to create.
     *
     * @return The new SVGGVTGlyphVector or null if any of the glyphs are
     * unavailable.
     */
    public Glyph[] createAltGlyphArray(BridgeContext ctx,
                                       Element altGlyphElement,
                                       float fontSize,
                                       AttributedCharacterIterator aci) {

        // get the referenced element
        String uri = XLinkSupport.getXLinkHref(altGlyphElement);

        Element refElement = null;

        try {
            refElement = ctx.getReferencedElement(altGlyphElement, uri);
        } catch (BridgeException e) {
            if (ERR_URI_UNSECURE.equals(e.getCode())) {
                ctx.getUserAgent().displayError(e);
            }
        }

        if (refElement == null) {
            // couldn't find the referenced element
            return null;
        }

        // if the referenced element is a glyph
        if (refElement.getTagName().equals(SVG_GLYPH_TAG)) {

            Glyph glyph = getGlyph(ctx, uri, altGlyphElement, fontSize, aci);

            if (glyph == null) {
                // failed to create a glyph for the specified glyph uri
                return null;
            }

            Glyph[] glyphArray = new Glyph[1];
            glyphArray[0] = glyph;
            return glyphArray;
        }

        // else should be an altGlyphDef element
        if (refElement.getTagName().equals(SVG_ALT_GLYPH_DEF_TAG)) {

            // if not local import the referenced altGlyphDef
            // into the current document
            SVGOMDocument document
                = (SVGOMDocument)altGlyphElement.getOwnerDocument();
            SVGOMDocument refDocument
                = (SVGOMDocument)refElement.getOwnerDocument();
            boolean isLocal = (refDocument == document);

            Element localRefElement = (isLocal) ? refElement
                                 : (Element)document.importNode(refElement, true);
            if (!isLocal) {
                // need to attach the imported element to the document and
                // then compute the styles and uris
                String base = XMLBaseSupport.getCascadedXMLBase(altGlyphElement);
                Element g = document.createElementNS(SVG_NAMESPACE_URI, SVG_G_TAG);
                g.appendChild(localRefElement);
                g.setAttributeNS(XMLBaseSupport.XML_NAMESPACE_URI,
                                 "xml:base",
                                 base);
                CSSUtilities.computeStyleAndURIs(refElement, 
                                                 localRefElement, 
                                                 uri);
            }

            // look for glyphRef children
            NodeList altGlyphDefChildren = localRefElement.getChildNodes();
            boolean containsGlyphRefNodes = false;
            int numAltGlyphDefChildren = altGlyphDefChildren.getLength();
            for (int i = 0; i < numAltGlyphDefChildren; i++) {
                Node altGlyphChild = altGlyphDefChildren.item(i);
                if (altGlyphChild.getNodeType() == Node.ELEMENT_NODE) {
                    if (((Element)altGlyphChild).getTagName().equals(SVG_GLYPH_REF_TAG)) {
                        containsGlyphRefNodes = true;
                        break;
                    }
                }
            }
            if (containsGlyphRefNodes) { // process the glyphRef children

                NodeList glyphRefNodes
                    = localRefElement.getElementsByTagNameNS(SVG_NAMESPACE_URI,
							     SVG_GLYPH_REF_TAG);
                int numGlyphRefNodes = glyphRefNodes.getLength();
                Glyph[] glyphArray = new Glyph[numGlyphRefNodes];
                for (int i = 0; i < numGlyphRefNodes; i++) {
                    // get the referenced glyph element
                    Element glyphRefElement = (Element)glyphRefNodes.item(i);
                    String glyphUri = XLinkSupport.getXLinkHref(glyphRefElement);

                    Glyph glyph
                        = getGlyph(ctx, glyphUri, glyphRefElement, fontSize, aci);
                    if (glyph == null) {
                        // failed to create a glyph for the specified glyph uri
                        return null;
                    }
                    glyphArray[i] = glyph;
                }
                return glyphArray;

            } else { // try looking for altGlyphItem children

                NodeList altGlyphItemNodes
                    = localRefElement.getElementsByTagNameNS
		    (SVG_NAMESPACE_URI, SVG_ALT_GLYPH_ITEM_TAG);
                int numAltGlyphItemNodes = altGlyphItemNodes.getLength();
                if (numAltGlyphItemNodes > 0) {
                    boolean foundMatchingGlyph = false;
                    Glyph[] glyphArray = null;

                    //look through all altGlyphItem to find the one
                    //that have all its glyphs available

                    for (int i = 0; i < numAltGlyphItemNodes && !foundMatchingGlyph ; i++) {

                        // try to find a resolvable glyphRef
                        Element altGlyphItemElement = (Element)altGlyphItemNodes.item(i);
                        NodeList altGlyphRefNodes
                            = altGlyphItemElement.getElementsByTagNameNS
			    (SVG_NAMESPACE_URI, SVG_GLYPH_REF_TAG);
                        int numAltGlyphRefNodes = altGlyphRefNodes.getLength();

                        glyphArray = new Glyph[numAltGlyphRefNodes];

                        // consider that all glyphs are available
                        // and check if they can be found
                        foundMatchingGlyph = true;

                        for (int j = 0; j < numAltGlyphRefNodes; j++) {
                            // get the referenced glyph element
                            Element glyphRefElement = (Element)altGlyphRefNodes.item(j);
                            String glyphUri = XLinkSupport.getXLinkHref(glyphRefElement);

                            Glyph glyph = getGlyph(ctx, glyphUri, glyphRefElement, fontSize, aci);
                            if (glyph != null) {
                                // found a matching glyph for this altGlyphItem
                                glyphArray[j] = glyph;
                            }
                            else{
                                //this altGlyphItem is not good
                                //seek for the next one
                                foundMatchingGlyph = false;
                                break;
                            }
                        }
                    }
                    if (!foundMatchingGlyph) {
                        // couldn't find a  alGlyphItem
                        // with all its glyphs available
                        // so stop and return null
                        return null;
                    }
                    
                    return glyphArray;
                }
            }
        }


        /*
        // reference is not to a valid element type, throw an exception
        throw new BridgeException(altGlyphElement, ERR_URI_BAD_TARGET,
                                  new Object[] {uri});
        */
        //reference not valid, no altGlyph created
        return null;
    }


    /**
     * Returns a Glyph object that represents the glyph at the specified URI
     * scaled to the required font size.
     *
     * @param ctx The bridge context.
     * @param glyphUri The URI of the glyph to retreive.
     * @param altGlyphElement The element that references the glyph.
     * @param fontSize Indicates the required size of the glyph.
     * @return The Glyph or null if the glyph URI is not available.
     */
    private Glyph getGlyph(BridgeContext ctx,
                           String glyphUri,
                           Element altGlyphElement,
                           float fontSize,
                           AttributedCharacterIterator aci) {

        Element refGlyphElement = null;
        try {
            refGlyphElement = ctx.getReferencedElement(altGlyphElement, 
                                                       glyphUri);
        } catch (BridgeException e) {
            // this is ok, it is possible that the glyph at the given
            // uri is not available. 

            // Display an error message if a security exception occured
            if (ERR_URI_UNSECURE.equals(e.getCode())) {
                ctx.getUserAgent().displayError(e);
            }
        }

        if (refGlyphElement == null
            || !refGlyphElement.getTagName().equals(SVG_GLYPH_TAG)) {
            // couldn't find the referenced glyph element,
            // or referenced element not a glyph
            return null;
        }

        // see if the referenced glyph element is local
        SVGOMDocument document
            = (SVGOMDocument)altGlyphElement.getOwnerDocument();
        SVGOMDocument refDocument
            = (SVGOMDocument)refGlyphElement.getOwnerDocument();
        boolean isLocal = (refDocument == document);

        // if not local, import both the glyph and its font-face element
        Element localGlyphElement = null;
        Element localFontFaceElement = null;

        if (isLocal) {
            localGlyphElement = refGlyphElement;
            Element fontElement = (Element)localGlyphElement.getParentNode();
            NodeList fontFaceElements
                = fontElement.getElementsByTagNameNS
		(SVG_NAMESPACE_URI, SVG_FONT_FACE_TAG);
            if (fontFaceElements.getLength() > 0) {
                localFontFaceElement = (Element)fontFaceElements.item(0);
            }

        } else {

            // import the whole font
            Element localFontElement
                = (Element)document.importNode(refGlyphElement.getParentNode(), true);
            String base = XMLBaseSupport.getCascadedXMLBase(altGlyphElement);
            Element g = document.createElementNS(SVG_NAMESPACE_URI, SVG_G_TAG);
            g.appendChild(localFontElement);
            g.setAttributeNS(XMLBaseSupport.XML_NAMESPACE_URI,
                             "xml:base",
                             base);
            CSSUtilities.computeStyleAndURIs(
                (Element)refGlyphElement.getParentNode(), 
                localFontElement, glyphUri);

            // get the local glyph element
            String glyphId = refGlyphElement.getAttributeNS(null, SVG_ID_ATTRIBUTE);
            NodeList glyphElements = localFontElement.getElementsByTagNameNS
		(SVG_NAMESPACE_URI, SVG_GLYPH_TAG);
            for (int i = 0; i < glyphElements.getLength(); i++) {
                Element glyphElem = (Element)glyphElements.item(i);
                if (glyphElem.getAttributeNS(null, SVG_ID_ATTRIBUTE).equals(glyphId)) {
                    localGlyphElement = glyphElem;
                    break;
                }
            }
            // get the local font-face element
            NodeList fontFaceElements
                = localFontElement.getElementsByTagNameNS
		(SVG_NAMESPACE_URI, SVG_FONT_FACE_TAG);
            if (fontFaceElements.getLength() > 0) {
                localFontFaceElement = (Element)fontFaceElements.item(0);
            }
        }

        // if couldn't find the glyph or its font-face return null
        if (localGlyphElement == null || localFontFaceElement == null) {
            return null;
        }

        SVGFontFaceElementBridge fontFaceBridge
            = (SVGFontFaceElementBridge)ctx.getBridge(localFontFaceElement);
        SVGFontFace fontFace
            = fontFaceBridge.createFontFace(ctx, localFontFaceElement);
        SVGGlyphElementBridge glyphBridge
            = (SVGGlyphElementBridge)ctx.getBridge(localGlyphElement);

        aci.first();
        Paint fillPaint = (Paint)aci.getAttribute(TextAttribute.FOREGROUND);
        Paint strokePaint = (Paint)aci.getAttribute(
            GVTAttributedCharacterIterator.TextAttribute.STROKE_PAINT);
        Stroke stroke = (Stroke)aci.getAttribute(
            GVTAttributedCharacterIterator.TextAttribute.STROKE);

        return glyphBridge.createGlyph(ctx, localGlyphElement, altGlyphElement,
                                      -1, fontSize, fontFace,
                                      fillPaint, strokePaint, stroke);
    }
}
