// Filename: win32ArgParser.cxx
// Created by:  drose (08Nov11)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University.  All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license.  You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////

#include "win32ArgParser.h"

#ifdef _WIN32

#include "memoryBase.h"
#include "textEncoder.h"
#include "globPattern.h"
#include "filename.h"
#include "executionEnvironment.h"

#include <windows.h>
#include <Tlhelp32.h>

////////////////////////////////////////////////////////////////////
//     Function: Win32ArgParser::Constructor
//       Access: Public
//  Description: 
////////////////////////////////////////////////////////////////////
Win32ArgParser::
Win32ArgParser() :
  _argv(NULL),
  _argc(0)
{
}

////////////////////////////////////////////////////////////////////
//     Function: Win32ArgParser::Destructor
//       Access: Public
//  Description: 
////////////////////////////////////////////////////////////////////
Win32ArgParser::
~Win32ArgParser() {
  clear();
}

////////////////////////////////////////////////////////////////////
//     Function: Win32ArgParser::clear
//       Access: Public
//  Description: Resets the parser to empty command line and
//               deallocates the internal argv array.
////////////////////////////////////////////////////////////////////
void Win32ArgParser::
clear() {
  assert(_argc == (int)_args.size());

  if (_argv != NULL) {
    for (int i = 0; i < _argc; ++i) {
      PANDA_FREE_ARRAY(_argv[i]);
    }
    PANDA_FREE_ARRAY(_argv);
    _argv = NULL;
  }

  _argc = 0;
  _args.clear();
}

////////////////////////////////////////////////////////////////////
//     Function: Win32ArgParser::set_command_line
//       Access: Public
//  Description: Sets the string that indicates the full Win32 command
//               line, and starts parsing this into argc, argv.
////////////////////////////////////////////////////////////////////
void Win32ArgParser::
set_command_line(const string &command_line) {
  clear();
  
  const char *p = command_line.c_str();
  while (*p != '\0') {
    parse_unquoted_arg(p);

    // Skip whitespace.
    while (*p != '\0' && isspace(*p)) {
      ++p;
    }
  }

  assert(_argc == 0 && _argv == NULL);
  _argc = (int)_args.size();
  _argv = (char **)PANDA_MALLOC_ARRAY(_argc * sizeof(char *));
  for (int i = 0; i < _argc; ++i) {
    const string &arg = _args[i];
    char *astr = (char *)PANDA_MALLOC_ARRAY(arg.size() + 1);
    memcpy(astr, arg.data(), arg.size());
    astr[arg.size()] = '\0';
    _argv[i] = astr;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: Win32ArgParser::set_command_line
//       Access: Public
//  Description: Sets the Unicode string that indicates the full Win32
//               command line, and starts parsing this into argc,
//               argv.
////////////////////////////////////////////////////////////////////
void Win32ArgParser::
set_command_line(const wstring &command_line) {
  TextEncoder encoder;
  encoder.set_encoding(Filename::get_filesystem_encoding());
  encoder.set_wtext(command_line);
  set_command_line(encoder.get_text());
}

////////////////////////////////////////////////////////////////////
//     Function: Win32ArgParser::set_system_command_line
//       Access: Public
//  Description: Tells the parser to call GetCommandLine() to query
//               the system command line string, and parse it into
//               argc, argv.
////////////////////////////////////////////////////////////////////
void Win32ArgParser::
set_system_command_line() {
  LPWSTR command_line = GetCommandLineW();
  set_command_line(command_line);
}

////////////////////////////////////////////////////////////////////
//     Function: Win32ArgParser::get_argv
//       Access: Public
//  Description: Returns the argv array as computed by
//               set_command_line() or set_system_command_line().
//               This array indexes directly into data allocated
//               within the Win32ArgParser object; it will remain
//               valid until set_command_line() or clear() is again
//               called, or until the parser object destructs.
////////////////////////////////////////////////////////////////////
char **Win32ArgParser::
get_argv() {
  return _argv;
}

////////////////////////////////////////////////////////////////////
//     Function: Win32ArgParser::get_argc
//       Access: Public
//  Description: Returns the number of elements in the argv array.
////////////////////////////////////////////////////////////////////
int Win32ArgParser::
get_argc() {
  return _argc;
}

////////////////////////////////////////////////////////////////////
//     Function: Win32ArgParser::do_glob
//       Access: Public, Static
//  Description: Returns true if we should attempt to process (and
//               apply glob matching) to the command line, or false if
//               we should not (for instance, because it has already
//               been done by the shell).
////////////////////////////////////////////////////////////////////
bool Win32ArgParser::
do_glob() {
  // First, we check for the PANDA_GLOB environment variable.  If this
  // is present, it overrides any other checks: "0" means not to do
  // the glob, "1" means to do it.
  string envvar = ExecutionEnvironment::get_environment_variable("PANDA_GLOB");
  if (!envvar.empty()) {
    istringstream strm(envvar);
    int value;
    strm >> value;
    if (!strm.fail()) {
      return (value != 0);
    }
  }

  // Nothing explicit, so the default is to perform globbing only if
  // we were launched from the Windows default command shell, cmd.exe.
  // Presumably if we were launched from something else, like Python,
  // the caller won't expect globbing to be performed; and if we were
  // launched from a Cygwin shell, it will already have been
  // performed.

  // Unfortunately, it is surprisingly difficult to determine the
  // parent process in Windows.  We have to enumerate all of the
  // processes to find it.

  HANDLE toolhelp = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

  PROCESSENTRY32 entry;
  memset(&entry, 0, sizeof(entry));
  entry.dwSize = sizeof(entry);

  DWORD current_id = GetCurrentProcessId();
  DWORD parent_id = -1;

  if (Process32First(toolhelp, &entry)) {
    do {
      if (entry.th32ProcessID == current_id) {
        parent_id = entry.th32ParentProcessID;
        break;
      }
    } while (Process32Next(toolhelp, &entry));
  }

  Filename parent_exe;
  if (parent_id != -1) {
    // Now we've got the parent process ID, go back through the list
    // to get its process name.
    if (Process32First(toolhelp, &entry)) {
      do {
        if (entry.th32ProcessID == parent_id) {
          parent_exe = Filename::from_os_specific(entry.szExeFile);
          break;
        }
      } while (Process32Next(toolhelp, &entry));
    }
  }

  CloseHandle(toolhelp);
  string basename = parent_exe.get_basename();
  if (basename == "cmd.exe") {
    return true;
  }

  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: Win32ArgParser::parse_quoted_arg
//       Access: Private
//  Description: Parses the quoted argument beginning at p and returns
//               it.  Advances p to the first character following the
//               close quote.
////////////////////////////////////////////////////////////////////
string Win32ArgParser::
parse_quoted_arg(const char *&p) {
  char quote = *p;
  ++p;
  string result;

  while (*p != '\0' && *p != quote) {
    // TODO: handle caret?  What does it mean?

    if (*p == '\\') {
      // A backslash is an escape character only when it precedes a
      // quote mark, or a series of backslashes precede a quote mark.
      int num_slashes = 1;
      ++p;
      while (*p == '\\') {
        ++p;
        ++num_slashes;
      }
      if (*p == quote) {
        // A series of backslashes precede a quote mark.  This means
        // something special.  First, each pair of backslashes means a
        // single backslash.
        for (int i = 0; i < num_slashes; i += 2) {
          result += '\\';
        }
        // And if there's no odd backslashes left over, we've reached
        // the closing quote and we're done.
        if ((num_slashes & 1) == 0) {
          ++p;
          return result;
        }
        
        // But if there's an odd backslash, it simply escapes the
        // quote mark.
        result += quote;
        ++p;

      } else {
        // A series of backslashes not followed by a quote mark is
        // interpreted literally, not even counting them by twos, per
        // Win32's weird rules.
        for (int i = 0; i < num_slashes; ++i) {
          result += '\\';
        }
      }

    } else {
      // Neither a backslash nor a quote mark, so just interpret it
      // literally.
      result += *p;
      ++p;
    }
  }

  if (*p == quote) {
    ++p;
  }

  return result;
}

////////////////////////////////////////////////////////////////////
//     Function: Win32ArgParser::parse_unquoted_arg
//       Access: Private
//  Description: Parses the unquoted argument beginning at p and saves
//               it in _char_args.  Advances p to the first whitespace
//               following the argument.
////////////////////////////////////////////////////////////////////
void Win32ArgParser::
parse_unquoted_arg(const char *&p) {
  string result;
  bool contains_quotes = false;
  while (*p != '\0' && !isspace(*p)) {
    if (*p == '"') {
      // Begin a quoted sequence.
      contains_quotes = true;
      result += parse_quoted_arg(p);
    } else {
      // A regular character.
      result += *p;
      ++p;
    }
  }

  Filename filename = Filename::from_os_specific(result);
  GlobPattern glob(filename);
  if (!contains_quotes && glob.has_glob_characters()) {
    // If the arg contains one or more glob characters (and no
    // quotation marks), we attempt to expand the files.  This means
    // we interpret it as a Windows-specific filename.
    vector_string expand;
    if (glob.match_files(expand) != 0) {
      // The files matched.  Add the expansions.
      vector_string::const_iterator ei;
      for (ei = expand.begin(); ei != expand.end(); ++ei) {
        Filename filename(*ei);
        save_arg(filename.to_os_specific());
      }
    } else {
      // There wasn't a match.  Just add the original, unexpanded
      // string, like bash does.
      save_arg(result);
    }
    
  } else {
    // No glob characters means we just store it directly.  Also, an
    // embedded quoted string, anywhere within the arg, means we can't
    // expand the glob characters.
    save_arg(result);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: Win32ArgParser::save_arg
//       Access: Private
//  Description: Stores the indicated string as the next argument in
//               _args.
////////////////////////////////////////////////////////////////////
void Win32ArgParser::
save_arg(const string &arg) {
  _args.push_back(arg);
}

#endif  // _WIN32
