/*  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/>.
*/

// There are two uses for 'Points'; cursors and pointers.
// 'cursors' are unbounded non-negative coordinates.
// 'pointers' always point to a valid character in 'Buffer' or to eof.
// They are different because characters can be more than a column wide,
// and cursors can go beyond the last character in a buffer line.
//
struct Point
  {
  int line, col;

  Point( int l = -1, int c = 0 ) throw() : line( l ), col( c ) {}

  Point & operator+=( const Point & p ) throw()
    { line += p.line; col += p.col; return *this; }
  Point & operator-=( const Point & p ) throw()
    { line -= p.line; col -= p.col; return *this; }

  Point operator+( const Point & p ) const throw()
    { Point r( *this ); r += p; return r; }
  Point operator-( const Point & p ) const throw()
    { Point r( *this ); r -= p; return r; }

  bool operator==( const Point & p ) const throw()
    { return ( line == p.line && col == p.col ); }
  bool operator!=( const Point & p ) const throw() { return !( *this == p ); }

  bool operator<( const Point & p ) const throw()
    { return ( line < p.line || ( line == p.line && col < p.col ) ); }
  bool operator>( const Point & p ) const throw()
    { return ( line > p.line || ( line == p.line && col > p.col ) ); }

  bool operator<=( const Point & p ) const throw() { return !( *this > p ); }
  bool operator>=( const Point & p ) const throw() { return !( *this < p ); }

  Point & cut() throw()
    { if( line < 0 ) line = 0; if( col < 0 ) col = 0; return *this; }
  };


// All the lines are newline terminated except the last one, whose
// next-to-last character position ( data[last_line()].end() ) is eof().
//
class Basic_buffer
  {
  std::vector< std::string > data;	// the lines

protected:
  void add_line( const char * const buf, const int len );
  bool detect_and_remove_cr() throw();

public:
  Basic_buffer() : data( 1 ) {}
  Basic_buffer( const Basic_buffer & b, const Point & p1, const Point & p2 );

  bool blank( const int line ) const throw()
    { Point p( line, 0 ); return ( !pisvalid( p ) || bot( p ) == eol( p ) ); }
  int  characters( const int line ) const throw();
  bool compare_lines( const int line1, const int line2 ) const throw();
  bool empty() const throw() { return ( data.size() == 1 && !data[0].size() ); }
  int  lines() const throw() { return data.size(); }
  int  last_line() const throw() { return lines() - 1; }
  bool one_character() const throw();		// buffer has only one character

  int  operator[]( const Point & p ) const throw();
  int  pgetc( Point & p ) const throw();	// get and move next
  int  pngetc( Point & p ) const throw();	// move next and get
  int  ppgetc( Point & p ) const throw();	// move prev and get
  bool pnext( Point & p ) const throw();
  bool pprev( Point & p ) const throw();
  bool pseek( Point & p, int n ) const throw();
  bool pvalid( Point & p ) const throw();

  // insert block b.[p1,p2) at p and move next
  bool pputb( Point & p, const Basic_buffer & b, const Point & p1, const Point & p2 );
  // insert char and move next
  bool pputc( Point & p, const unsigned char ch );
  // change char and move next
  bool pchgc( Point & p, const unsigned char ch ) throw();
  // delete block at [p1,p2)
  bool pdelb( const Point & p1, const Point & p2 );
  bool to_lowercase( const Point & p1, const Point & p2 ) throw();
  bool to_uppercase( const Point & p1, const Point & p2 ) throw();

  bool pisbow( const Point & p ) const throw();
  bool piseow( const Point & p ) const throw();
  bool pisvalid( const Point & p ) const throw()
    { return ( ( p.col >= 0 && p.col < characters( p.line ) ) || p == eof() ); }

  Point bof() const throw() { return Point( 0, 0 ); }
  Point eof() const throw() { return Point( last_line(), data[last_line()].size() ); }
  Point bol( const Point & p ) const throw();
  Point eol( const Point & p ) const throw();
  Point bot( const Point & p ) const throw();	// begin of text in line
  Point eot( const Point & p ) const throw();	// end of text in line
  Point bop( const Point & p ) const throw();	// begin of paragraph
  Point eop( const Point & p ) const throw();	// end of paragraph

  int set_to_matching_delimiter( Point & p, bool forward = true,
                                 const bool force = false ) const throw();
  bool find_text( Point & p, const std::string & text, const bool icase = false ) const throw();
  bool rfind_text( Point & p, const std::string & text, const bool icase = false ) const throw();

  Point to_cursor( const Point & pointer ) const throw();
  Point to_pointer( const Point & cursor, Point & pcursor ) const throw();
  int to_string( const Point & p1, const Point & p2, std::string & text ) const;
  };


class Buffer : public Basic_buffer
  {
  mutable bool _modified, saved;
  mutable std::string _name;

  struct Change_history
    {
    struct Atom
      {
      Point begin;	// Buffer position of delete or begin of insert
      Point end;		// Buffer position of end of insert
      Basic_buffer *bbp;	// Basic_buffer containing deleted data

      // Creates an insert Atom ([p1,p2) -> inserted_text)
      Atom( const Point & p1, const Point & p2 )
        : begin( p1 ), end( p2 ), bbp( 0 ) {}

      // Creates a delete Atom (bbp -> deleted_text)
      Atom( const Point & p1, const Point & p2, const Basic_buffer & b )
        : begin( p1 ), end( p1 ), bbp( 0 )
        { if( p1 < p2 ) bbp = new Basic_buffer( b, p1, p2 ); }

      // To create a replace Atom make a delete Atom, then move end.
      };

    struct Record
      {
      Point before, after;	// Pointer position before and after the change
      mutable bool modified;	// Status of modified flag before this record
      std::vector< Atom > atom_vector;

      Record( const Point & pb, const Point & pa, const bool m )
        : before( pb ), after( pa ), modified( m ) {}
      void add_atom( const Atom & a ) { atom_vector.push_back( a ); }
      bool append_record( const Basic_buffer & buffer, Record & r );
      void delete_atoms() throw();
      };

    int index;			// Index of record to be undone
    mutable bool appendable;	// True if last record added is appendable
    mutable bool force_append;
    std::vector< Record > record_vector;

    Change_history()
      : index( -1 ), appendable( false ), force_append( false ) {}
    ~Change_history() throw();

    void add_record( const Basic_buffer & buffer, Record & r );
    int records() const throw() { return record_vector.size(); }
    };

  Change_history change_history;

public:
  struct Error
    {
    const char * s;
    Error( const char * p ) { s = p; }
    };

  class Options
    {
    int _lmargin, _rmargin;
  public:
    bool auto_indent, overwrite, read_only, word_wrap;

    Options() throw()
      : _lmargin( 0 ), _rmargin( 71 ), auto_indent( false ),
      overwrite( false ), read_only( false ), word_wrap( false ) {}

    void reset() throw() { *this = Options(); }
    int lmargin() const throw() { return _lmargin; }
    int rmargin() const throw() { return _rmargin; }
    bool set_lmargin( const int l ) throw();
    bool set_rmargin( const int r ) throw();
    };

  Options options;
  Point mark[10];		// user bookmarks
  bool crlf;

  Buffer( const Options & opts, const std::string * namep = 0 );
  bool save( const std::string * namep = 0, bool set_name = true, bool append = false,
             Point p1 = Point(), Point p2 = Point() ) const;

  const std::string & character_info( const Point & p ) const throw();
  bool modified() const throw() { return _modified; }
  void name( const std::string & n ) { _name = n; }
  const std::string & name() const throw() { return _name; }
  const std::string & uname() const throw();

       // insert block b.[p1,p2) at p and move next
  bool pputb( Point & p, const Basic_buffer & b, const Point & p1,
              const Point & p2, const int save_change = 1 );
       // put char and move next
  bool pputc( Point & p, const unsigned char ch );

       // delete block at [p1,p2)
  bool pdelb( const Point & p1, const Point & p2, const int save_change = 1 );
       // delete char at 'p', or at '--p' if back is true
  bool pdelc( Point & p, const bool back = false );
  bool pdell( Point & p ) throw();	// delete line and set 'p' to bol()

       // replace block at [p1,p2) with b.[bp1,bp2) and move p2 next
  bool replace( const Point & p1, Point & p2, const Basic_buffer & b,
                const Point & bp1, const Point & bp2 );

  bool undo( Point & p );
  bool redo( Point & p );
  void force_append() const throw() { change_history.force_append = true; }
  void reset_appendable() const throw()
    { change_history.appendable = change_history.force_append = false; }
  };
