/* vim:expandtab:ts=2 sw=2:
*/
/*  Grafx2 - The Ultimate 256-color bitmap paint program

	Copyright owned by various GrafX2 authors, see COPYRIGHT.txt for details.

    Grafx2 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; version 2
    of the License.

    Grafx2 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 Grafx2; if not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file main.c
 * Program entry point, global variables and global functions.
 *
 * @mainpage
 * GrafX2 is a bitmap paint program inspired by the Amiga programs
 * Deluxe Paint and Brilliance. Specialized in 256-color drawing,
 * it includes a very large number of tools and effects that make
 * it particularly suitable for pixel art, game graphics,
 * and generally any detailed graphics painted with a mouse.
 *
 * The program is mostly developed on Haiku, Linux, FreeBSD
 * and Windows, but is also portable on many other platforms :
 * It can be built using SDL 1.x or SDL 2.x libraries (see https://www.libsdl.org/)
 * or Xlib or Win32 API.
 *
 * Web for the project users is http://grafx2.eu/.
 *
 * Developpers are welcome to contribute :
 * the code is hosted on gitlab https://gitlab.com/GrafX2/grafX2
 * and a bug tracker, wiki, etc. is available on
 * https://pulkomandy.tk/projects/GrafX2.
 *
 * This Doxygen documentation is browsable on
 * https://pulkomandy.tk/projects/GrafX2/doxygen/ (updated nightly) or
 * https://grafx2.gitlab.io/grafX2/doxygen/html/
 *
 * The inline help is also available here :
 * http://pulkomandy.tk/GrafX2/ or
 * https://grafx2.gitlab.io/grafX2/htmldoc/
 */
/// declare global variables in main.c
#define GLOBAL_VARIABLES

// time.h defines timeval which conflicts with the one in amiga SDK
#ifdef __amigaos__
  #include <devices/timer.h>
#else
  #include <time.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#ifndef _MSC_VER
#include <unistd.h>
#else
#define strdup _strdup
#if _MSC_VER < 1900
#define snprintf _snprintf
#endif
#endif
#if defined(USE_SDL) || defined(USE_SDL2)
#include <SDL.h>
#include <SDL_image.h>
#endif

#if defined(WIN32)
#include <windows.h>
#include <shlwapi.h>
#include <io.h>
#include <fcntl.h>
#elif defined (__MINT__)
#include <mint/osbind.h>
#elif defined(__macosx__)
#import <CoreFoundation/CoreFoundation.h>
#import <sys/param.h>
#elif defined(__FreeBSD__)
#include <sys/param.h>
#endif

#if defined(__macosx__) || defined(__SWITCH__) || defined(__amigaos__)
#include <machine/endian.h>
#elif defined(__FreeBSD__)
#include <sys/endian.h>
#elif !defined(WIN32)
#include <endian.h>
#endif

#include "gfx2log.h"
#include "gfx2mem.h"
#include "const.h"
#include "struct.h"
#include "global.h"
#include "graph.h"
#include "misc.h"
#include "init.h"
#include "buttons.h"
#include "engine.h"
#include "pages.h"
#include "loadsave.h"
#include "loadsavefuncs.h"
#include "screen.h"
#include "errors.h"
#include "readini.h"
#include "saveini.h"
#include "io.h"
#include "text.h"
#include "setup.h"
#include "windows.h"
#include "brush.h"
#include "palette.h"
#include "realpath.h"
#include "unicode.h"
#include "input.h"
#include "help.h"
#include "filesel.h"
#include "factory.h"
#if defined(WIN32) && !(defined(USE_SDL) || defined(USE_SDL2))
#include "win32screen.h"
#endif


#if defined (WIN32) && (defined(USE_SDL) || defined(USE_SDL2))
  // On Windows, SDL_putenv is not present in any compilable header.
  // It can be linked anyway, this declaration only avoids
  // a compilation warning.
  extern DECLSPEC int SDLCALL SDL_putenv(const char *variable);
#endif

extern char Program_version[]; // generated in pversion.c

static int setsize_width;
static int setsize_height;

#if (defined(USE_SDL) || defined(USE_SDL2)) && defined(USE_JOYSTICK)
/// Pointer to the current joystick controller.
static SDL_Joystick* Joystick;
#endif

/**
 * Show the command line syntax and available video modes.
 *
 * Output to standard outout (stdout) and show a message box under MS Windows,
 * where standard output is not available
 */
void Display_syntax(void)
{
  int mode_index, i;
  char modes[1024*2];
  const char * syntax =
    "Syntax: grafx2 [<arguments>] [<picture1>] [<picture2>]\n\n"
    "<arguments> can be:\n"
    "\t-? -h -H -help     for this help screen\n"
    "\t-verbose           to increase log verbosity\n"
    "\t-wide              to emulate a video mode with wide pixels (2x1)\n"
    "\t-tall              to emulate a video mode with tall pixels (1x2)\n"
    "\t-double            to emulate a video mode with double pixels (2x2)\n"
    "\t-wide2             to emulate a video mode with double wide pixels (4x2)\n"
    "\t-tall2             to emulate a video mode with double tall pixels (2x4)\n"
    "\t-triple            to emulate a video mode with triple pixels (3x3)\n"
    "\t-quadruple         to emulate a video mode with quadruple pixels (4x4)\n"
    "\t-rgb n             to reduce RGB precision (2 to 256, 256=max precision)\n"
    "\t-gamma n           to adjust Gamma correction (1 to 30, 10=no correction)\n"
    "\t-skin <filename>   to use an alternate file with the menu graphics\n"
    "\t-mode <videomode>  to set a video mode\n"
    "\t-size <resolution> to set the image size\n"
    "Arguments can be prefixed either by / - or --\n"
    "They can also be abbreviated.\n\n";
  fputs(syntax, stdout);

  i = snprintf(modes, sizeof(modes), "Available video modes:\n\n");
  for (mode_index = 0; mode_index < Nb_video_modes; mode_index += 6)
  {
    int k;
    for (k = 0; k < 6; k++)
    {
      if (mode_index + k >= Nb_video_modes) break;
      i += snprintf(modes + i, sizeof(modes) - i, "%12s", Mode_label(mode_index + k));
    }
    i += snprintf(modes + i, sizeof(modes) - i, "\n");
  }
  fputs(modes, stdout);

  GFX2_MessageBox(syntax, "GrafX2", GFX2_MB_INFO);
  GFX2_MessageBox(modes, "GrafX2", GFX2_MB_INFO);
}

// ---------------------------- Sortie impromptue ----------------------------
void Error_function(int error_code, const char *filename, int line_number, const char *function_name)
{
  T_Palette temp_palette;
  T_Palette backup_palette;
  int       index;
  char msg_buffer[512];

  snprintf(msg_buffer, sizeof(msg_buffer), "Error number %d occurred in file %s, line %d, function %s.\n", error_code, filename,line_number,function_name);
  fputs(msg_buffer, stderr);
#if defined(_MSC_VER) && defined(_DEBUG)
  OutputDebugStringA(msg_buffer);
#endif

  if (error_code==0)
  {
    // L'erreur 0 n'est pas une vraie erreur, elle fait seulement un flash rouge de l'écran pour dire qu'il y a un problème.
    // Toutes les autres erreurs déclenchent toujours une sortie en catastrophe du programme !
    memcpy(backup_palette, Get_current_palette(), sizeof(T_Palette));
    memcpy(temp_palette, backup_palette, sizeof(T_Palette));
    for (index=0;index<=255;index++)
      temp_palette[index].R=255;
    Set_palette(temp_palette);
    Delay_with_active_mouse(50); // Half a second of red flash
    Set_palette(backup_palette);
  }
  else
  {
    const char * msg = NULL;
    switch (error_code)
    {
      case ERROR_GUI_MISSING         : snprintf(msg_buffer, sizeof(msg_buffer), "Error: File containing the GUI graphics is missing!\n"
                                             "This program cannot run without this file.\n"
                                              "\n%s", Gui_loading_error_message);
                                       msg = msg_buffer;
                                       break;
      case ERROR_GUI_CORRUPTED       : msg = "Error: File containing the GUI graphics couldn't be parsed!\n"
                                             "This program cannot run without a correct version of this file.\n";
                                       break;
      case ERROR_INI_MISSING         : msg = "Error: File gfx2def.ini is missing!\n"
                                             "This program cannot run without this file.\n";
                                       break;
      case ERROR_MEMORY              : msg = "Error: Not enough memory!\n\n"
                                             "You should try exiting other programs to free some bytes for Grafx2.\n\n";
                                       break;
      case ERROR_FORBIDDEN_MODE      : msg = "Error: The requested video mode has been disabled from the resolution menu!\n"
                                             "If you want to run the program in this mode, you'll have to start it with an\n"
                                             "enabled mode, then enter the resolution menu and enable the mode you want.\n"
                                             "Check also if the 'Default_video_mode' parameter in gfx2.ini is correct.\n";
                                       break;
      case ERROR_FORBIDDEN_SIZE      : msg = "Error: The image dimensions all have to be in the range 1-9999!\n";
                                       break;
      case ERROR_COMMAND_LINE     : msg = "Error: Invalid parameter or file not found.\n\n";
                                       break;
      case ERROR_SAVING_CFG     : msg = "Error: Write error while saving settings!\n"
                                        "Settings have not been saved correctly, and the gfx2.cfg file may have been\n"
                                        "corrupt. If so, please delete it and Grafx2 will restore default settings.\n";
                                       break;
      case ERROR_MISSING_DIRECTORY : msg = "Error: Directory you ran the program from not found!\n";
                                       break;
      case ERROR_INI_CORRUPTED       : snprintf(msg_buffer, sizeof(msg_buffer), "Error: File %s%s is corrupt!\n"
                                                "It contains bad values at line %d.\n"
                                                "You can re-generate it by deleting the file and running GrafX2 again.\n",
                                                Config_directory, INI_FILENAME,
                                                Line_number_in_INI_file);
                                       msg = msg_buffer;
                                       break;
      case ERROR_SAVING_INI     : msg = "Error: Cannot rewrite file gfx2.ini!\n";
                                       break;
      case ERROR_SORRY_SORRY_SORRY  : msg = "Error: Sorry! Sorry! Sorry! Please forgive me!\n";
                                       break;
    }

    if(msg != NULL)
    {
      fputs(msg, stderr);
#if defined(WIN32) && defined(_DEBUG)
      OutputDebugStringA(msg);
#endif
      GFX2_MessageBox(msg, "GrafX2 error", GFX2_MB_ERROR);
    }

    if (error_code == ERROR_COMMAND_LINE)
      Display_syntax();

#if defined(USE_SDL) || defined(USE_SDL2)
    SDL_Quit();
#endif
    exit(error_code);
  }
}

enum CMD_PARAMS
{
    CMDPARAM_HELP,
    CMDPARAM_MODE,
    CMDPARAM_PIXELRATIO_TALL,
    CMDPARAM_PIXELRATIO_WIDE,
    CMDPARAM_PIXELRATIO_DOUBLE,
    CMDPARAM_PIXELRATIO_TRIPLE,
    CMDPARAM_PIXELRATIO_QUAD,
    CMDPARAM_PIXELRATIO_TALL2,
    CMDPARAM_PIXELRATIO_TALL3,
    CMDPARAM_PIXELRATIO_WIDE2,
    CMDPARAM_RGB,
    CMDPARAM_GAMMA,
    CMDPARAM_SKIN,
    CMDPARAM_SIZE,
    CMDPARAM_VERBOSE,
};

struct {
    const char *param;
    int id;
} cmdparams[] = {
    {"?", CMDPARAM_HELP},
    {"h", CMDPARAM_HELP},
    {"H", CMDPARAM_HELP},
    {"help", CMDPARAM_HELP},
    {"mode", CMDPARAM_MODE},
    {"tall", CMDPARAM_PIXELRATIO_TALL},
    {"wide", CMDPARAM_PIXELRATIO_WIDE},
    {"double", CMDPARAM_PIXELRATIO_DOUBLE},
    {"triple", CMDPARAM_PIXELRATIO_TRIPLE},
    {"quadruple", CMDPARAM_PIXELRATIO_QUAD},
    {"tall2", CMDPARAM_PIXELRATIO_TALL2},
    {"tall3", CMDPARAM_PIXELRATIO_TALL3},
    {"wide2", CMDPARAM_PIXELRATIO_WIDE2},
    {"rgb", CMDPARAM_RGB},
    {"gamma", CMDPARAM_GAMMA},
    {"skin", CMDPARAM_SKIN},
    {"size", CMDPARAM_SIZE},
    {"verbose", CMDPARAM_VERBOSE},
};

#define ARRAY_SIZE(x) (int)(sizeof(x) / sizeof(x[0]))

/**
 * Parse the command line.
 *
 * @param argc argument count
 * @param argv argument values
 * @param filenames pointers to receive file names
 * @param directories pointers to receive file directories
 * @param videomode_arg pointer to receive the -mode argument
 * @param pixel_ratio pointer to receive the pixel ratio requested
 * @return the number of file to open (0, 1 or 2)
 */
int Analyze_command_line(int argc, char * argv[], char * filenames[], char * directories[], const char ** videomode_arg, int * pixel_ratio)
{
  char *buffer;
  char *filename;
  int index;
  int file_in_command_line;

  file_in_command_line = 0;
  Resolution_in_command_line = 0;

  Current_resolution = Config.Default_resolution;

  for (index = 1; index<argc; index++)
  {
    char *s = argv[index];
    int is_switch = ((strchr(s,'/') == s) || (strchr(s,'-') == s) || (strstr(s,"--") == s));
    int tmpi;
    char *tmpcp;
    int paramtype = -1;
    if (is_switch)
    {
      int param_matches = 0;
      int param_match = -1;
      if (*s == '-')
      {
        s++;
        if (*s == '-')
          s++;
      }
      else
        s++;

#ifdef __macosx__
      // ignore -psn argument
      if (memcmp(s, "psn_", 4) ==0)
        continue;
#endif

      for (tmpi = 0; tmpi < ARRAY_SIZE(cmdparams); tmpi++)
      {
        if (!strcmp(s, cmdparams[tmpi].param))
        {
          paramtype = cmdparams[tmpi].id;
          break;
        }
        else if (strstr(cmdparams[tmpi].param, s))
        {
          param_matches++;
          param_match = cmdparams[tmpi].id;
        }
      }
      if (paramtype == -1 && param_matches == 1)
        paramtype = param_match;

    }
    switch (paramtype)
    {
      case CMDPARAM_HELP:
        Display_syntax();
        exit(0);
      case CMDPARAM_MODE:
        index++;
        if (index<argc)
        {
          // will be processed later, when video is initialized
          *videomode_arg = argv[index];
        }
        else
        {
          Error(ERROR_COMMAND_LINE);
          exit(0);
        }
        break;
      case CMDPARAM_PIXELRATIO_TALL:
        *pixel_ratio = PIXEL_TALL;
        break;
      case CMDPARAM_PIXELRATIO_WIDE:
        *pixel_ratio = PIXEL_WIDE;
        break;
      case CMDPARAM_PIXELRATIO_DOUBLE:
        *pixel_ratio = PIXEL_DOUBLE;
        break;
      case CMDPARAM_PIXELRATIO_TRIPLE:
        *pixel_ratio = PIXEL_TRIPLE;
        break;
      case CMDPARAM_PIXELRATIO_QUAD:
        *pixel_ratio = PIXEL_QUAD;
        break;
      case CMDPARAM_PIXELRATIO_TALL2:
        *pixel_ratio = PIXEL_TALL2;
        break;
      case CMDPARAM_PIXELRATIO_TALL3:
        *pixel_ratio = PIXEL_TALL3;
        break;
      case CMDPARAM_PIXELRATIO_WIDE2:
        *pixel_ratio = PIXEL_WIDE2;
        break;
      case CMDPARAM_RGB:
        /* RGB scale */
        index++;
        if (index<argc)
        {
          int scale;
          scale = atoi(argv[index]);
          if (scale < 2 || scale > 256)
          {
            Error(ERROR_COMMAND_LINE);
            exit(0);
          }
          Set_palette_RGB_scale(scale);
        }
        else
        {
          Error(ERROR_COMMAND_LINE);
          exit(0);
        }
        break;
      case CMDPARAM_GAMMA:
        /* Gamma correction */
        index++;
        if (index<argc)
        {
          int scale;
          scale = atoi(argv[index]);
          if (scale < 1 || scale > 30)
          {
            Error(ERROR_COMMAND_LINE);
            exit(0);
          }
          Set_palette_Gamma(scale);
        }
        else
        {
          Error(ERROR_COMMAND_LINE);
          exit(0);
        }
        break;
      case CMDPARAM_SKIN:
        // GUI skin file
        index++;
        if (index<argc)
        {
          strcpy(Config.Skin_file,argv[index]);
        }
        else
        {
          Error(ERROR_COMMAND_LINE);
          exit(0);
        }
        break;
      case CMDPARAM_SIZE:
        index++;
        if (index<argc)
        {
          setsize_width = atoi(argv[index]);
          tmpcp = strchr(argv[index], 'x');
          if (tmpcp == NULL)
            tmpcp = strchr(argv[index], 'X');
          if (tmpcp == NULL)
          {
            Error(ERROR_COMMAND_LINE);
            exit(0);
          }
          setsize_height = atoi(++tmpcp);
          if (setsize_height < 1 || setsize_height > 9999 ||
              setsize_width < 1 || setsize_width > 9999)
          {
            Error(ERROR_FORBIDDEN_SIZE);
            exit(0);
          }
        }
        else
        {
          Error(ERROR_COMMAND_LINE);
          exit(0);
        }
        break;
      case CMDPARAM_VERBOSE:
        GFX2_verbosity_level++;
        break;
      default:
        // Si ce n'est pas un paramètre, c'est le nom du fichier à ouvrir
        if (file_in_command_line > 1)
        {
          // Il y a déjà 2 noms de fichiers et on vient d'en trouver un 3ème
          Error(ERROR_COMMAND_LINE);
          exit(0);
        }
        else if (File_exists(argv[index]))
        {
          buffer = Realpath(argv[index]);
          filename = Find_last_separator(buffer);
          if (filename != NULL)
          {
            *filename = '\0';
            filename++;
            directories[file_in_command_line] = strdup(buffer);
            filenames[file_in_command_line] = strdup(filename);
          }
          else
          {
            directories[file_in_command_line] = strdup(".");
            filenames[file_in_command_line] = strdup(buffer);
          }
          free(buffer);
          buffer = NULL;
          file_in_command_line++;
        }
        else
        {
          Error(ERROR_COMMAND_LINE);
          exit(0);
        }
        break;
    }
  }
  return file_in_command_line;
}

// Compile-time assertions:
#define CT_ASSERT(e) extern char (*ct_assert(void)) [sizeof(char[1 - 2*!(e)])]

// This line will raise an error at compile time
// when sizeof(T_Components) is not 3.
CT_ASSERT(sizeof(T_Components)==3);

// This line will raise an error at compile time
// when sizeof(T_Palette) is not 768.
CT_ASSERT(sizeof(T_Palette)==768);

#if defined(__MINT__)
static void Exit_handler(void)
{
  printf("Press any key to quit.\n");
  (void)Cnecin();
}
#endif

/**
 * Initialize the  program.
 *
 * @param argc command line argument count
 * @param argv command line argument values
 * @return 0 on fail
 * @return 1 on success
 */
int Init_program(int argc,char * argv[])
{
  int temp;
  int starting_videomode;
  enum IMAGE_MODES starting_image_mode;
  char * program_directory;
  T_Gui_skin *gfx;
  int file_in_command_line;
  T_Gradient_array initial_gradients;
  char * filenames[2] = {NULL, NULL};
  char * directories[2] = {NULL, NULL};
  const char * videomode = NULL;
  int cmdline_pixelratio = -1;
  char * envvar_val = NULL;

  #if defined(__MINT__)
  printf("===============================\n");
  printf(" /|\\ GrafX2 %.19s\n", Program_version);
  printf(" compilation date: %.16s\n", __DATE__);
  printf("===============================\n");
  atexit(Exit_handler);
  #endif

#ifdef ENABLE_FILENAMES_ICONV
  // iconv is used to convert filenames
  cd = iconv_open(TOCODE, FROMCODE);  // From UTF8 to ANSI
  cd_inv = iconv_open(FROMCODE, TOCODE);  // From ANSI to UTF8
#if (defined(SDL_BYTEORDER) && (SDL_BYTEORDER == SDL_BIG_ENDIAN)) || (defined(BYTE_ORDER) && (BYTE_ORDER == BIG_ENDIAN))
  cd_utf16 = iconv_open("UTF-16BE", FROMCODE); // From UTF8 to UTF16
  cd_utf16_inv = iconv_open(FROMCODE, "UTF-16BE"); // From UTF16 to UTF8
#else
  cd_utf16 = iconv_open("UTF-16LE", FROMCODE); // From UTF8 to UTF16
  cd_utf16_inv = iconv_open(FROMCODE, "UTF-16LE"); // From UTF16 to UTF8
#endif
#endif /* ENABLE_FILENAMES_ICONV */

  // Analyse command-line as soon as possible.
  file_in_command_line = Analyze_command_line(argc, argv, filenames, directories, &videomode, &cmdline_pixelratio);

  // On crée dès maintenant les descripteurs des listes de pages pour la page
  // principale et la page de brouillon afin que leurs champs ne soient pas
  // invalide lors des appels aux multiples fonctions manipulées à
  // l'initialisation du programme.
  Main.backups=(T_List_of_pages *)GFX2_malloc(sizeof(T_List_of_pages));
  Spare.backups=(T_List_of_pages *)GFX2_malloc(sizeof(T_List_of_pages));
  Init_list_of_pages(Main.backups);
  Init_list_of_pages(Spare.backups);

  // Determine the executable directory
  program_directory = Get_program_directory(argv[0]);
  // Choose directory for data (read only)
  Data_directory = Get_data_directory(program_directory);
  // Choose directory for settings (read/write)
  Config_directory = Get_config_directory(program_directory);
#ifdef __macosx__
  // use user folder on macOS
  extern char* getMacosUserFolder(void);
  Main.selector.Directory = getMacosUserFolder();
#else
  // Get current directory
  Main.selector.Directory = Get_current_directory(NULL, &Main.selector.Directory_unicode, 0);
#endif
  GFX2_Log(GFX2_DEBUG, "program directory : %s\n", program_directory);
  GFX2_Log(GFX2_DEBUG, "Data directory : %s\n", Data_directory);
  GFX2_Log(GFX2_DEBUG, "Config directory : %s\n", Config_directory);
  GFX2_Log(GFX2_DEBUG, "Initial_directory : %s (unicode : %p)\n",
           Main.selector.Directory, Main.selector.Directory_unicode);
  free(program_directory);

  // On en profite pour le mémoriser dans le répertoire principal:
  Initial_directory = strdup(Main.selector.Directory);

  // On initialise les données sur le nom de fichier de l'image de brouillon:
  Spare.selector.Directory = strdup(Main.selector.Directory);
  Spare.selector.Directory_unicode = Unicode_strdup(Main.selector.Directory_unicode);

  Main.fileformat  = DEFAULT_FILEFORMAT;
  Spare.fileformat = DEFAULT_FILEFORMAT;

  Brush_selector.Directory = strdup(Main.selector.Directory);
  Brush_selector.Directory_unicode = Unicode_strdup(Main.selector.Directory_unicode);
  Brush_file_directory = strdup(Main.selector.Directory);
  Brush_filename = strdup("NO_NAME.GIF");
  Brush_filename_unicode = NULL;
  Brush_fileformat = DEFAULT_FILEFORMAT;

  Palette_selector.Directory = strdup(Main.selector.Directory);
  Palette_selector.Directory_unicode = Unicode_strdup(Main.selector.Directory_unicode);

  // On initialise ce qu'il faut pour que les fileselects ne plantent pas:

  Main.selector.Position=0; // Au début, le fileselect est en haut de la liste des fichiers
  Main.selector.Offset=0; // Au début, le fileselect est en haut de la liste des fichiers
  Main.selector.Format_filter=FORMAT_ALL_IMAGES;

  Main.current_layer=0;
  Main.layers_visible=0xFFFFFFFF;
  Main.layers_visible_backup=0xFFFFFFFF;
  Spare.current_layer=0;
  Spare.layers_visible=0xFFFFFFFF;
  Spare.layers_visible_backup=0xFFFFFFFF;

  Spare.selector.Position=0;
  Spare.selector.Offset=0;
  Spare.selector.Format_filter=FORMAT_ALL_IMAGES;
  Brush_selector.Position=0;
  Brush_selector.Offset=0;
  Brush_selector.Format_filter=FORMAT_ALL_IMAGES;

  Palette_selector.Position=0;
  Palette_selector.Offset=0;
  Palette_selector.Format_filter=FORMAT_ALL_PALETTES;

  // On initialise d'ot' trucs
  Main.offset_X=0;
  Main.offset_Y=0;
  Main.separator_position=0;
  Main.X_zoom=0;
  Main.separator_proportion=INITIAL_SEPARATOR_PROPORTION;
  Main.magnifier_mode=0;
  Main.magnifier_factor=DEFAULT_ZOOM_FACTOR;
  Main.magnifier_height=0;
  Main.magnifier_width=0;
  Main.magnifier_offset_X=0;
  Main.magnifier_offset_Y=0;
  Spare.offset_X=0;
  Spare.offset_Y=0;
  Spare.separator_position=0;
  Spare.X_zoom=0;
  Spare.separator_proportion=INITIAL_SEPARATOR_PROPORTION;
  Spare.magnifier_mode=0;
  Spare.magnifier_factor=DEFAULT_ZOOM_FACTOR;
  Spare.magnifier_height=0;
  Spare.magnifier_width=0;
  Spare.magnifier_offset_X=0;
  Spare.magnifier_offset_Y=0;
  Keyboard_click_allowed = 1;

  Main.safety_backup_prefix = SAFETYBACKUP_PREFIX_A[0];
  Spare.safety_backup_prefix = SAFETYBACKUP_PREFIX_B[0];
  Main.time_of_safety_backup = 0;
  Spare.time_of_safety_backup = 0;

#ifdef USE_SDL2
  render_scale = 1;
  envvar_val = getenv("GRAFX2_RENDER_SCALE");
  if (envvar_val != NULL) {
    render_scale = (int)strtol(envvar_val, NULL, 10);
    if (render_scale < 1)
      render_scale = 1;
  }
#endif

#if defined(USE_SDL) || defined(USE_SDL2)
  // SDL
  if (SDL_Init(SDL_INIT_VIDEO
#if defined(USE_JOYSTICK)
               | SDL_INIT_JOYSTICK
#endif
              ) < 0)
  {
    // The program can't continue without that anyway
    printf("Couldn't initialize SDL.\n");
    return(0);
  }

#if defined(USE_SDL2)
  SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_VERBOSE);
#endif
#endif
#if defined(USE_SDL)
  SDL_EnableKeyRepeat(250, 32);
  SDL_EnableUNICODE(SDL_ENABLE);
  SDL_WM_SetCaption("GrafX2","GrafX2");
#endif
  Define_icon();

  // Texte
  Init_text();

  // Initialize all video modes
  Set_all_video_modes();

#if defined(USE_JOYSTICK) && (defined(USE_SDL) || defined(USE_SDL2))
  GFX2_Log(GFX2_DEBUG, "%d joystick(s) attached\n", SDL_NumJoysticks());
  if (SDL_NumJoysticks() > 0)
  {
    Joystick = SDL_JoystickOpen(0);
    if (Joystick == NULL)
    {
      GFX2_Log(GFX2_ERROR, "Failed to open joystick #0 : %s\n", SDL_GetError());
    }
    else
    {
      GFX2_Log(GFX2_DEBUG, "Joystick #0 open : \"%s\" %d axes, %d buttons, %d balls, %d hats\n",
#if defined(USE_SDL2)
               SDL_JoystickName(Joystick),
#else
               SDL_JoystickName(0),
#endif
               SDL_JoystickNumAxes(Joystick),
               SDL_JoystickNumButtons(Joystick), SDL_JoystickNumBalls(Joystick),
               SDL_JoystickNumHats(Joystick));
      SDL_JoystickEventState(SDL_ENABLE);
    }
  }
#endif

  Pixel_ratio=PIXEL_SIMPLE;
  // On initialise les données sur l'état du programme:
  // Donnée sur la sortie du programme:
  Quit_is_required=0;
  Quitting=0;
  // Données sur l'état du menu:
  Menu_is_visible=1;
  // Données sur les couleurs et la palette:
  First_color_in_palette=0;
  // Données sur le curseur:
  Cursor_shape=CURSOR_SHAPE_TARGET;
  Cursor_hidden=0;
  // Données sur le pinceau:
  Paintbrush_X=0;
  Paintbrush_Y=0;
  Paintbrush_hidden=0;

  // On initialise tout ce qui concerne les opérations et les effets
  Operation_stack_size=0;
  Selected_freehand_mode=OPERATION_CONTINUOUS_DRAW;
  Selected_line_mode         =OPERATION_LINE;
  Selected_curve_mode        =OPERATION_3_POINTS_CURVE;
  Effect_function=No_effect;
    // On initialise les infos de la loupe:
  Main.magnifier_mode=0;
  Main.magnifier_factor=DEFAULT_ZOOM_FACTOR;
  Main.separator_proportion=INITIAL_SEPARATOR_PROPORTION;
  Spare.separator_proportion=INITIAL_SEPARATOR_PROPORTION;
    // On initialise les infos du mode smear:
  Smear_mode=0;
  Smear_brush_width=PAINTBRUSH_WIDTH;
  Smear_brush_height=PAINTBRUSH_HEIGHT;
    // On initialise les infos du mode smooth:
  Smooth_mode=0;
    // On initialise les infos du mode shade:
  Shade_mode=0;     // Les autres infos du Shade sont chargées avec la config
  Quick_shade_mode=0; // idem
    // On initialise les infos sur les dégradés:
  Gradient_pixel =Display_pixel; // Les autres infos sont chargées avec la config
    // On initialise les infos de la grille:
  Snap_mode=0;
  Snap_width=8;
  Snap_height=8;
  Snap_offset_X=0;
  Snap_offset_Y=0;
    // On initialise les infos du mode Colorize:
  Colorize_mode=0;          // Mode colorize inactif par défaut
  Colorize_opacity=50;      // Une interpolation de 50% par défaut
  Colorize_current_mode=0; // Par défaut, la méthode par interpolation
  Compute_colorize_table();
    // On initialise les infos du mode Tiling:
  Tiling_mode=0;  //   Pas besoin d'initialiser les décalages car ça se fait
                  // en prenant une brosse (toujours mis à 0).
    // On initialise les infos du mode Mask:
  Mask_mode=0;

    // Infos du Spray
  Airbrush_mode=1; // Mode Mono
  Airbrush_size=31;
  Airbrush_delay=1;
  Airbrush_mono_flow=10;
  memset(Airbrush_multi_flow,0,256);
  srand(time(NULL)); // On randomize un peu tout ça...

  // Initialisation des boutons
  Init_buttons();
  // Initialisation des opérations
  Init_operations();

  // Initialize the brush container
  Init_brush_container();

  Windows_open=0;

  // Paintbrush
  Paintbrush_sprite = (byte *)GFX2_malloc(MAX_PAINTBRUSH_SIZE*MAX_PAINTBRUSH_SIZE);
  if (!Paintbrush_sprite)
    Error(ERROR_MEMORY);

  // Load preset paintbrushes (uses Paintbrush_ variables)
  Init_paintbrushes();

  // Set a valid paintbrush afterwards
  *Paintbrush_sprite=1;
  Paintbrush_width=1;
  Paintbrush_height=1;
  Paintbrush_offset_X=0;
  Paintbrush_offset_Y=0;
  Paintbrush_shape=PAINTBRUSH_SHAPE_ROUND;

  #if defined(__GP2X__) || defined(__WIZ__) || defined(__CAANOO__)
  // Prefer cycling active by default
  Cycling_mode=1;
  #endif

  // Charger la configuration des touches
  Set_config_defaults();

  switch(Load_CFG(1))
  {
    case ERROR_CFG_MISSING:
      // Pas un problème, on a les valeurs par défaut.
      break;
    case ERROR_CFG_CORRUPTED:
      GFX2_Log(GFX2_ERROR, "Corrupted CFG file.\n");
      break;
    case ERROR_CFG_OLD:
      GFX2_Log(GFX2_WARNING, "Unknown CFG file version, not loaded.\n");
      break;
  }
  // Charger la configuration du .INI
  temp=Load_INI(&Config);
  if (temp)
    Error(temp);

  if(!Config.Allow_multi_shortcuts)
  {
    Remove_duplicate_shortcuts();
  }

  Compute_menu_offsets();

  Current_help_section=0;
  Help_position=0;

  // Load sprites, palette etc.
  gfx = Load_graphics(Config.Skin_file, &initial_gradients);
  if (gfx == NULL)
  {
    gfx = Load_graphics(DEFAULT_SKIN_FILENAME, &initial_gradients);
    if (gfx == NULL)
    {
      Error(ERROR_GUI_MISSING);
    }
  }
  Set_current_skin(Config.Skin_file, gfx);
  // Override colors
  Gfx->Default_palette[MC_Black]=Config.Fav_menu_colors[0];
  Gfx->Default_palette[MC_Dark] =Config.Fav_menu_colors[1];
  Gfx->Default_palette[MC_Light]=Config.Fav_menu_colors[2];
  Gfx->Default_palette[MC_White]=Config.Fav_menu_colors[3];

  // Even when using the skin's palette, if RGB range is small
  // the colors will be unusable.
  Compute_optimal_menu_colors(Gfx->Default_palette);

  // Infos sur les trames (Sieve)
  Sieve_mode=0;
  Copy_preset_sieve(0);

  // Font
  if (!(Menu_font=Load_font(Config.Font_file, 1)))
    if (!(Menu_font=Load_font(DEFAULT_FONT_FILENAME, 1)))
      {
        snprintf(Gui_loading_error_message, sizeof(Gui_loading_error_message),
                 "Unable to open the default font file: %s\n", DEFAULT_FONT_FILENAME);
        Error(ERROR_GUI_MISSING);
      }
  Load_Unicode_fonts();

  memcpy(Main.palette, Gfx->Default_palette, sizeof(T_Palette));

  Fore_color=Best_color_range(255,255,255,Config.Palette_cells_X*Config.Palette_cells_Y);
  Back_color=Best_color_range(0,0,0,Config.Palette_cells_X*Config.Palette_cells_Y);

  // Allocation de mémoire pour la brosse
  Brush = (byte *)GFX2_malloc(1*1);
  if (!Brush)
    Error(ERROR_MEMORY);
  Smear_brush = (byte *)GFX2_malloc(MAX_PAINTBRUSH_SIZE*MAX_PAINTBRUSH_SIZE);
  if (!Smear_brush)
    Error(ERROR_MEMORY);

  // set videomode according to the command line
  if (videomode)
  {
    Resolution_in_command_line = 1;
    Current_resolution = Convert_videomode_arg(videomode);
    if (Current_resolution == -1)
    {
      Error(ERROR_COMMAND_LINE);
      exit(0);
    }
    if ((Video_mode[Current_resolution].State & 0x7F) == 3)
    {
      Error(ERROR_FORBIDDEN_MODE);
      exit(0);
    }
  }

  starting_videomode=Current_resolution;
  Horizontal_line_buffer=NULL;
  Screen_width=Screen_height=Current_resolution=0;
  if (cmdline_pixelratio >= 0 && cmdline_pixelratio < (int)PIXEL_MAX)
    Pixel_ratio = cmdline_pixelratio;

  Init_mode_video(
    Video_mode[starting_videomode].Width,
    Video_mode[starting_videomode].Height,
    Video_mode[starting_videomode].Fullscreen,
    Pixel_ratio);

  // Windows only: move back the window to its original position.
  #if defined(WIN32)
  if (!Video_mode[starting_videomode].Fullscreen)
  {
    if (Config.Window_pos_x != 9999 && Config.Window_pos_y != 9999)
    {
      //RECT r;
      #if defined(USE_SDL) || defined(USE_SDL2)
      //GetWindowRect(window, &r);
      SetWindowPos(GFX2_Get_Window_Handle(), 0, Config.Window_pos_x, Config.Window_pos_y, 0, 0, SWP_NOSIZE);
      #endif
    }
  }

  if ((unsigned)GFX2_verbosity_level >= (unsigned)GFX2_DEBUG)
  {
    // Open a console for debugging...
    if (AllocConsole())
    {
      HANDLE handle_out = GetStdHandle(STD_OUTPUT_HANDLE);
      int hCrt = _open_osfhandle((long) handle_out, _O_TEXT);
      FILE* hf_out = _fdopen(hCrt, "w");
      setvbuf(hf_out, NULL, _IONBF, 2);
      *stdout = *hf_out;
      *stderr = *hf_out;
    }
  }
  #endif

  // If image size was specified on command line, set that now
  if (setsize_width != 0 && setsize_height != 0)
  {
    Main.image_width=setsize_width;
    Main.image_height=setsize_height;
    Spare.image_width=setsize_width;
    Spare.image_height=setsize_height;
  }
  else
  {
    Main.image_width=Screen_width/Pixel_width;
    Main.image_height=Screen_height/Pixel_height;
    Spare.image_width=Screen_width/Pixel_width;
    Spare.image_height=Screen_height/Pixel_height;
  }

  // set the Mouse cursor at the center of the screen
  Mouse_X = Screen_width / 2;
  Mouse_Y = Screen_height / 2;

  starting_image_mode = Config.Default_mode_layers ?
    IMAGE_MODE_LAYERED : IMAGE_MODE_ANIMATION;
  // Allocation de mémoire pour les différents écrans virtuels (et brosse)
  if (Init_all_backup_lists(starting_image_mode, Main.image_width, Main.image_height)==0)
    Error(ERROR_MEMORY);
  // Update toolbars' visibility, now that the current image has a mode
  Check_menu_mode();

  // Nettoyage de l'écran virtuel (les autres recevront celui-ci par copie)
  memset(Main_screen,0,Main.image_width*Main.image_height);

  // Now that the backup system is there, we can store the gradients.
  memcpy(Main.backups->Pages->Gradients->Range, initial_gradients.Range, sizeof(initial_gradients.Range));
  memcpy(Spare.backups->Pages->Gradients->Range, initial_gradients.Range, sizeof(initial_gradients.Range));

  Gradient_function=Gradient_basic;
  Gradient_lower_bound=0;
  Gradient_upper_bound=0;
  Gradient_random_factor=1;
  Gradient_bounds_range=1;

  Current_gradient=0;

  // Initialisation de diverses variables par calcul:
  Compute_magnifier_data();
  Compute_limits();
  Compute_paintbrush_coordinates();

  // On affiche le menu:
  Display_paintbrush_in_menu();
  Display_sprite_in_menu(BUTTON_PAL_LEFT,Config.Palette_vertical?MENU_SPRITE_VERTICAL_PALETTE_SCROLL:-1);
  Display_menu();
  Draw_menu_button(BUTTON_PAL_LEFT,BUTTON_RELEASED);
  Draw_menu_button(BUTTON_PAL_RIGHT,BUTTON_RELEASED);

  // On affiche le curseur pour débuter correctement l'état du programme:
  Display_cursor();

  if (Config.Display_image_limits)
    Display_image_limits();

  Spare.image_is_modified=0;
  Main.image_is_modified=0;

  // Gestionnaire de signaux, quand il ne reste plus aucun espoir
  Init_sighandler();

  // Le programme débute en mode de dessin à la main
  Select_button(BUTTON_DRAW,LEFT_SIDE);

  // On initialise la brosse initiale à 1 pixel blanc:
  Brush_width=1;
  Brush_height=1;
  for (temp=0;temp<256;temp++)
    Brush_colormap[temp]=temp;
  Capture_brush(0,0,0,0,0);
  *Brush=MC_White;
  *Brush_original_pixels=MC_White;

  // Make sure the load dialog points to the right place when first shown.
  // Done after loading everything else, but before checking for emergency
  // backups
  if (file_in_command_line > 0 && directories[0] != NULL)
  {
    free(Main.selector.Directory);
    Main.selector.Directory = strdup(directories[0]);
  }

  // Test de recuperation de fichiers sauvés
  switch (Check_recovery())
  {
    T_IO_Context context;

    default:
      // Some files were loaded from last crash-exit.
      // Do not load files from command-line, nor show splash screen.
      Compute_optimal_menu_colors(Main.palette);
      Check_menu_mode();
      Display_all_screen();
      Display_menu();
      Display_cursor();
      Verbose_message("Images recovered",
        "Grafx2 has recovered images from\n"
        "last session, before a crash or\n"
        "shutdown. Browse the history using\n"
        "the Undo/Redo button, and when\n"
        "you find a state that you want to\n"
        "save, use the 'Save as' button to\n"
        "save the image.\n"
        "Some backups can be present in\n"
        "the spare page too.\n");
      break;

    case -1: // Unable to write lock file
      Verbose_message("Warning",
        "Safety backups (every minute) are\n"
        "disabled because Grafx2 is running\n"
        "from a read-only device, or other\n"
        "instances are running.");
      break;

    case 0:

      switch (file_in_command_line)
      {
        case 0:
          if (Config.Opening_message)
            Button_Message_initial();
          // Load default palette
          {
            FILE * f;
            Init_context_layered_image(&context, DEFAULTPAL_FILENAME, Config_directory);
            context.Type = CONTEXT_PALETTE;
            context.Format = FORMAT_PAL;
            f = Open_file_read(&context);
            if (f != NULL)  // silently fail if the file cannot be open
            {
              fclose(f);
              Load_image(&context);
              if (File_error == 0)
              {
                Hide_cursor();
                Compute_optimal_menu_colors(Main.palette);
                Display_menu();
                Display_cursor();
                memcpy(Spare.palette, Main.palette, sizeof(T_Palette));
              }
            }
            Destroy_context(&context);
          }
          break;

        case 2:
          // Load this file
          Init_context_layered_image(&context, filenames[1], directories[1]);
          context.File_name_unicode = Get_Unicode_Filename(NULL, filenames[1], directories[1]);
          Load_image(&context);
          Destroy_context(&context);
          Redraw_layered_image();
          End_of_modification();

          Button_Page(BUTTON_PAGE);
          // no break ! proceed with the other file now
#if defined(__GNUC__) && (__GNUC__ >= 7)
          __attribute__ ((fallthrough));
#endif
        case 1:
          Init_context_layered_image(&context, filenames[0], directories[0]);
          context.File_name_unicode = Get_Unicode_Filename(NULL, filenames[0], directories[0]);
          Load_image(&context);
          Destroy_context(&context);
          Redraw_layered_image();
          End_of_modification();

          // If only one image was loaded, assume the spare has same image type
          if (file_in_command_line==1)
          {
            if (Main.backups->Pages->Image_mode <= IMAGE_MODE_ANIMATION)
              Spare.backups->Pages->Image_mode = Main.backups->Pages->Image_mode;
          }

          Hide_cursor();
          Compute_optimal_menu_colors(Main.palette);
          Back_color=Main.backups->Pages->Background_transparent ?
            Main.backups->Pages->Transparent_color :
            Best_color_range(0,0,0,Config.Palette_cells_X*Config.Palette_cells_Y);
          Fore_color=Main.palette[Back_color].R+Main.palette[Back_color].G+Main.palette[Back_color].B < 3*127 ?
            Best_color_range(255,255,255,Config.Palette_cells_X*Config.Palette_cells_Y) :
            Best_color_range(0,0,0,Config.Palette_cells_X*Config.Palette_cells_Y);
          Check_menu_mode();
          Display_all_screen();
          Display_menu();
          Display_cursor();
          Resolution_in_command_line = 0;
          break;

        default:
          break;
      }
  }

  Allow_drag_and_drop(1);

  while (file_in_command_line-- > 0)
  {
    free(directories[file_in_command_line]);
    free(filenames[file_in_command_line]);
  }
  return(1);
}

/// Free the memory and make sure the pointer is set to NULL
#define FREE_POINTER(p) free(p); p = NULL

/**
 * Program Shutdown.
 *
 * Free all allocated resources.
 */
void Program_shutdown(void)
{
  int      i;
  int      return_code;

  // Windows only: Recover the window position.
  #if defined(WIN32)
  {
    WINDOWPLACEMENT windowplacement;
    windowplacement.length = sizeof(WINDOWPLACEMENT);
    if (GetWindowPlacement(GFX2_Get_Window_Handle(), &windowplacement))
    {
      Config.Window_pos_x = windowplacement.rcNormalPosition.left;
      Config.Window_pos_y = windowplacement.rcNormalPosition.top;
    }
  }
  #elif !defined(USE_X11) && !defined(USE_SDL2)
  // All other targets: irrelevant dimensions.
  // Do not attempt to force them back on next program run.
    Config.Window_pos_x = 9999;
    Config.Window_pos_y = 9999;
  #endif

  // Remove the safety backups, this is normal exit
  Delete_safety_backups();

  // On libère le buffer de gestion de lignes
  free(Horizontal_line_buffer);
  Horizontal_line_buffer = NULL;

  // On libère le pinceau spécial
  FREE_POINTER(Paintbrush_sprite);

  // Free Brushes
  FREE_POINTER(Brush);
  FREE_POINTER(Smear_brush);
  FREE_POINTER(Brush_original_pixels);

  FREE_POINTER(Brush_file_directory);
  FREE_POINTER(Brush_filename);
  FREE_POINTER(Brush_filename_unicode);

  // Free all images
  Set_number_of_backups(-1); // even delete the main page

  FREE_POINTER(Main.visible_image.Image);
  FREE_POINTER(Spare.visible_image.Image);
  FREE_POINTER(Main_visible_image_backup.Image);
  FREE_POINTER(Main_visible_image_depth_buffer.Image);

  FREE_POINTER(Main.backups);
  FREE_POINTER(Spare.backups);

  // Free the skin (Gui graphics) data
  free(Gfx);
  Gfx=NULL;

  free(Menu_font);
  Menu_font = NULL;
  while (Unicode_fonts != NULL)
  {
    T_Unicode_Font * ufont = Unicode_fonts->Next;
    free(Unicode_fonts->FontData);
    free(Unicode_fonts);
    Unicode_fonts = ufont;
  }

  // On prend bien soin de passer dans le répertoire initial:
  if (Change_directory(Initial_directory)==0)
  {
    // On sauvegarde les données dans le .CFG et dans le .INI
    if (Config.Auto_save)
    {
      return_code=Save_CFG();
      if (return_code)
        Error(return_code);
      return_code=Save_INI(&Config);

      if (return_code)
        Error(return_code);
    }
  }
  else
    Error(ERROR_MISSING_DIRECTORY);

  FREE_POINTER(Initial_directory);
  FREE_POINTER(Config_directory);
  FREE_POINTER(Data_directory);

  FREE_POINTER(Main.selector.Directory);
  FREE_POINTER(Main.selector.Directory_unicode);
  FREE_POINTER(Main.selector.filename);
  FREE_POINTER(Main.selector.filename_unicode);
  FREE_POINTER(Spare.selector.Directory);
  FREE_POINTER(Spare.selector.Directory_unicode);
  FREE_POINTER(Spare.selector.filename);
  FREE_POINTER(Spare.selector.filename_unicode);
  FREE_POINTER(Brush_selector.Directory);
  FREE_POINTER(Brush_selector.Directory_unicode);
  FREE_POINTER(Brush_selector.filename);
  FREE_POINTER(Brush_selector.filename_unicode);
  FREE_POINTER(Palette_selector.Directory);
  FREE_POINTER(Palette_selector.Directory_unicode);
  FREE_POINTER(Palette_selector.filename);
  FREE_POINTER(Palette_selector.filename_unicode);

  // Free Config
  FREE_POINTER(Config.Skin_file);
  FREE_POINTER(Config.Font_file);
  for (i=0;i<NB_BOOKMARKS;i++)
  {
    FREE_POINTER(Config.Bookmark_directory[i]);
  }
  FREE_POINTER(Config.Scripts_directory);

  for (i = 0; i < 10; i++)
  {
    FREE_POINTER(Bound_script[i]);
  }

  Uninit_text();

#ifdef ENABLE_FILENAMES_ICONV
  if (cd != (iconv_t)-1)
    iconv_close(cd);
  if (cd_inv != (iconv_t)-1)
    iconv_close(cd_inv);
  if (cd_utf16 != (iconv_t)-1)
    iconv_close(cd_utf16);
  if (cd_utf16_inv != (iconv_t)-1)
    iconv_close(cd_utf16_inv);
#endif

#if defined(USE_SDL) || defined(USE_SDL2)
  SDL_Quit();
#endif

  #if defined(__GP2X__) || defined(__WIZ__) || defined(__CAANOO__)
  chdir("/usr/gp2x");
  execl("/usr/gp2x/gp2xmenu", "/usr/gp2x/gp2xmenu", NULL);
  #endif

}


/**
 * Program entry point
 */
#if defined(WIN32) && !defined(USE_SDL) && !defined(USE_SDL2)
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
#else
int main(int argc,char * argv[])
#endif
{
#if defined(WIN32) && !defined(USE_SDL) && !defined(USE_SDL2)
  WCHAR * ModuleFileName = NULL;
  DWORD ModuleFileNameSize = 64;
  DWORD Ret;
  WCHAR * TmpArg = NULL;
  DWORD TmpArgSize = 0;
  WCHAR * ShortFileName;
  DWORD ShortFileNameSize;
  DWORD i, j, k;
  int inquote = 0;
  int argc = 0;
  char arg_buffer[4096];
  char * argv[64] = {NULL};

  Init_Win32(hInstance, hPrevInstance);

  // GetModuleFileNameW() returns the size argument when the
  // path is truncated.
  do
  {
    ModuleFileNameSize *= 2;
    free(ModuleFileName);
    ModuleFileName = (WCHAR *)malloc(sizeof(WCHAR) * ModuleFileNameSize);
    if (ModuleFileName == NULL)
    {
      MessageBoxA(NULL, "Error initializing program (alloc)", NULL, MB_OK | MB_ICONERROR);
      return 1;
    }
    Ret = GetModuleFileNameW(NULL, ModuleFileName, ModuleFileNameSize);
    if (Ret == 0)
    {
      MessageBoxA(NULL, "Error initializing program\nGetModuleFileName()", NULL, MB_OK | MB_ICONERROR);
      return 1;
    }
  }
  while (Ret >= ModuleFileNameSize);

  ShortFileNameSize = GetShortPathNameW(ModuleFileName, NULL, 0);
  if (ShortFileNameSize == 0)
  {
    MessageBoxA(NULL, "Error initializing program\nGetShortPathName()", NULL, MB_OK | MB_ICONERROR);
    return 1;
  }
  ShortFileName = (WCHAR *)malloc(sizeof(WCHAR) * ShortFileNameSize);
  if (ShortFileName == NULL)
  {
    MessageBoxA(NULL, "Error initializing program (alloc)", NULL, MB_OK | MB_ICONERROR);
    return 1;
  }
  if (GetShortPathNameW(ModuleFileName, ShortFileName, ShortFileNameSize) == 0)
  {
    MessageBoxA(NULL, "Error initializing program\nGetShortPathName()", NULL, MB_OK | MB_ICONERROR);
    return 1;
  }
  free(ModuleFileName);

  // set argv[0]
  argv[argc++] = arg_buffer;
  for (i = 0; i < (int)sizeof(arg_buffer); )
  {
    arg_buffer[i] = (char)ShortFileName[i];
    if (arg_buffer[i++] == 0) break;
  }
  free(ShortFileName);

  // set remaining argv[]
  k = 0;
  for (j = 0; pCmdLine[j] != 0; j++)
  {
    if (k >= TmpArgSize)
    {
      TmpArgSize = (TmpArgSize == 0) ? 16 : 2 * TmpArgSize;
      TmpArg = (WCHAR *)realloc(TmpArg, sizeof(WCHAR) * TmpArgSize);
      if (TmpArg == NULL)
      {
        MessageBoxA(NULL, "Error initializing program (realloc)", NULL, MB_OK | MB_ICONERROR);
        return 1;
      }
    }
    if (inquote)
    {
      if (pCmdLine[j] == '"')
      {
        inquote = 0;
        continue;
      }
    }
    else
    {
      if (pCmdLine[j] == '"')
      {
        inquote = 1;
        continue;
      }
      if (pCmdLine[j] == ' ' || pCmdLine[j] == '\t')
      { // next argument
        TmpArg[k++] = '\0';
        argv[argc++] = arg_buffer + i;
        ShortFileNameSize = GetShortPathNameW(TmpArg, NULL, 0);
        if (ShortFileNameSize > 0)
        {
          ShortFileName = (WCHAR *)malloc(sizeof(WCHAR) * ShortFileNameSize);
          Ret = GetShortPathNameW(TmpArg, ShortFileName, ShortFileNameSize);
          for (k = 0; ShortFileName[k] != 0; k++)
            arg_buffer[i++] = ShortFileName[k];
          free(ShortFileName);
        }
        else
        {
          for (k = 0; TmpArg[k] != 0; k++)
            arg_buffer[i++] = TmpArg[k];
        }
        arg_buffer[i++] = 0;
        k = 0;
        continue;
      }
    }
    TmpArg[k++] = pCmdLine[j];
  }
  // process the last argument
  if (k > 0)
  {
    TmpArg[k] = '\0';
    argv[argc++] = arg_buffer + i;
    ShortFileNameSize = GetShortPathNameW(TmpArg, NULL, 0);
    if (ShortFileNameSize > 0)
    {
      ShortFileName = (WCHAR *)malloc(sizeof(WCHAR) * ShortFileNameSize);
      Ret = GetShortPathNameW(TmpArg, ShortFileName, ShortFileNameSize);
      for (k = 0; ShortFileName[k] != 0; k++)
        arg_buffer[i++] = ShortFileName[k];
      free(ShortFileName);
    }
    else
    {
      for (k = 0; TmpArg[k] != 0; k++)
        arg_buffer[i++] = TmpArg[k];
    }
    arg_buffer[i++] = 0;
  }

  free(TmpArg);
  // TODO : nCmdShow indicates if the window must be maximized, etc.
  (void)nCmdShow;
#endif
  if(!Init_program(argc,argv))
  {
    Program_shutdown();
    return 0;
  }

#ifdef _MSC_VER
  GFX2_Log(GFX2_DEBUG, "built with _MSC_VER=%d   Windows ANSI Code Page=%u OEM Code Page=%u\n", _MSC_VER, GetACP(), GetOEMCP());
  GFX2_Log(GFX2_DEBUG, "AreFileApisANSI() : %s\n", AreFileApisANSI() ? "ANSI" : "OEM");
#endif
  Main_handler();

  Program_shutdown();
  return 0;
}

#if defined(WIN32) && !defined(USE_SDL) && !defined(USE_SDL2) && !defined(_MSC_VER)
/**
 * MS Window entry point.
 *
 * This function is used when building with MinGW
 */
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR _lpCmdLine, int nCmdShow)
{
  WCHAR *lpCmdLine = GetCommandLineW();
  (void)_lpCmdLine;

  if (__argc == 1)
  { // avoids GetCommandLineW bug that does not always quote the program name if no arguments
    do { ++lpCmdLine; } while (*lpCmdLine);
  }
  else
  {
    BOOL quoted = lpCmdLine[0] == L'"';
    ++lpCmdLine; // skips the " or the first letter (all paths are at least 1 letter)
    while (*lpCmdLine)
    {
      if (quoted && lpCmdLine[0] == L'"') quoted = FALSE; // found end quote
      else if (!quoted && lpCmdLine[0] == L' ')
      { // found an unquoted space, now skip all spaces
        do { ++lpCmdLine; } while (lpCmdLine[0] == L' ');
        break;
      }
      ++lpCmdLine;
    }
  }
  return wWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
#endif
