/* This file is part of the KDE libraries
   Copyright (C) 2001-2003 Christoph Cullmann <cullmann@kde.org>
   Copyright (C) 2002 Joseph Wenninger <jowenn@kde.org>

   Based on:
     KateTextLine : Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de>

   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., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

#include "katetextline.h"
#include "katerenderer.h"

#include <kglobal.h>

#include <qregexp.h>

KateTextLine::KateTextLine ()
  : m_flags(0)
{
}

KateTextLine::~KateTextLine()
{
}

void KateTextLine::insertText (uint pos, uint insLen, const QChar *insText, uchar *insAttribs)
{
  // nothing to do
  if (insLen == 0)
    return;

  // calc new textLen, store old
  uint oldTextLen = m_text.length();
  m_text.insert (pos, insText, insLen);
  uint textLen = m_text.length();

  // resize the array
  m_attributes.resize (textLen);

  // HA, insert behind text end, fill with spaces
  if (pos >= oldTextLen)
  {
    for (uint z = oldTextLen; z < pos; z++)
      m_attributes[z] = 0;
  }
  // HA, insert in text, move the old text behind pos
  else if (oldTextLen > 0)
  {
    for (int z = oldTextLen -1; z >= (int) pos; z--)
      m_attributes[z+insLen] = m_attributes[z];
  }

  // BUH, actually insert the new text
  for (uint z = 0; z < insLen; z++)
  {
    if (insAttribs == 0)
      m_attributes[z+pos] = 0;
    else
      m_attributes[z+pos] = insAttribs[z];
  }
}

void KateTextLine::removeText (uint pos, uint delLen)
{
  // nothing to do
  if (delLen == 0)
    return;

  uint textLen = m_text.length();

  if (textLen == 0)
    return; // uh, again nothing real to do ;)

  if (pos >= textLen)
    return;

  if ((pos + delLen) > textLen)
    delLen = textLen - pos;

  // BU, MOVE THE OLD TEXT AROUND
  for (uint z = pos; z < textLen - delLen; z++)
    m_attributes[z] = m_attributes[z+delLen];

  m_text.remove (pos, delLen);
  m_attributes.resize (m_text.length ());
}

void KateTextLine::truncate(uint newLen)
{
  if (newLen < m_text.length())
  {
    m_text.truncate (newLen);
    m_attributes.truncate (newLen);
  }
}

int KateTextLine::nextNonSpaceChar(uint pos) const
{
  const uint len = m_text.length();
  const QChar *unicode = m_text.unicode();

  for(uint i = pos; i < len; i++)
  {
    if(!unicode[i].isSpace())
      return i;
  }

  return -1;
}

int KateTextLine::previousNonSpaceChar(uint pos) const
{
  const int len = m_text.length();

  if (pos >= (uint)len)
    pos = len - 1;

  const QChar *unicode = m_text.unicode();

  for(int i = pos; i >= 0; i--)
  {
    if(!unicode[i].isSpace())
      return i;
  }

  return -1;
}

int KateTextLine::firstChar() const
{
  return nextNonSpaceChar(0);
}

int KateTextLine::lastChar() const
{
  return previousNonSpaceChar(m_text.length() - 1);
}

const QChar *KateTextLine::firstNonSpace() const
{
  int first = firstChar();
  return (first > -1) ? ((QChar*)m_text.unicode())+first : m_text.unicode();
}

uint KateTextLine::indentDepth (uint tabwidth) const
{
  uint d = 0;
  const uint len = m_text.length();
  const QChar *unicode = m_text.unicode();

  for(uint i = 0; i < len; i++)
  {
    if(unicode[i].isSpace())
    {
      if (unicode[i] == QChar('\t'))
        d += tabwidth - (d % tabwidth);
      else
        d++;
    }
    else
      return d;
  }

  return d;
}

bool KateTextLine::stringAtPos(uint pos, const QString& match) const
{
  const uint len = m_text.length();
  const uint matchlen = match.length();

  if ((pos+matchlen) > len)
    return false;

  // (pos > len) in case the uint pos was assigned a signed -1, pos+matchlen can
  // overflow again which (pos+matchlen > len) does not catch; see bugs #129263 and #129580
  Q_ASSERT(pos < len);

  const QChar *unicode = m_text.unicode();
  const QChar *matchUnicode = match.unicode();

  for (uint i=0; i < matchlen; i++)
    if (unicode[i+pos] != matchUnicode[i])
      return false;

  return true;
}

bool KateTextLine::startingWith(const QString& match) const
{
  const uint matchlen = match.length();

  if (matchlen > m_text.length())
    return false;

  const QChar *unicode = m_text.unicode();
  const QChar *matchUnicode = match.unicode();

  for (uint i=0; i < matchlen; i++)
    if (unicode[i] != matchUnicode[i])
      return false;

  return true;
}

bool KateTextLine::endingWith(const QString& match) const
{
  const uint matchlen = match.length();

  if (matchlen > m_text.length())
    return false;

  const QChar *unicode = m_text.unicode();
  const QChar *matchUnicode = match.unicode();

  uint start = m_text.length() - matchlen;
  for (uint i=0; i < matchlen; i++)
    if (unicode[start+i] != matchUnicode[i])
      return false;

  return true;
}

int KateTextLine::cursorX(uint pos, uint tabChars) const
{
  uint x = 0;

  const uint n = kMin (pos, m_text.length());
  const QChar *unicode = m_text.unicode();

  for ( uint z = 0; z < n; z++)
  {
    if (unicode[z] == QChar('\t'))
      x += tabChars - (x % tabChars);
    else
      x++;
  }

  return x;
}


uint KateTextLine::lengthWithTabs (uint tabChars) const
{
  uint x = 0;
  const uint len = m_text.length();
  const QChar *unicode = m_text.unicode();

  for ( uint z = 0; z < len; z++)
  {
    if (unicode[z] == QChar('\t'))
      x += tabChars - (x % tabChars);
    else
      x++;
  }

  return x;
}

bool KateTextLine::searchText (uint startCol, const QString &text, uint *foundAtCol, uint *matchLen, bool casesensitive, bool backwards)
{
  int index;

  if (backwards)
  {
    int col = startCol;
    uint l = text.length();
    // allow finding the string ending at eol
    if ( col == (int) m_text.length() ) ++startCol;

    do {
      index = m_text.findRev( text, col, casesensitive );
      col--;
    } while ( col >= 0 && l + index >= startCol );
  }
  else
    index = m_text.find (text, startCol, casesensitive);

  if (index > -1)
  {
    if (foundAtCol)
      (*foundAtCol) = index;
    if (matchLen)
      (*matchLen)=text.length();
    return true;
  }

  return false;
}

bool KateTextLine::searchText (uint startCol, const QRegExp &regexp, uint *foundAtCol, uint *matchLen, bool backwards)
{
  int index;

  if (backwards)
  {
    int col = startCol;

    // allow finding the string ending at eol
    if ( col == (int) m_text.length() ) ++startCol;
    do {
      index = regexp.searchRev (m_text, col);
      col--;
    } while ( col >= 0 && regexp.matchedLength() + index >= (int)startCol );
  }
  else
    index = regexp.search (m_text, startCol);

  if (index > -1)
  {
    if (foundAtCol)
      (*foundAtCol) = index;

    if (matchLen)
      (*matchLen)=regexp.matchedLength();
    return true;
  }

  return false;
}

char *KateTextLine::dump (char *buf, bool withHighlighting) const
{
  uint l = m_text.length();
  char f = m_flags;

  if (!withHighlighting)
    f = f | KateTextLine::flagNoOtherData;

  memcpy(buf, (char *) &f, 1);
  buf += 1;

  memcpy(buf, &l, sizeof(uint));
  buf += sizeof(uint);

  memcpy(buf, (char *) m_text.unicode(), sizeof(QChar)*l);
  buf += sizeof(QChar) * l;

  if (!withHighlighting)
    return buf;

  memcpy(buf, (char *)m_attributes.data(), sizeof(uchar) * l);
  buf += sizeof (uchar) * l;

  uint lctx = m_ctx.size();
  uint lfold = m_foldingList.size();
  uint lind = m_indentationDepth.size();

  memcpy(buf, &lctx, sizeof(uint));
  buf += sizeof(uint);

  memcpy(buf, &lfold, sizeof(uint));
  buf += sizeof(uint);

  memcpy(buf, &lind, sizeof(uint));
  buf += sizeof(uint);

  memcpy(buf, (char *)m_ctx.data(), sizeof(short) * lctx);
  buf += sizeof (short) * lctx;

  memcpy(buf, (char *)m_foldingList.data(), sizeof(uint)*lfold);
  buf += sizeof (uint) * lfold;

  memcpy(buf, (char *)m_indentationDepth.data(), sizeof(unsigned short) * lind);
  buf += sizeof (unsigned short) * lind;

  return buf;
}

char *KateTextLine::restore (char *buf)
{
  uint l = 0;
  char f = 0;

  memcpy((char *) &f, buf, 1);
  buf += 1;

  // text + context length read
  memcpy((char *) &l, buf, sizeof(uint));
  buf += sizeof(uint);

  // text + attributes
  m_text.setUnicode ((QChar *) buf, l);
  buf += sizeof(QChar) * l;

  // we just restore a KateTextLine from a buffer first time
  if (f & KateTextLine::flagNoOtherData)
  {
    m_flags = 0;

    if (f & KateTextLine::flagAutoWrapped)
      m_flags = m_flags | KateTextLine::flagAutoWrapped;

    // fill with clean empty attribs !
    m_attributes.fill (0, l);

    return buf;
  }
  else
    m_flags = f;

  m_attributes.duplicate ((uchar *) buf, l);
  buf += sizeof(uchar) * l;

  uint lctx = 0;
  uint lfold = 0;
  uint lind = 0;

  memcpy((char *) &lctx, buf, sizeof(uint));
  buf += sizeof(uint);

  memcpy((char *) &lfold, buf, sizeof(uint));
  buf += sizeof(uint);

  memcpy((char *) &lind, buf, sizeof(uint));
  buf += sizeof(uint);

  m_ctx.duplicate ((short *) buf, lctx);
  buf += sizeof(short) * lctx;

  m_foldingList.duplicate ((uint *) buf, lfold);
  buf += sizeof(uint)*lfold;

  m_indentationDepth.duplicate ((unsigned short *) buf, lind);
  buf += sizeof(unsigned short) * lind;

  return buf;
}

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