/* 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., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.
*/
#ifndef __kstextview_h__
#define __kstextview_h__

#include <qscrollview.h>
#include <qpen.h>
#include <qptrlist.h>
#include <qvaluelist.h>
#include <qmap.h>
#include <qpixmap.h>

class QTimer;

namespace KSirc
{

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

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

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

    const QChar *ptr;
    uint len;
};

#define CONSTSTRING( substr ) QConstString( 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 QMap<StringPtr, StringPtr>
{
public:
    AttributeMap() {}
    AttributeMap( const AttributeMap &rhs ) : QMap<StringPtr, StringPtr>( rhs ) {}
    AttributeMap &operator=( const AttributeMap &rhs )
    { QMap<StringPtr, StringPtr>::operator=( rhs ); return *this; }

    // helper for 'const char *' key...
    ConstIterator findAttribute( const char *key ) const
    {
        QString qkey( key );
        return find( StringPtr( qkey ) );
    }
    Iterator findAttribute( const char *key ) 
    {
        QString 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 QMap<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 QFont &defaultFont );
    ItemProperties( const ItemProperties &other,
                    const Token &token );
    ItemProperties( const ItemProperties &rhs );
    ItemProperties &operator=( const ItemProperties &rhs );

    void updateFont( const QFont &newFont );

    // these three are inherited/merged
    QFont font;
    QColor color;
    QColor bgColor;
    // ### todo: inherit these, too
    AttributeMap attributes;
};

class TextParag;
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 void paint( QPainter *painter ) = 0;

    int width() const;

    int minWidth() const;

    int height() const;

    virtual Item *breakLine( int width );

    virtual LayoutResetStatus resetLayout( Item *originalItem ) = 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;

    // ###
    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;
    TextParag *m_parag;
    ItemProperties m_props;
};

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

    virtual void paint( QPainter *painter );

    virtual Item *breakLine( int width );

    virtual LayoutResetStatus resetLayout( Item *originalItem );

    virtual int calcSelectionOffset( int x );

    virtual StringPtr text() const;

    virtual void setProps( const ItemProperties &props );

protected:
    virtual void calcExtends() const;

private:
    void paintSelection( QPainter *p );
    int paintSelection( QPainter *p, int x, const StringPtr &text );
    int paintText( QPainter *p, int x, const StringPtr &text );

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

    StringPtr m_text;
    uint m_originalTextLength;
    QFontMetrics m_metrics;
};

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

    virtual void paint( QPainter *painter );

    virtual LayoutResetStatus resetLayout( Item *originalItem );

protected:
    virtual void calcExtends() const;

private:
    QPixmap 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 QValueList<TagIndex> TagIndexList;
 
    // preprocessed string
    struct PString
    {
        QString data;
        TagIndexList tags;
    };
    
    Tokenizer( PString &text );

    static PString preprocess( const QString &richText );

    static QString convertToRichText( const PString &ptext );

    bool parseNextToken( Token &tok );

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

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

    enum TagParsingState { ScanForName, ScanForEqual, ScanForValue };
    
    QString &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 QPtrList<Item> &items );

    int maxHeight() const { return m_maxHeight; }

    QString 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( QPtrList<Item> &remainingItems, Item *last );

    void paint( QPainter *p, int y );

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

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

    QString plainText() const;

    void fontChange( const QFont &newFont );

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

class TextView;
class SelectionPoint;

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

    void layout( int width );

    void paint( QPainter *p, int y );

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

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

    void clearSelection();

    void setRichText( const QString &richText );

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

    QString plainText() const;

    void fontChange( const QFont &newFont );

private:
    Tokenizer::PString m_processedRichText;
    QPtrList<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;
    QPoint 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; }

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

    QString 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 QPtrListIterator<TextParag> &paragIt )
        : m_paragIt( paragIt ) {}

private:
    QPtrListIterator<TextParag> m_paragIt;
};
 
class TextView : public QScrollView
{
    Q_OBJECT
    friend class Item;
    friend class TextChunk;
    friend class TextParag;
    friend class TextParagIterator;
public:
    TextView( QWidget *parent, const char *name  = 0 );
    virtual ~TextView();

    virtual void clear();

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

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

    QString selectedText() const { return m_selectedText; }

    TextParagIterator firstParag() const;

    QString plainText() const;

signals:
    void selectionChanged();
    void pasteReq(const QString&);
    void linkClicked( const QMouseEvent *ev, const QString &url );

public slots:
    void copy();

protected:
    virtual void viewportResizeEvent( QResizeEvent *ev );
    virtual void drawContents( QPainter *p, int cx, int cy, int cw, int ch );
    virtual void viewportMousePressEvent( QMouseEvent *ev );
    virtual void viewportMouseMoveEvent( QMouseEvent *ev );
    virtual void viewportMouseReleaseEvent( QMouseEvent *ev );
    virtual void fontChange( const QFont & );

private slots:
    void autoScroll();

private:
    void startAutoScroll();

    void stopAutoScroll();

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

    void updateSelectionOrder();

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

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

    void layout( bool force = true );

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

    void clearSelectionInternal();

    QPtrList<TextParag> m_parags;
    QPixmap m_paintBuffer;

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

    QTimer *m_autoScrollTimer;

    QString m_selectedText;
};

} // namespace KSirc

#endif
/*
 * vim: et sw=4
 */
