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

#include "buffer.h"
#include "block.h"
#include "rc.h"
#include "screen.h"


namespace Block {

Buffer *_bufferp = 0, *saved_bufferp = 0;
Point _anchor, _begin, _end, saved_begin, saved_end;


void enable_marking( Buffer & buffer, const Point & p ) throw()
  { _bufferp = &buffer; _begin = p; _anchor = p; _end = Point(); }

void disable_marking() throw() { _anchor = Point(); }

bool marking() throw() { return ( _anchor != Point() ); }

} // end namespace Block


Buffer * Block::bufferp() throw() { return _bufferp; }

Point & Block::anchor() throw() { return _anchor; }

Point & Block::begin() throw() { return _begin; }

Point & Block::end() throw() { return _end; }


bool Block::in_block( const Buffer & buffer, const Point & p ) throw()
  {
  return ( &buffer == _bufferp && _begin.line >= 0 && p >= _begin && p < _end &&
           ( !RC::editor_options().rectangle_mode ||
             ( p.col >= _begin.col && p.col < _end.col ) ) );
  }


bool Block::in_block_or_end( const Buffer & buffer, const Point & p ) throw()
  {
  return ( &buffer == _bufferp && _begin.line >= 0 && _begin < _end &&
           p >= _begin && p <= _end &&
           ( !RC::editor_options().rectangle_mode ||
             ( p.col >= _begin.col && p.col <= _end.col ) ) );
  }


bool Block::valid() throw()
  {
  return ( _bufferp != 0 && _begin.line >= 0 && _begin < _end &&
           ( !RC::editor_options().rectangle_mode || _begin.col < _end.col ) );
  }


void Block::save_block_position() throw()
  { saved_bufferp = _bufferp; saved_begin = _begin; saved_end = _end; }


void Block::restore_block_position() throw()
  {
  _bufferp = saved_bufferp; _begin = saved_begin; _end = saved_end;
  if( _bufferp )
    { _bufferp->pvalid( _begin ); _bufferp->pvalid( _end ); }
  disable_marking();
  }


void Block::reset() throw()
  { _bufferp = 0; _begin = _end = Point(); disable_marking(); }


void Block::set_block( Buffer & buffer, const Point & p1, const Point & p2 ) throw()
  { _bufferp = &buffer; _begin = p1; _end = p2; disable_marking(); }


void Block::set_begin( Buffer & buffer, const Point & p ) throw()
  {
  _begin = p; disable_marking();
  if( &buffer != _bufferp ) { _bufferp = &buffer; _end = Point(); }
  }


void Block::set_end( Buffer & buffer, const Point & p ) throw()
  {
  _end = p; disable_marking();
  if( &buffer != _bufferp ) { _bufferp = &buffer; _begin = Point(); }
  }


bool Block::follow_marking( const Buffer & buffer, const Point & p ) throw()
  {
  static Point old_p;
  if( marking() && &buffer == _bufferp && p != old_p )
    {
    if( p < _anchor ) _begin = p; else _begin = _anchor;
    if( p > _anchor ) _end = p; else _end = _anchor;
    old_p = p;
    return true;
    }
  return false;
  }


void Block::toggle_marking( Buffer & buffer, const Point & p ) throw()
  {
  const char * const str[2] = { "Selection started.", "Selection cleared." };
  const char * msg = 0;
  const Buffer * old_bufferp = _bufferp;
  if( marking() )
    {
    if( &buffer == _bufferp ) { if( valid() ) disable_marking(); else reset(); }
    else { enable_marking( buffer, p ); msg = str[0]; }
    }
  else
    {
    if( in_block_or_end( buffer, p ) ) { reset(); msg = str[1]; }
    else { enable_marking( buffer, p ); msg = str[0]; }
    }
  if( msg )
    {
    if( old_bufferp ) Screen::repaint( old_bufferp );
    Screen::show_message( msg );
    }
  }


bool Block::copy_block( Buffer & buffer, const Point & p, const bool move ) throw()
  {
  bool done = false;
  if( buffer.options.read_only || !buffer.pisvalid( p ) || !valid() ||
      ( move && _bufferp->options.read_only ) ||
      !_bufferp->pisvalid( _begin ) || !_bufferp->pisvalid( _end ) )
    return done;
  Point p_end;
  const bool same = ( &buffer == _bufferp );
  buffer.reset_appendable();
  if( !RC::editor_options().rectangle_mode )
    {
    Basic_buffer tmp( *_bufferp, _begin, _end );
    if( move ) { delete_block(); if( same ) buffer.force_append(); }
    p_end = p;
    done = buffer.pputb( p_end, tmp, tmp.bof(), tmp.eof() );
    }
  else
    {
    Basic_buffer tmp;
    Point p0 = tmp.bof();
    const int len = _end.col - _begin.col;
    for( int line = _begin.line; line <= _end.line; ++line )
      {
      Point p1( line, _begin.col ), p2( line, _end.col );
      _bufferp->pvalid( p1 ); _bufferp->pvalid( p2 );
      tmp.pputb( p0, *_bufferp, p1, p2 );
      for( int i = tmp.characters( p0.line ); i < len; ++i )
        tmp.pputc( p0, ' ' );
      tmp.pputc( p0, '\n' );
      }
    if( move ) { delete_block(); if( same ) buffer.force_append(); }
    for( int line = 0; line <= tmp.last_line(); ++line )
      {
      p0 = p; p0.line += line;
      for( int i = buffer.lines(); i <= p0.line; ++i )
        {
        Point dummy = buffer.eof();
        const bool ai = buffer.options.auto_indent;
        buffer.options.auto_indent = false;
        buffer.pputc( dummy, '\n' ); buffer.force_append();
        buffer.options.auto_indent = ai;
        }
      if( line >= tmp.last_line() ) break;
      for( int i = buffer.eol( p0 ).col; i < p0.col; ++i )
        { Point dummy = buffer.eol( p0 );
          buffer.pputc( dummy, ' ' ); buffer.force_append(); }
      Point p1( line, 0 ), p2 = tmp.eol( p1 );
      if( buffer.pputb( p0, tmp, p1, p2 ) ) { done = true; p_end = p0; }
      buffer.force_append();
      }
    }
  buffer.reset_appendable();
  if( done )
    { if( RC::editor_options().auto_unmark ) reset();
      else set_block( buffer, p, p_end ); }
  return done;
  }


bool Block::delete_block() throw()
  {
  bool done = false;
  if( valid() )
    {
    Buffer * const bp = _bufferp;
    _bufferp = 0;			// disable adjust functions on block
    bp->reset_appendable();
    if( !RC::editor_options().rectangle_mode )
      done = bp->pdelb( _begin, _end );
    else
      {
      for( int line = _end.line; line >= _begin.line; --line )
        {
        Point p1( line, _begin.col ), p2( line, _end.col );
        bp->pvalid( p1 ); bp->pvalid( p2 );
        if( bp->pdelb( p1, p2 ) ) done = true;
        bp->force_append();
        }
      }
    bp->reset_appendable();
    _bufferp = bp; _end = _begin; disable_marking();
    }
  return done;
  }
