/* GNU moe - My Own Editor
   Copyright (C) 2005-2025 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
   (invalid command-line options, I/O errors, etc), 2 to indicate an invalid
   or unreadable input file, 3 for an internal consistency error (e.g., 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 = "moe.conf";
const char * const     program_year = "2025";
const char * invocation_name = program_name;		// default value


void show_help()
  {
  std::printf(
    "GNU moe is a console text editor for ISO-8859 and ASCII character encodings.\n"
    "It has a modeless, user-friendly interface, online help, multiple windows,\n"
    "global search/replace (on all buffers at once), block operations, automatic\n"
    "indentation, word wrapping, file name completion, directory browser,\n"
    "duplicate removal from prompt histories, delimiter matching, text conversion\n"
    "from/to UTF-8, romanization, etc. The file size, line length, number of\n"
    "buffers, and undo/redo capability are only limited by the amount of memory\n"
    "available and the size of the address space of your machine.\n"
    "\nMoe respects your work. By default it won't automatically add, change, or\n"
    "remove a single byte in your files. Moe is a WYTIWYG (what you type is what\n"
    "you get) editor.\n"
    "\nMoe can easily edit thousands of files at the same time.\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 line, and optionally column, specified. 'line' and 'column' are\n"
    "applied to subsequent files until new values are specified for them.\n"
    "A '+' alone puts the cursor at end-of-buffer.\n"
    "\nA hyphen '-' 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"
    "  -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, --preserve-links     preserve (don't break) links when saving files\n"
    "  -s, --search-wrap        make search wrap at end of file\n"
    "  -u, --auto-unmark        turn off highlighting after any block operation\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"
    "  -W, --no-word-wrap       disable word wrapping\n"
    "  -2, --two-spaces         make reformat put two spaces after period\n"
    "\n*Exit status*\n"
    "0 for a normal exit, 1 for environmental problems (invalid command-line\n"
    "options, I/O errors, etc), 2 to indicate an invalid or unreadable input\n"
    "file, 3 for an internal consistency error (e.g., bug) which caused moe to\n"
    "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( SIGQUIT, 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( SIGQUIT, 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 maybe_process_config_file()
  {
  std::fputc( '\n', stderr );
  std::string name;
  const char * p = std::getenv( "XDG_CONFIG_HOME" ); if( p ) name = p;
  else { name = RC::home_directory(); if( name.size() ) name += "/.config"; }
  if( name.size() )
    {
    name += '/'; name += config_file_name;
    const int retval = RC::process_rcfile( name.c_str() );
    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.c_str() );
  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[] )
  {
  if( argc > 0 ) 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();

  maybe_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 25 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 );
  }
