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

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


namespace Menu {

const int columns = 3;
int current;		// current highlighted entry in menu
int entries;		// number of entries in menu
int _height;		// height in lines of menu


const char * autoindent_status( const Buffer & buffer ) throw()
  { return buffer.options.auto_indent ? "ON" : "OFF"; }

bool autoindent_change( Buffer & buffer ) throw()
  { buffer.options.auto_indent = !buffer.options.auto_indent; return true; }


const char * backup_status( const Buffer & ) throw()
  { return RC::editor_options().backup ? "ON" : "OFF"; }

bool backup_change( Buffer & ) throw()
  { RC::editor_options().backup = !RC::editor_options().backup; return true; }

/*
const char * beep_status( const Buffer & ) throw()
  { return RC::editor_options().beep ? "ON" : "OFF"; }

bool beep_change( Buffer & ) throw()
  { RC::editor_options().beep = !RC::editor_options().beep; return true; }
*/

const char * crlf_status( const Buffer & buffer ) throw()
  { return buffer.crlf ? "ON" : "OFF"; }

bool crlf_change( Buffer & buffer ) throw()
  { buffer.crlf = !buffer.crlf; return true; }


const char * exit_ask_status( const Buffer & ) throw()
  { return RC::editor_options().exit_ask ? "ON" : "OFF"; }

bool exit_ask_change( Buffer & ) throw()
  { RC::editor_options().exit_ask = !RC::editor_options().exit_ask; return true; }


const char * smart_home_status( const Buffer & ) throw()
  { return RC::editor_options().smart_home ? "ON" : "OFF"; }

bool smart_home_change( Buffer & ) throw()
  { RC::editor_options().smart_home = !RC::editor_options().smart_home; return true; }


const char * ignore_case_status( const Buffer & ) throw()
  { return RC::editor_options().ignore_case ? "ON" : "OFF"; }

bool ignore_case_change( Buffer & ) throw()
  { RC::editor_options().ignore_case = !RC::editor_options().ignore_case; return true; }


const char * indent_step_status( const Buffer & ) throw()
  {
  static char buf[8];
  snprintf( buf, sizeof buf, "%d", RC::editor_options().indent_step );
  return buf;
  }

bool indent_step_change( Buffer & )
  {
  static std::vector< std::string > history;

  history.push_back( std::string() );
  if( Screen::get_string( "Spaces to add when indenting a block (^C to abort): ", history ) <= 0 )
    return false;
  std::string & s = history.back();
  int i;
  if( !RC::parse_int( s, i ) || i < 1 || i >= Screen::width() )
    { Screen::show_message( "Value out of range" ); history.pop_back();
    return false; }
  RC::editor_options().indent_step = i; return true;
  }


const char * keep_lines_status( const Buffer & ) throw()
  {
  static char buf[8];
  snprintf( buf, sizeof buf, "%d", RC::editor_options().keep_lines );
  return buf;
  }

bool keep_lines_change( Buffer & )
  {
  static std::vector< std::string > history;

  history.push_back( std::string() );
  if( Screen::get_string( "Lines to keep for PgUp/PgDn or -1 for 1/2 window (^C to abort): ", history ) <= 0 )
    return false;
  std::string & s = history.back();
  int i;
  if( !RC::parse_int( s, i ) || i < -1 || i >= Screen::height() )
    { Screen::show_message( "Value out of range" ); history.pop_back();
    return false; }
  RC::editor_options().keep_lines = i; return true;
  }


const char * lmargin_status( const Buffer & buffer ) throw()
  {
  static char buf[8];
  snprintf( buf, sizeof buf, "%d", buffer.options.lmargin() + 1 );
  return buf;
  }

bool lmargin_change( Buffer & buffer )
  {
  static std::vector< std::string > history;

  history.push_back( std::string() );
  if( Screen::get_string( "Left margin (^C to abort): ", history ) <= 0 )
    return false;
  std::string & s = history.back();
  int i;
  if( RC::parse_int( s, i ) && buffer.options.set_lmargin( i - 1 ) )
    return true;
  Screen::show_message( "Value out of range" ); history.pop_back();
  return false;
  }


const char * max_windows_status( const Buffer & ) throw()
  {
  static char buf[8];
  snprintf( buf, sizeof buf, "%d", RC::editor_options().max_windows );
  return buf;
  }

bool max_windows_change( Buffer & )
  {
  static std::vector< std::string > history;

  history.push_back( std::string() );
  if( Screen::get_string( "Max number of windows to show or 0 for no limit (^C to abort): ", history ) <= 0 )
    return false;
  std::string & s = history.back();
  int i;
  if( !RC::parse_int( s, i ) || i < 0 || i > Screen::max_windows() )
    { Screen::show_message( "Value out of range" ); history.pop_back();
    return false; }
  RC::editor_options().max_windows = i; return true;
  }


const char * read_only_status( const Buffer & buffer ) throw()
  { return buffer.options.read_only ? "ON" : "OFF"; }

bool read_only_change( Buffer & buffer ) throw()
  { buffer.options.read_only = !buffer.options.read_only; return true; }


const char * rmargin_status( const Buffer & buffer ) throw()
  {
  static char buf[8];
  snprintf( buf, sizeof buf, "%d", buffer.options.rmargin() + 1 );
  return buf;
  }

bool rmargin_change( Buffer & buffer )
  {
  static std::vector< std::string > history;

  history.push_back( std::string() );
  if( Screen::get_string( "Right margin (^C to abort): ", history ) <= 0 )
    return false;
  std::string & s = history.back();
  int i;
  if( RC::parse_int( s, i ) && buffer.options.set_rmargin( i - 1 ) )
    return true;
  Screen::show_message( "Value out of range" ); history.pop_back();
  return false;
  }


const char * search_wrap_status( const Buffer & ) throw()
  { return RC::editor_options().search_wrap ? "ON" : "OFF"; }

bool search_wrap_change( Buffer & ) throw()
  { RC::editor_options().search_wrap = !RC::editor_options().search_wrap; return true; }


const char * auto_unmark_status( const Buffer & ) throw()
  { return RC::editor_options().auto_unmark ? "ON" : "OFF"; }

bool auto_unmark_change( Buffer & ) throw()
  { RC::editor_options().auto_unmark = !RC::editor_options().auto_unmark; return true; }


const char * word_wrap_status( const Buffer & buffer ) throw()
  { return buffer.options.word_wrap ? "ON" : "OFF"; }

bool word_wrap_change( Buffer & buffer ) throw()
  { buffer.options.word_wrap = !buffer.options.word_wrap; return true; }


const char * rectangle_mode_status( const Buffer & ) throw()
  { return RC::editor_options().rectangle_mode ? "ON" : "OFF"; }

bool rectangle_mode_change( Buffer & ) throw()
  {
  RC::editor_options().rectangle_mode = !RC::editor_options().rectangle_mode;
  Screen::show_status_lines();
  if( Block::bufferp() ) Screen::repaint( Block::bufferp() );
  return true;
  }


struct Entry
  {
  char ch;			// Menu letter
  const char * str;		// Menu string
  const char * (*status)( const Buffer & buffer ) throw();
  bool (*change)( Buffer & buffer );
  };

Entry table[] = {
  { 'A', "Autoindent ", &autoindent_status, &autoindent_change },
  { 'B', "Create backups ", &backup_status, &backup_change },
  { 'C', "CR-LF ended lines ", &crlf_status, &crlf_change },
  { 'E', "Exit ask ", &exit_ask_status, &exit_ask_change },
  { 'H', "Smart home key ", &smart_home_status, &smart_home_change },
  { 'I', "Search ignores case ", &ignore_case_status, &ignore_case_change },
  { 'K', "Keep PgUp/PgDn lines ", &keep_lines_status, &keep_lines_change },
  { 'L', "Left margin ", &lmargin_status, &lmargin_change },
  { 'M', "Max windows to show ", &max_windows_status, &max_windows_change },
  { 'N', "Indent step ", &indent_step_status, &indent_step_change },
  { 'O', "Read only ", &read_only_status, &read_only_change },
//  { 'P', "Beeps ", &beep_status, &beep_change },
  { 'R', "Right margin ", &rmargin_status, &rmargin_change },
  { 'S', "Search wrap ", &search_wrap_status, &search_wrap_change },
//  { 'T', "Path to backup files ", 0, 0 },
  { 'U', "Auto unmark ", &auto_unmark_status, &auto_unmark_change },
  { 'W', "Word wrap ", &word_wrap_status, &word_wrap_change },
  { 'X', "Rectangle mode ", &rectangle_mode_status, &rectangle_mode_change },
  { 0, 0, 0, 0 } };


struct Init_menu
  {
  Init_menu() throw()
    {
    current = 0;
    for( entries = 0; table[entries].ch; ++entries ) ;
    _height = ( entries + columns - 1 ) / columns;
    }
  } dummy;


bool set_current( int key ) throw()
  {
  if( key >= 0 && key < 128 )
    {
    if( std::islower( key ) ) key = std::toupper( key );
    for( int i = 0; i < entries; ++i )
      if( key == table[i].ch )
        { current = i; return true; }
    }
  return false;
  }


void show( const Buffer & buffer, Point & cursor ) throw()
  {
  for( int line = 0; line < _height; ++line )
    {
    int offset = 0;
    std::string s;
    for( int i = columns * line; i < columns * ( line + 1 ) && i < entries; ++i )
      {
      if( i == current )
        { cursor = Point( line, s.size() ); s += "@i"; offset += 2; }
      s += table[i].ch; s += ' '; s += table[i].str;
      if( table[i].status )
        { const char * st = table[i].status( buffer ); if( st ) s += st; }
      unsigned int col = ( ( ( i % columns ) + 1 ) * Screen::width() ) / columns + offset;
      while( s.size() < col ) s += ' ';
      if( i == current ) { s += "@i"; offset += 2; }
      }
    Screen::out_line( s, Screen::height() - _height + line );
    }
  }

} // end namespace Menu


void Menu::options_menu( Buffer & buffer, const int abort_key )
  {
  const int lines = _height;
  const int line = Screen::height() - lines;
  Point cursor( Screen::height() - 1, Screen::width() - 1 );

  Screen::save_lines_and_cursor( line, lines );

  int key = abort_key + 1;
  bool valid_command = false;
  while( key != abort_key && !valid_command )
    {
    show( buffer, cursor );
    Screen::move_to( Point( line, 0 ) + cursor );
    bool valid_key = false;
    while( !valid_key )
      {
      valid_key = true;
      key = Screen::wait_kbhit( line );
      if( key != abort_key ) switch( key )
        {
        case 3   : key = abort_key; break;		// ^C
        case ' ' :
        case '\r': valid_command = true; break;
        default: if( set_current( key ) ) valid_command = true;
                 else switch( Screen::convert_key( key ) )
                   {
                   case Screen::key_up   :
                     if( current >= columns ) current -= columns; break;
                   case Screen::key_down :
                     if( current + columns < entries ) current += columns; break;
                   case Screen::key_left : if( current > 0 ) --current; break;
                   case Screen::key_right: if( current < entries - 1 ) ++current; break;
                   case Screen::key_ppage:
                     while( current >= columns ) current -= columns; break;
                   case Screen::key_npage:
                     while( current + columns < entries ) current += columns; break;
                   case Screen::key_home : current = 0; break;
                   case Screen::key_end  : current = entries - 1; break;
                   default: valid_key = false;
                   }
        }
      }
    }
  Screen::restore_lines_and_cursor();
  if( valid_command )
    {
    const Entry & entry = table[current];
    if( entry.change && entry.change( buffer ) )
      {
      std::string msg( entry.str );
      if( entry.status )
        { const char * st = entry.status( buffer ); if( st ) msg += st; }
      if( msg.size() )
        { Screen::show_status_lines( &buffer ); Screen::show_message( msg ); }
      }
    }
  }
