/* This file is part of the KDE project
   Copyright (C) 2001 Simon Hausmann <hausmann@kde.org>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   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 GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/
#ifndef __kstextview_h__
#define __kstextview_h__

#include <tqscrollview.h>
#include <tqpen.h>
#include <tqptrlist.h>
#include <tqvaluelist.h>
#include <tqmap.h>
#include <tqpixmap.h>

class TQTimer;
class TQDragObject;

namespace KSirc
{

class TextView;

struct StringPtr
{
    StringPtr() : ptr( 0 ), len( 0 ) {}
    StringPtr( const TQChar *_ptr, uint _len )
        : ptr( _ptr ), len( _len ) {}
    explicit StringPtr( const TQString &s ) // use with care!
        : ptr( s.unicode() ), len( s.length() ) {}

    inline bool isNull() const { return ptr == 0; }

    // makes deep copy
    inline TQString toTQString() const 
    { return ( ptr && len > 0 ) ? TQString( ptr, len ) : TQString(); }

    const TQChar *ptr;
    uint len;
};

#define CONSTSTRING( substr ) TQConstString( substr.ptr, substr.len ).string()

inline bool operator<( const StringPtr &s1, const StringPtr &s2 )
{
    return CONSTSTRING( s1 ) < CONSTSTRING( s2 );
}

inline bool operator==( const StringPtr &s1, const StringPtr &s2 )
{
    return CONSTSTRING( s1 ) == CONSTSTRING( s2 );
}

inline bool operator==( const StringPtr &s1, const char *s2 )
{
    return CONSTSTRING( s1 ) == s2;
}

class AttributeMap : public TQMap<StringPtr, StringPtr>
{
public:
    AttributeMap() {}
    AttributeMap( const AttributeMap &rhs ) : TQMap<StringPtr, StringPtr>( rhs ) {}
    AttributeMap &operator=( const AttributeMap &rhs )
    { TQMap<StringPtr, StringPtr>::operator=( rhs ); return *this; }

    // helper for 'const char *' key...
    ConstIterator findAttribute( const char *key ) const
    {
        TQString qkey( key );
        return find( StringPtr( qkey ) );
    }
    Iterator findAttribute( const char *key ) 
    {
        TQString qkey( key );
        return find( StringPtr( qkey ) );
    }

    StringPtr operator[]( const char *key ) const
    {
        ConstIterator it = findAttribute( key );
        if ( it == end() )
            return StringPtr();
        return it.data();
    }
    StringPtr &operator[]( const StringPtr &key )
    {
        return TQMap<StringPtr, StringPtr>::operator[]( key );
    }
};

struct Token
{
    Token() : id( -1 ) {}

    enum Id { TagOpen, Text, TagClose };
    int id;
    StringPtr value;
    AttributeMap attributes;
};

struct ItemProperties
{
    ItemProperties();
    ItemProperties( const TQFont &defaultFont );
    ItemProperties( const ItemProperties &other,
                    const Token &token,
		    TextView *textView );
    ItemProperties( const ItemProperties &rhs );
    ItemProperties &operator=( const ItemProperties &rhs );

    void updateFont( const TQFont &newFont );

    // these three are inherited/merged
    TQFont font;
    TQColor color;
    TQColor selColor;
    TQColor bgColor;
    TQColor bgSelColor;
    bool reversed;
    // ### todo: inherit these, too
    AttributeMap attributes;
};

class TextParag;
class TextLine;
class SelectionPoint;

class Item
{
public:
    enum LayoutResetStatus { DeleteItem, KeepItem };
    enum SelectionStatus { SelectionStart = 0, InSelection, SelectionEnd, SelectionBoth, 
                           NoSelection };
    enum SelectionAccuracy { SelectExact, SelectFuzzy };

    Item( TextParag *parag, const ItemProperties &props = ItemProperties() );

    virtual ~Item();

    virtual const char *type() { return "Item"; }

    virtual void paint( TQPainter &painter ) = 0;

    int width() const;

    int minWidth() const;

    int height() const;

    virtual Item *breakLine( int width );

    virtual LayoutResetStatus resetLayout() = 0;

    virtual int calcSelectionOffset( int x );

    void setSelectionStatus( SelectionStatus status ) { m_selection = status; }

    SelectionStatus selectionStatus() const { return m_selection; }

    void selectionOffsets( int &startOffset, int &endOffset );

    int maxSelectionOffset() const;

    void setLine(TextLine *line);

    // ###
    virtual StringPtr text() const;

    virtual void setProps( const ItemProperties &props );
    ItemProperties &props() { return m_props; }

    static Item *create( TextParag *parag, const Token &tok, 
                         const ItemProperties &props = ItemProperties() );

protected:
    mutable bool m_extendsDirty;
    mutable int m_minWidth;
    mutable int m_width;
    mutable int m_height;

    virtual void calcExtends() const = 0;

    SelectionStatus m_selection;
    TextLine  *m_line;
    TextParag *m_parag;
    ItemProperties m_props;
};

class TextChunk : public Item
{
public:
    TextChunk( TextParag *parag, const StringPtr &text, const ItemProperties &props );

    virtual const char *type() { return "TextChunk"; }

    virtual void paint( TQPainter &painter );

    virtual Item *breakLine( int width );

    virtual LayoutResetStatus resetLayout();

    virtual int calcSelectionOffset( int x );

    virtual StringPtr text() const;

    virtual void setProps( const ItemProperties &props );

protected:
    virtual void calcExtends() const;

private:
    StringPtr breakInTheMiddle( int width );
    Item *hardBreak( const StringPtr &rightHandSide );

    void paintSelection( TQPainter &p );
    int paintSelection( TQPainter &p, int x, const StringPtr &text );
    int paintText( TQPainter &p, int x, const StringPtr &text );

    void mergeSelection( TextChunk *child, SelectionPoint *selection );

    StringPtr m_text;
    uint m_originalTextLength;
    TQFontMetrics m_metrics;
    class TextChunk *m_parent;
};

class ImageItem : public Item
{
public:
    ImageItem( TextParag *parag, const TQPixmap &pixmap );

    virtual const char *type() { return "Image"; }

    virtual void paint( TQPainter &painter );

    virtual LayoutResetStatus resetLayout();

protected:
    virtual void calcExtends() const;

private:
    TQPixmap m_pixmap;
};

class Tokenizer
{
public:
    struct TagIndex
    {
        enum Type { Open, Close };
        TagIndex() : index( 0 ), type( -1 ) {}
        TagIndex( int _index, int _type )
            : index( _index ), type( _type ) {}
        uint index;
        int type;
    };
    typedef TQValueList<TagIndex> TagIndexList;
 
    // preprocessed string
    struct PString
    {
        TQString data;
        TagIndexList tags;
    };
    
    Tokenizer( PString &text );

    static PString preprocess( const TQString &richText );

    static TQString convertToRichText( const PString &ptext );

    bool parseNextToken( Token &tok );

private:
    void parseTag( const StringPtr &text,
                   StringPtr &tag,
                   AttributeMap &attributes );

    static TagIndexList scanTagIndices( const TQString &text );
    static void resolveEntities( TQString &text, TagIndexList &tags );

    enum TagParsingState { ScanForName, ScanForEqual, ScanForValue };
    
    TQString &m_text;
    TagIndexList m_tags;
    TagIndexList::ConstIterator m_lastTag;
    bool m_textBeforeFirstTagProcessed;
    bool m_done;

    Tokenizer( const Tokenizer & );
    Tokenizer &operator=( const Tokenizer & );
};

class SelectionPoint;

class TextLine
{
public:
    enum LayoutPolicy { NoUpdate, UpdateMaxHeight };

    TextLine();
    // tranfers ownership of items! make sure that 'items' does not
    // have autodeletion enabled!
    TextLine( const TQPtrList<Item> &items );

    int maxHeight() const { return m_maxHeight; }

    TQString updateSelection( const SelectionPoint &start, const SelectionPoint &end );
    void clearSelection();

    // transfers ownership
    void appendItem( Item *i, int layoutUpdatePolicy = NoUpdate );

    bool isEmpty() const { return m_items.isEmpty(); }

    Item *resetLayout( TQPtrList<Item> &remainingItems);

    void paint( TQPainter &p, int y );

    Item *itemAt( int px, SelectionPoint *selectionInfo, 
                  Item::SelectionAccuracy accuracy = Item::SelectExact );

    TQPtrListIterator<Item> iterator() const { return TQPtrListIterator<Item>( m_items ); }

    TQString plainText() const;

    void fontChange( const TQFont &newFont );

private:
    TQPtrList<Item> m_items;
    int m_maxHeight;
};

class SelectionPoint;

class TextParag
{
public:
    TextParag( TextView *textView, const TQString &richText );
    
    ~TextParag();

    void layout( int width );

    void paint( TQPainter &p, int y, int maxY );

    inline void setLayouted( bool l ) { m_layouted = l; }
    inline bool isLayouted() const { return m_layouted; }

    inline int minWidth() const { return m_minWidth; }
    inline int height() const { return m_height; }

    Item *itemAt( int px, int py, SelectionPoint *selectionInfo,
                  Item::SelectionAccuracy accuracy = Item::SelectExact );

    TextView *textView() const { return m_textView; }

    TQString updateSelection( const SelectionPoint &start, const SelectionPoint &end );

    void clearSelection();

    void setRichText( const TQString &richText );

    Tokenizer::PString processedRichText() const { return m_processedRichText; }

    TQString plainText() const;

    void fontChange( const TQFont &newFont );

private:
    Tokenizer::PString m_processedRichText;
    TQPtrList<TextLine> m_lines;
    bool m_layouted;
    int m_height;
    int m_minWidth;
    TextView *m_textView;

    struct Tag
    {
        Tag() {}
        Tag( const StringPtr &_name, const ItemProperties &_props )
            : name( _name ), props( _props ) {}

        StringPtr name;
        ItemProperties props;
    };

    TextParag( const TextParag & );
    TextParag &operator=( const TextParag & );
};

struct SelectionPoint
{
    SelectionPoint() : item( 0 ), line( 0 ), parag( 0 ), offset( 0 ) {}
    Item *item;
    TextLine *line;
    TextParag *parag;
    uint offset;
    TQPoint pos;
};

class TextParagIterator
{
    friend class TextView;
public:
    TextParagIterator( const TextParagIterator &rhs )
        : m_paragIt( rhs.m_paragIt ) {}
    TextParagIterator &operator=( const TextParagIterator &rhs )
    { m_paragIt = rhs.m_paragIt; return *this; }

    TQString richText() const;
    void setRichText( const TQString &richText );

    TQString plainText() const;

    bool atEnd() const { return m_paragIt.current() == 0; }

    TextParagIterator &operator++() { ++m_paragIt; return *this; }
    TextParagIterator &operator++( int steps ) { m_paragIt += steps; return *this; }
    TextParagIterator &operator--() { --m_paragIt; return *this; }
    TextParagIterator &operator--( int steps ) { m_paragIt -= steps; return *this; }

protected:
    TextParagIterator( const TQPtrListIterator<TextParag> &paragIt )
        : m_paragIt( paragIt ) {}

private:
    TQPtrListIterator<TextParag> m_paragIt;
};

class ContentsPaintAlgorithm
{
public:
    ContentsPaintAlgorithm( const TQPtrListIterator<TextParag> &paragIt,
                            TQWidget *viewport, TQPixmap &paintBuffer,
                            TQPainter &painter, int clipX, int clipY, int clipHeight );

    void paint();

private:
    int goToFirstVisibleParagraph();
    int paint( TQPainter &bufferedPainter, int currentY );
    int adjustYAndIterator( int startY, int currentY, int nextY );

    TQPtrListIterator<TextParag> m_paragIt;
    TQWidget *m_viewport;
    TQPixmap &m_paintBuffer;
    TQPainter &m_painter;
    int m_clipX, m_clipY, m_clipHeight;
    int m_overshoot;
};

class TextView : public TQScrollView
{
    TQ_OBJECT
  
    friend class Item;
    friend class TextChunk;
    friend class TextParag;
    friend class TextParagIterator;
public:
    TextView( TQWidget *parent, const char *name  = 0 );
    virtual ~TextView();

    virtual void clear();

    TextParagIterator appendParag( const TQString &richText );
    
    bool removeParag( const TextParagIterator &parag );

    void clearSelection( bool repaint = false ); // ### re-consider the repaint arg...

    TQString selectedText() const { return m_selectedText; }

    TextParagIterator firstParag() const;

    TQString plainText() const;

    TQColor linkColor() const;
    void setLinkColor( const TQColor &linkColor );

    void scrollToBottom( bool force = false );

signals:
    void selectionChanged();
    void pasteReq(const TQString&);
    void linkClicked( const TQMouseEvent *ev, const TQString &url );

public slots:
    void copy();

protected slots:
    void scrolling(int value);

protected:
    virtual void viewportResizeEvent( TQResizeEvent *ev );
    virtual void drawContents( TQPainter *p, int cx, int cy, int cw, int ch );
    virtual void contentsMousePressEvent( TQMouseEvent *ev );
    virtual void contentsMouseMoveEvent( TQMouseEvent *ev );
    virtual void contentsMouseReleaseEvent( TQMouseEvent *ev );
    virtual void fontChange( const TQFont & );

    virtual void startDrag();

    virtual TQDragObject *dragObject( const TQString &dragURL );

private slots:
    void autoScroll();

private:
    void emitLinkClickedForMouseEvent( TQMouseEvent *ev );

    void startAutoScroll();

    void stopAutoScroll();

    void selectionOffsets( int &startOffset, int &endOffset );

    void updateSelectionOrder();

    TQString updateSelection( const SelectionPoint &start, const SelectionPoint &end );

    SelectionPoint *selectionStart();
    SelectionPoint *selectionEnd();

    void layout( bool force = true );

    Item *itemAt( const TQPoint &pos, SelectionPoint *selectionInfo = 0,
                  Item::SelectionAccuracy accuracy = Item::SelectExact );

    void clearSelectionInternal();

    void contentsChange(int heightChange, bool force = false);

    TQPtrList<TextParag> m_parags;
    TQPixmap m_paintBuffer;

    SelectionPoint m_selectionMaybeStart;
    SelectionPoint m_selectionStart;
    SelectionPoint m_selectionEnd;
    bool m_selectionEndBeforeStart;

    TQTimer *m_autoScrollTimer;

    TQString m_selectedText;

    TQPoint m_dragStartPos;
    TQString m_dragURL;
    bool m_mousePressed : 1;
    bool m_mmbPressed : 1;
    TQColor m_linkColor;
    TQColor m_selectionBackgroundColor;

    int m_height;
    bool m_inScroll;
    int m_lastScroll;
};

} // namespace KSirc

#endif
