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

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


Window::Window( const int tl, const int h, Buffer_handle & bh, const bool center ) throw()
  : _top_line( tl ), _height( h ), bhp( &bh )
  {
  bhp->buffer().pvalid( bhp->pointer );
  if( center ) center_cursor();
  else update_points( bhp->pointer, false );
  }


Point Window::clock_position() const throw()
  { return Point( _top_line, Screen::width() - 20 ); }


Point Window::absolute_cursor() const throw()	// cursor position on screen
  { return relative_cursor() + Point( _top_line, 0 ); }


Point Window::relative_cursor() const throw()	// cursor position on window
  { return Point( 1, 0 ) + bhp->cursor - bhp->top_left; }


void Window::center_cursor() throw()
  {
  bhp->top_left.line = bhp->buffer().lines();
  update_points( bhp->pointer, true, true );
  }


void Window::goto_bof() throw()
  {
  Point bof = bhp->buffer().bof();
  if( bhp->pointer != bof ) update_points( bof );
  }


void Window::goto_eof() throw()
  {
  Point eof = bhp->buffer().eof();
  if( bhp->pointer != eof || bhp->cursor != bhp->pcursor )
    update_points( eof );
  }


void Window::goto_home() throw()
  {
  Point p = bhp->pointer;
  const Buffer & buffer = bhp->buffer();
  if( bhp->cursor != bhp->pcursor || p != buffer.bol( p ) )
    p = buffer.bol( p );
  else
    {
    if( !RC::editor_options().smart_home || p == buffer.bot( p ) ) return;
    p = buffer.bot( p );
    }
  update_points( p );
  }


void Window::goto_eol() throw()
  {
  Point eol = bhp->buffer().eol( bhp->pointer );
  if( bhp->cursor != bhp->pcursor || bhp->pointer != eol )
    update_points( eol );
  }


void Window::goto_begin_of_block() throw()
  {
  if( Block::bufferp() == &buffer() && buffer().pisvalid( Block::begin() ) )
    update_points( Block::begin(), true, true );
  else Screen::show_message( "Begin of block is not in this file" );
  }


void Window::goto_end_of_block() throw()
  {
  if( Block::bufferp() == &buffer() && buffer().pisvalid( Block::end() ) )
    update_points( Block::end(), true, true );
  else Screen::show_message( "End of block is not in this file" );
  }


void Window::goto_line()
  {
  static std::vector< std::string > history;

  history.push_back( std::string() );
  if( Screen::get_string( "Go to line (^C to abort): ", history ) <= 0 )
    return;
  std::string & s = history.back();
  Point p( 0, 0 );
  if( !RC::parse_int( s, p.line ) || p.line <= 0 )
    { Screen::show_message( "Invalid line number" ); history.pop_back();
    return; }
  --p.line;
  if( !bhp->buffer().pisvalid( p ) ) p = bhp->buffer().eof();
  update_points( p, true, true );
  }


void Window::goto_column()
  {
  static std::vector< std::string > history;

  history.push_back( std::string() );
  if( Screen::get_string( "Go to column (^C to abort): ", history ) <= 0 )
    return;
  std::string & s = history.back();
  Point c = bhp->cursor;
  if( !RC::parse_int( s, c.col ) || c.col <= 0 )
    { Screen::show_message( "Invalid column number" ); history.pop_back();
    return; }
  --c.col;
  update_points( c, true, false, from_cursor );
  }


void Window::goto_mark( const int i ) throw()
  {
  if( i >= 0 && i <= 9 )
    {
    const Point & p = buffer().mark[i];
    if( bhp->buffer().pisvalid( p ) )
      update_points( p, true, true );
    else
      {
      char buf[32];
      snprintf( buf, sizeof buf, "Mark %d not set", i );
      Screen::show_message( buf );
      }
    }
  }


void Window::goto_matching_delimiter( const bool forward ) throw()
  {
  Point p = bhp->pointer;
  const Buffer & buffer = bhp->buffer();

  int res = buffer.set_to_matching_delimiter( p, forward );
  if( !res && p.col > 0 )
    {
    const Point eot = buffer.eot( bhp->pointer );
    if( eot.col > 0 && p >= eot )
      {
      p = eot;
      if( --p.col > 0 && buffer[p] == ';' ) --p.col;
      res = buffer.set_to_matching_delimiter( p, forward );
      }
    if( !res && --p.col >= 0 )
      res = buffer.set_to_matching_delimiter( p, forward );
    }
  if( !res )
    {
    p = buffer.bot( bhp->pointer );
    if( bhp->pointer < p && p < buffer.eol( p ) )
      res = buffer.set_to_matching_delimiter( p, forward );
    }
  if( res && p != bhp->pointer ) update_points( p );
  if( res < 0 ) Screen::show_message( "No matching delimiter" );
  }


void Window::goto_pnext() throw()
  {
  Point p = bhp->pointer;
  const Buffer & buffer = bhp->buffer();
  if( buffer.pnext( p ) || bhp->cursor != bhp->pcursor )
    update_points( p );
  }


void Window::goto_pprev() throw()
  {
  Point p = bhp->pointer;
  const Buffer & buffer = bhp->buffer();
  if( bhp->cursor != bhp->pcursor || buffer.pprev( p ) )
    update_points( p );
  }


void Window::move_page( const bool down ) throw()
  {
  const int keep_lines = RC::editor_options().keep_lines;
  int lines = ( _height - 1 ) / 2;

  if( keep_lines >= 0 && keep_lines <= lines )
    lines = _height - 1 - keep_lines;
  if( !down ) lines = -lines;
  move_vertical( lines );
  }


void Window::move_vertical( const int lines ) throw()
  {
  Point c = bhp->cursor;

  if( lines < 0 )
    { if( c.line == 0 ) { return; } c.line += lines; c.cut(); }
  else if( lines > 0 )
    {
    int last_line = bhp->buffer().last_line();
    if( c.line == last_line ) return;
    c.line += lines; if( c.line > last_line ) c.line = last_line;
    }
  else return;

  update_points( c, true, false, from_cursor );
  }


void Window::scroll_horizontal( const int cols ) throw()
  {
  Point tl = bhp->top_left;

  if( cols < 0 ) { if( tl.col == 0 ) { return; } tl.col += cols; tl.cut(); }
  else if( cols > 0 ) tl.col += cols;
  else return;

  update_points( tl, true, false, from_top_left );
  }


void Window::scroll_page( const bool down ) throw()
  {
  const int keep_lines = RC::editor_options().keep_lines;
  int lines = ( _height - 1 ) / 2;

  if( keep_lines >= 0 && keep_lines < lines )
    lines = _height - 1 - keep_lines;
  if( !down ) lines = -lines;
  scroll_vertical( lines );
  }


void Window::scroll_vertical( const int lines ) throw()
  {
  Point tl = bhp->top_left;

  if( lines < 0 ) { if( tl.line == 0 ) { return; } tl.line += lines; tl.cut(); }
  else if( lines > 0 )
    {
    int first_line = bhp->buffer().last_line() - ( _height - 2 );
    if( first_line < 0 || tl.line == first_line ) return;
    tl.line += lines; if( tl.line > first_line ) tl.line = first_line;
    }
  else return;

  update_points( tl, true, false, from_top_left );
  }


void Window::set_mark( const int i ) throw()
  {
  if( i >= 0 && i <= 9 )
    {
    buffer().mark[i] = bhp->pointer;
    char buf[32];
    snprintf( buf, sizeof buf, "Mark %d set", i );
    Screen::show_message( buf );
    }
  }


void Window::repaint() const throw()
  {
  show_status_line();
  for( int i = 0; i < _height - 1; ++i )
    {
    Screen::out_buffer_line( bhp->buffer(), bhp->top_left + Point( i, 0 ),
                             _top_line + i + 1 );
    }
  }


void Window::show_character_info() const throw()
  {
  Screen::show_message( bhp->buffer().character_info( bhp->pointer ) );
  }


// prefix == keyboard status ( ^K, ^Q, ^[, etc )
//
void Window::show_status_line( const char * prefix ) const throw()
  {
  if( _top_line < 0 ) return;
  char buf[Screen::width()+1];
  int offset = Screen::width() - 40;
  const int line = bhp->pcursor.line + 1, col = bhp->pcursor.col + 1;
  for( int i = line; i > 9999; i /= 10 ) --offset;
  for( int i = col; i > 999; i /= 10 ) --offset;
  const char * uname = buffer().uname().c_str();
  const int overlap = buffer().uname().size() + 11 - offset;
  if( overlap > 0 ) uname += overlap;
  const bool modified = buffer().modified();
  const int len = snprintf( buf, offset + 1, "%-3s %c%c%c%c%c%c %s%s",
                            prefix ? prefix : "   ",
                            buffer().options.overwrite ? 'O' : 'I',
                            buffer().options.word_wrap ? 'W' : ' ',
                            buffer().options.auto_indent ? 'A' : ' ',
                            buffer().options.read_only ? 'R' : ' ',
                            RC::editor_options().rectangle_mode ? 'X' : ' ',
                            modified ? '*' : ' ', uname,
                            modified ? " (Modified)" : "" );
  if( len < offset ) std::memset( buf + len, ' ', offset - len );
  snprintf( buf + offset, sizeof( buf ) - offset,
            "  Line %-4d Col %-3d %5s  F1 for help",
            line, col, Screen::clock_string().c_str() );
  if( RC::editor_options().show_code )
    {
    offset = Screen::width() - 13;
    const int ch = bhp->buffer()[bhp->pointer];
    if( ch >= 0 )
      snprintf( buf + offset, sizeof( buf ) - offset, "%3d(0x%02X)", ch, ch );
    else
      snprintf( buf + offset, sizeof( buf ) - offset, "End of file" );
    }
  Screen::out_line( buf, _top_line, false, 'i' );
  }


void Window::update_points( const Point & to, const bool show,
                    bool center, const From from ) throw()
  {
  Buffer_handle & bh = buffer_handle();
  const Buffer & buffer = bh.buffer();
  Point & tl = bh.top_left;
  Point & c = bh.cursor;
  Point & p = bh.pointer;
  Point & pc = bh.pcursor;
  Point old_tl = tl;

  switch( from )
    {
    case from_top_left:
      { Point dif = c - tl;
      tl = to; tl.cut(); c = tl + dif; p = buffer.to_pointer( c, pc );
      center = false; break; }
    case from_cursor: c = to; c.cut(), p = buffer.to_pointer( c, pc ); break;
    case from_pointer: p = to; c = pc = buffer.to_cursor( p ); break;
    }

  const Point rc = relative_cursor();
  if( rc.line > _height - 2 )
    {
    const int d = center ? ( _height - 2 ) / 2 : _height - 2;
    tl.line = std::max( 0, c.line - d );
    }
  else if( rc.line < 1 )
    {
    const int d = center ? ( _height - 2 ) / 2 : 0;
    tl.line = std::max( 0, c.line - d );
    }
  if( rc.col >= Screen::width() ) tl.col = c.col - Screen::width() + 1;
  else if( rc.col < 0 ) tl.col = std::max( 0, c.col - ( Screen::width() / 2 ) );

  if( show ) { if( tl == old_tl ) show_status_line(); else repaint(); }
  }
