/*****************************************************************************
 * 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.gvt.font;

import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphJustificationInfo;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.text.AttributedCharacterIterator;
import java.util.Iterator;
import java.util.List;

import org.apache.batik.gvt.text.AttributedCharacterSpanIterator;

public class MultiGlyphVector implements GVTGlyphVector {

    GVTGlyphVector [] gvs;
    int [] nGlyphs;
    int [] off;

    int nGlyph;

    public MultiGlyphVector(List gvs) {
        this.gvs     = new GVTGlyphVector[gvs.size()];
        this.nGlyphs = new int[gvs.size()];
        this.off     = new int[gvs.size()];

        Iterator iter = gvs.iterator();
        int i=0;
        while (iter.hasNext()) {
            off[i]      = nGlyph;

            GVTGlyphVector gv = (GVTGlyphVector)iter.next();
            this.gvs[i] = gv;
            nGlyphs[i]  = gv.getNumGlyphs();
            nGlyph     += nGlyphs[i];
            i++;
        }
        nGlyphs[i-1]++;
    }

    /**
     * Returns the number of glyphs in this GlyphVector.
     */
    public int getNumGlyphs() {
        return nGlyph;
    }


    int getGVIdx(int glyphIdx) {
        if (glyphIdx > nGlyph) return -1;
        if (glyphIdx == nGlyph) return gvs.length-1;
        for (int i=0; i<nGlyphs.length; i++)
            if (glyphIdx-off[i] < nGlyphs[i]) return i;
        return -1;
    }

    /**
     * Returns the Font associated with this GlyphVector.
     */
    public GVTFont getFont() {
        throw new IllegalArgumentException("Can't be correctly Implemented");
    }

    /**
     * Returns the FontRenderContext associated with this GlyphVector.
     */
    public FontRenderContext getFontRenderContext() {
        return gvs[0].getFontRenderContext();
    }

    /**
     * Returns the glyphcode of the specified glyph.
     */
    public int getGlyphCode(int glyphIndex) {
        int idx = getGVIdx(glyphIndex);
        return gvs[idx].getGlyphCode(glyphIndex-off[idx]);
    }

    /**
     * Returns the justification information for the glyph at the specified
     * index into this GlyphVector.
     */
    public GlyphJustificationInfo getGlyphJustificationInfo(int glyphIndex) {
        int idx = getGVIdx(glyphIndex);
        return gvs[idx].getGlyphJustificationInfo(glyphIndex-off[idx]);
    }

    /**
     *  Returns the logical bounds of the specified glyph within this
     *  GlyphVector.
     */
    public Shape getGlyphLogicalBounds(int glyphIndex) {
        int idx = getGVIdx(glyphIndex);
        return gvs[idx].getGlyphLogicalBounds(glyphIndex-off[idx]);
    }

    /**
     * Returns the metrics of the glyph at the specified index into this
     * GlyphVector.
     */
    public GVTGlyphMetrics getGlyphMetrics(int glyphIndex) {
        int idx = getGVIdx(glyphIndex);
        return gvs[idx].getGlyphMetrics(glyphIndex-off[idx]);
    }

    /**
     * Returns a Shape whose interior corresponds to the visual representation
     * of the specified glyph within this GlyphVector.
     */
    public Shape getGlyphOutline(int glyphIndex) {
        int idx = getGVIdx(glyphIndex);
        return gvs[idx].getGlyphOutline(glyphIndex-off[idx]);
    }

    /**
     * Returns the position of the specified glyph within this GlyphVector.
     */
    public Point2D getGlyphPosition(int glyphIndex) {
        int idx = getGVIdx(glyphIndex);
        return gvs[idx].getGlyphPosition(glyphIndex-off[idx]);
    }

    /**
     * Gets the transform of the specified glyph within this GlyphVector.
     */
    public AffineTransform getGlyphTransform(int glyphIndex) {
        int idx = getGVIdx(glyphIndex);
        return gvs[idx].getGlyphTransform(glyphIndex-off[idx]);
    }

    /**
     * Returns the visual bounds of the specified glyph within the GlyphVector.
     */
    public Shape getGlyphVisualBounds(int glyphIndex) {
        int idx = getGVIdx(glyphIndex);
        return gvs[idx].getGlyphVisualBounds(glyphIndex-off[idx]);
    }

    /**
     * Sets the position of the specified glyph within this GlyphVector.
     */
    public void setGlyphPosition(int glyphIndex, Point2D newPos) {
        int idx = getGVIdx(glyphIndex);
        // System.out.println("setting: " + idx + " - " + (glyphIndex-off[idx]) +
        //                    " -> " + newPos);
        gvs[idx].setGlyphPosition(glyphIndex-off[idx], newPos);
    }

    /**
     * Sets the transform of the specified glyph within this GlyphVector.
     */
    public void setGlyphTransform(int glyphIndex, AffineTransform newTX) {
        int idx = getGVIdx(glyphIndex);
        gvs[idx].setGlyphTransform(glyphIndex-off[idx], newTX);
    }

    /**
     * Tells the glyph vector whether or not to draw the specified glyph.
     */
    public void setGlyphVisible(int glyphIndex, boolean visible) {
        int idx = getGVIdx(glyphIndex);
        gvs[idx].setGlyphVisible(glyphIndex-off[idx], visible);
    }

    /**
     * Returns true if specified glyph will be drawn.
     */
    public boolean isGlyphVisible(int glyphIndex) {
        int idx = getGVIdx(glyphIndex);
        return gvs[idx].isGlyphVisible(glyphIndex-off[idx]);
    }

    /**
     * Returns an array of glyphcodes for the specified glyphs.
     */
    public int[] getGlyphCodes(int beginGlyphIndex, int numEntries, 
                        int[] codeReturn) {
        int [] ret = codeReturn;
        if (ret == null)
            ret = new int[numEntries];
        int [] tmp = null;

        int gvIdx = getGVIdx(beginGlyphIndex);
        int gi    = beginGlyphIndex-off[gvIdx];
        int i=0;
        GVTGlyphVector gv;
        while (numEntries != 0) {
            int len = numEntries;
            if (gi+len > nGlyphs[gvIdx])
                len = nGlyphs[gvIdx]-gi;
            gv = gvs[gvIdx];
            if (i == 0) {
                gv.getGlyphCodes(gi, len, ret);                
            } else {
                if ((tmp == null) || (tmp.length < len))
                    tmp = new int[len];

                gv.getGlyphCodes(gi, len, tmp);
                for (int j=0; j<len; j++)
                    ret[i+j] = tmp[j];
            }
            gi=0;
            gvIdx++;
            numEntries -= len;
            i+=len;
        }
        return ret;
    }


    /**
     * Returns an array of glyph positions for the specified glyphs
     */
    public float[] getGlyphPositions(int beginGlyphIndex, 
                              int numEntries,
                              float[] positionReturn) {
        float [] ret = positionReturn;
        if (ret == null)
            ret = new float[numEntries*2];
        float [] tmp = null;

        int gvIdx = getGVIdx(beginGlyphIndex);
        int gi    = beginGlyphIndex-off[gvIdx];
        int i=0;
        GVTGlyphVector gv;
        while (numEntries != 0) {
            int len = numEntries;
            if (gi+len > nGlyphs[gvIdx])
                len = nGlyphs[gvIdx]-gi;

            gv = gvs[gvIdx];
            if (i == 0) {
                gv.getGlyphPositions(gi, len, ret);
            } else {
                if ((tmp == null) || (tmp.length < len*2))
                    tmp = new float[len*2];

                gv.getGlyphPositions(gi, len, tmp);
                for (int j=0; j<len*2; j++)
                    ret[i+j] = tmp[j];
            }
            gi=0;
            gvIdx++;
            numEntries -= len;
            i+=len*2;
        }
        return ret;
    }


    /**
     *  Returns the logical bounds of this GlyphVector.
     */
    public Rectangle2D getLogicalBounds() {
        Rectangle2D ret = null;
        for (int idx=0; idx<gvs.length; idx++) {
            Rectangle2D b = gvs[idx].getLogicalBounds();
            if (ret == null) ret = b;
            else ret = ret.createUnion(b);
        }
        return ret;
    }

    /**
     * Returns a Shape whose interior corresponds to the visual representation
     * of this GlyphVector.
     */
    public Shape getOutline() {
        GeneralPath ret = null;
        for (int idx=0; idx<gvs.length; idx++) {
            Shape s = gvs[idx].getOutline();
            if (ret == null) ret = new GeneralPath(s);
            else ret.append(s, false);
        }
        return ret;
    }

    /**
     * Returns a Shape whose interior corresponds to the visual representation
     * of this GlyphVector, offset to x, y.
     */
    public Shape getOutline(float x, float y) {
        Shape outline = getOutline();
        AffineTransform tr = AffineTransform.getTranslateInstance(x,y);
        outline = tr.createTransformedShape(outline);
        return outline;
    }

    /**
     * Returns the  bounds of this GlyphVector. This includes
     * stroking effects.
     */
    public Rectangle2D getBounds2D(AttributedCharacterIterator aci) {
        Rectangle2D ret = null;
        int begin = aci.getBeginIndex();
        for (int idx=0; idx<gvs.length; idx++) {
            GVTGlyphVector gv = gvs[idx];
            int end = gv.getCharacterCount(0, gv.getNumGlyphs())+1;
            Rectangle2D b = gvs[idx].getBounds2D
                (new AttributedCharacterSpanIterator(aci, begin, end));
            if (ret == null) ret = b;
            else ret = ret.createUnion(b);
            begin = end;
        }
        return ret;
    }

    /**
     * Returns the geometric bounds of this GlyphVector. The geometric
     * bounds is the bounds of the geometry of the glyph vector,
     * disregarding stroking.
     */
    public Rectangle2D getGeometricBounds() {
        Rectangle2D ret = null;
        for (int idx=0; idx<gvs.length; idx++) {
            Rectangle2D b = gvs[idx].getGeometricBounds();
            if (ret == null) ret = b;
            else ret = ret.createUnion(b);
        }
        return ret;
    }

    public void performDefaultLayout() {
        for (int idx=0; idx<gvs.length; idx++) {
            gvs[idx].performDefaultLayout();
        }
    }


    /**
     * Returns the number of chars represented by the glyphs within the
     * specified range.
     *
     * @param startGlyphIndex The index of the first glyph in the range.
     * @param endGlyphIndex The index of the last glyph in the range.
     * @return The number of chars.
     */
    public int getCharacterCount(int startGlyphIndex, int endGlyphIndex) {
        int idx1 = getGVIdx(startGlyphIndex);
        int idx2 = getGVIdx(endGlyphIndex);
        int ret=0;
        for (int idx=idx1; idx<=idx2; idx++) {
            int gi1 = startGlyphIndex-off[idx];
            int gi2 = endGlyphIndex-off[idx];
            if (gi2 >= nGlyphs[idx]) {
                gi2 = nGlyphs[idx]-1;
            }
            ret += gvs[idx].getCharacterCount(gi1, gi2);
            startGlyphIndex += (gi2-gi1+1);
        }
        return ret;
    }

    /**
     * Draws the glyph vector.
     */
    public void draw(Graphics2D g2d,
              AttributedCharacterIterator aci) {
        int begin = aci.getBeginIndex();
        for (int idx=0; idx<gvs.length; idx++) {
            GVTGlyphVector gv = gvs[idx];
            int end = gv.getCharacterCount(0, gv.getNumGlyphs())+1;
            gv.draw(g2d, new AttributedCharacterSpanIterator(aci, begin, end));
            begin = end;
        }
    }

    
}
