/* This file is part of the KDE libraries
   Copyright (c) 2000 Waldo Bastian <bastian@kde.org>
   Copyright (C) 2002-2004 Christoph Cullmann <cullmann@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 version 2 as published by the Free Software Foundation.

   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 __KATE_BUFFER_H__
#define __KATE_BUFFER_H__

#include "katetextline.h"
#include "katecodefoldinghelpers.h"

#include <kvmallocator.h>

#include <qptrlist.h>
#include <qobject.h>
#include <qtimer.h>
#include <qvaluevector.h>

class KateLineInfo;
class KateDocument;
class KateHighlighting;
class KateBufBlockList;
class KateBuffer;
class KateFileLoader;

class QTextCodec;

/**
 * The KateBufBlock class contains an amount of data representing
 * a certain number of lines.
 *
 * @author Waldo Bastian <bastian@kde.org>
 * @author Christoph Cullmann <cullmann@kde.org>
 */
class KateBufBlock
{
  friend class KateBufBlockList;

  public:
    /**
     * Create an empty block. (empty == ONE line)
     * @param parent buffer the block belongs to
     * @param prev previous bufblock in the list
     * @param next next bufblock in the list
     * @param stream stream to load the content from, if any given
     */
    KateBufBlock ( KateBuffer *parent, KateBufBlock *prev = 0, KateBufBlock *next = 0,
                   KateFileLoader *stream = 0 );
    
    /**
     * destroy this block and take care of freeing all mem
     */
    ~KateBufBlock ();
    
  private:
    /**
     * fill the block with the lines from the given stream
     * @param stream stream to load data from
     */
    void fillBlock (KateFileLoader *stream);
    
  public:
    /**
     * state flags
     */
    enum State
    {
      stateSwapped = 0,
      stateClean = 1,
      stateDirty = 2
    };
    
    /**
     * returns the current state of this block
     * @return state
     */
    State state () const { return m_state; }
    
  public:
    /**
     * return line @p i
     * The first line of this block is line 0.
     * if you modifiy this line, please mark the block as dirty
     * @param i line to return
     * @return line pointer
     */
    KateTextLine::Ptr line(uint i);
    
    /**
     * insert @p line in front of line @p i
     * marks the block dirty
     * @param i where to insert
     * @param line line pointer
     */
    void insertLine(uint i, KateTextLine::Ptr line);
    
    /**
     * remove line @p i
     * marks the block dirty
     * @param i line to remove
     */
    void removeLine(uint i);
    
    /**
     * mark this block as dirty, will invalidate the swap data
     * insert/removeLine will mark the block dirty itself
     */
    void markDirty ();
  
  public:
    /**
     * startLine
     * @return first line in block
     */
    inline uint startLine () const { return m_startLine; };
    
    /**
     * update the first line, needed to keep it up to date
     * @param line new startLine
     */
    inline void setStartLine (uint line) { m_startLine = line; }
    
    /**
     * first line behind this block
     * @param line behind block
     */
    inline uint endLine () const { return m_startLine + m_lines; }
    
    /**
     * lines in this block
     * @return lines
     */
    inline uint lines () const { return m_lines; }

    /**
     * prev block
     * @return previous block
     */
    inline KateBufBlock *prev () { return m_prev; }
    
    /**
     * next block
     * @return next block
     */
    inline KateBufBlock *next () { return m_next; }
  
  /**
   * methodes to swap in/out
   */
  private:
    /**
     * swap in the kvmallocater data, create string list
     */
    void swapIn ();

    /**
     * swap our string list out, delete it !
     */
    void swapOut ();
            
  private:
    /**
     * VERY IMPORTANT, state of this block
     * this uchar indicates if the block is swapped, loaded, clean or dirty
     */
    KateBufBlock::State m_state;
    
    /**
     * IMPORTANT, start line
     */
    uint m_startLine;
    
    /**
     * IMPORTANT, line count
     */
    uint m_lines;

    /**
     * here we swap our stuff
     */
    KVMAllocator::Block *m_vmblock;
    
    /**
     * swapped size
     */
    uint m_vmblockSize;

    /**
     * list of textlines
     */
    QValueVector<KateTextLine::Ptr> m_stringList;

    /**
     * parent buffer.
     */
    KateBuffer* m_parent;

    /**
     * prev block
     */
    KateBufBlock *m_prev;
    
    /**
     * next block
     */
    KateBufBlock *m_next;
    
  private:
    /**
     * list pointer, to which list I belong
     * list element pointers for the KateBufBlockList ONLY !!!
     */
    KateBufBlockList *list;
    
    /**
     * prev list item
     */
    KateBufBlock *listPrev;
    
    /**
     * next list item
     */
    KateBufBlock *listNext;
};

/**
 * list which allows O(1) inserts/removes
 * will not delete the elements on remove
 * will use the next/prevNode pointers in the KateBufBlocks !
 * internal use: loaded/clean/dirty block lists
 *
 * @author Christoph Cullmann <cullmann@kde.org>
 */
class KateBufBlockList
{
  public:
    /**
     * Default Constructor
     */
    KateBufBlockList ();
    
  public:
    /**
     * count of blocks in this list
     * @return count of blocks
     */
    inline uint count() const { return m_count; }
  
    /**
     * first block in this list or 0
     * @return head of list
     */
    inline KateBufBlock *first () { return m_first; };
    
    /**
     * last block in this list or 0
     * @return end of list
     */
    inline KateBufBlock *last () { return m_last; };

    /**
     * is buf the last block?
     * @param buf block to test
     * @return is this block the first one?
     */
    inline bool isFirst (KateBufBlock *buf) { return m_first == buf; };
    
    /**
     * is buf the last block?
     * @param buf block to test
     * @return is this block the last one?
     */
    inline bool isLast (KateBufBlock *buf) { return m_last == buf; };
        
    /**
     * append a block to this list !
     * will remove it from the list it belonged before !
     * @param buf block to append
     */
    void append (KateBufBlock *buf);
    
    /**
     * remove the block from the list it belongs to !
     * @param buf block to remove
     */
    inline static void remove (KateBufBlock *buf)
    {
      if (buf->list)
        buf->list->removeInternal (buf);
    }
    
  private:
    /**
     * internal helper for remove
     * @param buf block to remove
     */
    void removeInternal (KateBufBlock *buf);
    
  private:
    /**
     * count of blocks in list
     */
    uint m_count;
    
    /**
     * first block
     */
    KateBufBlock *m_first;
    
    /**
     * last block
     */
    KateBufBlock *m_last;
};

/**
 * The KateBuffer class maintains a collections of lines.
 * It allows to maintain state information in a lazy way.
 * It handles swapping out of data using secondary storage.
 *
 * It is designed to handle large amounts of text-data efficiently
 * with respect to CPU and memory usage.
 *
 * @author Waldo Bastian <bastian@kde.org>
 * @author Christoph Cullmann <cullmann@kde.org>
 */
class KateBuffer : public QObject
{
  Q_OBJECT
  
  friend class KateBufBlock;
  
  public:
    /**
     * maximal loaded block count
     * @return max loaded blocks
     */
    inline static uint maxLoadedBlocks () { return m_maxLoadedBlocks; }
    
    /**
     * modifier for max loaded blocks limit
     * @param count new limit
     */
    static void setMaxLoadedBlocks (uint count);
    
  private:
    /**
     * global max loaded blocks limit
     */
    static uint m_maxLoadedBlocks;

  public:
    /**
     * Create an empty buffer.
     * @param doc parent document
     */
    KateBuffer (KateDocument *doc);

    /**
     * Goodbye buffer
     */
    ~KateBuffer ();
    
  public:
    /**
     * start some editing action
     */
    void editStart ();
    
    /**
     * finish some editing action
     */
    void editEnd ();
    
  private:
    /**
     * mark given line as dirty
     * @param line line to tag
     */
    void editTagLine (uint line);
    
    /**
     * mark the given line as removed
     * @param line line to tag
     */
    void editRemoveTagLine (uint line);
    
    /**
     * mark the given line as inserted
     * @param line line to tag
     */
    void editInsertTagLine (uint line);
  
  private:
    /**
     * edit session recursion
     */
    uint editSessionNumber;
    
    /**
     * is a edit session running
     */
    bool editIsRunning;
    
    /**
     * dirty lines start at line
     */
    uint editTagLineStart;
    
    /**
     * dirty lines end at line
     */
    uint editTagLineEnd;
    
  public:
    /**
     * Clear the buffer.
     */
    void clear();
  
    /**
     * Open a file, use the given filename
     * @param m_file filename to open
     * @return success
     */
    bool openFile (const QString &m_file);
    
    /**
     * was the last loading broken because of not enough tmp disk space ?
     * (will be reseted on successful save of the file, user gets warning if he really wants to do it)
     * @return was loading borked?
     */
    bool loadingBorked () const { return m_loadingBorked; }

    /**
     * Can the current codec handle all chars
     * @return chars can be encoded
     */
    bool canEncode ();

    /**
     * Save the buffer to a file, use the given filename + codec + end of line chars (internal use of qtextstream)
     * @param m_file filename to save to
     * @return success
     */
    bool saveFile (const QString &m_file);
    
  public:
    /**
     * Return line @p i
     */
    inline KateTextLine::Ptr line(uint i)
    {
      KateBufBlock *buf = findBlock(i);
      if (!buf)
        return 0;
        
      if (i < m_lineHighlighted)
        return buf->line (i - buf->startLine());
      
      return line_internal (buf, i);
    }
    
  private:
    /**
     * line needs hl
     */
     KateTextLine::Ptr line_internal (KateBufBlock *buf, uint i);

  public:
    /**
     * Return line @p i without triggering highlighting
     */
    inline KateTextLine::Ptr plainLine(uint i)
    {
      KateBufBlock *buf = findBlock(i);
      if (!buf)
        return 0;
    
      return buf->line(i - buf->startLine());
    }
    
    /**
     * Return the total number of lines in the buffer.
     */
    inline uint count() const { return m_lines; }
    
  private:
    /**
     * Find the block containing line @p i
     * index pointer gets filled with index of block in m_blocks
     * index only valid if returned block != 0 !
     */
    KateBufBlock *findBlock (uint i, uint *index = 0)
    {
      // out of range !
      if (i >= m_lines)
        return 0;
        
      if ((m_blocks[m_lastFoundBlock]->startLine() <= i) && (m_blocks[m_lastFoundBlock]->endLine() > i))
      {
        if (index)
          (*index) = m_lastFoundBlock;
          
        return m_blocks[m_lastFoundBlock];
      }
      
      return findBlock_internal (i, index);
    }
    
    KateBufBlock *findBlock_internal (uint i, uint *index = 0);
    
  public:
    /**
     * Mark line @p i as changed !
     */
    void changeLine(uint i);
  
    /**
     * Insert @p line in front of line @p i
     */
    void insertLine(uint i, KateTextLine::Ptr line);

    /**
     * Remove line @p i
     */
    void removeLine(uint i);
    
  public:
    inline uint countVisible () { return m_lines - m_regionTree.getHiddenLinesCount(m_lines); }
    
    inline uint lineNumber (uint visibleLine) { return m_regionTree.getRealLine (visibleLine); }
    
    inline uint lineVisibleNumber (uint line) { return m_regionTree.getVirtualLine (line); }
    
    inline void lineInfo (KateLineInfo *info, unsigned int line) { m_regionTree.getLineInfo(info,line); }

    inline uint tabWidth () const { return m_tabWidth; }
    
    inline KVMAllocator *vm () { return &m_vm; }
    
  public:
    void setTabWidth (uint w);  

    /**
     * Use @p highlight for highlighting
     *
     * @p highlight may be 0 in which case highlighting
     * will be disabled.
     */
    void setHighlight (KateHighlighting *highlight);

    KateHighlighting *highlight () { return m_highlight; };
    
    /**
     * Invalidate highlighting of whole buffer.
     */
    void invalidateHighlighting();
    
    KateCodeFoldingTree *foldingTree () { return &m_regionTree; };
    
  public slots:
    /**
     * change the visibility of a given line
     */
    void setLineVisible (unsigned int lineNr, bool visible);
  
  private:
    /**
     * Highlight information needs to be updated.
     *
     * @param buf The buffer being processed.
     * @param startState highlighting state of last line before range
     * @param from first line in range
     * @param to last line in range
     * @param invalidat should the rehighlighted lines be tagged ?
     *
     * @returns true when the highlighting in the next block needs to be updated,
     * false otherwise.
     */
    bool doHighlight (KateBufBlock *buf, uint from, uint to, bool invalidate);
    
  signals:
    /**
     * Emittend if codefolding returned with a changed list
     */
    void codeFoldingUpdated();

    /**
     * Emitted when the highlighting of a certain range has
     * changed.
     */
    void tagLines(int start, int end);

  private:
    /**
     * document we belong too
     */
    KateDocument *m_doc;
  
    /**
     * current line count
     */
    uint m_lines;
    
    /**
     * ALL blocks
     * in order of linenumbers
     */
    QValueVector<KateBufBlock*> m_blocks;
    
    /**
     * last block where the start/end line is in sync with real life
     */
    uint m_lastInSyncBlock;
    
    /**
     * last block found by findBlock, there to make searching faster
     */
    uint m_lastFoundBlock;

    /**
     * vm allocator
     */
    KVMAllocator m_vm;

    /**
     * status of the cache read/write errors
     * write errors get handled, read errors not really atm
     */
    bool m_cacheReadError;
    bool m_cacheWriteError;
    
    /**
     * had we cache error while loading ?
     */
    bool m_loadingBorked;

  /**
   * highlighting & folding relevant stuff
   */
  private:
    /**
     * current highlighting mode or 0
     */
    KateHighlighting *m_highlight;
    
    /**
     * folding tree
     */
    KateCodeFoldingTree m_regionTree;
    
    // for the scrapty indent sensitive langs
    uint m_tabWidth;
        
    uint m_lineHighlightedMax;
    uint m_lineHighlighted;
  
  /**
     * number of dynamic contexts causing a full invalidation
     */
    uint m_maxDynamicContexts;

  /**
   * only used from the KateBufBlocks !
   */
  private:
    /**
     * all not swapped blocks !
     */
    KateBufBlockList m_loadedBlocks;
};

#endif

// kate: space-indent on; indent-width 2; replace-tabs on;
