/* -*-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/TextBase>
#include <osgText/Font>

namespace osgText {

class OSGTEXT_EXPORT Text : public osgText::TextBase
{
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 ShaderTechnique hint to specify what fatures in the text shaders to enable.*/
    void setShaderTechnique(ShaderTechnique technique);

    /** Get the ShaderTechnique hint.*/
    ShaderTechnique getShaderTechnique() { return _shaderTechnique; }


    /**
     * Turns off writing to the depth buffer when rendering text. This only affects text
     * with no backdrop or text using the DELAYED_DEPTH_WRITES implementation, since
     * the other backdrop implementations are really only useful for backwards
     * compatibility and are not worth updating to utilize this flag.
     */
    void setEnableDepthWrites(bool enable) { _enableDepthWrites = enable; }
    bool getEnableDepthWrites() const { return _enableDepthWrites; }


    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
    };

    /**
     * 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 DROP_SHADOW_* modes do the same for their respective 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 getBackdropHorizontalOffset() 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; }

    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; }



    /** Draw the text.*/
    virtual void drawImplementation(osg::RenderInfo& renderInfo) 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 internal 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 internal primitives that this Drawable has.*/
    virtual void accept(osg::PrimitiveFunctor& pf) const;


    /** Get the coordinates of the character corners in local coordinates. Use Text::getMatrix() or Text::computeMatrix(..) to get the transform into model coordinates (see TextBase header.) */
    bool getCharacterCorners(unsigned int index, osg::Vec3& bottomLeft, osg::Vec3& bottomRight, osg::Vec3& topLeft, osg::Vec3& topRight) const;

    /** Resize any per context GLObject buffers to specified size. */
    virtual void resizeGLObjectBuffers(unsigned int maxSize);


    /** 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;

public:

    /** deprecated, value ignored.*/
    enum BackdropImplementation
    {
        POLYGON_OFFSET = 0,
        NO_DEPTH_BUFFER,
        DEPTH_RANGE,
        STENCIL_BUFFER,
        DELAYED_DEPTH_WRITES
    };

    /** deprecated, value ignored.*/
     void setBackdropImplementation(BackdropImplementation) {}
    /** deprecated, value should be ignored.*/
    BackdropImplementation getBackdropImplementation() const { return DELAYED_DEPTH_WRITES; }

    // internal structures, variable and methods used for rendering of characters.
    struct OSGTEXT_EXPORT GlyphQuads
    {
        typedef std::vector<Glyph*> Glyphs;

        Glyphs                          _glyphs;
        osg::ref_ptr<osg::DrawElements> _primitives;

        GlyphQuads();
        GlyphQuads(const GlyphQuads& gq);

        void setupPrimitives(Text::BackdropType backdropType);

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

        /** Resize any per context GLObject buffers to specified size. */
        void resizeGLObjectBuffers(unsigned int maxSize);

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

    private:

        GlyphQuads& operator = (const GlyphQuads&) { return *this; }
    };

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

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

        return &itGlyphQuad->second;
    }

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

    void addGlyphQuad(Glyph* glyph, const osg::Vec2& minc, const osg::Vec2& maxc, const osg::Vec2& mintc, const osg::Vec2& maxtc);

protected:

    virtual ~Text();

    virtual osg::StateSet* createStateSet();

    Font* getActiveFont();

    String::iterator computeLastCharacterOnLine(osg::Vec2& cursor, String::iterator first,String::iterator last);

    // members which have public access.

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

    void computeGlyphRepresentation();

    // internal caches of the positioning of the text.

    bool computeAverageGlyphWidthAndHeight(float& avg_width, float& avg_height) const;

    virtual void computePositionsImplementation();

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

    void drawImplementation(osg::State& state, const osg::Vec4& colorMultiplier) const;

    void drawImplementationSinglePass(osg::State& state, const osg::Vec4& colorMultiplier) const;

    ShaderTechnique _shaderTechnique;
    bool _enableDepthWrites;

    BackdropType _backdropType;

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

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


    // Helper function 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;
};

}


#endif