/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield 
 *
 * This library is open source and may be redistributed and/or modified under  
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or 
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 * 
 * This library 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 
 * OpenSceneGraph Public License for more details.
*/

#ifndef OSGTEXT_TEXT
#define OSGTEXT_TEXT 1

#include <osg/Drawable>
#include <osg/Quat>

#include <osgText/Font>
#include <osgText/String>

namespace osgText {


class OSGTEXT_EXPORT Text : public osg::Drawable
{
public:

    Text();
    Text(const Text& text,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);

    virtual osg::Object* cloneType() const { return new Text(); }
    virtual osg::Object* clone(const osg::CopyOp& copyop) const { return new Text(*this,copyop); }
    virtual bool isSameKindAs(const osg::Object* obj) const { return dynamic_cast<const Text*>(obj)!=NULL; }
    virtual const char* className() const { return "Text"; }
    virtual const char* libraryName() const { return "osgText"; }


    /** Set the Font to use to render the text.  
      * setFont(0) sets the use of the default font.*/
    void setFont(Font* font=0);

    /** Set the font, loaded from the specified front file, to use to render the text,
      * setFont("") sets the use of the default font.
      * See the osgText::readFontFile function for how the font file will be located. */
    void setFont(const std::string& fontfile);

    /** Get the font. Return 0 if default is being used.*/    
    const Font* getFont() const { return _font.get(); }

    
    /** Set the Font reference width and height resolution in texels.
      * Note, the size may not be supported by current font, 
      * the closest supported font size will be selected.*/
    void setFontResolution(unsigned int width, unsigned int height);

    unsigned int getFontWidth() const { return _fontWidth; }
    unsigned int getFontHeight() const { return _fontHeight; }
    
    
    /** Set the text using a osgText::String.*/
    void setText(const String& text);

    /** Set the text using a std::string, 
      * which is converted to an internal TextString.*/
    void setText(const std::string& text);

    /** Set the text using a Unicode encoded std::string, which is converted to an internal TextString.
      * The encoding parameter specificies which Unicode encodeding is used in the std::string. */
    void setText(const std::string& text,String::Encoding encoding);

    /** Set the text using a wchar_t string, 
      * which is converted to an internal TextString.*/
    void setText(const wchar_t* text);
    
    /** Get the text string. 
      * Note, if you modify the string you must call Text::update() for
      * the internal glyph reprentation to be updated.*/
    String& getText() { return _text; }

    /** Get the const text string.*/
    const String& getText() const { return _text; }
    
    /** update internal glyph respresnetation used for rendering, 
      * and bounding volume.*/
    void update() { computeGlyphRepresentation(); }


    /** Set the rendered character size in object coordinates.*/
    void setCharacterSize(float height,float aspectRatio=1.0f);
    
    float getCharacterHeight() const { return _characterHeight; }
    float getCharacterAspectRatio() const { return _characterAspectRatio; }

    enum CharacterSizeMode
    {
        OBJECT_COORDS, /// default
        SCREEN_COORDS, /// internally scale the characters to be constant screen size.
        OBJECT_COORDS_WITH_MAXIMUM_SCREEN_SIZE_CAPPED_BY_FONT_HEIGHT /// text that behavaves like OBJECT_COORDS sized text when a long distance way, but has its screen sized capped automatically when the viewer gets near.
    };

    /** Set how the CharacterSize value relates to the final rendered character.*/
    void setCharacterSizeMode(CharacterSizeMode mode) { _characterSizeMode = mode; }
    
    /** Get the CharacterSizeMode.*/
    CharacterSizeMode getCharacterSizeMode() const { return _characterSizeMode; }


    /** Set the maximum width of the text box.
      * With horizontal layouts any characters which do not fit are wrapped around.
      * 0 or negative values indicate that no maximum width is set, lines can be as long as 
      * they need be to fit thre required text*/
    void setMaximumWidth(float maximumWidth);
    
    /** Get the maximim width of the text box.*/
    float getMaximumWidth() const { return _maximumWidth; }

    /** Set the maximum height of the text box.
      * With horizontal layouts any characters which do not fit are wrapped around.
      * 0 or negative values indicate that no maximum height is set, lines can be as long as 
      * they need be to fit thre required text*/
    void setMaximumHeight(float maximumHeight);
    
    /** Get the maximum height of the text box.*/
    float getMaximumHeight() const { return _maximumHeight; }



    /** Set the position of text.*/
    void setPosition(const osg::Vec3& pos);
    
    /** Get the position of text.*/
    const osg::Vec3& getPosition() const { return _position; }
       

    enum AlignmentType
    {
        LEFT_TOP,
        LEFT_CENTER,
        LEFT_BOTTOM,

        CENTER_TOP,
        CENTER_CENTER,
        CENTER_BOTTOM,

        RIGHT_TOP,
        RIGHT_CENTER,
        RIGHT_BOTTOM,
        
        LEFT_BASE_LINE,
        CENTER_BASE_LINE,
        RIGHT_BASE_LINE,
    
        LEFT_BOTTOM_BASE_LINE,
        CENTER_BOTTOM_BASE_LINE,
        RIGHT_BOTTOM_BASE_LINE,
    
        BASE_LINE = LEFT_BASE_LINE /// default.
    
    };
    
    void setAlignment(AlignmentType alignment);
    AlignmentType getAlignment() const { return _alignment; }


    enum AxisAlignment
    {
        XY_PLANE,
        REVERSED_XY_PLANE,
        XZ_PLANE,
        REVERSED_XZ_PLANE,
        YZ_PLANE,
        REVERSED_YZ_PLANE,
        SCREEN
    };

    void setAxisAlignment(AxisAlignment axis);
    
    void setRotation(const osg::Quat& quat);
    const osg::Quat& getRotation() const { return _rotation; }

    void setAutoRotateToScreen(bool autoRotateToScreen);
    bool getAutoRotateToScreen() const { return _autoRotateToScreen; }

    enum Layout
    {
        LEFT_TO_RIGHT, /// default
        RIGHT_TO_LEFT,
        VERTICAL
    };
    
    void setLayout(Layout layout);
    
    Layout getLayout() const { return _layout; }


    void setColor(const osg::Vec4& color);
    
    const osg::Vec4& getColor() const { return _color; }


    enum DrawModeMask
    {
        TEXT        = 1, /// default
        BOUNDINGBOX = 2,
        ALIGNMENT   = 4
    };

    void setDrawMode(unsigned int mode);
    
    unsigned int getDrawMode() const { return _drawMode; }


    enum BackdropType
    {
        DROP_SHADOW_BOTTOM_RIGHT = 0,  // usually the type of shadow you see
        DROP_SHADOW_CENTER_RIGHT,
        DROP_SHADOW_TOP_RIGHT,
        DROP_SHADOW_BOTTOM_CENTER,
        DROP_SHADOW_TOP_CENTER,
        DROP_SHADOW_BOTTOM_LEFT,
        DROP_SHADOW_CENTER_LEFT,
        DROP_SHADOW_TOP_LEFT,
        OUTLINE,
        NONE
    };

    enum BackdropImplementation
    {
        POLYGON_OFFSET = 0,
        NO_DEPTH_BUFFER,
        DEPTH_RANGE,
        STENCIL_BUFFER
    };

    /**
     * BackdropType gives you a background shadow text behind your regular 
     * text. This helps give text extra contrast which can be useful when 
     * placing text against noisy backgrounds. 
     * The color of the background shadow text is specified by setBackdropColor().
     * DROP_SHADOW_BOTTOM_RIGHT will draw backdrop text to the right and down of 
     * the normal text. Other DROW_SHADOW_* modes do the same for their repective directions.
     * OUTLINE will draw backdrop text so that it appears the text has an outline
     * or border around the normal text. This mode is particularly useful against
     * really noisy backgrounds that may put text on top of things that have 
     * all types of colors which you don't have control over. 
     * Some real world examples of this general technique in use that I know of 
     * are Google Earth, Sid Meier's Pirates (2004 Remake), and Star Control 2 (PC 1993).
     * The default is NONE.
     */
    void setBackdropType(BackdropType type);

    BackdropType getBackdropType() const { return _backdropType; }

    /** 
     * Sets the amount text is offset to create the backdrop/shadow effect. 
     * Set the value too high and for example, in OUTLINE mode you will get a "Brady Bunch"
     * effect where you see duplicates of the text in a 3x3 grid.
     * Set the value too small and you won't see anything.
     * The values represent percentages. 1.0 means 100% so a value of 1.0
     * in DROW_SHADOW_LEFT_CENTER mode would cause each glyph to be echoed
     * next to it self. So the letter 'e' might look like 'ee'.
     * Good values tend to be in the 0.03 to 0.10 range (but will be subject
     * to your specific font and display characteristics).
     * Note that the text bounding boxes are updated to include backdrop offsets.
     * However, other metric information such as getCharacterHeight() are unaffected
     * by this. This means that individual glyph spacing (kerning?) are unchanged 
     * even when this mode is used.
     * The default is 0.07 (7% offset).
     */
    void setBackdropOffset(float offset = 0.07f);
    /**
     * This overloaded version lets you specify the offset for the horizontal
     * and vertical components separately.
     */
    void setBackdropOffset(float horizontal, float vertical);

    float getBackdropHorizontalOffet() const { return _backdropHorizontalOffset; }

    float getBackdropVerticalOffset() const { return _backdropVerticalOffset; }

    /**
     * This specifies the color of the backdrop text.
     * The default is black.
     */
    void setBackdropColor(const osg::Vec4& color);

    const osg::Vec4& getBackdropColor() const { return _backdropColor; }

    /**
     * This specifies the underlying backdrop rendering implementation.
     * Unfortunately, at this time, there is no "perfect" rendering solution
     * so this function is provided to let you 'pick your poison'. Each
     * implementation has trade-offs.
     *
     * POLYGON_OFFSET: 
     * This uses glPolygonOffset to draw the text multiple times to 
     * create the drop-shadow and outline effects. glPolygonOffset 
     * is used to prevent z-fighting of the overlapping text.
     * This probably should have been the best option, but all the ATI
     * cards we have encountered so far have serious problems with this.
     * We see little white holes/artifacts in the rendered glyph textures
     * which move around depending on the viewing angle. For moving text,
     * the moving holes give an extremely unpleasant flickering effect.
     * Pumping up the "units" parameter in glPolygonOffset can minimize 
     * this problem, but two other bad side-effects occur if you do this.
     * First, high values will cause problems with clipping, particularly
     * when there are objects behind the text. The drop-shadows or outline
     * may be culled because their computed offset is behind the object or 
     * z-far plane. Second, there is an additional problem associated with
     * the Z-slope. High values can make large chunks of the backdrop
     * suddenly disappear. This can be reduced by the "factor" parameter.
     * Making the "factor" value small, can help, but experimentally, we've
     * found that it creates a new, different kind of z-fighting problem.
     * So there is no perfect solution. With units, you trade off the 'holes'
     * for the large-section clipping.
     * Experimentally, we have found units values from 150-512 to be tolerable
     * to acceptable with respect to the 'holes'. A factor of .1 seems to 
     * bring down the large clipping problem without creating a new z-fighting
     * problem.
     * (You can experiment with these numbers by playing with the 
     * osg:PolygonOffset multipliers which this backend tries to respect.)
     *
     * If ATI ever fixes their cards/drivers, then this might become the 
     * best option.
     *
     * 
     * NO_DEPTH_BUFFER
     * Instead of using glPolygonOffset to prevent z-fighting, this mode
     * just disables the depth buffer when rendering the text. This allows
     * the text to be rendered without any z-fighting. The downside to this 
     * mode is that render order begins to matter and the text will not 
     * necessarily correctly appear above or behind other objects in the 
     * scene based on depth values.
     * This mode is best for text that only needs to be ontop and 
     * not obscured by any objects.
     * 
     * DEPTH_RANGE
     * This mode is inspired by Paul Martz's OpenGL FAQ, item 13.050.
     * This uses glDepthRange as a substitute for glPolygonOffset.
     * Strangely, experiments on ATI cards seem to produce cleaner results
     * than when using glPolygonOffset. The trade-off for this is that the 
     * backdrop still may be placed too far back and might be culled by objects
     * directly behind the object or by the far z-plane. If ATI ever fixes
     * the glPolygonOffset problem, polygon offset is probably a slightly 
     * better solution because you can use smaller offsets. But with the 
     * current ATI problem, this option may be preferable.
     *
     * STENCIL_BUFFER
     * (Assuming the backend is written correctly,) the Stencil Buffer is
     * the most "correct" and reliable way of producing backdrop text.
     * The stencil buffer is a multipass system that allows writing to the 
     * same z-values without needing to resort to offsets. This implementation
     * should not have any of the problems associated with the 3 previous
     * implementations. But the trade-off for this mode is that without 
     * hardware acceleration for the stencil buffer, rendering will be 
     * extremely slow. (There is also potentially more overhead for this 
     * algorithm so it could be slower than the other implementations. 
     * Benchmarking would be required to determine if the speed differences
     * are significant on your particular hardware.) This mode is best for 
     * when quality is important and stencil buffer hardware acceleration 
     * is available.
     */ 
     void setBackdropImplementation(BackdropImplementation implementation);

     BackdropImplementation getBackdropImplementation() const { return _backdropImplementation; }



    enum ColorGradientMode
    {
        SOLID = 0, // a.k.a. ColorGradients off
        PER_CHARACTER,
        OVERALL
    };

    /**
     * This sets different types of text coloring modes.
     * When the coloring mode is not set to SOLID, the 
     * colors specified in setColorGradientCorners() determine
     * the colors for the text.
     * When the gradient mode is OVERALL, the coloring scheme
     * attempts to approximate the effect as if the entire text box/region
     * were a single polygon and you had applied colors to each of the four
     * corners with GL_SMOOTH enabled. In this mode, OpenGL interpolates
     * the colors across the polygon, and this is what OVERALL tries to 
     * emulate. This can be used to give nice embellishments on things 
     * like logos and names.
     * PER_CHARACTER is similar to OVERALL except that it applies the 
     * color interpolation to the four corners of each character instead
     * of across the overall text box.
     * The default is SOLID (a.k.a. off).
     */
    void setColorGradientMode(ColorGradientMode mode);

    ColorGradientMode getColorGradientMode() const { return _colorGradientMode; }

    /**
     * Used only for gradient mode, let's you specify the colors of the 4 corners.
     * If ColorGradients are off, these values are ignored (and the value from setColor() 
     * is the only one that is relevant.
     */
    void setColorGradientCorners(const osg::Vec4& topLeft, const osg::Vec4& bottomLeft, const osg::Vec4& bottomRight, const osg::Vec4& topRight);

    const osg::Vec4& getColorGradientTopLeft() const { return _colorGradientTopLeft; }
    const osg::Vec4& getColorGradientBottomLeft() const { return _colorGradientBottomLeft; }
    const osg::Vec4& getColorGradientBottomRight() const { return _colorGradientBottomRight; }
    const osg::Vec4& getColorGradientTopRight() const { return _colorGradientTopRight; }


    void setKerningType(KerningType kerningType) { _kerningType = kerningType; }

    KerningType getKerningType() const { return _kerningType; }

    /** Get the number of wrapped lines - only valid after computeGlyphRepresentation() has been called, returns 0 otherwise */
    unsigned int getLineCount() const { return _lineCount; }

    /** Draw the text.*/
    virtual void drawImplementation(osg::State& state) const;

    /** return false, osgText::Text does not support accept(AttributeFunctor&).*/
    virtual bool supports(const osg::Drawable::AttributeFunctor&) const { return false; }

    /** return true, osgText::Text does support accept(ConstAttributeFunctor&).*/
    virtual bool supports(const osg::Drawable::ConstAttributeFunctor&) const { return true; }

    /** accept an ConstAttributeFunctor and call its methods to tell it about the interal attributes that this Drawable has.*/
    virtual void accept(osg::Drawable::ConstAttributeFunctor& af) const;

    /** return true, osgText::Text does support accept(PrimitiveFunctor&) .*/
    virtual bool supports(const osg::PrimitiveFunctor&) const { return true; }

    /** accept a PrimtiveFunctor and call its methods to tell it about the interal primtives that this Drawable has.*/
    virtual void accept(osg::PrimitiveFunctor& pf) const;

    /** If State is non-zero, this function releases OpenGL objects for
      * the specified graphics context. Otherwise, releases OpenGL objexts
      * for all graphics contexts. */
    virtual void releaseGLObjects(osg::State* state=0) const;

    // make Font a friend to allow it set the _font to 0 if the font is 
    // forcefully unloaded.
    friend class Font;
public:

    // internal structures, variable and methods used for rendering of characters.
    struct OSGTEXT_EXPORT GlyphQuads
    {
        typedef std::vector<Font::Glyph*> Glyphs;
        typedef std::vector<unsigned int> LineNumbers;
        typedef std::vector<osg::Vec2> Coords2;
        typedef std::vector<osg::Vec3> Coords3;
        typedef std::vector<osg::Vec2> TexCoords;
        typedef std::vector<osg::Vec4> ColorCoords;

        Glyphs                          _glyphs;
        Coords2                         _coords;
        osg::buffered_object<Coords3>   _transformedCoords;
        TexCoords                       _texcoords;
        LineNumbers                     _lineNumbers;

        osg::buffered_object<Coords3>   _transformedBackdropCoords[8];
        ColorCoords    _colorCoords;

        Glyphs getGlyphs() { return _glyphs; }
        const Glyphs getGlyphs() const { return _glyphs; }

        Coords2& getCoords() { return _coords; }
        const Coords2& getCoords() const { return _coords; }

        Coords3& getTransformedCoords(unsigned int contexID) { return _transformedCoords[contexID]; }
        const Coords3& getTransformedCoords(unsigned int contexID) const { return _transformedCoords[contexID]; }

        TexCoords& getTexCoords() { return _texcoords; }
        const TexCoords& getTexCoords() const { return _texcoords; }

        LineNumbers& getLineNumbers() { return _lineNumbers; }
        const LineNumbers& getLineNumbers() const { return _lineNumbers; }
    };

    typedef std::map<osg::ref_ptr<Font::GlyphTexture>,GlyphQuads> TextureGlyphQuadMap;

    /** Direct Access to GlyphQuads */
    const GlyphQuads* getGlyphQuads(Font::GlyphTexture* texture) const 
    {
        TextureGlyphQuadMap::iterator itGlyphQuad = _textureGlyphQuadMap.find(texture);
        if (itGlyphQuad == _textureGlyphQuadMap.end()) return NULL;

        return &itGlyphQuad->second;
    }

    const TextureGlyphQuadMap& getTextureGlyphQuadMap() const 
    {
        return _textureGlyphQuadMap;
    }

    virtual osg::BoundingBox computeBound() const;

protected:

    virtual ~Text();

    Font* getActiveFont();
    const Font* getActiveFont() const;

    String::iterator computeLastCharacterOnLine(osg::Vec2& cursor, String::iterator first,String::iterator last);
    
    // members which have public access.
    osg::ref_ptr<Font>                      _font;
    unsigned int                            _fontWidth;
    unsigned int                            _fontHeight;
    float                                   _characterHeight;
    float                                   _characterAspectRatio;
    CharacterSizeMode                       _characterSizeMode;
    float                                   _maximumWidth;
    float                                   _maximumHeight;

    String                                  _text;
    osg::Vec3                               _position;
    AlignmentType                           _alignment;
    osg::Quat                               _rotation;
    bool                                    _autoRotateToScreen;
    Layout                                  _layout;
    osg::Vec4                               _color;
    unsigned int                            _drawMode;
    KerningType                             _kerningType;
    unsigned int                            _lineCount;

    // iternal map used for rendering. Set up by the computeGlyphRepresentation() method.
    mutable TextureGlyphQuadMap            _textureGlyphQuadMap;
    
    void computeGlyphRepresentation();

    // internal caches of the positioning of the text.
    
    struct AutoTransformCache
    {    
        AutoTransformCache():
            _traversalNumber(-1),
            _width(0),
            _height(0) {}
    
        int         _traversalNumber;
        int         _width;
        int         _height;
        osg::Vec3   _transformedPosition;
        osg::Matrix _modelview;
        osg::Matrix _projection;
        osg::Matrix _matrix;
    };
    
    mutable osg::buffered_object<AutoTransformCache>    _autoTransformCache;
    mutable osg::Vec3                                   _offset;
    mutable osg::Vec3                                   _normal;
    mutable osg::BoundingBox                            _textBB;


    bool computeAverageGlypthWidthAndHeight(float& avg_width, float& avg_height) const;
    void computePositions();
    void computePositions(unsigned int contextID) const;
    
    void computeBackdropPositions(unsigned int contextID) const;
    void computeBackdropBoundingBox() const;

    void computeColorGradients() const;
    void computeColorGradientsOverall() const;
    void computeColorGradientsPerCharacter() const;

    void drawForegroundText(osg::State& state, const GlyphQuads& glyphquad) const;
    void renderOnlyForegroundText(osg::State& state) const;
    void renderWithPolygonOffset(osg::State& state) const;
    void renderWithNoDepthBuffer(osg::State& state) const;
    void renderWithDepthRange(osg::State& state) const;
    void renderWithStencilBuffer(osg::State& state) const;
    
    BackdropType _backdropType;
    BackdropImplementation _backdropImplementation;

    float _backdropHorizontalOffset;
    float _backdropVerticalOffset;
    osg::Vec4 _backdropColor;

    ColorGradientMode _colorGradientMode;
    osg::Vec4 _colorGradientTopLeft;
    osg::Vec4 _colorGradientBottomLeft;
    osg::Vec4 _colorGradientBottomRight;
    osg::Vec4 _colorGradientTopRight;

    // Helper functions for color interpolation
    float bilinearInterpolate(float x1, float x2, float y1, float y2, float x, float y, float q11, float q12, float q21, float q22) const;
    void convertHsvToRgb( float hsv[], float rgb[] ) const;
    void convertRgbToHsv( float rgb[], float hsv[] ) const;
};

}


#endif
