/*  GNU Moe - My Own Editor
    Copyright (C) 2005-2015 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/>.
*/

#include <algorithm>
#include <cstdio>
#include <cstring>
#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 "screen.h"
#include "window.h"


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


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


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


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


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


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


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


void Window::goto_home()
  {
  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()
  {
  Point eol = bhp->buffer().eol( bhp->pointer );
  if( bhp->cursor != bhp->pcursor || bhp->pointer != eol )
    update_points( eol );
  }


const char * Window::goto_begin_of_block()
  {
  const Buffer & buffer = bhp->buffer();
  if( Block::bufferp() == &buffer && buffer.pisvalid( Block::begin() ) )
    { update_points( Block::begin(), true, true ); return 0; }
  if( !Block::bufferp() || Block::begin().line < 0 )
    return "Begin of block is not set";
  return "Begin of block is not in this file";
  }


const char * Window::goto_end_of_block()
  {
  const Buffer & buffer = bhp->buffer();
  if( Block::bufferp() == &buffer && buffer.pisvalid( Block::end() ) )
    { update_points( Block::end(), true, true ); return 0; }
  if( !Block::bufferp() || Block::end().line < 0 )
    return "End of block is not set";
  return "End of block is not in this file";
  }


const char * 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 0;
  const std::string & s = history.back();
  Point c = bhp->cursor;
  ++c.line;
  if( !RC::parse_relative_int( s, c.line ) || c.line <= 0 )
    { history.pop_back(); return "Invalid line number"; }
  --c.line;
  if( c.line > bhp->buffer().last_line() ) c.line = bhp->buffer().last_line();
  update_points( c, true, true, from_cursor );
  return 0;
  }


const char * 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 0;
  const std::string & s = history.back();
  Point c = bhp->cursor;
  ++c.col;
  if( !RC::parse_relative_int( s, c.col ) || c.col <= 0 )
    { history.pop_back(); return "Invalid column number"; }
  --c.col;
  update_points( c, true, false, from_cursor );
  return 0;
  }


const char * Window::goto_offset()
  {
  static std::vector< std::string > history;
  const Buffer & buffer = bhp->buffer();

  history.push_back( std::string() );
  if( Screen::get_string( "Go to offset (^C to abort): ", history ) <= 0 )
    return 0;
  const std::string & s = history.back();
  const bool relative = RC::issigned( s );
  int offset = 0;
  if( !RC::parse_int( s, offset ) || ( !relative && offset < 0 ) )
    { history.pop_back(); return "Invalid offset value"; }
  const int cr = buffer.crlf;
  Point p( 0, 0 );
  if( relative ) p = bhp->pointer;
  p.col += offset;
  if( offset > 0 )
    {
    while( p.line < buffer.last_line() )
      {
      const int c = buffer.characters( p.line );
      if( p.col < c + cr ) { p.col = std::min( p.col, c - 1 ); break; }
      p.col -= c + cr; ++p.line;
      }
    if( !buffer.pisvalid( p ) ) p = buffer.eof();
    }
  else if( offset < 0 && p.col < 0 )
    {
    while( p.line > 0 )
      {
      const int c = buffer.characters( --p.line );
      p.col += c + cr;
      if( p.col >= 0 ) { p.col = std::min( p.col, c - 1 ); break; }
      }
    if( !buffer.pisvalid( p ) ) p = buffer.bof();
    }
  update_points( p, true, true );
  return 0;
  }


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


const char * Window::goto_matching_delimiter( const bool forward )
  {
  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 ) return "No matching delimiter";
  return 0;
  }


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


void Window::goto_pprev()
  {
  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, const bool scroll )
  {
  const int keep_lines =
    std::min( RC::editor_options().keep_lines(), height_ - 2 );
  int lines = height_ - 1;

  if( keep_lines >= 0 ) lines -= keep_lines;
  else lines /= 2;
  if( !down ) lines = -lines;
  if( !scroll ) move_vertical( lines );
  else scroll_vertical( lines );
  }


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

  if( lines < 0 )
    { if( c.line == 0 ) { return; } c.line += lines; c.cut(); }
  else if( lines > 0 )
    {
    const 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 )
  {
  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_vertical( const int lines )
  {
  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 );
  }


const char * Window::extend_marks()
  {
  const int c = bhp->buffer().set_marks();
  if( c > 0 )
    {
    int bufsize = 0;
    char * const buf = Screen::line_buf( &bufsize );
    snprintf( buf, bufsize, "%d new mark(s) set", c );
    return buf;
    }
  return "No new marks set";
  }


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


void Window::repaint() const
  {
  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 );
  }


const char * Window::show_character_info() const
  {
  const Buffer & buffer = bhp->buffer();
  const Point p = bhp->pointer;
  int bufsize = 0;
  char * const buf = Screen::line_buf( &bufsize );
  const int ch = buffer[p];

  if( ch < 0 && p != buffer.eof() )
    snprintf( buf, bufsize, "*** Error: Bad Pointer: Line %d  Pos %d",
              p.line + 1, p.col + 1 );
  else
    {
    int percent = 0;
    long offset = buffer.character_offset( p, percent );
    const int len = snprintf( buf, bufsize, "Line %d  Pos %d  Offset %ld(0x%lX)  %d%%  ",
                              p.line + 1, p.col + 1, offset, offset, percent );
    if( len > 0 && len < bufsize )
      {
      if( ch >= 0 )
        snprintf( buf + len, bufsize - len, "Code %d(0x%02X/0%o)", ch, ch ,ch );
      else
        snprintf( buf + len, bufsize - len, "(End Of File)" );
      }
    }
  return buf;
  }


const char * Window::show_multichar_value( const int size, const bool big_endian ) const
  {
  const Buffer & buffer = bhp->buffer();
  const Point p = bhp->pointer;
  int bufsize = 0;
  char * const buf = Screen::line_buf( &bufsize );
  if( size < 2 || size > 8 )
    {
    snprintf( buf, bufsize, "*** Error: Bad Multichar Size: %d", size );
    return buf;
    }
  std::string text;
  const int sz = buffer.to_string( p, size, text );
  int percent = 0;
  long offset = buffer.character_offset( p, percent );

  if( sz >= size )
    {
    unsigned long long value = 0;
    if( big_endian )
      for( int i = 0; i < size; ++i )
        { value <<= 8; value += (unsigned char)text[i]; }
    else
      for( int i = size - 1; i >= 0; --i )
        { value <<= 8; value += (unsigned char)text[i]; }
    snprintf( buf, bufsize, "Off %ld(0x%lX)  %s_en_%d %llu(0x%0*llX)",
              offset, offset, big_endian ? "Big" : "Lit",
              size, value, 2 * size, value );
    }
  else if( sz > 0 )
    snprintf( buf, bufsize, "Offset %ld(0x%lX)  %d%%  (Only %d Characters Left)",
              offset, offset, percent, sz );
  else
    snprintf( buf, bufsize, "Offset %ld(0x%lX)  %d%%  (End Of File)",
              offset, offset, percent );
  return buf;
  }


const char * Window::show_utf8_code()
  {
  const Buffer & buffer = bhp->buffer();
  Point p = bhp->pointer;
  const int byte_code = buffer[p];
  int code = byte_code, len = 1;
  if( code >= 128 )					// multibyte code
    {
    int utf8_offset = 0;
    while( ( code & 0xC0 ) == 0x80 && p.col > 0 && utf8_offset < 5 )
      {
      --p.col; ++utf8_offset; code = buffer[p];		// seek to first byte
      if( code < 128 ) break;
      }
    code = Encoding::utf8_to_ucs( buffer.get_line( p.line ), p.col, &len );
    if( code < 0 || len <= utf8_offset ) code = -1;
    }

  int bufsize = 0;
  char * const buf = Screen::line_buf( &bufsize );
  if( code >= 0 )
    {
    if( p != bhp->pointer ) update_points( p );
    snprintf( buf, bufsize, "UTF-8 Code %d(0x%04X)  Length %d",
              code, code, len );
    }
  else if( byte_code >= 0 )
    snprintf( buf, bufsize, "Invalid UTF-8 Byte %d(0x%02X)",
              byte_code, byte_code );
  else if( p == buffer.eof() )
    snprintf( buf, bufsize, "(End Of File)" );
  else
    snprintf( buf, bufsize, "*** Error: Bad Pointer: Line %d  Pos %d",
              p.line + 1, p.col + 1 );
  return buf;
  }


// prefix == keyboard status ( ^K, ^Q, ^[, etc )
//
void Window::show_status_line( const char * const prefix ) const
  {
  if( top_line_ < 0 ) return;
  const Buffer & buffer = bhp->buffer();
  int bufsize = 0;
  char * const buf = Screen::line_buf( &bufsize );
  int offset = bufsize - 41;
  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 * name = buffer.nename().c_str();
  const int overlap = buffer.nename().size() + 11 - offset;
  if( overlap > 0 ) name += 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 ? '*' : ' ', name,
                            modified ? " (Modified)" : "" );
  if( len > 0 && len < offset ) std::memset( buf + len, ' ', offset - len );
  snprintf( buf + offset, bufsize - offset,
            "  Line %-4d Col %-3d %5s   F1 for help",
            line, col, Screen::clock_string() );
  if( RC::editor_options().show_code )
    {
    offset = bufsize - 13;
    const int ch = buffer[bhp->pointer];
    if( ch >= 0 )
      snprintf( buf + offset, bufsize - offset, "%3d(0x%02X)", ch, ch );
    else
      snprintf( buf + offset, bufsize - 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 )
  {
  Buffer_handle & bh = *bhp;
  const Buffer & buffer = bh.buffer();
  Point & tl = bh.top_left;
  Point & c = bh.cursor;
  Point & p = bh.pointer;
  Point & pc = bh.pcursor;
  const Point old_tl = tl;

  switch( from )
    {
    case from_top_left:
      {
      const 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_ - 3 )
    {
    const int d = center ? ( height_ - 2 ) / 2 :
                  height_ - ( ( c.line >= buffer.last_line() ) ? 2 : 3 );
    tl.line = std::max( 0, c.line - d );
    }
  else if( rc.line < 1 )
    {
    const int d = center ? ( height_ - 2 ) / 2 : 1;
    tl.line = std::max( 0, c.line - d );
    }
  const int ahead = std::max( 1, std::min( 8,
                                 buffer.characters( p.line ) - p.col ) );
  if( rc.col + ahead > Screen::width() || center )
    tl.col = std::max( 0, c.col + ahead - Screen::width() );
  else if( rc.col < 8 )
    tl.col = std::max( 0, c.col - 8 );

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