/*  GNU moe - My Own Editor
    Copyright (C) 2005-2017 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 <cctype>
#include <cerrno>
#include <cstdlib>
#include <ctime>
#include <string>
#include <vector>
#include <unistd.h>
#include <ncurses.h>

#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"
#include "window_vector.h"


namespace Screen {

struct Lines_and_cursor
  {
  int line, lines;
  std::vector< chtype > buf;
  Point cursor;

  Lines_and_cursor( const int ln, const int lns )
    : line( ln ), lines( lns ) {}
  };

class Last_key
  {
  unsigned long prev, current;

public:
  Last_key() : prev( 0 ), current( -1UL ) {}
  void set( const int key ) { prev = current; current = key; }
  void unset() { prev = 0; current = -1UL; }
  void shift( const int key ) { current <<= 16; current += ( key & 0xFFFF ); }
  bool compare_prev() const { return ( prev == current ); }
  } last_key;

char * line_buf_;
int line_buf_size;
int height_, width_;
int clock_limit;		//   0: disable clock
				//   1: showing help
				// > 1: first line used by menu
enum { clock_string_size = 5 };
char clock_string_[clock_string_size+1] = " 0:00";

std::vector< Lines_and_cursor > lines_and_cursor_vector;


void dummy_endwin() { endwin(); }

void initialize_ncurses()
  {
  initscr();			// initializes curses data structures
  raw();			// read a char at a time and disable signals
  keypad( stdscr, true );	// enables single value for function keys
  nodelay( stdscr, false );	// forces getch() to wait for key
  noecho();			// disables echoing of getch()
  nonl();			// disables CR LF translation
  scrollok( stdscr, false );	// disables automatic scrolling
  }


inline void rep_addch( int n, const chtype ch )	// only for printable chars
  { for( ; n > 0; --n ) waddch( stdscr, ch ); }

void out_raw_char( unsigned char ch, chtype atr )
  {
  if( ch < 32 ) { ch += '@'; atr |= A_UNDERLINE; }
  else if( ch >= 0x7F && ch < 0xA0 )
    { ch = ( ch + '@' - 0x80 ); atr |= ( A_REVERSE | A_UNDERLINE ); }
  waddch( stdscr, ch | atr );
  }


void out_raw_string( const int line, const int col, const std::string & s,
                     const int len, int pos = 0, const chtype atr = A_NORMAL )
  {
  wmove( stdscr, line, col );
  int x = col, limit = std::min( x + len, width_ );
  for( ; x < limit && pos < (int)s.size(); ++pos, ++x )
    {
    unsigned char ch = s[pos];
    out_raw_char( ch, atr );
    }
  if( x < limit ) rep_addch( limit - x, atr | ' ' );
  }


// '@b' sets-resets bold mode
// '@i' sets-resets inverse video mode
// '@u' sets-resets underline mode
// '@@' shows a '@'
//
void out_string( const int line, const int col, const std::string & s,
                 const int len, int pos = 0 )
  {
  wmove( stdscr, line, col );
  int x = col, limit = std::min( x + len, width_ );
  chtype atr = A_NORMAL;
  for( ; x < limit && pos < (int)s.size(); ++pos )
    {
    unsigned char ch = s[pos];
    if( ch == '@' && pos + 1 < (int)s.size() )
      switch( s[pos+1] )
        {
        case '@': ++pos; break;
        case 'b': atr ^= A_BOLD; ++pos; ch = 0; break;
        case 'i': atr ^= A_REVERSE; ++pos; ch = 0; break;
        case 'u': atr ^= A_UNDERLINE; ++pos; ch = 0; break;
        }
    if( ch ) { out_raw_char( ch, atr ); ++x; }
    }
  if( x < limit ) rep_addch( limit - x, atr | ' ' );
  }


void remove_duplicates( std::vector< std::string > & history )
  {
  for( unsigned i = 0; i + 1 < history.size(); )
    {
    if( history[i].size() && history[i] != history.back() ) ++i;
    else history.erase( history.begin() + i );
    }
  }


const char * alt_key_loop( int key )
  {
  const char * msg = 0;
  Window & w = Window_vector::curwin();
  w.show_status_line( ISO_8859::control_name( key ) );
  do key = wait_kbhit(); while( key < 0 );
  w.show_status_line();
  key = ISO_8859::decontrolize( key );
  last_key.shift( key );
  if( std::isdigit( key ) ) msg = w.goto_mark( key - '0' );
  else switch( key )
    {
    case 'A': w.scroll_horizontal( -8 ); break;
    case 'B': msg = w.goto_begin_of_block(); break;
    case 'C': msg = w.goto_column(); break;
    case 'D': w.center_cursor(); break;
    case 'F': msg = w.goto_matching_delimiter(); break;
    case 'G': msg = w.goto_matching_delimiter( false ); break;
    case 'I': msg = w.show_character_info(); break;
    case 'K': msg = w.goto_end_of_block(); break;
    case 'L': msg = w.goto_line(); break;
    case 'O': msg = w.goto_offset(); break;
    case 'S': w.scroll_horizontal( 8 ); break;
    case 'T': msg = w.show_utf8_code(); break;
    case 'U': w.goto_bof(); break;
    case 'V': w.goto_eof(); break;
    case 'W': w.move_page( false, true ); break;
    case 'Z': w.move_page( true, true ); break;
    }
  return msg;
  }


const char * control_k_loop( int key )
  {
  const char * msg = 0;
  Window & w = Window_vector::curwin();
  const Buffer * const old_bufferp = Block::bufferp();
  w.show_status_line( ISO_8859::control_name( key ) );
  do key = wait_kbhit(); while( key < 0 );
  w.show_status_line();
  key = ISO_8859::decontrolize( key );
  last_key.shift( key );
  if( std::isdigit( key ) ) msg = w.set_mark( key - '0' );
  else switch( key )
    {
    case 'B': Block::set_begin( w.buffer(), w.pointer() );
              if( old_bufferp ) repaint( old_bufferp );
              break;
    case 'C': Window_vector::copy_block(); break;
    case 'E': msg = w.extend_marks(); break;
    case 'I': Window_vector::indent_block(); break;
    case 'K': Block::set_end( w.buffer(), w.pointer() );
              if( old_bufferp ) repaint( old_bufferp );
              break;
    case 'M': Window_vector::move_block(); break;
    case 'R': msg = Window_vector::read_block(); break;
    case 'U': msg = Window_vector::unindent_block(); break;
    case 'W': msg = Window_vector::write_block(); break;
    case 'Y': Window_vector::delete_block(); break;
    }
  return msg;
  }


const char * control_o_loop( int key )
  {
  const char * msg = 0;
  Window & w = Window_vector::curwin();
  w.show_status_line( ISO_8859::control_name( key ) );
  do key = wait_kbhit(); while( key < 0 );
  w.show_status_line();
  key = ISO_8859::decontrolize( key );
  last_key.shift( key );
  switch( key )
    {
    case '1': Window_vector::encode_base64(); break;
    case '2': msg = Window_vector::decode_base64(); break;
    case '3': Window_vector::encode_rot1347( true ); break;
    case '4': Window_vector::encode_rot1347( false ); break;
    case '5': Window_vector::encode_ascii_utf8( &msg, true ); break;
    case '6': msg = Window_vector::decode_quoted_printable_utf8( true ); break;
    case '7': if( Window_vector::encode_ascii_utf8( &msg, false,
                    last_key.compare_prev() ) ) { last_key.unset(); } break;
    case '8': msg = Window_vector::decode_quoted_printable_utf8( false,
                      last_key.compare_prev() ); break;
    case 'C': Window_vector::center_line(); break;
    case 'D': msg = Window_vector::remove_duplicate_lines(); break;
    case 'E': msg = Window_vector::remove_duplicate_lines( true ); break;
    case 'F': msg = Window_vector::search( 6, true, "", 2 ); break;
    case 'L': msg = Window_vector::change_case( false ); break;
    case 'N': msg = Window_vector::change_buffer_name(); break;
    case 'O': msg = Window_vector::remove_utf8_out_of_range(); break;
    case 'R': werase( stdscr ); wrefresh( stdscr ); repaint(); break;
    case 'S': Window_vector::split_window(); break;
    case 'U': msg = Window_vector::change_case( true ); break;
    }
  return msg;
  }


int control_q_loop( int key, const char ** const msgp )
  {
  Window & w = Window_vector::curwin();
  w.show_status_line( ISO_8859::control_name( key ) );
  do key = wait_kbhit(); while( key < 0 );
  w.show_status_line();
  key = ISO_8859::decontrolize( key );
  last_key.shift( key );
  switch( key )
    {
    case KEY_F(2): *msgp = Bufhandle_vector::save_all_named(); break;
    case 'C': if( Window_vector::close_and_exit( true ) == 0 ) return 0; break;
    case 'U': *msgp = Window_vector::copyright_update(); break;
    case 'X': if( Window_vector::close_and_exit( false ) == 0 ) return 0; break;
    }
  return -1;
  }


const char * control_s_loop( int key, const char * const version_msg )
  {
  const char * msg = 0;
  Window & w = Window_vector::curwin();
  w.show_status_line( ISO_8859::control_name( key ) );
  do key = wait_kbhit(); while( key < 0 );
  w.show_status_line();
  key = ISO_8859::decontrolize( key );
  last_key.shift( key );
  switch( key )
    {
    case KEY_F(2):
    case KEY_F(4):
    case KEY_F(8): msg = w.show_multichar_value( key - KEY_F(0), true ); break;
    case '2':
    case '4':
    case '8': msg = w.show_multichar_value( key - '0', false ); break;
    case 'C': RC::editor_options().show_code = !RC::editor_options().show_code;
              show_status_lines(); break;
    case 'G': msg = Bufhandle_vector::show_status(); break;
    case 'V': msg = version_msg; break;
    }
  return msg;
  }


const char * user_data( const std::string & s, const int base )
  {
  std::vector< int > buf;	// s is "big endian", buf is little endian
  int last = s.size() - 1;
  bool big_endian = false;
  if( s[last] == '+' ) { --last; big_endian = true; }

  if( base == 16 )
    for( int i = last; i >= 2; i -= 2 )
      {
      const int lsd = ISO_8859::xvalue( s[i] );
      const int msd = ( i > 2 ) ? ISO_8859::xvalue( s[i-1] ) : 0;
      if( lsd < 0 || msd < 0 ) return "Invalid hexadecimal data";
      buf.push_back( ( msd << 4 ) | lsd );
      }
  else
    for( int i = 0; i <= last; ++i )
      {
      if( base == 10 && !std::isdigit( s[i] ) )
        return "Invalid decimal data";
      if( base == 8 &&  !ISO_8859::isodigit( s[i] ) )
        return "Invalid octal data";
      int carry = s[i] - '0';
      for( unsigned j = 0; j < buf.size(); ++j )
        {
        buf[j] = ( buf[j] * base ) + carry;
        carry = buf[j] / 256;
        buf[j] %= 256;
        }
      if( carry ) buf.push_back( carry );
      }
  if( buf.empty() ) buf.push_back( 0 );
  if( big_endian )
    for( unsigned i = buf.size(); i > 0; --i )
      Window_vector::add_char( buf[i-1], true );
  else
    for( unsigned i = 0; i < buf.size(); ++i )
      Window_vector::add_char( buf[i], true );
  return 0;
  }


const char * user_control_char( const int abort_key )
  {
  static std::vector< std::string > history;
  const std::string prompt1( "(Ctrl)A-Z, Code(+) or Escape sequence (^C to abort): " );
  const std::string prompt2( "           Code(+) or Escape sequence (^C to abort): " );
  int key;

  while( true )
    {
    key = show_message( prompt1, false, true );
    if( key == 1 || key == 8 || key == KEY_F(1) )
      { wait_kbhit(); Menu::help_menu( 1, 8, KEY_F(1) ); }
    else if( key == 3 || key == abort_key ) { wait_kbhit(); return 0; }	// ^C
    else if( key >= 256 || key == '\\' || std::isalnum( key ) ) break;
    else wait_kbhit();
    }
  if( key < 256 && std::isalpha( key ) )
    {
    wait_kbhit();
    key = ISO_8859::controlize( key ); if( key >= 32 ) key = -1;
    }
  else
    {
    history.push_back( std::string() );
    const int size = get_string( prompt2, history, abort_key );
    if( size <= 0 ) return 0;
    const std::string & s = history.back();
    if( size >= 2 && s[0] == '\\' )
      { int len; key = ISO_8859::escape( s, 1, &len );
        if( key >= 0 && len + 1 != size ) key = -1; }
    else if( s[0] >= '1' && s[0] <= '9' )
      return user_data( s, 10 );
    else if( size >= 3 && s[0] == '0' && ( s[1] == 'x' || s[1] == 'X' ) )
      return user_data( s, 16 );
    else if( s[0] == '0' ) return user_data( s, 8 );
    else key = -1;
    }
  if( key < 0 || key >= 256 ) return "Invalid code";
  Window_vector::add_char( key, true );
  return 0;
  }


// 'read_key' implements a buffer to speed up pastes, because 'wgetch'
// always calls 'wrefresh'. 'read_key' also converts '\r' to '\n'.
//
int read_key( const bool wait, const bool get_key )
  {
  enum { buf_size = 1024 };
  static int buf[buf_size];
  static int size = 0;			// number of keys in buf
  static int index = 0;			// index of key to be returned

  if( index >= size )			// buf is empty
    {
    size = 0; index = 0;
    if( wait )
      {
      const int key = wgetch( stdscr );
      if( key == ERR ) return -1; else buf[size++] = key;
      }
    nodelay( stdscr, true );
    while( size < buf_size )
      {
      const int key = wgetch( stdscr );
      if( key == ERR ) break; else buf[size++] = key;
      }
    nodelay( stdscr, false );
    }
  if( index < size )
    {
    const int key = buf[index]; if( get_key ) ++index;
    if( key == '\r' ) return '\n';
    if( key < 0 ) return -1;
    return key;
    }
  return -1;
  }

void discard_control_c()		// discards an already pressed ^C
  { while( read_key( false, false ) == 3 ) read_key( false, true ); }

} // end namespace Screen


bool Screen::init()
  {
  initialize_ncurses();
  getmaxyx( stdscr, height_, width_ );
  if( height_ < 24 || width_ < 80 ) { endwin(); return false; }
  std::atexit( dummy_endwin );
  line_buf_size = width_ + 1;
  line_buf_ = new char[line_buf_size];
  clock_limit = 0;
  clock_handler( 0 );
  Window_vector::init();
  return true;
  }


int Screen::height() { return height_; }

int Screen::width() { return width_; }

int Screen::max_windows() { return height_ / Window::min_height; }


char * Screen::line_buf( int * const sizep )
  { *sizep = line_buf_size; return line_buf_; }


//void Screen::beep() { ::beep(); wrefresh( stdscr ); }


const char * Screen::clock_string() { return clock_string_; }


// updates the clock
extern "C" void Screen::clock_handler( int sig )
  {
  const time_t tt = std::time( 0 );
  const std::tm * const t = std::localtime( &tt );
  if( t )
    {
    const int h = t->tm_hour;
    clock_string_[0] = ( h < 10 ) ? ' ' : '0' + ( h / 10 );
    clock_string_[1] = '0' + ( h % 10 );
    clock_string_[2] = ( clock_string_[2] != ':' ) ? ':' : ' ';
    clock_string_[3] = '0' + ( t->tm_min / 10 );
    clock_string_[4] = '0' + ( t->tm_min % 10 );
    if( sig )
      {
      Point old_cursor; save_cursor( old_cursor );
      if( clock_limit == 1 )
        out_raw_string( 0, 60, clock_string_, clock_string_size, 0, A_REVERSE );
      else for( int i = 0; i < Window_vector::windows(); ++i )
        {
        const Point cursor = Window_vector::window( i ).clock_position();
        if( cursor.line >= clock_limit ) break;
        out_raw_string( cursor.line, cursor.col, clock_string_,
                        clock_string_size, 0, A_REVERSE );
        }
      wmove( stdscr, old_cursor.line, old_cursor.col );
      wrefresh( stdscr ); alarm( 1 );
      }
    }
  }


int Screen::kbhit() { return read_key( false, false ); }


// Wait for a keystroke. Returns -1 if no key is available.
// The clock only blinks when waiting here for a keystroke.
// The screen is mainly refreshed here (and in clock_handler).
// This function is the real "main loop" of the program.
//
int Screen::wait_kbhit( const int new_clock_limit, const bool get_key,
                        const bool force_refresh )
  {
  if( force_refresh || kbhit() < 0 )
    {
    if( new_clock_limit < 0 )
      move_to( Window_vector::curwin().absolute_cursor() );

    clock_limit = ( new_clock_limit >= 0 ) ? new_clock_limit : height_;
    if( clock_limit > 0 ) alarm( 1 );
    wrefresh( stdscr );
    }

  const int key = read_key( true, get_key );
  alarm( 0 );
  return key;
  }


// 'history.back()' contains the default result (may be empty), and
// stores the result (if any).
// Returns the size of the result string, 0 if none, -1 if aborted.
//
int Screen::get_string( const std::string & prompt,
                        std::vector< std::string > & history,
                        const int abort_key, const bool usetab )
  {
  const int line = height_ - 1, lines = 1;
  save_lines_and_cursor( line, lines );

  const std::string original( history.back() );
  remove_duplicates( history );
  std::string s( prompt );
  s += history.back();
  int scol = s.size();
  int idx = history.size() - 1;
  int key = 0;
  bool modified = false;
  while( key >= 0 )
    {
    const int ahead = std::max( 1, std::min( 8, (int)s.size() + 1 - scol ) );
    const int pos = std::max( 0, scol + ahead - width_ );
    out_raw_string( line, 0, s, width_, pos );
    wmove( stdscr, line, scol - pos );
    do key = wait_kbhit( line ); while( key < 0 );
    if( key == abort_key ) key = 3;
    switch( key )
      {
      case   3 : key = -1; break;				// ^C
      case '\t': if( usetab )
                   {
                   const int namelen = scol - prompt.size();
                   std::string name( s, prompt.size(), namelen );
                   if( !Menu::file_menu( prompt, name ) ) { key = -1; break; }
                   scol += name.size() - namelen; modified = true;
                   s.erase( prompt.size(), namelen );
                   if( s.size() > prompt.size() && name.size() &&
                       s[prompt.size()] == '/' && name[name.size()-1] == '/' )
                     name.erase( name.size() - 1 );
                   s.insert( prompt.size(), name );
                   }
                 else { s.insert( scol, "\\t" ); scol += 2; modified = true; }
                 break;
      case '\n': {
                 std::string name( s, prompt.size(), s.size() );
                 if( !usetab || name.empty() || Menu::validate_filename( name ) )
                   { key = -2; history.back() = name; }
                 }
                 break;
      case 1:							// ^A
      case 8:							// ^H
      case KEY_F(1) : Menu::help_menu( 1, 8, KEY_F(1) ); break;
      case KEY_F(9) : if( Block::valid() )
                        {
                        std::string tmp;
                        Block::bufferp()->to_string( Block::begin(), Block::end(), tmp );
                        ISO_8859::escapize( tmp );
                        if( tmp.size() )
                          { s.insert( scol, tmp ); scol += tmp.size(); modified = true; }
                        }
                      break;
      case 127          :					// delete
      case KEY_BACKSPACE: if( scol > (int)prompt.size() )
                            { s.erase( --scol, 1 ); modified = true; } break;
      case KEY_DC   : if( scol < (int)s.size() )
                        { s.erase( scol, 1 ); modified = true; } break;
      case KEY_HOME : scol = prompt.size(); break;
      case KEY_END  : scol = s.size(); break;
      case KEY_LEFT : if( scol > (int)prompt.size() ) { --scol; } break;
      case KEY_RIGHT: if( scol < (int)s.size() ) { ++scol; } break;
      case 25       : if( s.size() > prompt.size() )		// ^Y
                        { s.resize( prompt.size() ); scol = s.size();
                          modified = true; }		// fall through
      case KEY_PPAGE:
      case KEY_UP   : if( idx > 0 )
        {
        if( modified )
          {
          modified = false;
          if( s.size() > prompt.size() )
            history[idx].assign( s, prompt.size(), s.size() );
          else if( idx == (int)history.size() - 1 ) history[idx].clear();
          else history.erase( history.begin() + idx );
          }
        if( key == KEY_PPAGE ) idx = 0; else --idx;
        s.erase( prompt.size(), s.size() ); s += history[idx]; scol = s.size();
        } break;
      case KEY_NPAGE:
      case KEY_DOWN : if( idx + 1 < (int)history.size() )
        {
        if( modified )
          {
          modified = false;
          if( s.size() > prompt.size() )
            history[idx].assign( s, prompt.size(), s.size() );
          else { history.erase( history.begin() + idx ); --idx; }
          }
        if( key == KEY_DOWN ) ++idx; else idx = history.size() - 1;
        s.erase( prompt.size(), s.size() ); s += history[idx]; scol = s.size();
        } break;
      default: if( key < 256 && ISO_8859::isprint( key ) )
                 { s.insert( scol++, 1, key ); modified = true; }
      }
    }

  restore_lines_and_cursor();
  if( original.size() && original != history.back() )
    history.insert( history.end() - 1, original );
  if( key != -1 ) remove_duplicates( history );
  if( ( key == -1 && original.empty() ) || history.back().empty() )
    { history.pop_back(); if( key != -1 ) return 0; }
  if( key == -1 ) return -1;
  return history.back().size();
  }


void Screen::move_to( const Point & cursor )
  { wmove( stdscr, cursor.line, cursor.col ); }


void Screen::save_cursor( Point & cursor )
  { getyx( stdscr, cursor.line, cursor.col ); }


void Screen::save_lines_and_cursor( const int line, const int lines )
  {
  lines_and_cursor_vector.push_back( Lines_and_cursor( line, lines ) );
  Lines_and_cursor & lc = lines_and_cursor_vector.back();

  save_cursor( lc.cursor );
  for( int y = line; y < line + lines; ++y )
    for( int x = 0; x < width_; ++x )
      lc.buf.push_back( mvinch( y, x ) );
  }


void Screen::restore_lines_and_cursor()
  {
  const Lines_and_cursor & lc = lines_and_cursor_vector.back();

  for( int y = lc.line, i = 0; y < lc.line + lc.lines; ++y )
    for( int x = 0; x < width_; ++x )
      { wmove( stdscr, y, x ); waddch( stdscr, lc.buf[i++] ); }
  wmove( stdscr, lc.cursor.line, lc.cursor.col );
  lines_and_cursor_vector.pop_back();
  }


void Screen::out_buffer_line( const Buffer & buffer, Point c, const int line )
  {
  wmove( stdscr, line, 0 );
  if( c.line < 0 || c.line >= buffer.lines() )
    { rep_addch( width_, ' ' ); return; }
  Point pc, p = buffer.to_pointer( c, pc );
//  if( pc.col < c.col ) pc.col = c.col;		// landed in a tab

  for( int i = 0; i < width_; ++i, ++c.col, ++p.col )
    {
    const int ch = buffer[p];
    if( ch < 0 || ( p == buffer.eol( p ) && ch == '\n' ) )
      { rep_addch( width_ - i, ' ' ); break; }
    int block_attr = Block::in_block( buffer, p ) ? A_REVERSE : 0;
    if( ch == '\t' )
      {
      while( i + 1 < width_ && c.col % 8 != 7 )
        { waddch( stdscr, block_attr | ' ' ); ++i; ++c.col; }
      waddch( stdscr, block_attr | ' ' );
      }
    else out_raw_char( ch, block_attr );
    }
  }


bool Screen::out_line( const std::string & s, const int line, const bool shift,
                       const char mode )
  {
  if( line < 0 || line >= height_ ) return false;
  int pos = s.size(); pos -= width_ - 1;
  if( !shift || pos < 0 ) pos = 0;
  if( !mode ) out_string( line, 0, s, width_, pos );
  else
    {
    chtype atr = A_NORMAL;
    if( mode == 'b' ) atr = A_BOLD;
    else if( mode == 'i' ) atr = A_REVERSE;
    else if( mode == 'u' ) atr = A_UNDERLINE;
    out_raw_string( line, 0, s, width_, pos, atr );
    }
  return true;
  }


bool Screen::out_menu_line( const std::string & s, const int line,
                            const int hbegin, const int hend )
  {
  if( line < 0 || line >= height_ ) return false;
  if( hbegin >= 0 && hbegin < hend && hend <= (int)s.size() )
    {
    if( hbegin > 0 )
      out_raw_string( line, 0, s, hbegin, 0, A_NORMAL );
    if( hbegin < width_ )
      out_raw_string( line, hbegin, s, hend - hbegin, hbegin, A_REVERSE );
    if( hend < width_ )
      out_raw_string( line, hend, s, width_, hend, A_NORMAL );
    }
  else out_raw_string( line, 0, s, width_, 0, A_NORMAL );
  return true;
  }


void Screen::repaint( const Buffer * const bufp )
  {
  for( int i = 0; i < Window_vector::windows(); ++i )
    {
    Window & w = Window_vector::window( i );
    if( !bufp || bufp == &w.buffer() )
      {
      if( bufp == &w.buffer() ) w.update_points( w.pointer(), false );
      w.repaint();
      }
    }
  }


void Screen::show_feedback( const std::string & s )
  {
  const int line = height_ - 1;

  out_string( line, 0, s, width_ );
  wmove( stdscr, line, std::min( (int)s.size(), width_ - 1 ) );
  wrefresh( stdscr );
  }


int Screen::show_message( const std::string & s, const bool get_key,
                          const bool take_cursor )
  {
  const int line = height_ - 1;

  save_lines_and_cursor( line, 1 );

  out_string( line, 0, s, width_ );
  wmove( stdscr, line, std::min( (int)s.size(), width_ - 1 ) );
  discard_control_c();
  int key;
  do key = wait_kbhit( take_cursor ? line : -1, get_key ); while( key < 0 );

  restore_lines_and_cursor();
  return key;
  }


void Screen::show_status_lines( const Buffer * const bufp )
  {
  for( int i = 0; i < Window_vector::windows(); ++i )
    if( !bufp || bufp == &Window_vector::window( i ).buffer() )
      Window_vector::window( i ).show_status_line();
  }


Screen::Keys Screen::convert_key( const int key )
  {
  switch( key )
    {
    case KEY_UP   : return key_up;
    case KEY_DOWN : return key_down;
    case KEY_LEFT : return key_left;
    case KEY_RIGHT: return key_right;
    case KEY_NPAGE: return key_npage;
    case KEY_PPAGE: return key_ppage;
    case KEY_HOME : return key_home;
    case KEY_END  : return key_end;
    default: return key_invalid;
    }
  }


int Screen::function_key( const int key )
  {
  if( key >= KEY_F(1) && key <= KEY_F(12) ) return ( key - KEY_F(0) );
  return -1;
  }


const char * Screen::cut_filename( const std::string & filename,
                                   const int used_size )
  {
  static std::string short_name;

  const unsigned available_size = std::max( 0, width_ - used_size );
  if( filename.size() <= available_size || filename.size() <= 3 )
    return filename.c_str();
  const unsigned pos = filename.size() + 3 - std::max( 4U, available_size );
  short_name = "...";
  short_name.append( filename, pos, filename.size() );
  return short_name.c_str();
  }


int Screen::user_loop( const char * const version_msg )
  {
  show_message( version_msg );
  int retval = -1;
  bool force_refresh = false;
  while( true )
    {
    const int key = wait_kbhit( -1, true, force_refresh );
    if( key < 0 ) continue;
    last_key.set( key );
    const char * msg = 0;
    force_refresh = false;
    { Window & w = Window_vector::curwin();
    switch( key )
      {
      case  0: msg = Block::toggle_marking( w.buffer(), w.pointer() );
               break;						// ^SPACE
      case  2: msg = Window_vector::reformat(); break;		// ^B
      case  3: if( !Window_vector::close( true ) ) retval = 0; break;	// ^C
      case  6: msg = Window_vector::search( key ); break;	// ^F
      case  7: msg = Window_vector::search( key, true ); break;	// ^G
      case 11: msg = control_k_loop( key ); break;		// ^K
      case 15: msg = control_o_loop( key ); break;		// ^O
      case 16: msg = user_control_char( key ); break;		// ^P
      case 17: retval = control_q_loop( key, &msg ); break;	// ^Q
      case 19: msg = control_s_loop( key, version_msg ); break;	// ^S
      case 23: msg = Window_vector::search_word( key ); break;	// ^W
      case 24: if( !Window_vector::close( false ) ) retval = 0; break;	// ^X
      case 25: Window_vector::delete_line(); break;		// ^Y
      case 27: msg = alt_key_loop( key ); break;		// Alt or Esc
      case 1:							// ^A
      case 8:							// ^H
      case KEY_F(1) : Menu::help_menu( 1, 8, KEY_F(1) ); break;
      case KEY_F(2) : msg = Window_vector::save_file( key ); break;
      case KEY_F(3) : msg = Window_vector::load_file( key ); break;
      case KEY_F(4) : if( !Window_vector::load( -1 ) ) retval = 3; break;
      case KEY_F(5) : Window_vector::prev(); break;
      case KEY_F(6) : Window_vector::next(); break;
      case KEY_F(7) : msg = Window_vector::undo(); break;
      case KEY_F(8) : msg = Window_vector::redo(); break;
      case KEY_F(9) : Window_vector::copy_block(); break;
      case KEY_F(10): Menu::options_menu( w.buffer(), key ); break;
      case KEY_F(11): Window_vector::bufhandle_menu( key ); break;
      case KEY_F(12): Window_vector::last_visited(); break;
      case 127:							// delete
      case KEY_BACKSPACE: Window_vector::delete_char( true ); break;
      case KEY_DC   : Window_vector::delete_char(); break;
      case KEY_IC   : w.buffer().options.overwrite = !w.buffer().options.overwrite;
                      show_status_lines( &w.buffer() ); break;
      case KEY_HOME : w.goto_home(); break;
      case KEY_END  : w.goto_eol(); break;
      case KEY_PPAGE: w.move_page( false ); break;
      case KEY_NPAGE: w.move_page( true ); break;
      case KEY_UP   : w.move_vertical( -1 ); break;
      case KEY_DOWN : w.move_vertical( +1 ); break;
      case KEY_LEFT : w.goto_pprev(); break;
      case KEY_RIGHT: w.goto_pnext(); break;
      default       : if( key == '\n' ) force_refresh = true;
                      msg = Window_vector::add_char( key );
      } }
    if( retval >= 0 ) break;
    { Window & w = Window_vector::curwin();
    if( Block::follow_marking( w.buffer(), w.pointer() ) )
      repaint( &w.buffer() ); }
    if( msg ) show_message( msg );
    }
  wmove( stdscr, height_ - 1, 0 );	// clear bottom line on exit
  wclrtoeol( stdscr ); wrefresh( stdscr );
  return retval;
  }
