/*  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 <ctime>
#include <string>
#include <vector>

#include "buffer.h"
#include "buffer_handle.h"
#include "block.h"
#include "encoding.h"
#include "iso_8859.h"
#include "menu.h"
#include "rc.h"
#include "regex.h"
#include "screen.h"
#include "window.h"
#include "window_vector.h"


namespace Window_vector {

int _curwin;			// current window (the one with the cursor in)
std::vector< Window > data;


bool no_block() throw()
  {
  if( !Block::valid() ) { Screen::show_message( "No block" ); return true; }
  return false;
  }


bool no_block_in_this_file( const Buffer & buffer ) throw()
  {
  if( Block::bufferp() != &buffer )
    { Screen::show_message( "Block is not in this file" ); return true; }
  return false;
  }


bool rectangular_block() throw()
  {
  if( RC::editor_options().rectangle_mode )
    {
    Screen::show_message( "Operation not available in rectangular block mode" );
    return true;
    }
  return false;
  }


bool read_only( const Buffer & buffer ) throw()
  {
  if( buffer.options.read_only )
    { Screen::show_message( "Read only" ); return true; }
  return false;
  }


void replace_and_repaint( Buffer & buffer, const Basic_buffer & tmp ) throw()
  {
  Point p = Block::end();
  buffer.reset_appendable();
  buffer.replace( Block::begin(), p, tmp, tmp.bof(), tmp.eof() );
  buffer.reset_appendable();
  if( RC::editor_options().auto_unmark ) Block::reset();
  else Block::set_end( buffer, p );
  Screen::repaint( &buffer );
  }

} // end namespace Window_vector


int Window_vector::init()
  {
  _curwin = 0;
  return load( RC::editor_options().orphan_extra ? 1 : Bufhandle_vector::handles() );
  }


// 'new_size' == 0: reload same number of windows
// 'new_size' < 0: toggle 1 window <--> max_windows
//
int Window_vector::load( int new_size )
  {
  const int handles = Bufhandle_vector::handles();
  const bool toggle = ( new_size < 0 && handles > 1 );
  const bool center = ( data.size() == 0 );		// center all on init

  if( !handles ) { _curwin = 0; data.clear(); return 0; }
  if( new_size == 0 ) new_size = data.size();
  else if( new_size < 0 )
    {
    for( int i = 0; i < _curwin; ++i ) Bufhandle_vector::next();
    new_size = (data.size() != 1) ? 1 : handles;
    _curwin = 0;
    }
  new_size = std::min( new_size, handles );
  new_size = std::min( new_size, Screen::max_windows() );
  if( RC::editor_options().max_windows > 0 )
    new_size = std::min( new_size, RC::editor_options().max_windows );
  new_size = std::max( new_size, 1 );
  while( _curwin >= new_size ) { --_curwin; Bufhandle_vector::next(); }
  const int wheight = Screen::height() / new_size;

  data.clear();
  int i = 0, y = 0;
  while( i + 1 < new_size )
    {
    data.push_back( Window( y, wheight, Bufhandle_vector::handle( i ), center ) );
    ++i; y += wheight;
    }
  data.push_back( Window( y, Screen::height() - y, Bufhandle_vector::handle( i ), center ) );
  if( toggle && data.size() == 1 ) data[0].center_cursor();
  for( i = 0; i < (int)data.size(); ++i ) data[i].repaint();
  return data.size();
  }


void Window_vector::bufhandle_menu( const int abort_key )
  {
  const int handle = Menu::bufhandle_menu( _curwin, abort_key );
  if( handle >= 0 ) { _curwin = handle; if( _curwin >= windows() ) load(); }
  }


void Window_vector::change_buffer_name()
  {
  Buffer & buffer = data[_curwin].buffer();

  std::string name( buffer.name() );
  if( Menu::get_filename( "New buffer name (^C to abort): ", name ) < 0 )
    return;
  buffer.name( name );
  Screen::show_status_lines( &buffer );
  }


void Window_vector::load_file( const int abort_key )
  {
  std::string name;
  if( Menu::get_filename( "Name of file to edit (^C to abort): ", name, abort_key ) < 0 )
    return;
  const int old_handles = Bufhandle_vector::handles();
  int handle;
  try { handle = Bufhandle_vector::find_or_add_handle( RC::default_buffer_options(),
                                                       &name, _curwin + 1 ); }
  catch( Buffer::Error e ) { Screen::show_message( e.s ); return; }
  _curwin = handle;
  if( windows() > 1 && Bufhandle_vector::handles() > old_handles )
    load( windows() + 1 );
  else load();
  }


void Window_vector::split_window()
  {
  Bufhandle_vector::duplicate_handle( _curwin++ );
  load( windows() + 1 );
  }


void Window_vector::save_file( const int abort_key )
  {
  const Buffer & buffer = data[_curwin].buffer();

  std::string name( buffer.name() );
  if( Menu::get_filename( "Name of file to save (^C to abort): ", name, abort_key ) <= 0 )
    return;
  bool done;
  try { done = buffer.save( &name ); }
  catch( Buffer::Error e ) { Screen::show_message( e.s ); return; }
  if( done )
    {
    Screen::show_status_lines( &buffer );
    std::string s( "File '" ); s += name; s += "' saved";
    Screen::show_message( s );
    }
  else if( !read_only( buffer ) ) Screen::show_message( "File not saved" );
  }


// Returns number of windows, 0 if none, -1 if aborted.
//
int Window_vector::close( bool abort, const bool quiet )
  {
  Window & w = data[_curwin];
  Buffer_handle & bh = w.buffer_handle();
  const bool last_handle = Bufhandle_vector::handles( bh.buffer() ) <= 1;
  const bool modified = bh.buffer().modified();
  const bool exit_ask = RC::editor_options().exit_ask && !quiet;

  if( !abort || !quiet )
    {
    if( abort && modified && last_handle )
      {
      int key = Screen::show_message( "Lose changes to this file ? (y/n/^C) ", true, true );
      if( key == 'n' || key == 'N' ) abort = false;
      else if( key != 'y' && key != 'Y' ) return -1;
      }
    if( !abort && modified )
      {
      std::string name( bh.buffer().name() );
      if( exit_ask || !name.size() )
        while( !Menu::get_filename( "Name of file to save (^C to abort): ", name ) ) ;
      if( !name.size() ) return -1;
      try { if( !bh.buffer().save( &name ) ) return -1; }
      catch( Buffer::Error e )
        { if( !quiet ) Screen::show_message( e.s ); return -1; }
      }
    else if( exit_ask && last_handle && !modified )
      {
      int key = Screen::show_message( "Abandon this file ? (y/N) ", true, true );
      if( key != 'y' && key != 'Y' ) return -1;
      }
    }
  Bufhandle_vector::delete_handle( _curwin );
  if( _curwin > 0 ) --_curwin;
  return load();
  }


int Window_vector::close_and_exit( const bool abort )
  {
  int key;
  if( abort ) key = Screen::show_message( "Are you sure you want to abort ? (y/N) ", true, true );
  else key = Screen::show_message( "Are you sure you want to exit ? (y/N) ", true, true );
  if( key == 'y' || key == 'Y' )
    {
    prev();
    while( data.size() && close( abort, true ) >= 0 ) ;
    }
  return data.size();
  }


Window & Window_vector::curwin() throw() { return data[_curwin]; }


void Window_vector::last_visited()
  {
  const int handles = Bufhandle_vector::handles();
  if( handles <= 1 ) return;
  _curwin = Bufhandle_vector::last_visited( _curwin );
  if( _curwin >= windows() ) load();
  }


void Window_vector::next()
  {
  const int handles = Bufhandle_vector::handles();
  if( handles <= 1 ) return;
  if( _curwin + 1 < windows() ) ++_curwin;
  else
    {
    if( handles <= windows() ) _curwin = 0;
    else { Bufhandle_vector::next(); load(); }
    }
  }


void Window_vector::prev()
  {
  const int handles = Bufhandle_vector::handles();
  if( handles <= 1 ) return;
  if( _curwin > 0 ) --_curwin;
  else
    {
    if( handles <= windows() ) _curwin = windows() - 1;
    else { Bufhandle_vector::prev(); load(); }
    }
  }


Window & Window_vector::window( int i ) throw()
  {
  if( i < 0 ) i = 0; else if( i >= windows() ) i = windows() - 1;
  return data[i];
  }


int Window_vector::windows() throw() { return data.size(); }


void Window_vector::add_char( unsigned char ch, const bool force ) throw()
  {
  Window & w = data[_curwin];
  if( w.buffer().options.read_only )
    {
    if( ch == ' ' && !force ) w.move_vertical( w.height() - 2 );
    else read_only( w.buffer() );
    return;
    }

  if( ch == '\r' && !force ) ch = '\n';
  if( ( force || ch == '\t' || ch == '\n' || ISO_8859::isprint( ch ) ) &&
      w.buffer().pputc( w.pointer(), ch ) )
    Screen::repaint( &w.buffer() );
  }


void Window_vector::delete_char( const bool back ) throw()
  {
  Window & w = data[_curwin];
  Point & p = w.pointer();

  if( !read_only( w.buffer() ) && w.buffer().pdelc( p, back ) )
    Screen::repaint( &w.buffer() );
  }


void Window_vector::delete_line() throw()
  {
  Window & w = data[_curwin];

  if( !read_only( w.buffer() ) && w.buffer().pdell( w.pointer() ) )
    Screen::repaint( &w.buffer() );
  }


void Window_vector::copy_block() throw()
  {
  Window & w = data[_curwin];

  if( !no_block() && !read_only( w.buffer() ) )
    {
    const Buffer & buffer = *Block::bufferp();
    if( Block::copy_block( w.buffer(), w.pointer() ) )
      {
      if( &buffer != &w.buffer() ) Screen::repaint( &buffer );
      Screen::repaint( &w.buffer() );
      }
    }
  }


void Window_vector::delete_block() throw()
  {
  Buffer & buffer = data[_curwin].buffer();

  if( !no_block() && !no_block_in_this_file( buffer ) &&
      !read_only( buffer ) && Block::delete_block() )
    Screen::repaint( &buffer );
  }


void Window_vector::move_block() throw()
  {
  Window & w = data[_curwin];
  Point & p = w.pointer();

  if( !no_block() && !Block::in_block_or_end( w.buffer(), p ) )
    {
    const Buffer & buffer = *Block::bufferp();
    if( !read_only( w.buffer() ) && !read_only( buffer ) &&
        Block::copy_block( w.buffer(), p, true ) )
      {
      if( &buffer != &w.buffer() ) Screen::repaint( &buffer );
      Screen::repaint( &w.buffer() );
      }
    }
  }


void Window_vector::read_block()
  {
  Window & w = data[_curwin];
  if( read_only( w.buffer() ) ) return;

  std::string name;
  if( Menu::get_filename( "Name of file to insert (^C to abort): ", name ) <= 0 )
    return;
  try {
    Buffer tmp( RC::default_buffer_options(), &name );
    if( tmp.empty() ) throw Buffer::Error( "File not found" );
    Point p = w.pointer();
    if( w.buffer().pputb( p, tmp, tmp.bof(), tmp.eof() ) )
      Screen::repaint( &w.buffer() );
    }
  catch( Buffer::Error e ) { Screen::show_message( e.s ); }
  }


void Window_vector::write_block()
  {
  if( no_block() ) return;
  std::string name;
  if( Menu::get_filename( "Name of file to write (^C to abort): ", name ) <= 0 )
    return;
  try {
    if( !RC::editor_options().rectangle_mode )
      Block::bufferp()->save( &name, false, false, Block::begin(), Block::end() );
    else
      {
      Buffer::Options options;
      Buffer tmp( options );
      Block::save_block_position();
      Block::copy_block( tmp, tmp.bof() );
      Block::restore_block_position();
      tmp.save( &name );
      }
    if( RC::editor_options().auto_unmark )
      {
      const Buffer * bufferp = Block::bufferp();
      Block::reset(); Screen::repaint( bufferp );
      }
    }
  catch( Buffer::Error e ) { Screen::show_message( e.s ); }
  }


void Window_vector::indent_block() throw()
  {
  Buffer & buffer = data[_curwin].buffer();

  if( no_block() || no_block_in_this_file( buffer ) || rectangular_block() ||
      read_only( buffer ) ) return;
  Point p = Block::begin();
  bool first_post = true;
  const bool ow = buffer.options.overwrite;
  buffer.options.overwrite = false;
  buffer.reset_appendable();
  while( buffer.bol( p ) < Block::end() )
    {
    p = buffer.bot( p );
    if( p.col > 0 || p < buffer.eol( p ) )
      {
      if( first_post ) first_post = false; else buffer.force_append();
      for( int i = 0; i < RC::editor_options().indent_step; ++i )
        buffer.pputc( p, ' ' );
      }
    ++p.line;
    }
  buffer.reset_appendable();
  buffer.options.overwrite = ow;
  if( RC::editor_options().auto_unmark ) Block::reset();
  Screen::repaint( &buffer );
  }


void Window_vector::unindent_block() throw()
  {
  Buffer & buffer = data[_curwin].buffer();

  if( no_block() || no_block_in_this_file( buffer ) || rectangular_block() ||
      read_only( buffer ) ) return;
  Point p = Block::begin();
  bool fail = false;
  while( buffer.bol( p ) < Block::end() && !fail )
    {
    if( buffer.eol( p ).col > 0 &&
        buffer.to_cursor( buffer.bot( p ) ).col < RC::editor_options().indent_step )
      fail = true;
    ++p.line;
    }
  if( fail ) { Screen::show_message( "Can't unindent more" ); return; }

  bool first_post = true;
  const bool ow = buffer.options.overwrite;
  buffer.options.overwrite = false;
  buffer.reset_appendable();
  p = Block::begin();
  while( buffer.bol( p ) < Block::end() )
    {
    const int target_col = buffer.to_cursor( buffer.bot( p ) ).col - RC::editor_options().indent_step;
    if( buffer.eol( p ).col > 0 )
      while( true )
        {
        p = buffer.bot( p );
        const int col = buffer.to_cursor( p ).col;
        if( col > target_col )
          {
          if( first_post ) first_post = false; else buffer.force_append();
          buffer.pdelc( p, true );
          }
        else if( col < target_col )
          {
          if( first_post ) first_post = false; else buffer.force_append();
          buffer.pputc( p, ' ' );
          }
        else break;
        }
    ++p.line;
    }
  buffer.reset_appendable();
  buffer.options.overwrite = ow;
  if( RC::editor_options().auto_unmark ) Block::reset();
  Screen::repaint( &buffer );
  }


void Window_vector::undo() throw()
  {
  Window & w = data[_curwin];

  if( !read_only( w.buffer() ) )
    {
    if( w.buffer().undo( w.pointer() ) ) Screen::repaint( &w.buffer() );
    else Screen::show_message( "Nothing to undo" );
    }
  }


void Window_vector::redo() throw()
  {
  Window & w = data[_curwin];

  if( !read_only( w.buffer() ) )
    {
    if( w.buffer().redo( w.pointer() ) ) Screen::repaint( &w.buffer() );
    else Screen::show_message( "Nothing to redo" );
    }
  }


void Window_vector::center_line() throw()
  {
  Window & w = data[_curwin];
  Buffer & buffer = w.buffer();
  const Point bol = buffer.bol( w.pointer() );
  const Point bot = buffer.bot( bol );
  const Point cbot = buffer.to_cursor( bot );
  const Point ceot = buffer.to_cursor( buffer.eot( bol ) );
  const int l = cbot.col - buffer.options.lmargin();
  const int r = buffer.options.rmargin() - ceot.col;
  int dif = std::max( -bot.col, ( r - l ) / 2 );

  if( !read_only( buffer ) && cbot < ceot && dif )
    {
    buffer.reset_appendable();
    if( dif > 0 || bot != cbot )
      {
      if( bot != cbot )
        { buffer.pdelb( bol, bot ); dif = ( l + r ) / 2; buffer.force_append(); }
      Point p = bol; while( --dif >= 0 ) buffer.pputc( p, ' ' );
      }
    else buffer.pdelb( bol, Point( bot.line, -dif ) );
    buffer.reset_appendable();
    Screen::repaint( &buffer );
    }
  }


void Window_vector::reformat() throw()
  {
  Window & w = data[_curwin];
  Buffer & buffer = w.buffer();
  const Point bop = buffer.bop( w.pointer() );
  const Point eop = buffer.eop( w.pointer() );
  const Point bot = buffer.bot( bop );

  if( !read_only( buffer ) && bop < eop )
    {
    Buffer tmp( buffer.options );
    tmp.options.auto_indent = false;
    tmp.options.overwrite = false;
    tmp.options.word_wrap = true;
    Point dummy = tmp.bof();
    if( bop < bot && buffer.to_cursor( bot ).col > buffer.options.lmargin() )
      tmp.Basic_buffer::pputb( dummy, buffer, bop, bot );
    bool space = false;
    for( Point p = bot; p < eop; )
      {
      unsigned char ch = buffer.pgetc( p );
      if( !std::isspace( ch ) )
        { tmp.pputc( dummy, ch ); dummy = tmp.eof(); space = false; }
      else if( !space ) { tmp.pputc( dummy, ' ' ); space = true; }
      if( Screen::kbhit() >= 0 && Screen::wait_kbhit() == 3 )
        { Screen::show_message( "Aborted" ); return; }
      }
    if( space ) tmp.pdelc( dummy, true );
    if( dummy != tmp.bol( dummy ) ) tmp.pputc( dummy, '\n' );
    bool change = ( tmp.eof().line != ( eop.line - bop.line ) );
    if( !change )
      for( int line = 0; line < tmp.lines(); ++line )
        if( tmp.characters( line ) != buffer.characters( bop.line + line ) &&
            ( !tmp.blank( line ) || !buffer.blank( bop.line + line ) ) )
          { change = true; break; }
    if( change )
      {
      buffer.reset_appendable();
      Point p = eop;
      buffer.replace( bop, p, tmp, tmp.bof(), tmp.eof() );
      buffer.reset_appendable();
      Screen::repaint( &buffer );
      }
    }
  }


void Window_vector::change_case( const bool to_caps ) throw()
  {
  Buffer & buffer = data[_curwin].buffer();
  if( no_block() || no_block_in_this_file( buffer ) || read_only( buffer ) )
    return;
  Basic_buffer tmp;
  Point p = tmp.bof();
  bool change = false;
  if( to_caps )
    for( Point p1 = Block::begin(); p1 < Block::end(); )
      {
      const bool in_block = Block::in_block( buffer, p1 );
      unsigned char ch = buffer.pgetc( p1 );
      if( in_block && ISO_8859::islower( ch ) )
        { ch = ISO_8859::toupper( ch ); change = true; }
      tmp.pputc( p, ch );
      }
  else
    for( Point p1 = Block::begin(); p1 < Block::end(); )
      {
      const bool in_block = Block::in_block( buffer, p1 );
      unsigned char ch = buffer.pgetc( p1 );
      if( in_block && ISO_8859::isupper( ch ) )
        { ch = ISO_8859::tolower( ch ); change = true; }
      tmp.pputc( p, ch );
      }
  if( change ) replace_and_repaint( buffer, tmp );
  else Screen::show_message( "No letters to change in block" );
  }


void Window_vector::encode_base64() throw()
  {
  Buffer & buffer = data[_curwin].buffer();
  if( no_block() || no_block_in_this_file( buffer ) || rectangular_block() ||
      read_only( buffer ) ) return;
  std::string in, out;
  Block::bufferp()->to_string( Block::begin(), Block::end(), in );
  Encoding::base64_encode( in, out );
  Basic_buffer tmp;
  Point p = tmp.bof();
  int c = 0;
  for( unsigned int i = 0; i < out.size(); ++i )
    {
    tmp.pputc( p, out[i] );
    if( ++c >= 72 ) { c = 0; tmp.pputc( p, '\n' ); }
    }
  if( c ) tmp.pputc( p, '\n' );
  replace_and_repaint( buffer, tmp );
  }


void Window_vector::decode_base64() throw()
  {
  Buffer & buffer = data[_curwin].buffer();
  if( no_block() || no_block_in_this_file( buffer ) || rectangular_block() ||
      read_only( buffer ) ) return;
  std::string in, out;
  Block::bufferp()->to_string( Block::begin(), Block::end(), in );
  for( int i = in.size() - 1; i >= 0; --i )
    if( std::isspace( in[i] ) )
      in.erase( i, 1 );
  if( !Encoding::base64_decode( in, out ) )
    { Screen::show_message( "Invalid Base64 data in block" ); return; }
  Basic_buffer tmp;
  Point p = tmp.bof();
  for( unsigned int i = 0; i < out.size(); ++i )
    tmp.pputc( p, out[i] );
  replace_and_repaint( buffer, tmp );
  }


void Window_vector::decode_quoted_printable_utf8( const bool mode_q ) throw()
  {
  Buffer & buffer = data[_curwin].buffer();
  if( no_block() || no_block_in_this_file( buffer ) || rectangular_block() ||
      read_only( buffer ) ) return;
  std::string in, out;
  Block::bufferp()->to_string( Block::begin(), Block::end(), in );
  if( mode_q )
    { if( !Encoding::quoted_printable_decode( in, out ) )
      { Screen::show_message( "Invalid Quoted-Printable data in block" ); return; } }
  else
    { if( !Encoding::utf8_decode( in, out ) )
      { Screen::show_message( "Invalid UTF-8 data in block" ); return; } }
  if( in.size() == out.size() )
    { Screen::show_message( "Text in block is already decoded" ); return; }
  Basic_buffer tmp;
  Point p = tmp.bof();
  for( unsigned int i = 0; i < out.size(); ++i )
    tmp.pputc( p, out[i] );
  replace_and_repaint( buffer, tmp );
  }


void Window_vector::encode_rot1347( const bool mode13 ) throw()
  {
  Buffer & buffer = data[_curwin].buffer();
  if( no_block() || no_block_in_this_file( buffer ) || rectangular_block() ||
      read_only( buffer ) ) return;
  Basic_buffer tmp;
  Point p = tmp.bof();
  if( mode13 )
    for( Point p1 = Block::begin(); p1 < Block::end(); )
      tmp.pputc( p, Encoding::rot13( buffer.pgetc( p1 ) ) );
  else
    for( Point p1 = Block::begin(); p1 < Block::end(); )
      tmp.pputc( p, Encoding::rot47( buffer.pgetc( p1 ) ) );
  replace_and_repaint( buffer, tmp );
  }


void Window_vector::remove_duplicate_lines( const bool back ) throw()
  {
  Buffer & buffer = data[_curwin].buffer();
  if( no_block() || no_block_in_this_file( buffer ) || rectangular_block() ||
      read_only( buffer ) ) return;

  bool change = false;
  if( back )
    {
    int first_line = Block::begin().line;
    if( Block::begin().col > 0 ) ++first_line;
    for( int line = Block::end().line - 1; line > first_line; --line )
      for( Point p( line - 1, 0 ); p.line >= first_line; --p.line )
        if( buffer.compare_lines( line, p.line ) )
          {
          if( change ) buffer.force_append(); else change = true;
          buffer.pdell( p );
          --line;
          }
    }
  else
    {
    int line = Block::begin().line;
    if( Block::begin().col > 0 ) ++line;
    for( ; line < Block::end().line - 1; ++line )
      for( Point p( Block::end().line - 1, 0 ); p.line > line; --p.line )
        if( buffer.compare_lines( line, p.line ) )
          {
          if( change ) buffer.force_append(); else change = true;
          buffer.pdell( p );
          }
    }
  if( change )
    {
    buffer.reset_appendable();
    if( Block::valid() )
      {
      if( back ) data[_curwin].update_points( Block::end(), true, true );
      else data[_curwin].update_points( Block::begin(), true, true );
      }
    if( RC::editor_options().auto_unmark ) Block::reset();
    Screen::repaint( &buffer );
    }
  else Screen::show_message( "No duplicate lines in block" );
  }


void Window_vector::copyright_update()
  {
  static std::vector< std::string > history;
  const std::string search_string( "\\<Copyright\\>\\+\\[^\\n]\\+\\+\\+\\[0-9]\\*\\<\\[^0-9]" );

  std::string year_string;
  time_t dummy = time( 0 ); struct tm *t = localtime( &dummy );
  if( t )
    {
    int year = 1900 + t->tm_year;
    while( year )
      { year_string.insert( 0U, 1, '0' + ( year % 10 ) ); year /= 10; }
    }
  history.push_back( year_string );
  if( Screen::get_string( "Year to add (^C to abort): ", history ) <= 0 )
    return;
  year_string = history.back();
  for( unsigned int i = 0; i < year_string.size(); ++i )
    if( !std::isdigit( year_string[i] ) )
      year_string.clear();
  if( !year_string.size() || year_string[0] == '0' )
    { Screen::show_message( "Invalid year" ); return; }
  year_string.insert( 0, "\\<" ); year_string += "\\>";
  bool ask = true, found = false, modified = false;
  for( int i = 0; i < Bufhandle_vector::handles(); next(), ++i )
    {
    Window & w = data[_curwin];
    if( w.buffer().options.read_only ) continue;
    Point p = w.buffer().bof();
    std::vector< std::string > regex_pieces;
    while( true )
      {
      Point p1 = p;
      if( !Regex::find( w.buffer(), p1, p, search_string, regex_pieces ) )
        break;
      found = true;
      const Point p0 = w.buffer().bol( p1 );
      const Point p2 = w.buffer().eol( p );
      while( w.buffer().pprev( p ) && !w.buffer().piseow( p ) ) ;
      Basic_buffer tmp( w.buffer(), p0, p );
      Point pp = tmp.bof();
      if( p1 < p && w.buffer().piseow( p ) &&
          !Regex::find( tmp, pp, pp, year_string, regex_pieces ) )
        {
        const int rmargin = w.buffer().options.rmargin();
        pp = tmp.eof();
        tmp.pputc( pp, ',' );
        if( tmp.to_cursor( pp ).col + (int)year_string.size() - 4 > rmargin + 1 )
          {
          tmp.pputc( pp, '\n' );
          tmp.pputb( pp, w.buffer(), p0, p1 );
          }
        else tmp.pputc( pp, ' ' );
        for( unsigned int i = 2; i < year_string.size() - 2; ++i )
          tmp.pputc( pp, year_string[i] );
        while( p < p2 && std::ispunct( w.buffer()[p] ) && w.buffer().pnext( p ) ) ;
        while( p < p2 && std::isspace( w.buffer()[p] ) && w.buffer().pnext( p ) ) ;
        if( p < p2 )
          {
          Point pp1 = pp;
          tmp.pputb( pp, w.buffer(), p, p2 );
          if( tmp.to_cursor( pp ).col + 1 > rmargin + 1 )
            {
            tmp.pputc( pp1, '\n' );
            tmp.pputb( pp1, w.buffer(), p0, p1 );
            }
          else tmp.pputc( pp1, ' ' );
          }
        p = p2;
        w.update_points( p, true, true );
        int key = 0;
        if( ask || Screen::kbhit() >= 0 )
          {
          key = Screen::show_message( "Update (Y)es (N)o (R)est (^C to abort): ", true );
          if( key == 3 ) return;
          if( ask && ( key == 'r' || key == 'R' ) ) ask = false;
          }
        if( !ask || key == 'y' || key == 'Y' )
          {
          w.buffer().replace( p0, p, tmp, tmp.bof(), tmp.eof() );
          w.update_points( p );
          Screen::repaint( &w.buffer() );
          modified = true;
          }
        }
      }
    }
  if( !found ) Screen::show_message( "No copyright notices found" );
  else if( !modified )
    Screen::show_message( "No copyright notices have been modified" );
  else Screen::show_message( "Finished" );
  }


void Window_vector::search_word( const int abort_key )
  {
  const Buffer & buffer = data[_curwin].buffer();
  Point p1 = data[_curwin].pointer();
  if( ISO_8859::isalnum_( buffer[p1] ) || buffer.piseow( p1 ) )
    {
    Point p2 = p1;
    while( !buffer.pisbow( p1 ) ) if( !buffer.pprev( p1 ) ) break;
    while( !buffer.piseow( p2 ) ) if( !buffer.pnext( p2 ) ) break;
    if( buffer.pisbow( p1 ) && buffer.piseow( p2 ) )
      {
      std::string s_ini;
      buffer.to_string( p1, p2, s_ini );
      s_ini.insert( 0, "\\<" ); s_ini += "\\>";
      search( abort_key, false, s_ini );
      }
    }
  }


void Window_vector::search( const int abort_key, const bool again,
                            const std::string & s_ini )
  {
  static std::vector< std::string > search_history;
  static std::vector< std::string > replace_history;
  static std::vector< std::string > options_history;
  static std::string search_string;
  static std::string replace_string;
  static bool backward = false, block = false, global = false, icase = false;
  static bool replace = false;

  if( !again || search_history.size() == 0 )
    {
    search_history.push_back( s_ini );
    if( Screen::get_string( "Find (^C to abort): ", search_history, abort_key, true ) <= 0 )
      return;
    search_string = search_history.back(); ISO_8859::deescapize( search_string );
    std::string prompt;
    if( RC::editor_options().ignore_case )
      prompt = "(N)o_ignore (R)eplace (B)ackwards (G)lobal Bloc(K) (^C to abort): ";
    else prompt = "(I)gnore (R)eplace (B)ackwards (G)lobal Bloc(K) (^C to abort): ";
    options_history.push_back( std::string() );
    const int result = Screen::get_string( prompt, options_history, abort_key );
    if( result < 0 ) return;
    backward = false; block = false; global = false; replace = false;
    icase = RC::editor_options().ignore_case;
    if( result > 0 )
      {
      const std::string & options = options_history.back();
      for( unsigned int i = 0; i < options.size(); ++i )
        switch( ISO_8859::toupper( options[i] ) )
          {
          case 'B': backward = true; break;
          case 'G': global = true; break;
          case 'I': icase = true; break;
          case 'K': block = true; break;
          case 'N': icase = false; break;
          case 'R': replace = true; break;
          }
      }
    if( replace )
      {
      replace_string.clear();
      replace_history.push_back( std::string() );
      const int len = Screen::get_string( "Replace with (^C to abort): ", replace_history, abort_key, true );
      if( len < 0 ) { replace = false; return; }
      if( len > 0 )
        { replace_string = replace_history.back();
          ISO_8859::deescapize( replace_string ); }
      }
    }

  Point p = curwin().pointer();
  if( block )
    {
    if( no_block() || no_block_in_this_file( curwin().buffer() ) ) return;
    if( !again || !Block::in_block_or_end( curwin().buffer(), p ) )
      { if( backward ) p = Block::end(); else p = Block::begin(); }
    }
  bool found = false, ask = true, found_any = false, wrapped = false;
  bool empty_replace = false;
  for( int i = (global ? Bufhandle_vector::handles() - 1 : 0); i >= 0 && !found; --i )
    {
    Window & w = data[_curwin];
    std::vector< std::string > regex_pieces;
    if( replace && empty_replace && !backward ) w.buffer().pnext( p );
    Point p1 = p, p2 = p;
    found = Regex::find( w.buffer(), p1, p2, search_string, regex_pieces,
                         icase, backward );
    empty_replace = ( found && ( p1 == p2 ) );
    if( found && block &&
        ( p1 < Block::begin() || p1 >= Block::end() || p2 > Block::end() ) )
      found = false;
    if( backward ) p = p1; else p = p2;

    if( found )
      {
      if( block && RC::editor_options().rectangle_mode &&
          ( p1.col < Block::begin().col || p2.col > Block::end().col ) )
        { ++i; found = false; continue; }
      found_any = true;
      if( !replace ) w.update_points( p, true, true );
      else
        {
        std::string error;
        Block::save_block_position(); w.update_points( p, false, true );
        if( ask )
          {
          const bool rm = RC::editor_options().rectangle_mode;
          RC::editor_options().rectangle_mode = false;
          Block::set_block( w.buffer(), p1, p2 ); w.repaint();
          RC::editor_options().rectangle_mode = rm;
          }
        while( true )
          {
          int key = 0;
          if( ask || Screen::kbhit() >= 0 )
            {
            key = Screen::show_message( "Replace (Y)es (N)o (R)est (^C to abort): ", true );
            if( key == 3 ) break;
            if( ask )
              { if( key == 'n' || key == 'N' ) { found = false; break; }
                if( key == 'r' || key == 'R' ) ask = false; }
            }
          if( !ask || key == 'y' || key == 'Y' )
            {
            Block::restore_block_position();
            if( ask ) w.buffer().reset_appendable();
            const bool b = Regex::replace( w.buffer(), p1, p2, replace_string, regex_pieces );
            if( ask ) w.buffer().reset_appendable();
            Block::save_block_position();
            if( b )
              { if( empty_replace && ( p1 < p2 ) ) empty_replace = false;
                found = false; if( backward ) p = p1; else p = p2;
                w.update_points( p, false, true ); }
            else if( w.buffer().options.read_only ) error = "Read only";
            else error = "Replace error: check syntax";
            break;
            }
          }
        ++i; Block::restore_block_position();
        if( ask || found ) Screen::repaint( &w.buffer() );
        if( error.size() ) Screen::show_message( error );
        }
      continue;
      }
    if( !ask ) w.repaint();
    if( block ) break;
    if( !wrapped )
      {
      if( global )
        {
        if( backward ) { prev(); p = curwin().buffer().eof(); }
        else { next(); p = curwin().buffer().bof(); }
        }
      if( ( global && i == 0 ) || ( !global && RC::editor_options().search_wrap ) )
        {
        if( backward ) p = curwin().buffer().eof();
        else p = w.buffer().bof();
        wrapped = true; ++i;
        }
      }
    }
  if( !found_any )
    {
    if( block ) Screen::show_message( "Not found (search restricted to marked block)" );
    else Screen::show_message( "Not found" );
    }
  else if( wrapped ) Screen::show_message( "Wrapped" );
  }
