// Filename: configTable.cxx
// Created by:  drose (15May00)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license.  You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://www.panda3d.org/license.txt .
//
// To contact the maintainers of this program write to
// panda3d@yahoogroups.com .
//
////////////////////////////////////////////////////////////////////

#include "dconfig.h"
#include "configTable.h"
#include "dSearchPath.h"
#include "executionEnvironment.h"
#include "config_dconfig.h"
#include "pfstream.h"
#include "serialization.h"

#ifdef PENV_PS2

#include <string.h>
#include <sifdev.h>
#include <eekernel.h>

#endif // PENV_PS2

//#define DISABLE_CONFIG

using namespace Config;

ConfigTable* ConfigTable::_instance = (ConfigTable*)0L;

void ConfigTable::CropString(ConfigString& S) {
  size_t i = S.find_first_not_of(" \t\r\f\n");
  if (i != ConfigString::npos) {
    size_t j = S.find_last_not_of(" \t\r\f\n");
    if (j != ConfigString::npos)
      S = S.substr(i, j-i+1);
    else
      S = S.substr(i, ConfigString::npos);
  } else
    S.erase(0, ConfigString::npos);
}

bool ConfigTable::IsComment(const ConfigString& S)
{
  if (!S.empty()) {
    for (ConfigString::iterator i=configcmt.begin();
         i!=configcmt.end(); ++i)
      if (S[0] == (*i))
        return true;
  }
  return false;
}

void ConfigTable::UpCase(ConfigString& S)
{
   for (ConfigString::iterator i=S.begin(); i!=S.end(); ++i)
      (*i) = toupper(*i);
}

ConfigString ConfigTable::NextWord(const ConfigString& S) {
  int i(S.find_first_of(" \t\r\f\n"));
  return S.substr(0, i);
}

ConfigString ConfigTable::PopNextWord(ConfigString& S) {
  int i(S.find_first_of(" \t\r\f\n"));
  ConfigString ret(S.substr(0, i));
  S.erase(0, i);
  CropString(S);
  return ret;
}

void ConfigTable::ParseConfigFile(istream& is, const ConfigString& Filename)
{
   ConfigString line;

   while (!is.eof()) {
      std::getline(is, line);
      if (microconfig_cat->is_spam())
         microconfig_cat->spam() << "read from " << Filename << ": '" << line
                                 << "'" << endl;
      CropString(line);
      if (microconfig_cat->is_spam())
         microconfig_cat->spam() << "cropped line to: '" << line << "'"
                                 << endl;
      if (!IsComment(line)) {
         ConfigString protosym(PopNextWord(line));
         if (microconfig_cat->is_spam())
            microconfig_cat->spam() << "protosym is '" << protosym
                                    << "' with value of '" << line << "'"
                                    << endl;
         size_t i(protosym.find("."));
         if (i == ConfigString::npos) {
            if (microconfig_cat->is_spam())
               microconfig_cat->spam() << "this is an unqualified symbol"
                                       << endl;
            unqualified[protosym].push_back(SymEnt(SymEnt::ConfigFile, line, Filename));
         } else {
           ConfigString scope(protosym.substr(0, i));
           ConfigString sym(protosym.substr(i+1, ConfigString::npos));
           if (microconfig_cat->is_spam())
             microconfig_cat->spam() << "this is a qualified symbol."
                                     << " scope '" << scope
                                     << "', symbol '" << sym << "'" << endl;
           (qualified[scope])[sym].push_back(SymEnt(SymEnt::ConfigFile, line, Filename));
         }
      } else if (microconfig_cat->is_spam())
        microconfig_cat->spam() << "line is detected as a comment" << endl;
   }
}

void ConfigTable::ReadConfigFile(void) {
  if (configpath.empty()) {

#ifdef PENV_PS2

    // roight. PS2 has no environment variables, so the config path
    // has to be explicitly set with the CONFIG_PATH compiler variable.
    // this is currently done in makefile.config.so...

    // CSN.

#ifndef CONFIG_PATH
#define CONFIG_PATH "."
#endif
    configpath = CONFIG_PATH;
#else
    configpath = ".";
#endif
  } else {
    ConfigString S;
    while (!configpath.empty()) {
      int i = configpath.find_first_of(" ");
      ConfigString stmp = configpath.substr(0, i);
      if (ExecutionEnvironment::has_environment_variable(stmp)) {
        S += " ";
        S += ExecutionEnvironment::get_environment_variable(stmp);
      }
      configpath.erase(0, i);
      CropString(configpath);
    }
    if (S.empty())
      S = ".";
    CropString(S);
    configpath = S;
  }
  if (microconfig_cat->is_spam())
    microconfig_cat->spam() << "evaluated value of configpath '"
                            << configpath << "'" << endl;

  DSearchPath config_search(configpath);
  DSearchPath::Results config_files;

  if (!configsuffix.empty()) {
    if (microconfig_cat->is_spam())
      microconfig_cat->spam() << "agregate config name is: "
                              << (configname + configsuffix) << endl;
    config_search.find_all_files(configname + configsuffix, config_files);
    if (microconfig_cat->is_spam())
      microconfig_cat->spam() << "found " << config_files.get_num_files()
                              << " files" << endl;
  } else {
    if (microconfig_cat->is_spam())
      microconfig_cat->spam() << "searching for '" << configname << "'"
                              << endl;
    config_search.find_all_files(configname, config_files);
    if (microconfig_cat->is_spam())
      microconfig_cat->spam() << "found " << config_files.get_num_files()
                              << " files" << endl;
  }

  if (microconfig_cat->is_spam())
    microconfig_cat->spam() << "configpath parsed and searched"
                            << endl;

  int num_config_files = config_files.get_num_files();
  for (int i = num_config_files - 1; i >= 0; i--) {
    Filename config_file = config_files.get_file(i);

    if (microconfig_cat->is_spam())
      microconfig_cat->spam() << "examining file '" << config_file << "'"
                               << endl;
    if (config_file.is_executable()) {
      ConfigString line = config_file.to_os_specific() + " "
        + ExecutionEnvironment::get_binary_name();
      if (microconfig_cat->is_spam())
        microconfig_cat->spam() << "file is executable, running '"
                                 << line << "'" << endl;
      IPipeStream ifs(line);
      ParseConfigFile(ifs, config_file);
    } else {
      if (microconfig_cat->is_spam())
        microconfig_cat->spam()
          << "file is not executable, reading normally" << endl;

#ifdef PENV_PS2
      ConfigString line = PS2_FILE_PREFIX + convert_pathname(config_file);

      int fd = sceOpen((char *) line.c_str(), SCE_RDONLY);
      char line_buffer[2048];

      memset(line_buffer, 0, 2048);

      ConfigString file_buffer;

      while (sceRead(fd, line_buffer, 2048) > 0)
      {
        file_buffer += line_buffer;
        memset(line_buffer, 0, 2048);
      }

      sceClose(fd);

      istrstream ifs(file_buffer.c_str());
#else
      ifstream ifs(config_file.to_os_specific().c_str());
#endif
      ParseConfigFile(ifs, config_file);
    }
  }
}

void ConfigTable::ParseCommandEnv(ConfigString& S, const ConfigString& sym)
{
   if (microconfig_cat->is_spam())
      microconfig_cat->spam() << "value of '" << sym << "' is '" << S << "'"
                               << endl;
   while (!S.empty()) {
      ConfigString protosym(PopNextWord(S));
      bool ok = false;
      bool state = false;
      if (protosym[0] == '-')
         ok = true;
      else if (protosym[0] == '+') {
         ok = true;
         state = true;
      }
      if (ok) {
         protosym.erase(0, 1);
         CropString(protosym);
         size_t i(protosym.find("."));
         if (i == ConfigString::npos) {
            unqualified[protosym].push_back(SymEnt(SymEnt::CommandEnv,
                                                   NextWord(S), sym, state));
            if (microconfig_cat->is_spam())
               microconfig_cat->spam() << "unqualified symbol '" << protosym
                                       << "' with value '" << NextWord(S)
                                       << "'" << endl;
         } else {
            ConfigString scope(protosym.substr(0, i));
            ConfigString sym(protosym.substr(i+1, ConfigString::npos));
            (qualified[scope])[sym].push_back(SymEnt(SymEnt::CommandEnv,
                                                     NextWord(S), sym, state));
            if (microconfig_cat->is_spam())
               microconfig_cat->spam() << "qualified symbol '" << sym
                                       << "' in scope '" << scope
                                       << "' and value '" << NextWord(S)
                                       << "'" << endl;
         }
      } else if (microconfig_cat->is_spam())
         microconfig_cat->spam() << "'" << protosym
                                 << "' was not recognized as an option"
                                 << endl;
   }
}

void ConfigTable::ParseArgs(void)
{
   int n = 0;
   int num_args = ExecutionEnvironment::get_num_args();

   while ( n < num_args) {
     bool ok = false;
     bool state = false;
     ConfigString line(ExecutionEnvironment::get_arg(n));
     CropString(line);
     if (line[0] == '-')
       ok = true;
     else if (line[0] == '+') {
       ok = true;
       state = true;
     }
     if (ok) {
       line.erase(0, 1);
       CropString(line);
       size_t i(line.find("."));
       ConfigString aparam;
       if (n + 1 < num_args) {
         aparam = ExecutionEnvironment::get_arg(n + 1);
       }

       if (i == ConfigString::npos) {
         unqualified[line].push_back(SymEnt(SymEnt::Commandline,
                                            aparam, "", state));
         if (microconfig_cat->is_spam())
           microconfig_cat->spam() << "unqualified symbol '" << line
                                   << "' with value '" << aparam
                                   << "'" << endl;
       } else {
         ConfigString scope(line.substr(0, i));
         ConfigString sym(line.substr(i+1, ConfigString::npos));
         (qualified[scope])[sym].push_back(SymEnt(SymEnt::Commandline,
                                                  aparam, "", state));
         if (microconfig_cat->is_spam())
           microconfig_cat->spam() << "qualified symbol '" << sym
                                   << "' with scope '" << scope
                                   << "' and value '" << aparam
                                   << "'" << endl;
       }
     } else if (microconfig_cat->is_spam()) {
       microconfig_cat->spam() << "argument #" << n << " ('" << line
                               << "') is not recognized as an option"
                               << endl;
     }
     ++n;
   }
}

void ConfigTable::MicroConfig(void)
{
/*
#ifndef NDEBUG
   NotifySeverity mcs = microconfig_cat->get_severity();
   microconfig_cat->set_severity(NS_spam);

   NotifySeverity cs = config_cat->get_severity();
   config_cat->set_severity(NS_spam);
#else * NDEBUG *
*/
  //   NotifySeverity mcs = microconfig_cat->get_severity();
   microconfig_cat->set_severity(NS_info);

   //   NotifySeverity cs = dconfig_cat->get_severity();
   dconfig_cat->set_severity(NS_info);
/*
#endif * NDEBUG *
*/
   string cc = ExecutionEnvironment::get_environment_variable("CONFIG_CONFIG");
   if (microconfig_cat->is_spam())
      microconfig_cat->spam() << "CONFIG_CONFIG = '" << cc << "'" << endl;
   bool cdbg = false;
   bool psep = false;
   bool fsep = false;
   bool cname = false;
   bool csuff = false;
   bool cpath = false;
   bool ccmt = false;
   bool asuff = false;
   bool cstub = false;
   bool rdarg = false;
   bool rdenv = false;
   if (!cc.empty()) {
      ConfigString configconfig(cc);
      if (configconfig.length() > 1) {
         ConfigString assign = "=";
         ConfigString sep = configconfig.substr(0, 1);
         if (microconfig_cat->is_spam())
            microconfig_cat->spam() << "separator character is: '" << sep
                                    << "'" << endl;
         typedef std::vector<ConfigString> strvec;
         typedef Serialize::Deserializer<strvec, Serialize::StdExt<ConfigString> > deser;
         configconfig.erase(0, 1);
         deser ds(configconfig, sep);
         strvec sv = ds;
         if (microconfig_cat->is_spam())
            microconfig_cat->spam()
               << "extracted vector of microconfig options" << endl;
         for (strvec::iterator i=sv.begin(); i!=sv.end(); ++i) {
            if (microconfig_cat->is_spam())
               microconfig_cat->spam() << "parsing microconfig option '"
                                       << *i << "'" << endl;
            if ((*i).length() == 1) {
              // new assignment character
              assign += *i;
              continue;
            }
            size_t j = (*i).find_first_of(assign);
            if (j != ConfigString::npos) {
               ConfigString tok = (*i).substr(0, j);
               ConfigString rest = (*i).substr(j+1, ConfigString::npos);
               if (microconfig_cat->is_spam())
                  microconfig_cat->spam() << "split microconfig option into '"
                                          << tok << "' and '" << rest << "'"
                                          << endl;
               if (tok == "pathsep") {
                  pathsep = rest;
                  psep = true;
                  if (microconfig_cat->is_spam())
                     microconfig_cat->spam()
                        << "got a microconfig pathsep directive, "
                        << "setting the path separator to '" << pathsep << "'"
                        << endl;
               } else if (tok == "filesep") {
                  filesep = rest;
                  fsep = true;
                  if (microconfig_cat->is_spam())
                     microconfig_cat->spam()
                        << "got a microconfig filesep directive, "
                        << "setting the file separator to '" << filesep << "'"
                        << endl;
               } else if (tok == "configname") {
                  configname = rest;
                  cname = true;
                  if (microconfig_cat->is_spam())
                     microconfig_cat->spam()
                        << "got a microconfig configname directive, "
                        << "setting the configfile name to '" << configname
                        << "'" << endl;
               } else if (tok == "configsuffix") {
                  configsuffix = rest;
                  csuff = true;
                  if (microconfig_cat->is_spam())
                     microconfig_cat->spam()
                        << "got a microconfig configsuffix directive, "
                        << "setting the config file suffix to '"
                        << configsuffix << "'"
                        << endl;
               } else if (tok == "configpath") {
                  if (cpath) {
                    configpath += " " + rest;
                    if (microconfig_cat->is_spam())
                      microconfig_cat->spam()
                        << "got a microconfig configpath directive, "
                        << "adding '" << rest << "' to the configpath"
                        << endl;
                  } else {
                    configpath = rest;
                    if (microconfig_cat->is_spam())
                      microconfig_cat->spam()
                        << "got a microconfig configpath directive, "
                        << "setting the configpath to '" << configpath << "'"
                        << endl;
                  }
                  cpath = true;
               } else if (tok == "configcmt") {
                  configcmt = rest;
                  ccmt = true;
                  if (microconfig_cat->is_spam())
                     microconfig_cat->spam()
                        << "got a microconfig configcmt directive, "
                        << "setting the config comment to '" << configcmt
                        << "'" << endl;
               } else if (tok == "argsuffix") {
                  argsuffix = rest;
                  asuff = true;
                  if (microconfig_cat->is_spam())
                     microconfig_cat->spam()
                        << "got a microconfig argsuffix directive, "
                        << "setting the argument environment suffix to '"
                        << argsuffix << "'" << endl;
               } else if (tok == "commandstub") {
                  commandstub = rest;
                  cstub = true;
                  if (microconfig_cat->is_spam())
                     microconfig_cat->spam()
                        << "got a microconfig commandstub directive, "
                        << "setting the command environment stub "
                        << "to '" << commandstub << "'" << endl;
               } else if (tok == "configdbg") {
                  configdbg = TrueOrFalse(rest);
                  cdbg = true;
                  if (configdbg) {
                     microconfig_cat->set_severity(NS_spam);
                     dconfig_cat->set_severity(NS_spam);
                  } else {
                     microconfig_cat->set_severity(NS_info);
                     dconfig_cat->set_severity(NS_info);
                  }
                  if (microconfig_cat->is_spam())
                     microconfig_cat->spam()
                        << "got a microconfig configdbg directive, "
                        << "setting the config spam state to " << configdbg
                        << endl;
               } else if (tok == "readargs") {
                  readargs = TrueOrFalse(rest);
                  rdarg = true;
                  if (microconfig_cat->is_spam())
                     microconfig_cat->spam()
                        << "got a microconfig readargs directive, "
                        << (readargs?"will":"will not")
                        << " read from the commandline." << endl;
               } else if (tok == "readenv") {
                  readenvs = TrueOrFalse(rest);
                  rdenv = true;
                  if (microconfig_cat->is_spam())
                     microconfig_cat->spam()
                        << "got a microconfig readenv directive, "
                        << (readargs?"will":"will not")
                        << " read the environment." << endl;
               }
            } else if (microconfig_cat->is_spam())
               microconfig_cat->spam()
                  << "no '=' in microconfig option, ignoring it" << endl;
         }
      } else if (microconfig_cat->is_spam())
         microconfig_cat->spam()
            << "CONFIG_CONFIG contains only a single character" << endl;
   } else if (microconfig_cat->is_spam())
      microconfig_cat->spam() << "CONFIG_CONFIG is empty" << endl;
   if (!cdbg)
      ConfigDbgDefault();
   if (!psep) {
      PathSepDefault();
      if (microconfig_cat->is_spam())
         microconfig_cat->spam() << "no microconfig for pathsep, "
                                  << "setting to default '" << pathsep << "'"
                                  << endl;
   }
   if (!fsep) {
      FileSepDefault();
      if (microconfig_cat->is_spam())
        microconfig_cat->spam() << "no microconfig for filesep, "
                                << "setting to default '" << filesep << "'"
                                << endl;
   }
   if (!cname) {
      ConfigNameDefault();
      if (microconfig_cat->is_spam())
        microconfig_cat->spam() << "no microconfig for configname, "
                                << "setting to default '" << configname
                                << "'" << endl;
   }
   if (!csuff) {
      ConfigSuffixDefault();
      if (microconfig_cat->is_spam())
        microconfig_cat->spam() << "no microconfig for configsuffix, "
                                << "setting to default '" << configsuffix
                                << "'" << endl;
   }
   if (!cpath) {
      ConfigPathDefault();
      if (microconfig_cat->is_spam())
        microconfig_cat->spam() << "no microconfig for configpath, "
                                << "setting to default '" << configpath
                                << "'" << endl;
   }
   if (!ccmt) {
      ConfigCmtDefault();
      if (microconfig_cat->is_spam())
        microconfig_cat->spam() << "no microconfig for configcmt, "
                                << "setting to default '" << configcmt
                                << "'" << endl;
   }
   if (!asuff) {
      ArgSuffixDefault();
      if (microconfig_cat->is_spam())
        microconfig_cat->spam() << "no microconfig for argsuffix, "
                                << "setting to default '" << argsuffix
                                << "'" << endl;
   }
   if (!cstub) {
      CommandStubDefault();
      if (microconfig_cat->is_spam())
        microconfig_cat->spam() << "no microconfig for commandstub, "
                                << "setting to default '" << commandstub
                                << "'" << endl;
   }
   if (!rdarg) {
      ReadArgsDefault();
      if (microconfig_cat->is_spam())
        microconfig_cat->spam() << "no microconfig for readargs, "
                                << "setting to default: "
                                << (readargs?"true":"false") << endl;
   }
   if (!rdenv) {
      ReadEnvsDefault();
      if (microconfig_cat->is_spam())
        microconfig_cat->spam() << "no microconfig for readenv, "
                                << "setting to default: "
                                << (readargs?"true":"false") << endl;
   }
}

void ConfigTable::GetData(void) {
  MicroConfig();
#ifndef DISABLE_CONFIG
  ReadConfigFile();
  if (readenvs) {
    ConfigString comarg = commandstub + argsuffix;
    if (microconfig_cat->is_spam())
      microconfig_cat->spam() << "comarg is '" << comarg << "'"
                              << endl;
    if (ExecutionEnvironment::has_environment_variable(comarg)) {
      ConfigString env = ExecutionEnvironment::get_environment_variable(comarg);
      ParseCommandEnv(env, comarg);
    }
    ConfigString line = ExecutionEnvironment::get_binary_name() + argsuffix;
    UpCase(line);
    if (microconfig_cat->is_spam())
      microconfig_cat->spam() << "binarg is '" << line << "'"
                              << endl;
    if (ExecutionEnvironment::has_environment_variable(line)) {
      ConfigString env = ExecutionEnvironment::get_environment_variable(line);
      ParseCommandEnv(env, line);
    }
  }
  if (readargs)
    ParseArgs();
#endif  // DISABLE_CONFIG
}

ConfigTable* ConfigTable::Instance(void) {
  if (_instance == (ConfigTable*)0L) {
    _instance = new ConfigTable;
    _instance->GetData();
    _instance->_initializing = false;
    Notify::ptr()->config_initialized();
  }
  return _instance;
}

bool ConfigTable::AmInitializing(void) {
  return _initializing;
}

bool ConfigTable::TrueOrFalse(const ConfigString& in, bool def) {
  bool ret = def;
  ConfigString S = in;
  UpCase(S);
  if (S[0] == '#') {
    if (S[1] == 'F')
      ret = false;
    else if (S[1] == 'T')
      ret = true;
  } else if (S == "0") {
    ret = false;
  } else if (S == "1") {
    ret = true;
  } else if (S == "FALSE") {
    ret = false;
  } else if (S == "TRUE") {
    ret = true;
  }
  return ret;
}

bool ConfigTable::Defined(const ConfigString& sym,
                                 const ConfigString qual) {
#ifdef DISABLE_CONFIG
  return false;
#else
  if (qual.empty()) {
    return (unqualified.count(sym) != 0 ||
            ExecutionEnvironment::has_environment_variable(sym));

  } else {
    TableMap::const_iterator ti;
    ti = qualified.find(qual);
    if (ti != qualified.end()) {
      const SymbolTable &table = (*ti).second;
      if (table.count(sym) != 0) {
        return true;
      }
    }

    return ExecutionEnvironment::has_environment_variable(qual + "." + sym);
  }
#endif // DISABLE_CONFIG
}

ConfigTable::SymEnt ConfigTable::Get(const ConfigString& sym,
                                     const ConfigString qual) {
#ifndef DISABLE_CONFIG
  const ConfigTable::Symbol &symbol = GetSym(sym, qual);
  if (!symbol.empty()) {
    return symbol.back();
  }

  // No explicit config definition; fall back to the environment.
  string envvar = sym;
  if (!qual.empty()) {
    envvar = qual + "." + sym;
  }

  if (ExecutionEnvironment::has_environment_variable(sym)) {
    string def = ExecutionEnvironment::get_environment_variable(sym);
    return ConfigTable::SymEnt(ConfigTable::SymEnt::Environment, def);
  }
#endif // DISABLE_CONFIG

  // No definition for the variable.  Too bad for you.
  return ConfigTable::SymEnt();
}

const ConfigTable::Symbol& ConfigTable::GetSym(const ConfigString& sym,
                                               const ConfigString qual) {
  static ConfigTable::Symbol empty_symbol;

#ifndef DISABLE_CONFIG
  total_num_get++;
  if (qual.empty()) {
    SymbolTable::const_iterator si;
    si = unqualified.find(sym);
    if (si != unqualified.end()) {
      return (*si).second;
    }

  } else {
    TableMap::const_iterator ti;
    ti = qualified.find(qual);
    if (ti != qualified.end()) {
      const SymbolTable &table = (*ti).second;
      SymbolTable::const_iterator si;
      si = table.find(sym);
      if (si != table.end()) {
        return (*si).second;
      }
    }
  }
#endif // DISABLE_CONFIG

  return empty_symbol;
}
