/**********************************************************************
 * $id$
 *
 * Project:  MapServer
 * Purpose:  Config File Support
 * Author:   Steve Lime and the MapServer team.
 *
 **********************************************************************
 * Copyright (c) 1996-2005 Regents of the University of Minnesota.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of this Software or works derived from this Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 ****************************************************************************/
#include "mapserver.h"
#include "mapfile.h" // format shares a couple of tokens

#include "cpl_conv.h"

#include <string>

extern "C" int msyylex(void);
extern "C" void msyyrestart(FILE *);

extern "C" FILE *msyyin;
extern "C" int msyystate;
extern "C" int msyylineno;
extern "C" char *msyystring_buffer;

static int initConfig(configObj *config) {
  if (config == NULL) {
    msSetError(MS_MEMERR, "Config object is NULL.", "initConfig()");
    return MS_FAILURE;
  }

  if (initHashTable(&(config->env)) != MS_SUCCESS)
    return MS_FAILURE;
  if (initHashTable(&(config->maps)) != MS_SUCCESS)
    return MS_FAILURE;
  if (initHashTable(&(config->plugins)) != MS_SUCCESS)
    return MS_FAILURE;

  return MS_SUCCESS;
}

void msFreeConfig(configObj *config) {
  if (config == NULL)
    return;
  msFreeHashItems(&(config->env));
  msFreeHashItems(&(config->maps));
  msFreeHashItems(&(config->plugins));

  msFree(config);
}

static int loadConfig(configObj *config) {
  int token;

  if (config == NULL)
    return MS_FAILURE;

  token = msyylex();
  if (token != MS_CONFIG_SECTION) {
    msSetError(MS_IDENTERR,
               "First token must be CONFIG, this doesn't look like a mapserver "
               "config file.",
               "msLoadConfig()");
    return MS_FAILURE;
  }

  for (;;) {
    switch (msyylex()) {
    case (MS_CONFIG_SECTION_ENV): {
      if (loadHashTable(&(config->env)) != MS_SUCCESS)
        return MS_FAILURE;
      break;
    }
    case (MS_CONFIG_SECTION_MAPS):
      if (loadHashTable(&config->maps) != MS_SUCCESS)
        return MS_FAILURE;
      break;
    case (MS_CONFIG_SECTION_PLUGINS):
      if (loadHashTable(&config->plugins) != MS_SUCCESS)
        return MS_FAILURE;
      break;
    case (EOF):
      msSetError(MS_EOFERR, NULL, "msLoadConfig()");
      return MS_FAILURE;
    case (END):
      if (msyyin) {
        fclose(msyyin);
        msyyin = NULL;
      }
      return MS_SUCCESS;
      break;
    default:
      msSetError(MS_IDENTERR, "Parsing error near (%s):(line %d)",
                 "msLoadConfig()", msyystring_buffer, msyylineno);
      return MS_FAILURE;
    }
  }
}

static void msConfigSetConfigOption(const char *key, const char *value) {
  CPLSetConfigOption(key, value);
  if (strcasecmp(key, "PROJ_DATA") == 0 || strcasecmp(key, "PROJ_LIB") == 0) {
    msSetPROJ_DATA(value, nullptr);
  }
}

std::string msGetMapserverConfigFilePath() {

  const char *config_path = CPLGetConfigOption("MAPSERVER_CONFIG_FILE", NULL);
  if (config_path != nullptr) {
    return std::string(config_path);
  }

  const char *env_file = getenv("MAPSERVER_CONFIG_FILE");
  if (env_file != nullptr) {
    // get config filename from environment
    return std::string(env_file);
  }

  if (MAPSERVER_CONFIG_FILE[0] != '\0') {
    // fallback to hardcoded file name
    return std::string(MAPSERVER_CONFIG_FILE);
  }
  return std::string();
}

configObj *msLoadConfig(const char *ms_config_file) {
  configObj *config = NULL;

  std::string config_path;

  if (ms_config_file == NULL) {
    config_path = msGetMapserverConfigFilePath();
    if (config_path.empty()) {
      msSetError(MS_MISCERR, "No config file set.", "msLoadConfig()");
      return NULL;
    }
    ms_config_file = config_path.c_str();
  }

  config = (configObj *)calloc(1, sizeof(configObj));
  MS_CHECK_ALLOC(config, sizeof(configObj), NULL);

  if (initConfig(config) != MS_SUCCESS) {
    msFreeConfig(config);
    return NULL;
  }

  msAcquireLock(TLOCK_PARSER);

  if ((msyyin = fopen(ms_config_file, "r")) == NULL) {
    msDebug("Cannot open configuration file %s.\n", ms_config_file);
    msSetError(MS_IOERR,
               "See mapserver.org/mapfile/config.html for more information.",
               "msLoadConfig()");
    msReleaseLock(TLOCK_PARSER);
    msFreeConfig(config);
    return NULL;
  }

  msyystate = MS_TOKENIZE_CONFIG;
  msyylex(); // sets things up, but doesn't process any tokens

  msyyrestart(msyyin); // start at line 1
  msyylineno = 1;

  if (loadConfig(config) != MS_SUCCESS) {
    msFreeConfig(config);
    msReleaseLock(TLOCK_PARSER);
    if (msyyin) {
      fclose(msyyin);
      msyyin = NULL;
    }
    return NULL;
  }
  msReleaseLock(TLOCK_PARSER);

  // load all env key/values using CPLSetConfigOption() - only do this *after*
  // we have a good config
  const char *key = msFirstKeyFromHashTable(&config->env);
  if (key != NULL) {
    msConfigSetConfigOption(key, msLookupHashTable(&config->env, key));

    const char *last_key = key;
    while ((key = msNextKeyFromHashTable(&config->env, last_key)) != NULL) {
      msConfigSetConfigOption(key, msLookupHashTable(&config->env, key));
      last_key = key;
    }
  }

  // also store the path to the CONFIG file
  msConfigSetConfigOption("MAPSERVER_CONFIG_FILE", ms_config_file);

  return config;
}

const char *msConfigGetEnv(const configObj *config, const char *key) {
  if (config == NULL || key == NULL)
    return NULL;
  return msLookupHashTable(&config->env, key);
}

/**
 * Get the path to a Mapfile, from the MapServer CONFIG file using
 * its key. If the path is relative then the CONFIG file location
 * is used to determine the absolute path.
 * The provided pszReturnPath must be at least MS_MAXPATHLEN long.
 */
const char *msConfigGetMap(const configObj *config, const char *key,
                           char *pszReturnPath) {
  if (config == NULL || key == NULL || pszReturnPath == NULL)
    return NULL;

  const char *mapFile = msLookupHashTable(&config->maps, key);
  if (!mapFile)
    return NULL;

  // check for relative Mapfile paths
  if (CPLIsFilenameRelative(mapFile)) {

    std::string config_path = msGetMapserverConfigFilePath();
    if (config_path.empty()) {
      return NULL;
    }
    char *configPath = msGetPath(config_path.c_str());

    if (!configPath)
      return NULL;

    if (msBuildPath(pszReturnPath, configPath, mapFile) == NULL) {
      free(configPath);
      return NULL;
    }
    free(configPath);
  } else {
    // just copy absolute path directly
    strlcpy(pszReturnPath, mapFile, MS_MAXPATHLEN);
  }
  return pszReturnPath;
}

const char *msConfigGetPlugin(const configObj *config, const char *key) {
  if (config == NULL || key == NULL)
    return NULL;
  return msLookupHashTable(&config->plugins, key);
}
