/* GNU moe - My Own Editor
   Copyright (C) 2005-2025 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 2 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 one column wide,
   and cursors can go beyond the last character in a buffer line.
*/
struct Point
  {
  long line, col;

  explicit Point( long l = -1, long c = 0 ) : line( l ), col( c ) {}
  Point & assign( long l, long c ) { line = l; col = c; return *this; }

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

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

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

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

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

  Point & cut()
    { 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

  Basic_buffer( const Basic_buffer & );		// declared as private
  void operator=( const Basic_buffer & );	// declared as private

protected:
  bool add_line( const char * const buf, const int len );
  bool detect_and_remove_cr();
  const std::vector< std::string > & get_data() const { return data; }

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

  // true if line is blank (invalid, empty, or containing only whitespace)
  bool blank( const long line ) const;
  long characters( const unsigned long line ) const
    { return ( line < data.size() ) ? data[line].size() : 0; }
  bool compare_lines( const long line1, const long line2 ) const
    { return data[line1] == data[line2]; }
  bool empty() const { return data.size() == 1 && data[0].empty(); }
  long lines() const { return data.size(); }
  long last_line() const { return lines() - 1; }
  bool one_character() const		// buffer has only one character
    { return data[0].size() == 1 &&
             ( lines() == 1 || ( lines() == 2 && data[1].empty() ) ); }

  const std::string & get_line( const long line ) const { return data[line]; }
  int  operator[]( const Point & p ) const;
  int  pgetc( Point & p ) const;	// get and move next
  int  pngetc( Point & p ) const;	// move next and get
  int  ppgetc( Point & p ) const;	// move prev and get
  bool pnext( Point & p ) const;
  bool pprev( Point & p ) const;
  bool pseek( Point & p, const long n ) const;
  bool pvalid( Point & p ) const;	// make p valid, return old p status

       // 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 );
       // delete block at [p1,p2)
  bool pdelb( const Point & p1, const Point & p2 );
  bool capitalize( const Point & p1, const Point & p2 );
  bool to_lowercase( const Point & p1, const Point & p2 );
  bool to_uppercase( const Point & p1, const Point & p2 );

  bool pisbow( const Point & p ) const;
  bool piseow( const Point & p ) const;
  bool pisescaped( Point p ) const
    { bool escaped = false; while( ppgetc( p ) == '\\' ) escaped = !escaped;
      return escaped; }
  bool pisvalid( const Point & p ) const
    { return ( p.col >= 0 && p.col < characters( p.line ) ) || p == eof(); }

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

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

  Point to_cursor( const Point & pointer ) const;
  Point to_pointer( const Point & cursor, Point & pcursor ) const;
  long to_string( const Point & p1, const Point & p2, std::string & text ) const;
  long to_string( const Point & p1, const long size, std::string & text ) const;
  long to_string_esc( const Point & p1, const Point & p2,
                      std::string & text ) const;
  long to_string_ws( const Point & p1, const Point & p2,
                     std::string & text ) const;
  };


class Buffer : public Basic_buffer
  {
  static const std::string unnamed;
  mutable bool backuped, 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

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

      // Create 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();
      };

    long 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();

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

  Change_history change_history;

public:
  struct Error
    {
    const char * const msg;
    explicit Error( const char * const s ) : msg( s ) {}
    };

  class Options
    {
    int lmargin_, rmargin_;
  public:
    bool auto_indent, overwrite, read_only, two_spaces, word_wrap;

    Options()
      : lmargin_( 0 ), rmargin_( 75 ), auto_indent( false ),
      overwrite( false ), read_only( false ), two_spaces( false ),
      word_wrap( false ) {}

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

  int handnum;			// used by delete_all_handles, show_status
  Options options;
  Point mark[10];		// user bookmarks
  bool crlf, existed;

  explicit Buffer( const char * const namep = "", const bool read_only = false );

  int set_marks();		// set initial or extended user bookmarks
  bool save( const std::string * namep = 0, bool save_to = false,
             const bool append = false, Point p1 = Point(), Point p2 = Point() ) const;

  long character_offset( const Point & p, int & percent ) const;
  bool modified() const { return modified_; }
  void name( const std::string & n ) { name_ = n; }
  const std::string & name() const { return name_; }
  const std::string & nename() const			// non-empty name
    { if( name_.size() ) return name_; else return unnamed; }
  void mark_as_unmodified() const { modified_ = false; } // for emergency_save
  bool precious() const { return modified_ && !empty(); }

       // 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 bool save_end = false );
       // 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 bool save_end = false );
       // delete char at 'p', or at '--p' if back is true
  bool pdelc( Point & p, const bool back = false );
  bool pdell( Point & p );	// 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 { change_history.force_append = true; }
  void reset_appendable() const
    { change_history.appendable = change_history.force_append = false; }

  bool reformat_paragraph( Point & cursor, const bool append_change );
  };
