/****************************************************************************
 * tools/configure.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <sys/stat.h>

#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <dirent.h>
#include <libgen.h>
#include <errno.h>

#include "cfgparser.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define BUFFER_SIZE 1024

#ifdef WIN32
#  define strndup(x, y) strdup(x)
#endif

#define HOST_NOCHANGE  0
#define HOST_LINUX     1
#define HOST_MACOS     2
#define HOST_WINDOWS   3
#define HOST_BSD       4

#define WINDOWS_NATIVE 1
#define WINDOWS_CYGWIN 2
#define WINDOWS_MSYS   3

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

static void show_usage(const char *progname, int exitcode);
static void dumpcfgs(void);
static void debug(const char *fmt, ...);
static void parse_args(int argc, char **argv);
static int run_make(const char *arg);
static bool filecmp(const char *f1, const char *f2);
static bool check_directory(const char *directory);
static void verify_directory(const char *directory);
static bool verify_optiondir(const char *directory);
static bool verify_file(const char *path);
static void find_topdir(void);
typedef void (*config_callback)(const char *boarddir, const char *archname,
                                const char *chipname, const char *boardname,
                                const char *configname, void *data);
static void config_search(const char *boarddir,
                          config_callback callback, void *data);
static void archname_callback(const char *boarddir, const char *archname,
                              const char *chipname, const char *boardname,
                              const char *configname, void *data);
static void find_archname(void);
static void enumerate_callback(const char *boarddir, const char *archname,
                               const char *chipname, const char *boardname,
                               const char *configname, void *data);
static void enumerate_configs(void);
static void check_configdir(void);
static void check_configured(void);
static void read_configfile(void);
static void read_versionfile(void);
static void get_verstring(void);
static bool verify_appdir(const char *appdir);
static void check_appdir(void);
static void check_configuration(void);
static void copy_file(const char *srcpath,
                      const char *destpath, mode_t mode);
static void substitute(char *str, int ch1, int ch2);
static char *double_appdir_backslashes(char *old_appdir);
static void copy_optional(void);
static void enable_feature(const char *destconfig, const char *varname);
static void disable_feature(const char *destconfig, const char *varname);
static void set_host(const char *destconfig);
static void configure(void);
static void refresh(void);
static void save_original_config(void);

/****************************************************************************
 * Private Data
 ****************************************************************************/

#ifdef CONFIG_WINDOWS_NATIVE
static char        g_delim         = '\\';  /* Delimiter to use when forming paths */
static bool        g_winpaths      = true;  /* True: Windows style paths */
#else
static char        g_delim         = '/';   /* Delimiter to use when forming paths */
static bool        g_winpaths      = false; /* False: POSIX style paths */
#endif
static bool        g_debug         = false; /* Enable debug output */
static bool        g_enforce       = false; /* Enfore distclean */
static bool        g_distclean     = false; /* Distclean if configured */

static const char *g_appdir        = NULL;  /* Relative path to the application directory */
static const char *g_archdir       = NULL;  /* Name of architecture subdirectory */
static const char *g_chipdir       = NULL;  /* Name of chip subdirectory */
static const char *g_boarddir      = NULL;  /* Name of board subdirectory */
static char       *g_configdir     = NULL;  /* Name of configuration subdirectory */

static char       *g_topdir        = NULL;  /* Full path to top-level NuttX build directory */
static char       *g_apppath       = NULL;  /* Full path to the application directory */
static char       *g_configtop     = NULL;  /* Full path to the top-level configuration directory */
static char       *g_configpath    = NULL;  /* Full path to the configuration sub-directory */
static char       *g_scriptspath   = NULL;  /* Full path to the scripts sub-directory */
static char       *g_verstring     = "0.0"; /* Version String */

static char       *g_srcdefconfig  = NULL;  /* Source defconfig file */
static char       *g_srcmakedefs   = NULL;  /* Source Make.defs file */

static char      **g_makeargv      = NULL;  /* Arguments pass to make */

static bool        g_winnative     = false; /* True: Windows native configuration */
static bool        g_oldnative     = false; /* True: Was Windows native configuration */
static bool        g_needapppath   = true;  /* Need to add app path to the .config file */

static uint8_t     g_host          = HOST_NOCHANGE;
static uint8_t     g_windows       = WINDOWS_CYGWIN;

static char        g_buffer[BUFFER_SIZE];   /* Scratch buffer for forming full paths */

static struct variable_s *g_configvars = NULL;
static struct variable_s *g_versionvars = NULL;

/* Optional configuration files */

static const char *g_optfiles[] =
{
  ".gdbinit",
  ".cproject",
  ".project"
};

#define N_OPTFILES (sizeof(g_optfiles) / sizeof(const char *))

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static void show_usage(const char *progname, int exitcode)
{
  fprintf(stderr, "\nUSAGE: %s  [-d] [-E] [-e] [-b|f] [-L] [-l|m|c|g|n|B] "
          "[-a <app-dir>] <board-name>:<config-name> [make-opts]\n",
          progname);
  fprintf(stderr, "\nUSAGE: %s  [-h]\n", progname);
  fprintf(stderr, "\nWhere:\n");
  fprintf(stderr, "  -d:\n");
  fprintf(stderr, "    Enables debug output\n");
  fprintf(stderr, "  -E:\n");
  fprintf(stderr, "    Enforce distclean if already configured\n");
  fprintf(stderr, "  -e:\n");
  fprintf(stderr, "    Performs distclean if configuration changed\n");
  fprintf(stderr, "  -b:\n");
#ifdef CONFIG_WINDOWS_NATIVE
  fprintf(stderr, "    Informs the tool that it should use Windows style\n");
  fprintf(stderr, "    paths like C:\\Program Files instead of POSIX\n");
  fprintf(stderr, "    style paths are used like /usr/local/bin. Windows\n");
  fprintf(stderr, "    style paths are used by default.\n");
#else
  fprintf(stderr, "    Informs the tool that it should use Windows style\n");
  fprintf(stderr, "    paths like C:\\Program Files. By default, POSIX\n");
  fprintf(stderr, "    style paths like /usr/local/bin are used.\n");
#endif
  fprintf(stderr, "  -f:\n");
#ifdef CONFIG_WINDOWS_NATIVE
  fprintf(stderr, "    Informs the tool that it should use POSIX style\n");
  fprintf(stderr, "    paths like /usr/local/bin. By default, Windows\n");
  fprintf(stderr, "    style paths like C:\\Program Files are used.\n");
#else
  fprintf(stderr, "    Informs the tool that it should use POSIX style\n");
  fprintf(stderr, "    paths like /usr/local/bin instead of Windows\n");
  fprintf(stderr, "    style paths like C:\\Program Files are used.\n");
  fprintf(stderr, "    POSIX style paths are used by default.\n");
#endif
  fprintf(stderr, "  [-l|m|c|g|n]\n");
  fprintf(stderr, "    Selects the host environment.\n");
  fprintf(stderr, "    -l Selects the Linux (l) host environment.\n");
  fprintf(stderr, "    -m Selects the macOS (m) host environment.\n");
  fprintf(stderr, "    -B Selects the *BSD (B) host environment.\n");
  fprintf(stderr, "    -c Selects the Windows Cygwin (c) environment.\n");
  fprintf(stderr, "    -g Selects the Windows MinGW/MSYS environment.\n");
  fprintf(stderr, "    -n Selects the Windows native (n) environment.\n");
  fprintf(stderr, "  Default: Use host setup in the defconfig file.\n");
  fprintf(stderr, "  Default Windows: Cygwin.\n");
  fprintf(stderr, "  -L:\n");
  fprintf(stderr, "    Lists all available configurations.\n");
  fprintf(stderr, "  -a <app-dir>:\n");
  fprintf(stderr, "    Informs the configuration tool where the\n");
  fprintf(stderr, "    application build directory.  This is a relative\n");
  fprintf(stderr, "    path from the top-level NuttX build directory.\n");
  fprintf(stderr, "    But default, this tool will look in the usual\n");
  fprintf(stderr, "    places to locate the application directory:\n");
  fprintf(stderr, "    ..%capps or\n", g_delim);
  fprintf(stderr, "    ..%capps-xx.yy where xx.yy is the version number.\n",
          g_delim);
  fprintf(stderr, "  <board-name>:\n");
  fprintf(stderr, "    Identifies the board.  This must correspond to a\n");
  fprintf(stderr, "    board directory under nuttx%cboards%c.\n",
          g_delim, g_delim);
  fprintf(stderr, "  <config-name>:\n");
  fprintf(stderr, "    Identifies the specific configuration for the\n");
  fprintf(stderr, "    selected <board-name>. This must correspond to\n");
  fprintf(stderr, "    a sub-directory under the board directory at\n");
  fprintf(stderr, "    under nuttx%cboards%c<board-name>%cconfigs%c.\n",
          g_delim, g_delim, g_delim, g_delim);
  fprintf(stderr, "  [make-opts]:\n");
  fprintf(stderr, "    Options directly pass to make\n");
  fprintf(stderr, "  -h:\n");
  fprintf(stderr, "    Prints this message and exits.\n");
  exit(exitcode);
}

static void dumpcfgs(void)
{
  find_topdir();
  snprintf(g_buffer, BUFFER_SIZE, "%s%cboards", g_topdir, g_delim);
  verify_directory(g_buffer);
  g_configtop = strdup(g_buffer);
  enumerate_configs();
  free(g_configtop);
  exit(EXIT_SUCCESS);
}

static void debug(const char *fmt, ...)
{
  va_list ap;

  if (g_debug)
    {
      va_start(ap, fmt);
      vprintf(fmt, ap);
      va_end(ap);
    }
}

static void parse_args(int argc, char **argv)
{
  char *ptr;
  int ch;

  /* Parse command line options */

  while ((ch = getopt(argc, argv, "a:bcdEefghLlmBnu")) > 0)
    {
      switch (ch)
        {
          case 'a' :
            g_appdir = optarg;
            break;

          case 'b' :
             g_delim = '\\';
             g_winpaths = false;
             break;

          case 'c' :
            g_host    = HOST_WINDOWS;
            g_windows = WINDOWS_CYGWIN;
            break;

          case 'd' :
            g_debug = true;
            break;

          case 'E' :
            g_enforce = true;
            break;

          case 'e' :
            g_distclean = true;
            break;

          case 'f' :
             g_delim = '/';
             g_winpaths = true;
             break;

          case 'g' :
            g_host    = HOST_WINDOWS;
            g_windows = WINDOWS_MSYS;
            break;

          case 'h' :
            show_usage(argv[0], EXIT_SUCCESS);

          case 'L' :
            dumpcfgs();

          case 'l' :
            g_host = HOST_LINUX;
            break;

          case 'm' :
            g_host = HOST_MACOS;
            break;

          case 'B' :
            g_host = HOST_BSD;
            break;

          case 'n' :
            g_host    = HOST_WINDOWS;
            g_windows = WINDOWS_NATIVE;
            break;

          case '?' :
            fprintf(stderr, "ERROR: Unrecognized option: %c\n", optopt);
            show_usage(argv[0], EXIT_FAILURE);

          case ':' :
            fprintf(stderr, "ERROR: Missing option argument, option: %c\n",
                    optopt);
            show_usage(argv[0], EXIT_FAILURE);

          default:
            fprintf(stderr, "ERROR: Unexpected option: %c\n", ch);
            show_usage(argv[0], EXIT_FAILURE);
        }
    }

  /* There should be exactly one argument following the options */

  if (optind >= argc)
    {
      fprintf(stderr, "ERROR: Missing <board-name>:<config-name>\n");
      show_usage(argv[0], EXIT_FAILURE);
    }

  /* The required option should be the board directory name and the
   * configuration directory name separated by ':', '/' or '\'.  Any are
   * acceptable in this context. Or using the custom board relative or
   * absolute path directly here.
   */

  g_boarddir = argv[optind];
  optind++;

  if (!verify_optiondir(g_boarddir))
    {
      ptr = strchr(g_boarddir, ':');
      if (ptr == NULL)
        {
          ptr = strchr(g_boarddir, '/');
          if (!ptr)
            {
              ptr = strchr(g_boarddir, '\\');
            }
        }

      if (ptr == NULL)
        {
          fprintf(stderr, "ERROR: Invalid <board-name>:<config-name>\n");
          show_usage(argv[0], EXIT_FAILURE);
        }

      *ptr++ = '\0';
      g_configdir = ptr;
    }
  else
    {
      /* custom board case with relative or absolute path */

      g_configpath = strdup(g_boarddir);
    }

  /* The left arguments will pass to make */

  g_makeargv = &argv[optind];
}

static int run_make(const char *arg)
{
  char **argv;

  snprintf(g_buffer, BUFFER_SIZE, "make %s", arg);

  for (argv = g_makeargv; *argv; argv++)
    {
      strncat(g_buffer, " ", BUFFER_SIZE - 1);
      strncat(g_buffer, *argv, BUFFER_SIZE - 1);
    }

  return system(g_buffer);
}

static bool filecmp(const char *f1, const char *f2)
{
  FILE *stream1;
  FILE *stream2;
  char ch1;
  char ch2;

  stream1 = fopen(f1, "r");
  stream2 = fopen(f2, "r");

  if (stream1 == NULL || stream2 == NULL)
    {
      return false;
    }

  do
    {
      ch1 = fgetc(stream1);
      ch2 = fgetc(stream2);

      if (ch1 != ch2)
        {
          return false;
        }
    }
  while (ch1 != EOF && ch2 != EOF);

  fclose(stream1);
  fclose(stream2);

  return true;
}

static bool check_directory(const char *directory)
{
  struct stat buf;

  if (stat(directory, &buf) < 0)
    {
      debug("stat of %s failed: %s\n", directory, strerror(errno));
      return false;
    }

  if (!S_ISDIR(buf.st_mode))
    {
      debug("%s exists but is not a directory\n", directory);
      return false;
    }

  return true;
}

static void verify_directory(const char *directory)
{
  struct stat buf;

  if (stat(directory, &buf) < 0)
    {
      fprintf(stderr, "ERROR: stat of %s failed: %s\n",
              directory, strerror(errno));
      exit(EXIT_FAILURE);
    }

  if (!S_ISDIR(buf.st_mode))
    {
      fprintf(stderr, "ERROR: %s exists but is not a directory\n",
              directory);
      exit(EXIT_FAILURE);
    }
}

static bool verify_optiondir(const char *directory)
{
  struct stat buf;

  if (stat(directory, &buf) < 0)
    {
      /* It may be okay if the directory does not exist */

      /* It may be okay if the file does not exist */

      int errcode = errno;
      if (errcode == ENOENT)
        {
          debug("verify_optiondir: stat of %s failed: %s\n",
                directory, strerror(errno));
          return false;
        }
      else
        {
          fprintf(stderr, "ERROR: stat of %s failed: %s\n",
                  directory, strerror(errno));
          exit(EXIT_FAILURE);
        }
    }

  if (!S_ISDIR(buf.st_mode))
    {
      fprintf(stderr, "ERROR: %s exists but is not a directory\n",
              directory);
      exit(EXIT_FAILURE);
    }

  return true;
}

static bool verify_file(const char *path)
{
  struct stat buf;

  if (stat(path, &buf) < 0)
    {
      /* It may be okay if the file does not exist */

      int errcode = errno;
      if (errcode == ENOENT)
        {
          debug("verify_file: stat of %s failed: %s\n",
                path, strerror(errno));
          return false;
        }
      else
        {
          fprintf(stderr, "ERROR: stat of %s failed: %s\n",
                  path, strerror(errno));
          exit(EXIT_FAILURE);
        }
    }

  if (!S_ISREG(buf.st_mode))
    {
      fprintf(stderr, "ERROR: %s exists but is not a regular file\n", path);
      exit(EXIT_FAILURE);
    }

  return true;
}

static void find_topdir(void)
{
  char *currdir;

  /* Get and verify the top-level NuttX directory */

  /* First get the current directory.  We expect this to be either
   * the nuttx root directory or the tools subdirectory.
   */

  if (getcwd(g_buffer, BUFFER_SIZE) == NULL)
    {
      fprintf(stderr, "ERROR: getcwd failed: %s\n", strerror(errno));
      exit(EXIT_FAILURE);
    }

  /* Assume that we are in the tools sub-directory and the directory above
   * is the nuttx root directory.
   */

  currdir  = strdup(g_buffer);
  g_topdir = strdup(dirname(g_buffer));

  debug("get_topdir: Checking parent directory: %s\n", g_topdir);
  verify_directory(g_topdir);

  /* Check if the current directory is the nuttx root directory.
   * If so, then the tools directory should be a sub-directory.
   */

  snprintf(g_buffer, BUFFER_SIZE, "%s%ctools", currdir, g_delim);
  debug("get_topdir: Checking topdir/tools=%s\n", g_buffer);
  if (check_directory(g_buffer))
    {
      /* There is a tools sub-directory under the current directory.
       * We must have already been in the nuttx root directory.  We
       * will find out for sure in later tests.
       */

      free(g_topdir);
      g_topdir = currdir;
    }
  else
    {
      /* Yes, we are probably in the tools/ sub-directory */

      free(currdir);
      if (chdir(g_topdir) < 0)
        {
          fprintf(stderr, "ERROR: Failed to ch to %s\n", g_topdir);
          exit(EXIT_FAILURE);
        }
    }
}

static void config_search(const char *boarddir,
                          config_callback callback, void *data)
{
  DIR *dir;
  struct dirent *dp;
  struct stat buf;
  char *parent;
  char *child;

  /* Skip over any leading '/' or '\\'.  This happens on the first second
   * call because the starting boarddir is ""
   */

  if (boarddir[0] == g_delim)
    {
      boarddir++;
    }

  /* Get the full directory path and open it */

  snprintf(g_buffer, BUFFER_SIZE, "%s%c%s", g_configtop, g_delim, boarddir);
  dir = opendir(g_buffer);
  if (!dir)
    {
      fprintf(stderr, "ERROR: Could not open %s: %s\n",
              g_buffer, strerror(errno));
      return;
    }

  /* Make a copy of the path to the directory */

  parent = strdup(g_buffer);

  /* Visit each entry in the directory */

  while ((dp = readdir(dir)) != NULL)
    {
      /* Ignore directory entries that start with '.' */

      if (dp->d_name[0] == '.')
        {
          continue;
        }

      /* Get a properly terminated copy of d_name (if d_name is long it may
       * not include a NUL terminator.
       */

      child = strndup(dp->d_name, NAME_MAX);

      /* Get the full path to d_name and stat the file/directory */

      snprintf(g_buffer, BUFFER_SIZE, "%s%c%s", parent, g_delim, child);
      if (stat(g_buffer, &buf) < 0)
        {
          fprintf(stderr, "ERROR: stat of %s failed: %s\n",
                  g_buffer, strerror(errno));
          free(child);
          continue;
        }

      /* If it is a directory, then recurse */

      if (S_ISDIR(buf.st_mode))
        {
          char *tmppath;
          snprintf(g_buffer, BUFFER_SIZE, "%s%c%s",
                   boarddir, g_delim, child);
          tmppath = strdup(g_buffer);
          config_search(tmppath, callback, data);
          free(tmppath);
        }

      /* If it is a regular file named 'defconfig' then we have found a
       * configuration directory.  We could terminate the search in this case
       * because we do not expect sub-directories within configuration
       * directories.
       */

      else if (S_ISREG(buf.st_mode) && strcmp("defconfig", child) == 0)
        {
          char *archname;
          char *chipname;
          char *boardname;
          char *configname;
          char *delim;

          /* Get the board directory near the beginning of the 'boarddir':
           * <archdir>/<chipdir>/<boarddir>/configs/<configdir>
           */

          /* Make a modifiable copy */

          strncpy(g_buffer, boarddir, BUFFER_SIZE - 1);

          /* Save the <archdir> */

          archname = g_buffer;

          delim = strchr(g_buffer, g_delim);
          if (delim == NULL)
            {
              debug("ERROR: delimiter not found in path: %s\n", boarddir);
            }
          else
            {
              /* Save the <chipdir> */

              *delim   = '\0';
              chipname = delim + 1;

              delim = strchr(chipname, g_delim);
              if (delim == NULL)
                {
                  debug("ERROR: delimiter not found in path: %s\n",
                        chipname);
                }
              else
                {
                  /* Save the <boardir> */

                  *delim    = '\0';
                  boardname = delim + 1;

                  delim = strchr(boardname, g_delim);
                  if (delim == NULL)
                    {
                      debug("ERROR: delimiter not found in path: %s\n",
                            boardname);
                    }
                  else
                    {
                      /* Save the <configdir>  */

                      *delim = '\0';
                      delim  = strrchr(delim + 1, g_delim);
                      if (delim == NULL)
                        {
                          debug("ERROR: directory not found in path: %s\n",
                                boardname);
                        }
                      else
                        {
                          configname = delim + 1;
                          callback(boarddir, archname, chipname,
                                   boardname, configname, data);
                        }
                    }
                }
            }
        }

      free(child);
    }

  free(parent);
  closedir(dir);
}

static void archname_callback(const char *boarddir, const char *archname,
                              const char *chipname, const char *boardname,
                              const char *configname, void *data)
{
  if (strcmp(g_boarddir, boardname) == 0 &&
      strcmp(g_configdir, configname) == 0)
    {
      g_archdir = strdup(archname);
      g_chipdir = strdup(chipname);
    }
}

static void find_archname(void)
{
  config_search("", archname_callback, NULL);
  if (g_archdir == NULL || g_chipdir == NULL)
    {
      g_archdir = "unknown";
      g_chipdir = "unknown";
    }
}

static void enumerate_callback(const char *boarddir, const char *archname,
                               const char *chipname, const char *boardname,
                               const char *configname, void *data)
{
  fprintf(stderr, "  %s:%s\n", boardname, configname);
}

static void enumerate_configs(void)
{
  fprintf(stderr, "Options for <board-name>:<config-name> include:\n\n");
  config_search("", enumerate_callback, NULL);
}

static void check_configdir(void)
{
  if (g_configpath && verify_optiondir(g_configpath))
    {
      /* Get the path to the custom board scripts directory */

      snprintf(g_buffer, BUFFER_SIZE, "%s%c..%c..%cscripts",
               g_configpath, g_delim, g_delim, g_delim);
      if (verify_optiondir(g_buffer))
        {
          g_scriptspath = strdup(g_buffer);
        }
    }
  else
    {
      /* Get the path to the top level configuration directory: boards/ */

      snprintf(g_buffer, BUFFER_SIZE, "%s%cboards", g_topdir, g_delim);
      debug("check_configdir: Checking configtop=%s\n", g_buffer);

      verify_directory(g_buffer);
      g_configtop = strdup(g_buffer);

      /* Get and verify the path to the selected configuration:
       * boards/<archdir>/<chipdir>/<boarddir>/configs/<configdir>
       */

      find_archname();

      snprintf(g_buffer, BUFFER_SIZE, "%s%cboards%c%s%c%s%c%s%cconfigs%c%s",
               g_topdir, g_delim, g_delim, g_archdir, g_delim, g_chipdir,
               g_delim, g_boarddir, g_delim, g_delim, g_configdir);
      debug("check_configdir: Checking configpath=%s\n", g_buffer);

      if (!verify_optiondir(g_buffer))
        {
          fprintf(stderr, "ERROR: No configuration at %s\n", g_buffer);
          fprintf(stderr, "Run tools/configure -L"
                          " to list available configurations.\n");
          exit(EXIT_FAILURE);
        }

      g_configpath = strdup(g_buffer);

      /* Get and verify the path to the scripts directory:
       * boards/<archdir>/<chipdir>/<boarddir>/scripts
       */

      snprintf(g_buffer, BUFFER_SIZE, "%s%cboards%c%s%c%s%c%s%cscripts",
               g_topdir, g_delim, g_delim, g_archdir, g_delim,
               g_chipdir, g_delim, g_boarddir, g_delim);
      debug("check_configdir: Checking scripts path=%s\n", g_buffer);

      g_scriptspath = NULL;
      if (verify_optiondir(g_buffer))
        {
          g_scriptspath = strdup(g_buffer);
        }
    }
}

static void check_configured(void)
{
  /* If we are already configured then there will be a .config and
   * a Make.defs file in the top-level directory.
   */

  snprintf(g_buffer, BUFFER_SIZE, "%s%c.config", g_topdir, g_delim);
  debug("check_configured: Checking %s\n", g_buffer);

  if (!verify_file(g_buffer))
    {
      return;
    }

  if (g_enforce)
    {
      run_make("distclean");
    }
  else
    {
      char *defcfgpath = NULL;

      snprintf(g_buffer, BUFFER_SIZE, "%s%cdefconfig",
               g_configpath, g_delim);
      defcfgpath = strdup(g_buffer);

      snprintf(g_buffer, BUFFER_SIZE, "%s%cdefconfig",
               g_topdir, g_delim);

      if (filecmp(g_buffer, defcfgpath))
        {
          fprintf(stderr, "No configuration change.\n");
          free(defcfgpath);
          exit(EXIT_SUCCESS);
        }
      else
        {
          free(defcfgpath);
          if (g_distclean)
            {
              run_make("distclean");
            }
          else
            {
              fprintf(stderr, "Already configured!\n");
              fprintf(stderr, "Please 'make distclean' and try again.\n");
              exit(EXIT_FAILURE);
            }
        }
    }
}

static void read_configfile(void)
{
  FILE *stream;

  snprintf(g_buffer, BUFFER_SIZE, "%s%cdefconfig", g_configpath, g_delim);
  stream = fopen(g_buffer, "r");
  if (!stream)
    {
       fprintf(stderr, "ERROR: failed to open %s for reading: %s\n",
               g_buffer, strerror(errno));
       exit(EXIT_FAILURE);
    }

  parse_file(stream, &g_configvars);
  fclose(stream);
}

static void read_versionfile(void)
{
  FILE *stream;

  snprintf(g_buffer, BUFFER_SIZE, "%s%c.version", g_topdir, g_delim);
  stream = fopen(g_buffer, "r");
  if (!stream)
    {
      /* It may not be an error if there is no .version file */

       debug("Failed to open %s for reading: %s\n",
             g_buffer, strerror(errno));
    }
  else
    {
      parse_file(stream, &g_versionvars);
      fclose(stream);
    }
}

static void get_verstring(void)
{
  struct variable_s *var;

  if (g_versionvars)
    {
      var = find_variable("CONFIG_VERSION_STRING", g_versionvars);
      if (var && var->val)
        {
          g_verstring = strdup(var->val);
        }
    }

  debug("get_verstring: Version string=%s\n", g_verstring);
}

static bool verify_appdir(const char *appdir)
{
  /* Does this directory exist? */

  snprintf(g_buffer, BUFFER_SIZE, "%s%c%s", g_topdir, g_delim, appdir);
  debug("verify_appdir: Checking apppath=%s\n", g_buffer);
  if (verify_optiondir(g_buffer))
    {
      /* Yes.. Use this application directory path */

      g_appdir  = strdup(appdir);
      g_apppath = strdup(g_buffer);
      return true;
    }

  debug("verify_appdir: apppath=%s does not exist\n", g_buffer);
  return false;
}

static void check_appdir(void)
{
  char tmp[16];

  /* Get and verify the full path to the application directory */

  /* Was the appdir provided on the command line? */

  debug("check_appdir: Command line appdir=%s\n",
        g_appdir ? g_appdir : "<null>");

  if (!g_appdir)
    {
      /* If no application directory was provided on the command line and we
       * are switching between a windows native host and some other host then
       * ignore any path to the apps/ directory in the defconfig file.  It
       * will most certainly not be in a usable form.
       */

      if (g_winnative == g_oldnative)
        {
          /* No, was the path provided in the configuration? */

          struct variable_s *var =
            find_variable("CONFIG_APPS_DIR", g_configvars);

          if (var != NULL)
            {
              debug("check_appdir: Config file appdir=%s\n",
                    var->val ? var->val : "<null>");

              /* Yes.. does this directory exist? */

              if (var->val && verify_appdir(var->val))
                {
                  /* We are using the CONFIG_APPS_DIR setting already in the
                   * defconfig file.
                   */

                  g_needapppath = false;
                  return;
                }
            }
        }

      /* Now try some canned locations */

      /* Try ../apps-xx.yy where xx.yy is the version string */

      snprintf(tmp, 16, "..%capps-%s", g_delim, g_verstring);
      debug("check_appdir: Try appdir=%s\n", tmp);
      if (verify_appdir(tmp))
        {
          return;
        }

      /* Try ../apps with no version */

      snprintf(tmp, 16, "..%capps", g_delim);
      debug("check_appdir: Try appdir=%s\n", tmp);
      if (verify_appdir(tmp))
        {
          return;
        }

      /* Try ../apps-xx.yy where xx.yy are the NuttX version number */

      fprintf(stderr, "ERROR: Could not find the path to the appdir\n");
      exit(EXIT_FAILURE);
    }
  else if (!verify_appdir(g_appdir))
    {
      fprintf(stderr, "ERROR: Command line path to appdir does not exist\n");
      exit(EXIT_FAILURE);
    }
}

static void check_configuration(void)
{
  struct variable_s *var;

  /* Check if this is a Windows native configuration */

  var = find_variable("CONFIG_WINDOWS_NATIVE", g_configvars);
  if (var && var->val && strcmp("y", var->val) == 0)
    {
      debug("check_configuration: Windows native configuration\n");
      g_oldnative = true;
    }

  /* If we are going to some host other than windows native or to a windows
   * native host, then don't ignore what is in the defconfig file.
   */

  if (g_host == HOST_NOCHANGE)
    {
      /* Use whatever we found in the configuration file */

      g_winnative = g_oldnative;
    }
  else if (g_host == HOST_WINDOWS && g_windows == WINDOWS_NATIVE)
    {
      /* The new configuration is windows native */

      g_winnative = true;
    }

  /* All configurations must provide a defconfig and Make.defs file */

  snprintf(g_buffer, BUFFER_SIZE, "%s%cdefconfig", g_configpath, g_delim);
  debug("check_configuration: Checking %s\n", g_buffer);
  if (!verify_file(g_buffer))
    {
      fprintf(stderr, "ERROR: No configuration in %s\n", g_configpath);
      fprintf(stderr, "       No defconfig file found.\n");
      fprintf(stderr, "Run tools/configure -L"
                      " to list available configurations.\n");
      exit(EXIT_FAILURE);
    }

  g_srcdefconfig = strdup(g_buffer);

  /* Try the Make.defs file */

  snprintf(g_buffer, BUFFER_SIZE, "%s%cMake.defs", g_configpath, g_delim);
  debug("check_configuration: Checking %s\n", g_buffer);
  if (!verify_file(g_buffer))
    {
      /* An alternative location is the scripts/ directory */

      if (g_scriptspath != NULL)
        {
          snprintf(g_buffer, BUFFER_SIZE, "%s%cMake.defs",
                   g_scriptspath, g_delim);
          debug("check_configuration: Checking %s\n", g_buffer);
          if (!verify_file(g_buffer))
            {
              /* Let’s check if there is a script in the common directory */

              snprintf(g_buffer, BUFFER_SIZE,
                       "%s%c..%c..%c..%ccommon%cscripts%cMake.defs",
                       g_configpath, g_delim, g_delim, g_delim, g_delim,
                       g_delim, g_delim);
              if (!verify_file(g_buffer))
                {
                  fprintf(stderr, "ERROR: No Make.defs file found\n");
                  fprintf(stderr, "Run tools/configure -L"
                                  " to list available configurations.\n");
                  exit(EXIT_FAILURE);
                }
            }
        }
      else
        {
          fprintf(stderr, "ERROR: No Make.defs file in %s\n", g_configpath);
          fprintf(stderr, "Run tools/configure -L"
                          " to list available configurations.\n");
          exit(EXIT_FAILURE);
        }
    }

  g_srcmakedefs = strdup(g_buffer);
}

static void copy_file(const char *srcpath,
                      const char *destpath, mode_t mode)
{
  int nbytesread;
  int nbyteswritten;
  int rdfd;
  int wrfd;

  /* Open the source file for reading */

  rdfd = open(srcpath, O_RDONLY);
  if (rdfd < 0)
    {
      fprintf(stderr, "ERROR: Failed to open %s for reading: %s\n",
              srcpath, strerror(errno));
      exit(EXIT_FAILURE);
    }

  /* Now open the destination for writing */

  wrfd = open(destpath, O_WRONLY | O_CREAT | O_TRUNC, mode);
  if (wrfd < 0)
    {
      fprintf(stderr, "ERROR: Failed to open %s for writing: %s\n",
              destpath, strerror(errno));
      exit(EXIT_FAILURE);
    }

  /* Now copy the file */

  for (; ; )
    {
      do
        {
          nbytesread = read(rdfd, g_buffer, BUFFER_SIZE);
          if (nbytesread == 0)
            {
              /* End of file */

              close(rdfd);
              close(wrfd);
              return;
            }
          else if (nbytesread < 0)
            {
              /* EINTR is not an error (but will still stop the copy) */

              fprintf(stderr, "ERROR: Read failure: %s\n", strerror(errno));
              exit(EXIT_FAILURE);
            }
        }
      while (nbytesread <= 0);

      do
        {
          nbyteswritten = write(wrfd, g_buffer, nbytesread);
          if (nbyteswritten >= 0)
            {
              nbytesread -= nbyteswritten;
            }
          else
            {
              /* EINTR is not an error (but will still stop the copy) */

              fprintf(stderr, "ERROR: Write failure: %s\n", strerror(errno));
              exit(EXIT_FAILURE);
            }
        }
      while (nbytesread > 0);
    }
}

static void substitute(char *str, int ch1, int ch2)
{
  for (; *str; str++)
    {
      if (*str == ch1)
        {
          *str = ch2;
        }
    }
}

static char *double_appdir_backslashes(char *old_appdir)
{
  char *new_appdir = NULL;
  char *p_old = NULL;
  char *p_new = NULL;
  int oldlen = 0;
  int occurrences = 0;
  int alloclen = 0;

  p_old = old_appdir;
  while ((p_old = strchr(p_old, '\\')) != NULL)
    {
      occurrences++;
      p_old++;
    }

  if (occurrences != 0)
    {
      oldlen = strlen(old_appdir);
      alloclen = oldlen + occurrences + sizeof((char) '\0');
      new_appdir = malloc(alloclen);

      if (new_appdir != NULL)
        {
          p_old = old_appdir;
          p_new = new_appdir;
          while (oldlen)
            {
              if (*p_old != '\\')
                {
                  *p_new++ = *p_old;
                }
                else
                {
                  *p_new++ = '\\';
                  *p_new++ = '\\';
                }

              ++p_old;
              --oldlen;
            }

          *p_new = '\0';
        }
    }
  else
    {
      new_appdir = strdup(old_appdir);
    }

  return new_appdir;
}

static void copy_optional(void)
{
  int i;

  for (i = 0; i < N_OPTFILES; i++)
    {
      snprintf(g_buffer, BUFFER_SIZE, "%s%c%s",
               g_configpath, g_delim, g_optfiles[i]);

      if (verify_file(g_buffer))
        {
          char *optsrc = strdup(g_buffer);

          snprintf(g_buffer, BUFFER_SIZE, "%s%c%s",
                   g_topdir, g_delim, g_optfiles[i]);

          debug("copy_optional: Copying from %s to %s\n", optsrc, g_buffer);
          copy_file(optsrc, g_buffer, 0644);

          free(optsrc);
        }
    }
}

static void enable_feature(const char *destconfig, const char *varname)
{
  int ret;

  snprintf(g_buffer, BUFFER_SIZE,
           "kconfig-tweak --file %s --enable %s",
           destconfig, varname);

  ret = system(g_buffer);

#ifdef WEXITSTATUS
  if (ret < 0 || WEXITSTATUS(ret) != 0)
#else
  if (ret < 0)
#endif
    {
      fprintf(stderr, "ERROR: Failed to enable %s\n", varname);
      fprintf(stderr, "       command: %s\n", g_buffer);
      exit(EXIT_FAILURE);
    }
}

static void disable_feature(const char *destconfig, const char *varname)
{
  int ret;

  snprintf(g_buffer, BUFFER_SIZE,
           "kconfig-tweak --file %s --disable %s",
           destconfig, varname);

  ret = system(g_buffer);

#ifdef WEXITSTATUS
  if (ret < 0 || WEXITSTATUS(ret) != 0)
#else
  if (ret < 0)
#endif
    {
      fprintf(stderr, "ERROR: Failed to disable %s\n", varname);
      fprintf(stderr, "       command: %s\n", g_buffer);
      exit(EXIT_FAILURE);
    }
}

/* Select the host build development environment */

static void set_host(const char *destconfig)
{
  switch (g_host)
    {
      case HOST_LINUX:
        {
          printf("  Select the Linux host\n");

          enable_feature(destconfig, "CONFIG_HOST_LINUX");
          disable_feature(destconfig, "CONFIG_HOST_WINDOWS");
          disable_feature(destconfig, "CONFIG_HOST_MACOS");
          disable_feature(destconfig, "CONFIG_HOST_BSD");

          disable_feature(destconfig, "CONFIG_WINDOWS_NATIVE");
          disable_feature(destconfig, "CONFIG_WINDOWS_CYGWIN");
          disable_feature(destconfig, "CONFIG_WINDOWS_MSYS");
          disable_feature(destconfig, "CONFIG_WINDOWS_OTHER");

          enable_feature(destconfig, "CONFIG_SIM_X8664_SYSTEMV");
          disable_feature(destconfig, "CONFIG_SIM_X8664_MICROSOFT");
        }
        break;

      case HOST_MACOS:
        {
          printf("  Select the macOS host\n");

          disable_feature(destconfig, "CONFIG_HOST_LINUX");
          disable_feature(destconfig, "CONFIG_HOST_WINDOWS");
          disable_feature(destconfig, "CONFIG_HOST_BSD");
          enable_feature(destconfig, "CONFIG_HOST_MACOS");

          disable_feature(destconfig, "CONFIG_WINDOWS_NATIVE");
          disable_feature(destconfig, "CONFIG_WINDOWS_CYGWIN");
          disable_feature(destconfig, "CONFIG_WINDOWS_MSYS");
          disable_feature(destconfig, "CONFIG_WINDOWS_OTHER");

          enable_feature(destconfig, "CONFIG_SIM_X8664_SYSTEMV");
          disable_feature(destconfig, "CONFIG_SIM_X8664_MICROSOFT");
        }
        break;

      case HOST_BSD:
        {
          printf("  Select the BSD host\n");

          disable_feature(destconfig, "CONFIG_HOST_LINUX");
          disable_feature(destconfig, "CONFIG_HOST_WINDOWS");
          disable_feature(destconfig, "CONFIG_HOST_MACOS");
          enable_feature(destconfig, "CONFIG_HOST_BSD");

          disable_feature(destconfig, "CONFIG_WINDOWS_NATIVE");
          disable_feature(destconfig, "CONFIG_WINDOWS_CYGWIN");
          disable_feature(destconfig, "CONFIG_WINDOWS_MSYS");
          disable_feature(destconfig, "CONFIG_WINDOWS_OTHER");

          enable_feature(destconfig, "CONFIG_SIM_X8664_SYSTEMV");
          disable_feature(destconfig, "CONFIG_SIM_X8664_MICROSOFT");
        }
        break;

      case HOST_WINDOWS:
        {
          enable_feature(destconfig, "CONFIG_HOST_WINDOWS");
          disable_feature(destconfig, "CONFIG_HOST_LINUX");
          disable_feature(destconfig, "CONFIG_HOST_MACOS");
          disable_feature(destconfig, "CONFIG_HOST_BSD");

          disable_feature(destconfig, "CONFIG_WINDOWS_OTHER");

          enable_feature(destconfig, "CONFIG_SIM_X8664_MICROSOFT");
          disable_feature(destconfig, "CONFIG_SIM_X8664_SYSTEMV");

          switch (g_windows)
            {
              case WINDOWS_CYGWIN:
                printf("  Select Windows/Cygwin host\n");
                enable_feature(destconfig, "CONFIG_WINDOWS_CYGWIN");
                disable_feature(destconfig, "CONFIG_WINDOWS_MSYS");
                disable_feature(destconfig, "CONFIG_WINDOWS_NATIVE");
                break;

              case WINDOWS_MSYS:
                printf("  Select Windows/MSYS host\n");
                disable_feature(destconfig, "CONFIG_WINDOWS_CYGWIN");
                enable_feature(destconfig, "CONFIG_WINDOWS_MSYS");
                disable_feature(destconfig, "CONFIG_WINDOWS_NATIVE");
                break;

              case WINDOWS_NATIVE:
                printf("  Select Windows native host\n");
                disable_feature(destconfig, "CONFIG_WINDOWS_CYGWIN");
                disable_feature(destconfig, "CONFIG_WINDOWS_MSYS");
                enable_feature(destconfig, "CONFIG_EXPERIMENTAL");
                enable_feature(destconfig, "CONFIG_WINDOWS_NATIVE");
                break;

              default:
               fprintf(stderr,
                       "ERROR: Unrecognized  windows configuration: %d\n",
                       g_windows);
               exit(EXIT_FAILURE);
            }
        }
        break;

      case HOST_NOCHANGE:
        break;

      default:
        {
          fprintf(stderr, "ERROR: Unrecognized  host configuration: %d\n",
                  g_host);
          exit(EXIT_FAILURE);
        }
    }
}

static void configure(void)
{
  char *destconfig;

  /* Copy the defconfig to toplevel */

  snprintf(g_buffer, BUFFER_SIZE, "%s%cdefconfig", g_topdir, g_delim);
  copy_file(g_srcdefconfig, g_buffer, 0644);

  /* Copy the defconfig file as .config */

  snprintf(g_buffer, BUFFER_SIZE, "%s%c.config", g_topdir, g_delim);
  destconfig = strdup(g_buffer);
  debug("configure: Copying from %s to %s\n", g_srcdefconfig, destconfig);
  copy_file(g_srcdefconfig, destconfig, 0644);

  /* Copy the Make.defs file as Make.defs */

  snprintf(g_buffer, BUFFER_SIZE, "%s%cMake.defs", g_topdir, g_delim);
  debug("configure: Copying from %s to %s\n", g_srcmakedefs, g_buffer);
  copy_file(g_srcmakedefs, g_buffer, 0644);

  /* Copy optional files */

  copy_optional();

  /* Select the host build development environment */

  set_host(destconfig);

  /* If we did not use the CONFIG_APPS_DIR that was in the defconfig config
   * file, then append the correct application information to the tail of the
   * .config file
   */

  if (g_needapppath)
    {
      FILE *stream;
      char *appdir = strdup(g_appdir);
      char *boardcfg = strdup(g_boarddir);

      /* One complexity is if we are using Windows paths, but the
       * configuration needs POSIX paths (or vice versa).
       */

      if (g_winpaths != g_winnative)
        {
          /* Not the same */

          if (g_winpaths)
            {
              /* Using Windows paths, but the configuration wants POSIX
               * paths.
               */

              substitute(appdir, '\\', '/');
            }
          else
            {
              /* Using POSIX paths, but the configuration wants Windows
               * paths.
               */

              substitute(appdir, '/', '\\');
            }
        }

      /* Looks like prebuilt winnative kconfig-conf interprets "..\apps" as
       * "..apps" (possibly '\a' as escape-sequence) so expand winnative path
       * to double-backslashed variant "..\\apps".
       */

      if (g_winnative)
        {
          char *tmp_appdir = double_appdir_backslashes(appdir);
          if (NULL == tmp_appdir)
            {
              fprintf(stderr,
                      "ERROR: Failed to double appdir backslashes\n");
              exit(EXIT_FAILURE);
            }

          free(appdir);
          appdir = tmp_appdir;
        }

      /* Open the file for appending */

      stream = fopen(destconfig, "a");
      if (!stream)
        {
          fprintf(stderr,
                  "ERROR: Failed to open %s for append mode: %s\n",
                  destconfig, strerror(errno));
          exit(EXIT_FAILURE);
        }

      fprintf(stream, "\n# Application configuration\n\n");
      fprintf(stream, "CONFIG_APPS_DIR=\"%s\"\n", appdir);

      substitute(boardcfg, '\\', '/');
      fprintf(stream, "CONFIG_BASE_DEFCONFIG=\"%s\"\n", boardcfg);

      fclose(stream);
      free(appdir);
    }

  free(destconfig);
}

static void refresh(void)
{
  int ret;

  printf("  Refreshing...\n");
  fflush(stdout);

  ret = run_make("olddefconfig");
  putchar('\n');

#ifdef WEXITSTATUS
  if (ret < 0 || WEXITSTATUS(ret) != 0)
#else
  if (ret < 0)
#endif
    {
      fprintf(stderr, "ERROR: Failed to refresh configurations\n");
      fprintf(stderr, "       kconfig-conf --olddefconfig Kconfig\n");
    }
}

static void save_original_config(void)
{
  snprintf(g_buffer, BUFFER_SIZE, "%s%c.config", g_topdir, g_delim);
  char *source_config = strdup(g_buffer);
  snprintf(g_buffer, BUFFER_SIZE, "%s%c.config.orig", g_topdir, g_delim);
  char *dest_config = strdup(g_buffer);

  FILE *src_file = fopen(source_config, "r");
  FILE *dest_file = fopen(dest_config, "w");

  if (src_file == NULL || dest_file == NULL)
    {
      fprintf(stderr, "ERROR: Failed to open files\n");
      exit(EXIT_FAILURE);
    }

  debug("save_original_config: Copying from %s to %s\n",
        source_config, dest_config);

  while (fgets(g_buffer, BUFFER_SIZE, src_file) != NULL)
    {
      if (strstr(g_buffer, "CONFIG_BASE_DEFCONFIG") == NULL)
        {
          fputs(g_buffer, dest_file);
        }
    }

  fclose(src_file);
  fclose(dest_file);
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

int main(int argc, char **argv, char **envp)
{
  debug("main: Checking arguments\n");
  parse_args(argc, argv);

  debug("main: Checking NuttX Directories\n");
  find_topdir();
  check_configdir();
  check_configured();

  debug("main: Reading the configuration/version files\n");
  read_configfile();
  read_versionfile();
  get_verstring();

  debug("main: Checking Configuration Directory\n");
  check_configuration();

  debug("main: Checking Application Directories\n");
  check_appdir();
  debug("main: Using apppath=%s\n", g_apppath ? g_apppath : "<null>");

  debug("main: Configuring\n");
  configure();

  debug("main: Refresh configuration\n");
  refresh();

  debug("main: Save original configuration\n");
  save_original_config();
  return EXIT_SUCCESS;
}