/*  GNU moe - My Own Editor
    Copyright (C) 2005-2019 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/>.
*/
/*
    Exit status: 0 for a normal exit, 1 for environmental problems
    (file not found, invalid flags, I/O errors, etc), 2 to indicate a
    corrupt or invalid input file, 3 for an internal consistency error
    (eg, bug) which caused moe to panic.
*/

#include <cctype>
#if defined(__CYGWIN__)
#include <clocale>
#endif
#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include <unistd.h>

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


namespace {

const char * const     program_name = "moe";
const char * const config_file_name = "moerc";
const char * const     program_year = "2019";
const char * invocation_name = 0;


void show_help()
  {
  std::printf( "GNU moe is a powerful, 8-bit clean, console text editor for ISO-8859 and\n"
               "ASCII character encodings. It has a modeless, user-friendly interface,\n"
               "online help, multiple windows, unlimited undo/redo capability, unlimited\n"
               "line length, unlimited buffers, global search/replace (on all buffers at\n"
               "once), block operations, automatic indentation, word wrapping, file name\n"
               "completion, directory browser, duplicate removal from prompt histories,\n"
               "delimiter matching, text conversion from/to UTF-8, romanization, etc.\n"
               "\nMoe can easily edit thousands of files at the same time.\n"
               "\nMoe uses ISO-8859-15 instead of UTF-8 because an 8-bit character set\n"
               "(combined with romanization if needed) can convey meaning safely and more\n"
               "efficiently than UTF-8 can.\n"
               "\nUsage: %s [global_options] [[+line[,col]] file_name [file_options]]...\n",
               invocation_name );
  std::printf( "\n'global_options' are a mix of editor options and default 'file_options'.\n"
               "Most long option names have a negative form '--backup' '--no-backup'.\n"
               "\nEach file name may be preceded by '+line[,column]' to start the cursor\n"
               "at the specified line and, optionally, column. 'line' and 'column' are\n"
               "applied to subsequent files until new values are specified for them.\n"
               "\n'-' used as a file name argument means standard input.\n"
               "Directories are recursively descended into.\n"
               "\nEditor options:\n"
               "  -h, --help               display this help and exit\n"
               "  -V, --version            output version information and exit\n"
               "  -1, --orphan             put extra files in orphaned buffers\n"
               "  -b, --backup             create backup files (default)\n"
               "  -B, --no-backup          don't create backup files\n"
               "  -e, --exit-ask           make save-and-close ask always for confirmation\n"
               "  -H, --smart-home         go home, then go to first non-blank character\n"
               "  -i, --ignore-case        make search case insensitive by default\n"
               "  -k, --keep-lines=<n>     number of lines to keep for PgUp/PgDn\n"
               "  -m, --max-windows=<n>    max number of windows to show at once\n"
               "  -n, --indent-step=<n>    number of spaces to add when indenting a block\n"
//               "  -p, --beep               beep on errors and when cursor goes past extremes\n"
               "  -s, --search-wrap        make search wrap at end of file\n"
               "  -u, --auto-unmark        turn off highlighting after block copy or move\n"
               "  -x, --rectangle          rectangular block mode\n"
               "\nFile options:\n"
               "  -a, --auto-indent        enable auto indent\n"
               "  -l, --lmargin=<col>      set left margin\n"
               "  -r, --rmargin=<col>      set right margin\n"
               "  -o, --read-only          make buffer read-only\n"
               "  -O, --overwrite          disable insert mode\n"
               "  -w, --word-wrap          enable word wrapping\n"
               "\nExit status: 0 for a normal exit, 1 for environmental problems (file not\n"
               "found, invalid flags, I/O errors, etc), 2 to indicate a corrupt or invalid\n"
               "input file, 3 for an internal consistency error (eg, bug) which caused moe\n"
               "to panic.\n"
               "\nReport bugs to bug-moe@gnu.org\n"
               "Moe home page: http://www.gnu.org/software/moe/moe.html\n"
               "General help using GNU software: http://www.gnu.org/gethelp\n" );
  }


void show_version()
  {
  std::printf( "GNU %s %s\n", program_name, PROGVERSION );
  std::printf( "Copyright (C) %s Antonio Diaz Diaz.\n", program_year );
  std::printf( "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>\n"
               "This is free software: you are free to change and redistribute it.\n"
               "There is NO WARRANTY, to the extent permitted by law.\n" );
  }


extern "C" void termination_handler( int ) { std::exit( 1 ); }


int set_signal( const int signum, void (*handler)( int ) )
  {
  struct sigaction new_action;

  new_action.sa_handler = handler;
  sigemptyset( &new_action.sa_mask );
  new_action.sa_flags = SA_RESTART;
  return sigaction( signum, &new_action, 0 );
  }


void restore_signals()
  {
  set_signal( SIGALRM, SIG_DFL );
  set_signal( SIGBUS, SIG_DFL );
  set_signal( SIGFPE, SIG_DFL );
  set_signal( SIGHUP, SIG_DFL );
  set_signal( SIGILL, SIG_DFL );
  set_signal( SIGINT, SIG_DFL );
  set_signal( SIGPIPE, SIG_DFL );
  set_signal( SIGSEGV, SIG_DFL );
  set_signal( SIGTERM, SIG_DFL );
  set_signal( SIGUSR1, SIG_DFL );
  set_signal( SIGUSR2, SIG_DFL );
  }


void set_signals()
  {
  std::atexit( restore_signals );
  set_signal( SIGALRM, Screen::clock_handler );
  set_signal( SIGBUS, termination_handler );
  set_signal( SIGFPE, SIG_IGN );
  set_signal( SIGHUP, termination_handler );
  set_signal( SIGILL, termination_handler );
  set_signal( SIGINT, SIG_IGN );
  set_signal( SIGPIPE, SIG_IGN );
  set_signal( SIGSEGV, termination_handler );
  set_signal( SIGTERM, termination_handler );
  set_signal( SIGUSR1, SIG_IGN );
  set_signal( SIGUSR2, SIG_IGN );
  }


bool use_it_anyway( const std::string & name )
  {
  if( !isatty( fileno( stdin ) ) ) return false;
  std::fprintf( stderr, "There are errors in '%s'. Use it anyway (y/N)? ",
                name.c_str() );
  std::fflush( stderr );
  return ( std::tolower( std::fgetc( stdin ) ) == 'y' );
  }


void process_config_file()
  {
  std::fputc( '\n', stderr );
  std::string name( RC::home_directory() );
  if( name.size() )
    {
    name += "/."; name += config_file_name;
    const int retval = RC::process_rcfile( name );
    if( retval == 0 || ( retval == 2 && use_it_anyway( name ) ) ) return;
    RC::reset();
    }
  name = SYSCONFDIR; name += '/'; name += config_file_name;
  const int retval = RC::process_rcfile( name );
  if( retval == 0 || ( retval == 2 && use_it_anyway( name ) ) ) return;
  RC::reset();
  std::fprintf( stderr, "Couldn't open '%s'\n", name.c_str() );
  }

} // end namespace


const std::string & RC::home_directory()
  {
  static bool first_time = true;
  static std::string home;

  if( first_time )
    {
    first_time = false;
    const char * const p = std::getenv( "HOME" ); if( p ) home = p;
    }
  return home;
  }


void RC::show_error( const char * const msg, const int errcode,
                     const bool help )
  {
  if( msg && msg[0] )
    std::fprintf( stderr, "%s: %s%s%s\n", program_name, msg,
                  ( errcode > 0 ) ? ": " : "",
                  ( errcode > 0 ) ? std::strerror( errcode ) : "" );
  if( help )
    std::fprintf( stderr, "Try '%s --help' for more information.\n",
                  invocation_name );
  }


int main( const int argc, const char * const argv[] )
  {
  invocation_name = argv[0];
#if defined(__CYGWIN__)
  // set an 8-bit "C" locale in Cygwin so that ncurses can show chars > 127
  std::setlocale( LC_ALL, "C.ISO-8859-15" );
#endif

  int retval = RC::read_options( argc, argv );
  if( retval )
    {
    if( retval == 'h' ) { show_help(); return 0; }
    if( retval == 'V' ) { show_version(); return 0; }
    return retval;
    }

  Bufhandle_vector::init();

  process_config_file();
  retval = RC::process_options();
  if( retval == 2 )
    {
    std::fputs( "Press RETURN to continue, Ctrl-C to abort.\n", stderr );
    std::fgetc( stdin ); retval = 0;
    }
  if( retval ) return retval;

  if( !Screen::init() )
    { RC::show_error( "Screen too small (min size 24 x 80)." ); return 1; }
  set_signals();

  char buf[88];
  snprintf( buf, sizeof buf,
    "@i ** My Own Editor v%s ** (ISO-8859) ** Copyright (C) %s Antonio Diaz ** ",
    PROGVERSION, program_year );
  return Screen::user_loop( buf );
  }
