/**************************************************************************

    This file is part of Xclass95, a Win95-looking GUI toolkit.
    Copyright (C) 1996, 1997 David Barth, Hector Peraza.

    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; if not, write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

**************************************************************************/


#include "OText.h"



 OTextLine::OTextLine()
{
 _length = 0;
 _string = NULL;
 _prev = _next = NULL;
}


//--------------------------------------------------

OTextLine::OTextLine(OTextLine *line)
{
 _length = line->GetLineLength();
 _string = NULL;
 if (_length > 0)
  _string = line->GetText(0, line->GetLineLength());
 _prev = _next = NULL;
}

//--------------------------------------------------

OTextLine::OTextLine(char *string)
{
 if (string != NULL)
 {
  _length = strlen(string);
  _string = new char[_length+1];
  strcpy(_string, string);
 }
 else
 {
  _length = 0;
  _string = NULL;
 }
 _prev = _next = NULL;
}

//--------------------------------------------------

OTextLine::~OTextLine()
{
 if (_string != NULL)
  delete _string;
}

//--------------------------------------------------

void OTextLine::Clear()
{
 if (_length > 0)
  delete _string;
 _string = NULL;
 _length = 0;
}

//--------------------------------------------------

void OTextLine::DelText(unsigned long pos, unsigned long length)
{
 char *newstring;

 if ((_length == 0) || (pos >= _length) || (pos+length > _length))
  return;
 
 if (_length - length <= 0)
 {
  delete _string;
  _length = 0;
  _string = NULL;
  return;
 } 
 newstring = new char[_length - length+1];
 strncpy(newstring, _string, pos);
 strncpy(newstring+pos, _string+pos+length, _length- pos -length);
 delete _string;
 _string = newstring;
 _length = _length - length;
 _string[_length] = '\0';
}

//--------------------------------------------------

void OTextLine::InsText(unsigned long pos, char *text)
{
 char *newstring;
 if (pos > _length)
  return;
 newstring = new char[strlen(text)+_length+1];
 if (_string != NULL)
  strncpy(newstring, _string, pos);
 strcpy(newstring+pos, text);
 if (_length - pos  > 0) 
  strncpy(newstring+pos+strlen(text), _string+pos, _length-pos);
 _length = _length + strlen(text);
 delete _string;
 _string = newstring;
 _string[_length] ='\0';
}

//--------------------------------------------------

char *OTextLine::GetText(unsigned long pos, unsigned long length)
{
 char *retstring;
 
 if ((pos >= _length) || (length > _length) || (pos+length > _length))
  return NULL;
 retstring = new char[length+1];
 retstring[length] = '\0';
 strncpy(retstring, _string+pos, length);
 return retstring;
}

//--------------------------------------------------

void OTextLine::DelChar(unsigned long pos)
{
 char *newstring;
 if ((_length <= 0) || (pos > _length))
  return;
 newstring = new char[_length -1];
 strncpy(newstring, _string, pos-1);
 if (pos < _length)
  strncpy(newstring+pos-1, _string+pos, _length-pos);
 delete _string;
 _string = newstring;
 _length--;
}


void OTextLine::InsChar(unsigned long pos,char character)
{
 char *newstring;
 if (pos > _length)
  return;
 newstring = new char[_length+2];
 newstring[_length+1] = '\0';
 if (_length > 0)
   strncpy (newstring, _string, pos);
 newstring[pos] = character;
 if (_length - pos > 0)
  strncpy(newstring+pos+1, _string+pos, _length-pos);
 delete _string;
 _string = newstring;
 _length++;
}

//--------------------------------------------------

char OTextLine::GetChar(unsigned long pos)
{
 if ((_length <= 0) || (pos >= _length))
  return -1;
 return _string[pos];
}

//*************************************************************************************************

void OText::_OText()
{
 first = new OTextLine();
 current = first;
 isSaved = True;
 colCount = 0;
 rowCount = 1;
 current_row = 0;
}

//--------------------------------------------------

OText::OText()
{
 _OText();
}

//--------------------------------------------------

OText::OText(OText *text)
{
 OLongPosition pos, end;
 
 pos.x = pos.y = 0;
 end.y = text->RowCount()-1;
 end.x = text->GetLineLength(end.y)-1;
 _OText();
 this->InsText(pos, text, pos, end);
}

//--------------------------------------------------

OText::OText(char *string)
{
 OLongPosition pos;
 
 pos.x = pos.y = 0;
 _OText();
 this->InsText(pos, string);
}

//--------------------------------------------------

OText::~OText()                         
{
  Clear();
  delete first;
}

//--------------------------------------------------

void OText::Clear()
{
 OTextLine *travel = first->_next;
 OTextLine *toDelete;
 while (travel != NULL)
 {
  toDelete = travel;
  travel = travel->_next;
  delete toDelete;
 }
 first->Clear();
 current = first;
 current_row = 0;
 colCount = 0; 
 rowCount = 1;
 isSaved = True;
}

//--------------------------------------------------

bool OText::Load(const char *fn, long startpos, long length)
{
  bool isFirst = True;
  bool finished = False;
  long count, charcount;
  long n;
  FILE *fp;
  int  i, cnt,a;
  char buf[8000], c, *src;
  char  *dst;
  char *buffer;
  OTextLine *travel, *temp;
  char *buf2;
  travel = first;
  if ((fp = fopen(fn, "r")) == NULL) return False;
  i = 0;
  fseek(fp, startpos, SEEK_SET);
  charcount = 0;
  while (fgets(buf, 8000, fp) != NULL) {
    // Expand tabs
    if ((length != -1) && (charcount+strlen(buf) > length))
    {
     count = length - charcount;
     finished = True;
    }
    else
     count = 8000;
    charcount += strlen(buf);
    buf2 = new char[count+1];
    buf2[count]='\0';   
    src = buf;
    dst = buf2;
    cnt = 0;   
    while (c = *src++) {
      if (c == 0x0D || c == 0x0A)
        break;
      else 
       if (c == 0x09)
       {
        *dst++='\t';
        do
         *dst++ = 16;
        while (((dst-buf2) & 0x7) && (cnt++ < count-1));
       }
       else
        *dst++ = c;
      if (cnt++ >= count-1) break;
    }
    *dst = '\0';
    temp = new OTextLine();
    buffer = new char[strlen(buf2)+1];
    strcpy(buffer, buf2);
    temp->_length = strlen(buf2);
    temp->_string = buffer;
    temp->_next = NULL;
    temp->_prev = NULL;
    if (isFirst)
    { 
      delete first;
      first = temp;
      current = temp;
      travel = first;
      isFirst = False;
    }
    else
    {
     travel->_next = temp;
     temp->_prev = travel;
     travel = travel->_next; 
    }
   ++i;
   delete buf2;
  if (finished)
   break;
  }
  fclose(fp);

  rowCount = i;
  if (rowCount == 0)
   rowCount++;
  isSaved = True;
  LongestLine();
  return True;
}

//--------------------------------------------------

bool OText::Save(const char *fn)        
{
 char *buffer;
 OTextLine *travel=first;
 FILE *fp;
 unsigned long i = 0;
 if ((fp = fopen(fn, "w")) == NULL) return False;
 while (travel != NULL)
 {
  i = 0;
  buffer = new char[travel->_length+2];
  strncpy(buffer, travel->_string, travel->_length);
  buffer[travel->_length] = '\n';
  buffer[travel->_length+1] = '\0';
  while (buffer[i] != '\0')
  {
   if (buffer[i] == '\t')
    strcpy(buffer+i+1, buffer+i+8);
   i++;
  } 
 if (fputs(buffer, fp) == EOF) 
  {
   delete buffer;
   fclose(fp);
   return False;
  }
  delete buffer;
  travel = travel->_next;
 }
 isSaved = True;
   fclose(fp);

 return True;
}

//--------------------------------------------------

bool OText::Append(const char *fn)
{
 char *buffer;
 OTextLine *travel=first;
 FILE *fp;
 if ((fp = fopen(fn, "a")) == NULL) return False;
 while (travel != NULL)
 {
  buffer = new char[travel->_length+2];
  strncpy(buffer, travel->_string, travel->_length);
  buffer[travel->_length] = '\n';
  buffer[travel->_length+1] = '\0';
  if (fputs(buffer, fp) == EOF)
  {
   delete buffer;
   fclose(fp);
   return False;
  }
  delete buffer;
  travel = travel->_next;
 }
 isSaved = True;   
   fclose(fp);   
   
 return True; 
} 

//--------------------------------------------------
                                        
bool OText::DelChar(OLongPosition pos)    
{
 if (pos.y >= rowCount)
  return False;
 SetCurrentRow(pos.y);
 current->DelChar(pos.x);
 isSaved = False;
 LongestLine();
}

//--------------------------------------------------

bool OText::InsChar(OLongPosition pos, char c)          
{
  if (pos.y >= rowCount)
  return False;
 SetCurrentRow(pos.y);
 current->InsChar(pos.x, c);
 isSaved = False;
 LongestLine();
}

//--------------------------------------------------

char OText::GetChar(OLongPosition pos)
{
 if (pos.y >= rowCount)
  return -1;
 SetCurrentRow(pos.y);
 return current->GetChar(pos.x);
}

//--------------------------------------------------

bool OText::DelText(OLongPosition start, OLongPosition end)
{
 char * tempbuffer; 
 if ((start.y >= rowCount) || (end.y >= rowCount))
 return False;
 SetCurrentRow(start.y);
 if (start.y == end.y)
 {
  current->DelText(start.x, end.x-start.x+1);
  return True;
 }
 current->DelText(start.x, current->_length-start.x);
 SetCurrentRow(current_row+1);
 for (long i=start.y+1; i < end.y;i++)
  DelLine(current_row);
 tempbuffer = current->GetText(end.x+1, current->_length - end.x-1);
 DelLine(current_row);
 SetCurrentRow(start.y);
 if (tempbuffer != NULL)
  current->InsText(current->GetLineLength(), tempbuffer);
 else
 {
  if (current->_next != NULL)
  {
   current->InsText(current->_length, current->_next->_string);
   DelLine(current_row+1);
   SetCurrentRow(start.y);
  }
 }
 isSaved = False;
 LongestLine();  
}

//--------------------------------------------------

bool OText::InsText(OLongPosition ins_pos, OText *src,OLongPosition 
start_src, OLongPosition end_src) 
{
 OLongPosition pos;
 unsigned long len;
 char *lineString;
 char *restString;
 OTextLine *following;

 if (ins_pos.y > rowCount) // opsie, wrong coords ??
  return False;
 if (ins_pos.y == rowCount) // for appending text
 {
  pos.y = rowCount -1;
  pos.x = GetLineLength(pos.y);
  BreakLine(pos); // current row is set by this
 }
 else
  SetCurrentRow(ins_pos.y); // otherwise going to the desired row
// preparing first line to be inserted
 restString = current->GetText(ins_pos.x, current->_length - ins_pos.x);
 current->DelText(ins_pos.x, current->_length - ins_pos.x);
 following = current->_next;
// inserting first line
 if (start_src.y == end_src.y)
  len = end_src.x - start_src.x+1;
 else
  len = src->GetLineLength(start_src.y) - start_src.x;
 if (len > 0)
 {
  lineString = src->GetLine(start_src, len);
  current->InsText(ins_pos.x, lineString);
  delete lineString;
 }
 else
  BreakLine(ins_pos);
// [...] inserting possible lines
 pos.y = start_src.y+1;
 pos.x= 0;
 for (pos.y; pos.y < end_src.y;pos.y++)
 {
  lineString = src->GetLine(pos, src->GetLineLength(pos.y));
  current->_next = new OTextLine(lineString);
  current->_next->_prev = current;
  current = current->_next;
  rowCount++;
  current_row++;
   delete lineString;  
 }
// last line of inserted text is as special as first line
 if (start_src.y != end_src.y)
 {
  pos.y = end_src.y;
  pos.x = 0;
  lineString = src->GetLine(pos, end_src.x+1);
  if (lineString != NULL)
  {
   current->_next = new OTextLine(lineString);
   current->_next->_prev = current;
   current = current->_next;
   rowCount++;
   current_row++;
   delete lineString;  
  }
  else
  {
   pos.y = current_row;
   pos.x = 0;
   BreakLine(pos);
  }
 }
// ok, now we have to add the rest of the first destination line
 if (restString != NULL)
 {
  current->InsText(current->_length, restString);
  delete restString;
 }
// now re linking the rest of the origin text
 current->_next = following;
 if (current->_next != NULL)
  current->_next->_prev = current;
 LongestLine();
 isSaved = False;
return True;
}

//--------------------------------------------------

bool OText::AddText(OText *text)
{
 OLongPosition end, start_src, end_src;
 
 end.y = rowCount;
 end.x = 0;
 start_src.x = start_src.y = 0;
 end_src.y = text->RowCount()-1;
 end_src.x = text->GetLineLength(end_src.y)-1;
 isSaved = False;
 return InsText(end, text, start_src, end_src);
}
 

bool OText::InsLine(unsigned long pos,char *string)
{
 OTextLine *following, *previous, *newline;
 if (pos > rowCount)
  return False;
 if (pos < rowCount)
  SetCurrentRow(pos);
 else
  SetCurrentRow(rowCount-1);
 previous = current->_prev;
 newline = new OTextLine(string);
 newline->_prev = previous;
 if (previous != NULL)
  previous->_next = newline;
 else
  first = newline;
 newline->_next = current;
 current->_prev = newline;
 rowCount++;
 current_row++;
 LongestLine();
 isSaved = False;
 return True;
} 

//--------------------------------------------------

bool OText::InsText(OLongPosition pos, char *buffer)
{
 if (pos.y > rowCount)
  return False;
 if (pos.y == rowCount)
 {
  SetCurrentRow(rowCount-1);
  current->_next = new OTextLine(buffer);
  current->_next->_prev = current;
  rowCount++;
 }
 else
 {
  SetCurrentRow(pos.y);
  current->InsText(pos.x, buffer);
 } 
 LongestLine();
 isSaved = False;
 return True;
}

//--------------------------------------------------

bool OText::DelLine(unsigned long pos)
{
 if ((!SetCurrentRow(pos)) || (rowCount == 1))
   return False;
 OTextLine *travel = current;
 if (travel == first)
 {
  first = first->_next;
  first->_prev == NULL;
 }
 else
 {
  travel->_prev->_next = travel->_next;
  if (travel->_next != NULL)
  {
   travel->_next->_prev = travel->_prev;
   current = current->_next;
  }
  else
  {
   current = current->_prev;
   current_row--;
  }
 }
 delete travel;
 rowCount--;
 isSaved = False;
 LongestLine();
}

//--------------------------------------------------

char *OText::GetLine(OLongPosition pos, unsigned long length)
{
 if (pos.y >= rowCount)
  return NULL; 
 SetCurrentRow(pos.y);
 return current->GetText(pos.x, length);
}

//--------------------------------------------------

bool OText::BreakLine(OLongPosition pos)
{
 OTextLine *temp; 
 char *tempbuffer;
 if (SetCurrentRow(pos.y) == False)
  return False;
 if ((pos.x < 0) || (pos.x > current->_length))
  return False;
 if (pos.x < current->_length)
 {
  tempbuffer = current->GetText(pos.x, current->_length-pos.x);
  temp = new OTextLine(tempbuffer);
  current->DelText(pos.x, current->_length - pos.x);
  delete tempbuffer;
 }
 else
  temp = new OTextLine();
 temp->_prev = current;
 temp->_next = current->_next;
 current->_next = temp;
 if (temp->_next != NULL)
  temp->_next->_prev = temp;
 isSaved = False;
 rowCount++;
 current_row++;
 current = current->_next;
 LongestLine();
 return True;
}   


//--------------------------------------------------
                                                        
long OText::GetLineLength(long row)
{
 if (row >= rowCount)
  return -1;
 SetCurrentRow(row);
 return current->GetLineLength();  
}

//--------------------------------------------------

bool OText::SetCurrentRow(long row)
{
 int count;
 if ((row < 0) || (row >= rowCount))
  return False; 
 if (row > current_row)
 {
  for (count = current_row; count < row; count++)
  {
   if (current->_next == NULL)
   {
    current_row = count;
    return False;
   }
   current = current->_next;
  }
 }   
 else
 {
  if (current_row == row)
   return True;
  for (count = current_row; count > row; count--)
  {
   if (current->_prev == NULL)
   {
    current_row = count;
    return False;
   }
   current = current->_prev;
  }
 }
 current_row = row;
 return True;
}

//--------------------------------------------------
 
bool OText::Search(OLongPosition *foundPos, OLongPosition start, char 
*searchString,  
                        bool direction, bool caseSensitive)
{
 if (SetCurrentRow(start.y) == False)
  return False;
 if (direction) 
 {
  while(1)
  {
   foundPos->x =  DownSearchBM(current->_string+start.x, 
                                 searchString, current->_length - start.x,
                                 caseSensitive);   
   if (foundPos->x != -1)
   {
    foundPos->x += start.x;
    foundPos->y = current_row;
    return True;
   }
   if (SetCurrentRow(current_row+1) == False)
    break;
   start.x = 0;
  }  
 }
 else
 {
  while(1)
  {
   foundPos->x = UpSearchBM(current->_string, searchString, start.x,
                                 caseSensitive);  
   if (foundPos->x != -1)
   { 
    foundPos->y = current_row;
    return True;
   }
   if (SetCurrentRow(current_row-1) == False)
    break;
   start.x = current->_length;
  }  
 } 
 return False;
}

//--------------------------------------------------

long OText::DownSearchBM(char *actualLine, char *searchPattern, long len, bool cS) {  
 char d[95];
 long i, j, k, m,n;
 
 m = strlen(searchPattern);
 n = len;
 if ((len <= 0) || (m > n))
  return -1;

 for (i = 0; i < 95; i++)
  d[i] = m;
 for (i = 0; i < m - 1; i++)
 {
  d[searchPattern[i]-32] = m - i - 1;
  if (cS == False)
   if ((searchPattern[i] >= 65) && (searchPattern[i] <= 90))
    d[searchPattern[i]] == m - i - 1;
   else
    if ((searchPattern[i] >= 97) && (searchPattern[i] <= 122))   
     d[searchPattern[i]-64] = m - i - 1;
 }
 i = m;
 do
 {
  j = m;
  k = i;
  do
  {
   k--;
   j--;
  } while ((j >= 0) && CharEqual(searchPattern[j],actualLine[k],cS));
  i = i + d[actualLine[i-1]-32];
 } while ((j >= 0) && (i <= n));
 if (j < 0)
  return i - m - d[searchPattern[m-1] - 32];
 else
  return -1;
}

//--------------------------------------------------

long OText::UpSearchBM(char *actualLine, char *searchPattern, long len, 
bool cS) {
 int d[95];
 long i, j, k, m,n;

 m = strlen(searchPattern); 
 n = len;
 if ((len <= 0) || (m > n))
  return -1;
 for (i = 0; i < 95; i++)
  d[i] = m;
 for (i = m - 1; i > 0; i--)
 {
  d[searchPattern[i]-32] = i;
  if (cS== False)
   if ((searchPattern[i] >= 65) && (searchPattern[i] <= 90))
    d[searchPattern[i]] = i;
   else
    if ((searchPattern[i] >= 97) && (searchPattern[i] <= 122))   
     d[searchPattern[i]-64] = i;
 }
 i = n - m-1;
 do
 {
  j = -1;
  k = i;
  do
  {
   k++;
   j++;
  } while ((j < m) && CharEqual(searchPattern[j],actualLine[k], cS));
  i = i - d[actualLine[i+1]-32];
 } while ((j < m) && (i >= 0));
 if (j >= m)
  return  i + m + d[searchPattern[1] - 32];
 else
  return -1;
}

//--------------------------------------------------

bool OText::CharEqual(char first, char second, bool cS)
{
 if (first == second)
  return True;
 
 if (cS == False)
 {
  if ((first >= 65) && (first <= 90))
   if (first+32 == second)
    return True;

  if ((first >= 97) && (first <= 122))
   if (first-32 == second)
    return True;
 }

  return False;
}

//--------------------------------------------------

bool OText::Replace(OLongPosition pos, char *oldText, char *newText, bool 
                     direction, bool caseSensitive ) {
 OLongPosition delEnd;
 if (SetCurrentRow(pos.y) == False)
  return False;
 if (direction)
 { 
  if (DownSearchBM(current->_string+pos.x,oldText, current->_length-pos.x, caseSensitive) == False)
   return False;
 }
 else
 {
  if (UpSearchBM(current->_string+pos.x,oldText, current->_length-pos.x, caseSensitive) == False)
   return False;
  pos.x = pos.x - strlen(oldText)-1;  
 }
 delEnd.y = pos.y;
 delEnd.x = pos.x+strlen(oldText)-1;
 DelText(pos, delEnd);
 InsText(pos, newText);
 return True;
}

//--------------------------------------------------

void OText::LongestLine()
{
 long line_count = 0;
 OTextLine *travel = first;
 colCount = 0;
 while(travel != NULL)
 {
  if (travel->_length > colCount)
  {
   colCount = travel->_length;
   longestLine = line_count;
  }
  travel = travel->_next;
  line_count++;
 }
}







