/*  GNU Moe - My Own Editor
    Copyright (C) 2005, 2006, 2007, 2008, 2009 Antonio Diaz Diaz.

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

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <algorithm>
#include <cctype>
#include <string>
#include <vector>

#include "buffer.h"
#include "iso_8859.h"


namespace {

void insert_lines( std::vector< std::string > & data,
                   const int line, const int lines )
  {
  if( lines > 0 )
    {
    int i = data.size();
    data.resize( i + lines );
    while( --i >= line ) data[i+lines].swap( data[i] );
    }
  }


void delete_lines( std::vector< std::string > & data,
                   const int line, const int lines ) throw()
  {
  if( lines > 0 && line < (int)data.size() )
    {
    for( unsigned int i = line + lines; i < data.size(); ++i )
      data[i-lines].swap( data[i] );
    const int len = std::min( lines, (int)data.size() - line );
    data.erase( data.end() - len, data.end() );
    }
  }

} // end namespace


void Basic_buffer::add_line( const char * const buf, const int len )
  {
  data.back().append( buf, len );
  if( data.back().size() && data.back()[data.back().size()-1] == '\n' )
    {
    if( data.size() >= data.capacity() && data.size() >= 10000 )
      data.reserve( data.size() + ( data.size() / 10 ) );
    data.push_back( std::string() );
    }
  }


     // If all lines end with CR-LF, assume DOS file
bool Basic_buffer::detect_and_remove_cr() throw()
  {
  if( lines() <= 1 ) return false;
  for( int i = 0; i < last_line(); ++i )
    {
    const std::string & s = data[i];
    const int size = s.size();
    if( size < 2 || s[size-2] != '\r' ) return false;
    }
  for( int i = 0; i < last_line(); ++i )
    data[i].erase( data[i].size() - 2, 1 );
  return true;
  }


Basic_buffer::Basic_buffer( const Basic_buffer & b, const Point & p1, const Point & p2 )
  : data( 1 )
  {
  Point p = bof();
  pputb( p, b, p1, p2 );
  }


int Basic_buffer::characters( const int line ) const throw()
  {
  if( line < 0 || line >= lines() ) return -1;
  return data[line].size();
  }


bool Basic_buffer::compare_lines( const int line1, const int line2 ) const throw()
  {
  if( line1 < 0 || line1 >= lines() || line2 < 0 || line2 >= lines() )
    return false;
  return ( data[line1] == data[line2] );
  }


bool Basic_buffer::one_character() const throw()
  {
  return ( data[0].size() == 1 &&
           ( lines() == 1 || ( lines() == 2 && data[1].size() == 0 ) ) );
  }


int Basic_buffer::operator[]( const Point & p ) const throw()
  {
  if( p.col < 0 || p.col >= characters( p.line ) ) return -1;
  unsigned char ch = data[p.line][p.col];
  return ch;
  }


int Basic_buffer::pgetc( Point & p ) const throw()
  {
  Point p1 = p;
  if( !pisvalid( p ) || !pnext( p ) ) return -1;
  unsigned char ch = data[p1.line][p1.col];
  return ch;
  }


int Basic_buffer::pngetc( Point & p ) const throw()
  {
  if( !pnext( p ) || p == eof() ) return -1;
  unsigned char ch = data[p.line][p.col];
  return ch;
  }


int Basic_buffer::ppgetc( Point & p ) const throw()
  {
  if( !pprev( p ) ) return -1;
  unsigned char ch = data[p.line][p.col];
  return ch;
  }


bool Basic_buffer::pnext( Point & p ) const throw()
  {
  if( ++p.col < characters( p.line ) || p == eof() ) return true;
  if( ++p.line < lines() ) { p.col = 0; return true; }
  --p.line; --p.col; return false;
  }


bool Basic_buffer::pprev( Point & p ) const throw()
  {
  int c = characters( p.line );
  if( c >= 0 && p.col > c ) p.col = c;
  if( --p.col >= 0 ) return true;
  int col = characters( --p.line ) - 1;
  if( col >= 0 ) { p.col = col; return true; }
  ++p.line; ++p.col; return false;
  }


bool Basic_buffer::pseek( Point & p, int n ) const throw()
  {
  Point saved_p = p;
  p.col += n;
  if( n > 0 )
    {
    while( p.line < last_line() )
      {
      int c = characters( p.line );
      if( p.col < c || p == eof() ) break;
      p.col -= c; ++p.line;
      }
    if( p.col >= 0 && ( p.line < last_line() || p <= eof() ) ) return true;
    }
  else if( n < 0 )
    {
    while( p.line > 0 && p.col < 0 ) p.col += characters( --p.line );
    if( p.line >= 0 && p.col >= 0 ) return true;
    }
  else return true;	// n == 0
  p = saved_p; return false;
  }


bool Basic_buffer::pvalid( Point & p ) const throw()
  {
  if( p < bof() ) p = bof();
  else if( p > eof() ) p = eof();
  else if( p.col < 0 ) p.col = 0;
  else if( p.col > 0 && p.col >= (int)data[p.line].size() && p != eof() )
    p.col = data[p.line].size() - 1;
  else return true;
  return false;
  }


// insert block b.[p1,p2) at p and move next
bool Basic_buffer::pputb( Point & p, const Basic_buffer & b, const Point & p1, const Point & p2 )
  {
  if( !pisvalid( p ) || !b.pisvalid( p1 ) || !b.pisvalid( p2 ) || p1 >= p2 )
    return false;
  const int lines = p2.line - p1.line;
  if( lines == 0 )
    {
    data[p.line].insert( p.col, b.data[p1.line], p1.col, p2.col - p1.col );
    p.col += p2.col - p1.col;
    }
  else
    {
    insert_lines( data, p.line + 1, lines );
    if( p2.col > 0 ) data[p.line+lines].assign( b.data[p2.line], 0, p2.col );
    data[p.line+lines].append( data[p.line], p.col, data[p.line].size() - p.col );
    data[p.line].erase( p.col, data[p.line].size() - p.col );
    data[p.line].append( b.data[p1.line], p1.col, b.data[p1.line].size() - p1.col );
    for( int i = 1; i < lines; ++i )
      data[p.line+i].assign( b.data[p1.line+i] );
    p.line += lines; p.col = p2.col;
    }
  return true;
  }


// insert char and move next
bool Basic_buffer::pputc( Point & p, const unsigned char ch )
  {
  if( !pisvalid( p ) ) return false;
  data[p.line].insert( data[p.line].begin() + p.col, ch );
  ++p.col;
  if( ch == '\n' )
    {
    if( p == eof() ) data.push_back( std::string() );
    else
      {
      insert_lines( data, p.line + 1, 1 );
      data[p.line+1].assign( data[p.line], p.col, data[p.line].size() - p.col );
      data[p.line].erase( p.col, data[p.line].size() - p.col );
      }
    ++p.line; p.col = 0;
    }
  return true;
  }


// change char and move next
bool Basic_buffer::pchgc( Point & p, const unsigned char ch ) throw()
  {
  int ch1 = (*this)[p];
  if( ch == '\n' || ch1 < 0 || ch1 == '\n' ) return false;
  data[p.line][p.col++] = ch;
  return true;
  }


// delete block at [p1,p2)
bool Basic_buffer::pdelb( const Point & p1, const Point & p2 )
  {
  if( p1 >= p2 || !pisvalid( p1 ) || !pisvalid( p2 ) ) return false;
  const int lines = p2.line - p1.line;
  if( lines == 0 ) data[p1.line].erase( p1.col, p2.col - p1.col );
  else
    {
    if( p2.col > 0 ) data[p2.line].erase( 0, p2.col );
    if( p1.col == 0 ) delete_lines( data, p1.line, lines );
    else
      {
      data[p1.line].erase( p1.col, data[p1.line].size() - p1.col );
      data[p1.line].append( data[p2.line] );
      delete_lines( data, p1.line + 1, lines );
      }
    }
  return true;
  }


bool Basic_buffer::to_lowercase( const Point & p1, const Point & p2 ) throw()
  {
  bool change = false;
  if( p1 < p2 && pisvalid( p1 ) && pisvalid( p2 ) )
    for( Point p = p1; p < p2; pnext( p ) )
      {
      const unsigned char ch = data[p.line][p.col];
      if( ISO_8859::isupper( ch ) )
        { data[p.line][p.col] = ISO_8859::tolower( ch ); change = true; }
      }
  return change;
  }


bool Basic_buffer::to_uppercase( const Point & p1, const Point & p2 ) throw()
  {
  bool change = false;
  if( p1 < p2 && pisvalid( p1 ) && pisvalid( p2 ) )
    for( Point p = p1; p < p2; pnext( p ) )
      {
      const unsigned char ch = data[p.line][p.col];
      if( ISO_8859::islower( ch ) )
        { data[p.line][p.col] = ISO_8859::toupper( ch ); change = true; }
      }
  return change;
  }


// Is p at beginning of word?
bool Basic_buffer::pisbow( const Point & p ) const throw()
  {
  Point p1 = p;
  int ch = (*this)[p];
  return ( ch >= 0 && ISO_8859::isalnum_( ch ) &&
           ( p == bol( p ) || !ISO_8859::isalnum_( ppgetc( p1 ) ) ) );
  }


// Is p at end of word?
bool Basic_buffer::piseow( const Point & p ) const throw()
  {
  Point p1 = p;
  int ch = ppgetc( p1 );
  return ( ch >= 0 && ISO_8859::isalnum_( ch ) &&
           ( p == eol( p ) || !ISO_8859::isalnum_( (*this)[p] ) ) );
  }


Point Basic_buffer::bol( const Point & p ) const throw()
  {
  if( p.line >= lines() ) return eof();
  else if( p.line < 0 ) return bof();
  else return Point( p.line, 0 );
  }


Point Basic_buffer::eol( const Point & p ) const throw()
  {
  if( p.line >= last_line() ) return eof();
  else if( p.line < 0 ) return eol( bof() );
  else return Point( p.line, data[p.line].size() - 1 );
  }


// returns position of first non whitespace character in line (eol if none)
Point Basic_buffer::bot( const Point & p ) const throw()
  {
  Point p1 = bol( p );
  const std::string & s = data[p1.line];
  while( p1.col < (int)s.size() && std::isspace( s[p1.col] ) && s[p1.col] != '\n' )
    ++p1.col;
  return p1;
  }


// returns position following last non whitespace character in line (bol if none)
Point Basic_buffer::eot( const Point & p ) const throw()
  {
  Point p1 = eol( p );
  const std::string & s = data[p1.line];
  while( p1.col > 0 && std::isspace( s[p1.col-1] ) ) --p1.col;
  return p1;
  }


// begin of paragraph
Point Basic_buffer::bop( const Point & p ) const throw()
  {
  Point p1 = p;
  if( !blank( p1.line ) ) while( !blank( p1.line - 1 ) ) --p1.line;
  return bol( p1 );
  }


// end of paragraph
Point Basic_buffer::eop( const Point & p ) const throw()
  {
  Point p1 = p;
  while( !blank( p1.line ) ) ++p1.line;
  return bol( p1 );
  }


// Move 'p' to the matching delimiter.
// If (force && forward), fail if [p] is closing delimiter.
// If (force && !forward), fail if [p] is opening delimiter.
// Returns +1 if done, 0 if [p] is not a delimiter, -1 if no matching delimiter.
//
int Basic_buffer::set_to_matching_delimiter( Point & p, bool forward,
                                             const bool force ) const throw()
  {
  int ch1 = (*this)[p];
  if( ch1 < 0 ) return 0;
  unsigned char ch2;
  const bool forward_orig = forward;
  bool comment = false;
  switch( ch1 )
    {
    case '(': ch2 = ')'; forward = true; break;
    case '[': ch2 = ']'; forward = true; break;
    case '{': ch2 = '}'; forward = true; break;
    case '<': ch2 = '>'; forward = true; break;
    case ')': ch2 = '('; forward = false; break;
    case ']': ch2 = '['; forward = false; break;
    case '}': ch2 = '{'; forward = false; break;
    case '>': ch2 = '<'; forward = false; break;
    case '/': ch2 = '*'; forward = true; comment = true; break;
    case '*': ch2 = '/'; forward = false; comment = true; break;
    case '"':
    case '\'':
    case '`': ch2 = ch1; break;
    default: return 0;
    }
  if( force && ( forward != forward_orig ) ) return -1;

  if( comment )
    {
    Point p1 = p;
    const int ch = pngetc( p1 );
    if( ch >= 0 && ch == ch2 )
      {
      if( forward )
        { if( pngetc( p1 ) >= 0 && find_text( p1, std::string( "*/" ) ) )
          { pseek( p1, -2 ); p = p1; return +1; } }
      else
        { p1 = p; if( rfind_text( p1, std::string( "/*" ) ) )
          { p = p1; return +1; } }
      return -1;
      }
    return 0;
    }

  {
  Point pt = p;		// no match if delimiter is escaped
  if( ppgetc( pt ) == '\\' )
    { Point pt2 = pt; if( ppgetc( pt2 ) != '\\' ) return -1; }
  }

  Point p1 = p;
  int level = 0;	// nesting level of delimiters
  bool quote = false;	// ignore double-quoted text

  if( forward )
    while( true )
      {
      const int ch = pngetc( p1 ); if( ch < 0 ) break;
      if( !quote )
        {
        if( ch == ch2 ) { if( --level < 0 ) { p = p1; return +1; } }
        else if( ch == ch1 ) { ++level; continue; }
        else if( ch == '\'' )		// ignore single-quoted characters
          {
          Point pt = p1; pnext( pt );
          if( pngetc( pt ) == '\'' ) p1 = pt;
          continue;
          }
        }

      if( ch == '\\' ) pnext( p1 );	// ignore escaped characters
      else if( ch == '"' && ch1 != '\'' ) quote = !quote;
      }
  else
    while( true )
      {
      const int ch = ppgetc( p1 ); if( ch < 0 ) break;
      if( !quote )
        {
        Point pt = p1;
        if( ppgetc( pt ) == '\\' )
          { Point pt2 = pt; if( ppgetc( pt2 ) != '\\' ) { p1 = pt; continue; } }
        if( ch == ch2 ) { if( --level < 0 ) { p = p1; return +1; } }
        else if( ch == ch1 ) { ++level; continue; }
        else if( ch == '\'' )		// ignore single-quoted characters
          {
          Point pt = p1; pprev( pt );
          if( ppgetc( pt ) == '\'' ) p1 = pt;
          continue;
          }
        }

      if( ch == '"' && ch1 != '\'' )
        {
        Point pt = p1;
        if( ppgetc( pt ) == '\\' )	// ignore escaped quotes
          { Point pt2 = pt; if( ppgetc( pt2 ) != '\\' ) { p1 = pt; continue; } }
        quote = !quote;
        }
      }
  return -1;
  }


// Search forward from 'p' for 'pattern' (Boyer-Moore algorithm)
// If found, set 'p' after found substring
//
bool Basic_buffer::find_text( Point & p, const std::string & text, const bool icase ) const throw()
  {
  if( text.size() == 0 ) return true;
  Point p1 = p;
  if( !pseek( p1, text.size() ) ) return false;
  const std::string * textp = &text;
  std::string itext;

  if( icase )
    {
    for( unsigned int i = 0; i < text.size(); ++i )
      itext += ISO_8859::tolower( text[i] );
    textp = &itext;
    }

  std::vector< int > table( 256, textp->size() );
  for( unsigned int i = 0; i < textp->size() - 1; ++i )
    { unsigned char ch = (*textp)[i]; table[ch] = textp->size() - i - 1; }

  while( true )
    {
    Point p2 = p1;
    int i, delta = 0;
    for( i = textp->size() - 1; i >= 0; --i )
      {
      unsigned char ch = (*textp)[i];
      int ch2 = ppgetc( p2 ); if( ch2 < 0 ) return false;
      if( icase ) ch2 = ISO_8859::tolower( ch2 );
      if( !delta ) delta = table[ch2];
      if( ch != ch2 ) break;
      }
    if( i < 0 ) { p = p1; return true; }
    if( !pseek( p1, delta ) ) return false;
    }
  }


// Search backward from 'p-1' for 'pattern' (Boyer-Moore algorithm)
// If found, set 'p' to the first char of found substring
//
bool Basic_buffer::rfind_text( Point & p, const std::string & text, const bool icase ) const throw()
  {
  if( text.size() == 0 ) return true;
  Point p1 = p;
  if( !pseek( p1, -text.size() ) ) return false;
  const std::string * textp = &text;
  std::string itext;

  if( icase )
    {
    for( unsigned int i = 0; i < text.size(); ++i )
      itext += ISO_8859::tolower( text[i] );
    textp = &itext;
    }

  std::vector< int > table( 256, textp->size() );
  for( unsigned int i = textp->size() - 1; i > 0; --i )
    { unsigned char ch = (*textp)[i]; table[ch] = i; }

  while( true )
    {
    Point p2 = p1;
    unsigned int i; int delta = 0;
    for( i = 0; i < textp->size(); ++i )
      {
      unsigned char ch = (*textp)[i];
      int ch2 = pgetc( p2 ); if( ch2 < 0 ) return false;
      if( icase ) ch2 = ISO_8859::tolower( ch2 );
      if( !delta ) delta = table[ch2];
      if( ch != ch2 ) break;
      }
    if( i >= textp->size() ) { p = p1; return true; }
    if( !pseek( p1, -delta ) ) return false;
    }
  }


Point Basic_buffer::to_cursor( const Point & pointer ) const throw()
  {
  Point p = bol( pointer );
  Point cursor = p;
  while( p.col < pointer.col )
    {
    int ch = (*this)[p];
    if( ch < 0 ) break;
    if( ch == '\t' ) cursor.col += 8 - ( cursor.col % 8 );
    else ++cursor.col;
    ++p.col;
    }
  return cursor;
  }


Point Basic_buffer::to_pointer( const Point & cursor, Point & pcursor ) const throw()
  {
  pcursor = bol( cursor );
  Point pointer = pcursor;
  const int peolcol = eol( pointer ).col;
  while( pcursor.col < cursor.col && pointer.col < peolcol )
    {
    int ch = (*this)[pointer];
    if( ch < 0 ) break;
    if( ch == '\t' )
      {
      int col = pcursor.col + 8 - ( pcursor.col % 8 );
      if( col <= cursor.col ) pcursor.col = col; else break;
      }
    else ++pcursor.col;
    ++pointer.col;
    }
  return pointer;
  }


int Basic_buffer::to_string( const Point & p1, const Point & p2,
                             std::string & text ) const
  {
  text.clear();
  if( p1 < p2 && pisvalid( p1 ) && pisvalid( p2 ) )
    {
    if( p1.line == p2.line )
      text.assign( data[p1.line], p1.col, p2.col - p1.col );
    else
      {
      int line = p1.line;
      text.assign( data[line], p1.col, data[line].size() - p1.col );
      while( ++line < p2.line ) text += data[line];
      if( p2.col ) text.append( data[line], 0, p2.col );
      }
    }
  return text.size();
  }
