// Filename: panda3d.cxx
// Created by:  drose (03Jun09)
//
////////////////////////////////////////////////////////////////////
//
// 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 "panda3d.h"
#include "httpClient.h"
#include "load_plugin.h"
#include "find_root_dir.h"
#include "p3d_plugin_config.h"

// We can include this header file to get the DTOOL_PLATFORM
// definition, even though we don't link with dtool.
#include "dtool_platform.h"
#include "pandaVersion.h"

#include <ctype.h>
#include <sstream>
#include <algorithm>
#ifdef _WIN32
#include <windows.h>
#else
#include <signal.h>
#endif

#ifndef HAVE_GETOPT
  #include "gnu_getopt.h"
#else
  #ifdef PHAVE_GETOPT_H
    #include <getopt.h>
  #endif
#endif


// The amount of time in seconds to wait for new messages.
static const double wait_cycle = 0.2;

////////////////////////////////////////////////////////////////////
//     Function: Panda3D::Constructor
//       Access: Public
//  Description: 
////////////////////////////////////////////////////////////////////
Panda3D::
Panda3D(bool console_environment) {
  _console_environment = console_environment;

  _root_dir = find_root_dir();
  _reporting_download = false;
  _enable_security = false;

  _window_type = P3D_WT_toplevel;

  _win_x = 0;
  _win_y = 0;
  _win_width = 640;
  _win_height = 480;

  _exit_with_last_instance = true;
  _host_url = PANDA_PACKAGE_HOST_URL;
  _this_platform = DTOOL_PLATFORM;
  _verify_contents = false;

  // Seed the lame random number generator in rand(); we use it to
  // select a mirror for downloading.
  srand((unsigned int)time(NULL));
}

////////////////////////////////////////////////////////////////////
//     Function: Panda3D::run_command_line
//       Access: Public
//  Description: Starts the program going, with command-line arguments.  
//               Returns 0 on success, nonzero on failure.
////////////////////////////////////////////////////////////////////
int Panda3D::
run_command_line(int argc, char *argv[]) {
  extern char *optarg;
  extern int optind;

  // We prefix a "+" sign to tell gnu getopt not to parse options
  // following the first not-option parameter.  (These will be passed
  // into the sub-process.)
  const char *optstr = "+mu:M:Sp:fw:t:s:o:l:ih";

  bool allow_multiple = false;

  int flag = getopt(argc, argv, optstr);

  while (flag != EOF) {
    switch (flag) {
    case 'm':
      allow_multiple = true;
      break;

    case 'u':
      _host_url = optarg;
      break;

    case 'M':
      _super_mirror_url = optarg;
      break;

    case 'S':
      _enable_security = true;
      break;

    case 'p':
      _this_platform = optarg;
      break;

    case 'f':
      _verify_contents = true;
      break;

    case 'w':
      if (strcmp(optarg, "toplevel") == 0) {
        _window_type = P3D_WT_toplevel;
      } else if (strcmp(optarg, "embedded") == 0) {
        _window_type = P3D_WT_embedded;
      } else if (strcmp(optarg, "fullscreen") == 0) {
        _window_type = P3D_WT_fullscreen;
      } else if (strcmp(optarg, "hidden") == 0) {
        _window_type = P3D_WT_hidden;
      } else {
        cerr << "Invalid value for -w: " << optarg << "\n";
        return 1;
      }
      break;

    case 't':
      if (!parse_token(optarg)) {
        cerr << "Web tokens (-t) must be of the form token=value: " << optarg << "\n";
        return 1;
      }
      break;

    case 's':
      if (!parse_int_pair(optarg, _win_width, _win_height)) {
        cerr << "Invalid value for -s: " << optarg << "\n";
        return 1;
      }
      break;

    case 'o':
      if (!parse_int_pair(optarg, _win_x, _win_y)) {
        cerr << "Invalid value for -o: " << optarg << "\n";
        return 1;
      }
      break;

    case 'l':
      _log_dirname = Filename::from_os_specific(optarg).to_os_specific();
      _log_basename = "p3dcore";
      break;

    case 'i':
      {
        P3D_token token;
        token._keyword = "keep_pythonpath";
        token._value = "1";
        _tokens.push_back(token);
        token._keyword = "interactive_console";
        token._value = "1";
        _tokens.push_back(token);

        // We should also ignore control-C in this case, so that an
        // interrupt will be delivered to the subordinate Python
        // process and return to a command shell, and won't just kill
        // the panda3d process.
#ifdef _WIN32
        SetConsoleCtrlHandler(NULL, true);
#else
        struct sigaction ignore;
        memset(&ignore, 0, sizeof(ignore));
        ignore.sa_handler = SIG_IGN;
        sigaction(SIGINT, &ignore, NULL);
#endif  // _WIN32
      }
      break;

    case 'h':
    case '?':
    case '+':
    default:
      usage();
      return 1;
    }
    flag = getopt(argc, argv, optstr);
  }

  argc -= (optind-1);
  argv += (optind-1);

  if (argc < 2 && _exit_with_last_instance) {
    // No instances on the command line--that *might* be an error.
    usage();
    return 1;
  }

  if (!post_arg_processing()) {
    return 1;
  }

  int num_instance_filenames, num_instance_args;
  char **instance_filenames, **instance_args;

  if (argc > 1) {
    if (allow_multiple) {
      // With -m, the remaining arguments are all instance filenames.
      num_instance_filenames = argc - 1;
      instance_filenames = argv + 1;
      num_instance_args = 0;
      instance_args = argv + argc;
      
    } else {
      // Without -m, there is one instance filename, and everything else
      // gets delivered to that instance.
      num_instance_filenames = 1;
      instance_filenames = argv + 1;
      num_instance_args = argc - 2;
      instance_args = argv + 2;
    }
    
    if (_window_type == P3D_WT_embedded) {
      // The user asked for an embedded window.  Create a toplevel
      // window to be its parent, of the requested size.
      if (_win_width == 0 && _win_height == 0) {
        _win_width = 640;
        _win_height = 480;
      }
      
      make_parent_window();
      
      // Center the child window(s) within the parent window.
#ifdef _WIN32
      assert(_parent_window._window_handle_type == P3D_WHT_win_hwnd);
      HWND parent_hwnd = _parent_window._handle._win_hwnd._hwnd;
      
      RECT rect;
      GetClientRect(parent_hwnd, &rect);
      
      _win_x = (int)(rect.right * 0.1);
      _win_y = (int)(rect.bottom * 0.1);
      _win_width = (int)(rect.right * 0.8);
      _win_height = (int)(rect.bottom * 0.8);
#endif
      
      // Subdivide the window into num_x_spans * num_y_spans sub-windows.
      int num_y_spans = int(sqrt((double)num_instance_filenames));
      int num_x_spans = (num_instance_filenames + num_y_spans - 1) / num_y_spans;
      
      int inst_width = _win_width / num_x_spans;
      int inst_height = _win_height / num_y_spans;
      
      for (int yi = 0; yi < num_y_spans; ++yi) {
        for (int xi = 0; xi < num_x_spans; ++xi) {
          int i = yi * num_x_spans + xi;
          if (i >= num_instance_filenames) {
            continue;
          }
          
          // Create instance i at window slot (xi, yi).
          int inst_x = _win_x + xi * inst_width;
          int inst_y = _win_y + yi * inst_height;
          
          P3D_instance *inst = create_instance
            (instance_filenames[i], true,
             inst_x, inst_y, inst_width, inst_height,
             instance_args, num_instance_args);
          _instances.insert(inst);
        }
      }
      
    } else {
      // Not an embedded window.  Create each window with the same parameters.
      for (int i = 0; i < num_instance_filenames; ++i) {
        P3D_instance *inst = create_instance
          (instance_filenames[i], true, 
           _win_x, _win_y, _win_width, _win_height,
           instance_args, num_instance_args);
        _instances.insert(inst);
      }
    }
  }
    
  run_main_loop();

  // All instances have finished; we can exit.
  unload_plugin();
  return 0;
}

////////////////////////////////////////////////////////////////////
//     Function: Panda3D::run_main_loop
//       Access: Public
//  Description: Gets lost in the application main loop, waiting for
//               system events and notifications from the open
//               instance(s).
////////////////////////////////////////////////////////////////////
void Panda3D::
run_main_loop() {
#ifdef _WIN32
  if (_window_type == P3D_WT_embedded) {
    // Wait for new messages from Windows, and new requests from the
    // plugin.
    MSG msg;
    int retval;
    retval = GetMessage(&msg, NULL, 0, 0);
    while (retval != 0 && !time_to_exit()) {
      if (retval == -1) {
        cerr << "Error processing message queue.\n";
        return;
      }
      TranslateMessage(&msg);
      DispatchMessage(&msg);

      // Check for new requests from the Panda3D plugin.
      P3D_instance *inst = P3D_check_request(wait_cycle);
      while (inst != (P3D_instance *)NULL) {
        P3D_request *request = P3D_instance_get_request(inst);
        if (request != (P3D_request *)NULL) {
          handle_request(request);
        }
        inst = P3D_check_request(wait_cycle);
      }

      while (!_url_getters.empty() && 
             !PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
        // If there are no Windows messages, check the download tasks.
        run_getters();
      }
      retval = GetMessage(&msg, NULL, 0, 0);
    }
    
    // WM_QUIT has been received.  Terminate all instances, and fall
    // through.
    while (!_instances.empty()) {
      P3D_instance *inst = *(_instances.begin());
      delete_instance(inst);
    }

  } else {
    // Not an embedded window, so we don't have our own window to
    // generate Windows events.  Instead, just wait for requests.
    while (!time_to_exit()) {
      P3D_instance *inst = P3D_check_request(wait_cycle);
      if (inst != (P3D_instance *)NULL) {
        P3D_request *request = P3D_instance_get_request(inst);
        if (request != (P3D_request *)NULL) {
          handle_request(request);
        }
      }
      run_getters();
    }
  }

#elif defined(__APPLE__)
  // OSX really prefers to own the main loop, so we install a timer to
  // call out to our instances and getters, rather than polling within
  // the event loop as we do in the Windows case, above.
  EventLoopRef main_loop = GetMainEventLoop();
  EventLoopTimerUPP timer_upp = NewEventLoopTimerUPP(st_timer_callback);
  EventLoopTimerRef timer;
  EventTimerInterval interval = wait_cycle * kEventDurationSecond;
  InstallEventLoopTimer(main_loop, interval, interval,
                        timer_upp, this, &timer);
  RunApplicationEventLoop();
  RemoveEventLoopTimer(timer);
  
  // Terminate all instances, and fall through.
  while (!_instances.empty()) {
    P3D_instance *inst = *(_instances.begin());
    delete_instance(inst);
  }
    
#else  // _WIN32, __APPLE__

  // Now wait while we process pending requests.
  while (!time_to_exit()) {
    P3D_instance *inst = P3D_check_request(wait_cycle);
    if (inst != (P3D_instance *)NULL) {
      P3D_request *request = P3D_instance_get_request(inst);
      if (request != (P3D_request *)NULL) {
        handle_request(request);
      }
    }
    run_getters();
  }

#endif  // _WIN32, __APPLE__
}

////////////////////////////////////////////////////////////////////
//     Function: Panda3D::post_arg_processing
//       Access: Protected
//  Description: Sets up some internal state after processing the
//               command-line arguments.  Returns true on success,
//               false on failure.
////////////////////////////////////////////////////////////////////
bool Panda3D::
post_arg_processing() {
  // Set host_url_prefix to end with a slash.
  _host_url_prefix = _host_url;
  if (!_host_url_prefix.empty() && _host_url_prefix[_host_url_prefix.length() - 1] != '/') {
    _host_url_prefix += '/';
  }
  _download_url_prefix = _host_url_prefix;

  // If the "super mirror" URL is a filename, convert it to a file:// url.
  if (!_super_mirror_url.empty()) {
    if (!is_url(_super_mirror_url)) {
      Filename filename = Filename::from_os_specific(_super_mirror_url);
      filename.make_absolute();
      string path = filename.to_os_generic();
      if (!path.empty() && path[0] != '/') {
        // On Windows, a leading drive letter must be preceded by an
        // additional slash.
        path = "/" + path;
      }
      _super_mirror_url = "file://" + path;
    }

    // And make sure the super_mirror_url_prefix ends with a slash.
    _super_mirror_url_prefix = _super_mirror_url;
    if (!_super_mirror_url_prefix.empty() && _super_mirror_url_prefix[_super_mirror_url_prefix.length() - 1] != '/') {
      _super_mirror_url_prefix += '/';
    }
  }

  if (!get_plugin()) {
    cerr << "Unable to load Panda3D plugin.\n";
    return false;
  }

  // Set up the "super mirror" URL, if specified.
  if (!_super_mirror_url.empty()) {
    P3D_set_super_mirror(_super_mirror_url.c_str());
  }

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: Panda3D::get_plugin
//       Access: Protected
//  Description: Downloads the contents.xml file from the named URL
//               and attempts to use it to load the core API.  Returns
//               true on success, false on failure.
////////////////////////////////////////////////////////////////////
bool Panda3D::
get_plugin() {
  // First, look for the existing contents.xml file.
  Filename contents_filename = Filename(Filename::from_os_specific(_root_dir), "contents.xml");
  if (!_verify_contents && read_contents_file(contents_filename)) {
    // Got the file, and it's good.
    return true;
  }

  // Couldn't read it, so go get it.
  HTTPClient *http = HTTPClient::get_global_ptr();

  // Try the super_mirror first.
  if (!_super_mirror_url_prefix.empty()) {
    // We don't bother putting a uniquifying query string when we're
    // downloading this file from the super_mirror.  The super_mirror
    // is by definition a cache, so it doesn't make sense to bust
    // caches here.
    string url = _super_mirror_url_prefix + "contents.xml";
    PT(HTTPChannel) channel = http->make_channel(false);
    channel->get_document(url);

    Filename tempfile = Filename::temporary("", "p3d_");
    if (!channel->download_to_file(tempfile)) {
      cerr << "Unable to download " << url << "\n";
      tempfile.unlink();
    } else {
      // Successfully downloaded from the super_mirror; move it into
      // place and try to read it.
      contents_filename.make_dir();
      contents_filename.unlink();
      tempfile.rename_to(contents_filename);
      if (read_contents_file(contents_filename)) {
        return true;
      }
    }

    // Failed to download from the super_mirror.
  }

  // Go download contents.xml from the actual host.
  ostringstream strm;
  strm << _host_url_prefix << "contents.xml";
  // Append a uniquifying query string to the URL to force the
  // download to go all the way through any caches.  We use the time
  // in seconds; that's unique enough.
  strm << "?" << time(NULL);
  string url = strm.str();

  // We might as well explicitly request the cache to be disabled too,
  // since we have an interface for that via HTTPChannel.
  DocumentSpec request(url);
  request.set_cache_control(DocumentSpec::CC_no_cache);

  PT(HTTPChannel) channel = http->make_channel(false);
  channel->get_document(request);

  // First, download it to a temporary file.
  Filename tempfile = Filename::temporary("", "p3d_");
  if (!channel->download_to_file(tempfile)) {
    cerr << "Unable to download " << url << "\n";
    tempfile.unlink();

    // Couldn't download, but fall through and try to read the
    // contents.xml file anyway.  Maybe it's good enough.
  } else {
    // Successfully downloaded; move the temporary file into place.
    contents_filename.make_dir();
    contents_filename.unlink();
    tempfile.rename_to(contents_filename);
  }

  // Since we had to download some of it, might as well ask the core
  // API to check all of it.
  _verify_contents = true;

  return read_contents_file(contents_filename);
}

////////////////////////////////////////////////////////////////////
//     Function: Panda3D::read_contents_file
//       Access: Protected
//  Description: Attempts to open and read the contents.xml file on
//               disk, and uses that data to load the plugin, if
//               possible.  Returns true on success, false on failure.
////////////////////////////////////////////////////////////////////
bool Panda3D::
read_contents_file(const Filename &contents_filename) {
  string os_contents_filename = contents_filename.to_os_specific();
  TiXmlDocument doc(os_contents_filename.c_str());
  if (!doc.LoadFile()) {
    return false;
  }

  TiXmlElement *xcontents = doc.FirstChildElement("contents");
  if (xcontents != NULL) {
    // Look for the <host> entry; it might point us at a different
    // download URL, and it might mention some mirrors.
    find_host(xcontents);

    // Now look for the core API package.
    TiXmlElement *xpackage = xcontents->FirstChildElement("package");
    while (xpackage != NULL) {
      const char *name = xpackage->Attribute("name");
      if (name != NULL && strcmp(name, "coreapi") == 0) {
        const char *platform = xpackage->Attribute("platform");
        if (platform != NULL && _this_platform == string(platform)) {
          return get_core_api(contents_filename, xpackage);
        }
      }
    
      xpackage = xpackage->NextSiblingElement("package");
    }
  }

  // Couldn't find the coreapi package description.
  nout << "No coreapi package defined in contents file for "
       << _this_platform << "\n";
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: Panda3D::find_host
//       Access: Protected
//  Description: Scans the <contents> element for the matching <host>
//               element.
////////////////////////////////////////////////////////////////////
void Panda3D::
find_host(TiXmlElement *xcontents) {
  TiXmlElement *xhost = xcontents->FirstChildElement("host");
  if (xhost != NULL) {
    const char *url = xhost->Attribute("url");
    if (url != NULL && _host_url == string(url)) {
      // We're the primary host.  This is the normal case.
      read_xhost(xhost);
      return;
      
    } else {
      // We're not the primary host; perhaps we're an alternate host.
      TiXmlElement *xalthost = xhost->FirstChildElement("alt_host");
      while (xalthost != NULL) {
        const char *url = xalthost->Attribute("url");
        if (url != NULL && _host_url == string(url)) {
          // Yep, we're this alternate host.
          read_xhost(xhost);
          return;
        }
        xalthost = xalthost->NextSiblingElement("alt_host");
      }
    }

    // Hmm, didn't find the URL we used mentioned.  Assume we're the
    // primary host.
    read_xhost(xhost);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: Panda3D::read_xhost
//       Access: Protected
//  Description: Reads the host data from the <host> (or <alt_host>)
//               entry in the contents.xml file.
////////////////////////////////////////////////////////////////////
void Panda3D::
read_xhost(TiXmlElement *xhost) {
  // Get the "download" URL, which is the source from which we
  // download everything other than the contents.xml file.
  const char *download_url = xhost->Attribute("download_url");
  if (download_url == NULL) {
    download_url = xhost->Attribute("url");
  }

  if (download_url != NULL) {
    _download_url_prefix = download_url;
  } else {
    _download_url_prefix = _host_url_prefix;
  }
  if (!_download_url_prefix.empty()) {
    if (_download_url_prefix[_download_url_prefix.size() - 1] != '/') {
      _download_url_prefix += "/";
    }
  }
        
  TiXmlElement *xmirror = xhost->FirstChildElement("mirror");
  while (xmirror != NULL) {
    const char *url = xmirror->Attribute("url");
    if (url != NULL) {
      add_mirror(url);
    }
    xmirror = xmirror->NextSiblingElement("mirror");
  }
}

////////////////////////////////////////////////////////////////////
//     Function: Panda3D::add_mirror
//       Access: Protected
//  Description: Adds a new URL to serve as a mirror for this host.
//               The mirrors will be consulted first, before
//               consulting the host directly.
////////////////////////////////////////////////////////////////////
void Panda3D::
add_mirror(string mirror_url) {
  // Ensure the URL ends in a slash.
  if (!mirror_url.empty() && mirror_url[mirror_url.size() - 1] != '/') {
    mirror_url += '/';
  }
  
  // Add it to the _mirrors list, but only if it's not already
  // there.
  if (find(_mirrors.begin(), _mirrors.end(), mirror_url) == _mirrors.end()) {
    _mirrors.push_back(mirror_url);
  }
}
    
////////////////////////////////////////////////////////////////////
//     Function: Panda3D::choose_random_mirrors
//       Access: Public
//  Description: Selects num_mirrors elements, chosen at random, from
//               the _mirrors list.  Adds the selected mirrors to
//               result.  If there are fewer than num_mirrors elements
//               in the list, adds only as many mirrors as we can get.
////////////////////////////////////////////////////////////////////
void Panda3D::
choose_random_mirrors(vector_string &result, int num_mirrors) {
  pvector<size_t> selected;

  size_t num_to_select = min(_mirrors.size(), (size_t)num_mirrors);
  while (num_to_select > 0) {
    size_t i = (size_t)(((double)rand() / (double)RAND_MAX) * _mirrors.size());
    while (find(selected.begin(), selected.end(), i) != selected.end()) {
      // Already found this i, find a new one.
      i = (size_t)(((double)rand() / (double)RAND_MAX) * _mirrors.size());
    }
    selected.push_back(i);
    result.push_back(_mirrors[i]);
    --num_to_select;
  }
}


////////////////////////////////////////////////////////////////////
//     Function: Panda3D::get_core_api
//       Access: Protected
//  Description: Checks the core API DLL file against the
//               specification in the contents file, and downloads it
//               if necessary.
////////////////////////////////////////////////////////////////////
bool Panda3D::
get_core_api(const Filename &contents_filename, TiXmlElement *xpackage) {
  _core_api_dll.load_xml(xpackage);

  if (!_core_api_dll.quick_verify(_root_dir)) {
    // The DLL file needs to be downloaded.  Build up our list of
    // URL's to attempt to download it from, in reverse order.
    string url;
    vector_string core_urls;

    // Our last act of desperation: hit the original host, with a
    // query uniquifier, to break through any caches.
    ostringstream strm;
    strm << _download_url_prefix << _core_api_dll.get_filename()
         << "?" << time(NULL);
    url = strm.str();
    core_urls.push_back(url);

    // Before we try that, we'll hit the original host, without a
    // uniquifier.
    url = _download_url_prefix;
    url += _core_api_dll.get_filename();
    core_urls.push_back(url);

    // And before we try that, we'll try two mirrors, at random.
    vector_string mirrors;
    choose_random_mirrors(mirrors, 2);
    for (vector_string::iterator si = mirrors.begin();
         si != mirrors.end(); 
         ++si) {
      url = (*si) + _core_api_dll.get_filename();
      core_urls.push_back(url);
    }

    // The very first thing we'll try is the super_mirror, if we have
    // one.
    if (!_super_mirror_url_prefix.empty()) {
      url = _super_mirror_url_prefix + _core_api_dll.get_filename();
      core_urls.push_back(url);
    }

    // Now pick URL's off the list, and try them, until we have
    // success.
    Filename pathname = Filename::from_os_specific(_core_api_dll.get_pathname(_root_dir));
    pathname.make_dir();
    HTTPClient *http = HTTPClient::get_global_ptr();

    bool success = false;
    while (!core_urls.empty()) {
      url = core_urls.back();
      core_urls.pop_back();
    
      PT(HTTPChannel) channel = http->get_document(url);
      if (!channel->download_to_file(pathname)) {
        cerr << "Unable to download " << url << "\n";

      } else if (!_core_api_dll.full_verify(_root_dir)) {
        cerr << "Mismatched download for " << url << "\n";

      } else {
        // successfully downloaded!
        success = true;
        break;
      }
    }

    if (!success) {
      cerr << "Couldn't download core API.\n";
      return false;
    }

    // Since we had to download some of it, might as well ask the core
    // API to check all of it.
    _verify_contents = true;
  }

  // Now we've got the DLL.  Load it.
  string pathname = _core_api_dll.get_pathname(_root_dir);

#ifdef P3D_PLUGIN_P3D_PLUGIN
  // This is a convenience macro for development.  If defined and
  // nonempty, it indicates the name of the plugin DLL that we will
  // actually run, even after downloading a possibly different
  // (presumably older) version.  Its purpose is to simplify iteration
  // on the plugin DLL.
  string override_filename = P3D_PLUGIN_P3D_PLUGIN;
  if (!override_filename.empty()) {
    pathname = override_filename;
  }
#endif  // P3D_PLUGIN_P3D_PLUGIN

  bool trusted_environment = !_enable_security;

  if (!load_plugin(pathname, contents_filename.to_os_specific(),
                   _host_url, _verify_contents, _this_platform, _log_dirname,
                   _log_basename, trusted_environment, _console_environment,
                   cerr)) {
    cerr << "Unable to launch core API in " << pathname << "\n" << flush;
    return false;
  }

  // Successfully loaded.
#ifdef PANDA_OFFICIAL_VERSION
  static const bool official = true;
#else
  static const bool official = false;
#endif
  P3D_set_plugin_version(P3D_PLUGIN_MAJOR_VERSION, P3D_PLUGIN_MINOR_VERSION,
                         P3D_PLUGIN_SEQUENCE_VERSION, official,
                         PANDA_DISTRIBUTOR,
                         PANDA_PACKAGE_HOST_URL, _core_api_dll.get_timestamp());

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: Panda3D::run_getters
//       Access: Protected
//  Description: Polls all of the active URL requests.
////////////////////////////////////////////////////////////////////
void Panda3D::
run_getters() {
  URLGetters::iterator gi;
  gi = _url_getters.begin();
  while (gi != _url_getters.end()) {
    URLGetter *getter = (*gi);
    if (getter->run()) {
      // This URLGetter is still working.  Leave it.
      ++gi;
    } else {
      // This URLGetter is done.  Remove it and delete it.
      URLGetters::iterator dgi = gi;
      ++gi;
      _url_getters.erase(dgi);
      delete getter;
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: Panda3D::handle_request
//       Access: Protected
//  Description: Handles a single request received via the plugin API
//               from a p3d instance.
////////////////////////////////////////////////////////////////////
void Panda3D::
handle_request(P3D_request *request) {
  bool handled = false;
  switch (request->_request_type) {
  case P3D_RT_stop:
    delete_instance(request->_instance);
#ifdef _WIN32
    // Post a silly message to spin the event loop.
    PostMessage(NULL, WM_USER, 0, 0);
#endif
    handled = true;
    break;

  case P3D_RT_get_url:
    {
      int unique_id = request->_request._get_url._unique_id;
      const string &url = request->_request._get_url._url;
      URLGetter *getter = new URLGetter
        (request->_instance, unique_id, URLSpec(url), "");
      _url_getters.insert(getter);
      handled = true;
    }
    break;

  case P3D_RT_notify:
    {
      if (strcmp(request->_request._notify._message, "ondownloadnext") == 0) {
        // Tell the user we're downloading a package.
        report_downloading_package(request->_instance);
      } else if (strcmp(request->_request._notify._message, "ondownloadcomplete") == 0) {
        // Tell the user we're done downloading.
        report_download_complete(request->_instance);
      }
    }
    break;

  default:
    break;
  };

  P3D_request_finish(request, handled);
}

#ifdef _WIN32
LONG WINAPI
window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
  switch (msg) {
  case WM_DESTROY:
    PostQuitMessage(0);
    break;
  };

  return DefWindowProc(hwnd, msg, wparam, lparam);
}

////////////////////////////////////////////////////////////////////
//     Function: Panda3D::make_parent_window
//       Access: Protected
//  Description: Creates a toplevel window to contain the embedded
//               instances.  Windows implementation.
////////////////////////////////////////////////////////////////////
void Panda3D::
make_parent_window() {
  WNDCLASS wc;

  HINSTANCE application = GetModuleHandle(NULL);
  ZeroMemory(&wc, sizeof(WNDCLASS));
  wc.lpfnWndProc = window_proc;
  wc.hInstance = application;
  wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  wc.lpszClassName = "panda3d";

  if (!RegisterClass(&wc)) {
    cerr << "Could not register window class!\n";
    exit(1);
  }

  DWORD window_style = 
    WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
    WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX |
    WS_SIZEBOX | WS_MAXIMIZEBOX;

  HWND toplevel_window = 
    CreateWindow("panda3d", "Panda3D", window_style,
                 CW_USEDEFAULT, CW_USEDEFAULT, _win_width, _win_height,
                 NULL, NULL, application, 0);
  if (!toplevel_window) {
    cerr << "Could not create toplevel window!\n";
    exit(1);
  }

  ShowWindow(toplevel_window, SW_SHOWNORMAL);

  memset(&_parent_window, 0, sizeof(_parent_window));
  _parent_window._window_handle_type = P3D_WHT_win_hwnd;
  _parent_window._handle._win_hwnd._hwnd = toplevel_window;
}

#else

////////////////////////////////////////////////////////////////////
//     Function: Panda3D::make_parent_window
//       Access: Protected
//  Description: Creates a toplevel window to contain the embedded
//               instances.
////////////////////////////////////////////////////////////////////
void Panda3D::
make_parent_window() {
  // TODO.
  assert(false);
}

#endif

////////////////////////////////////////////////////////////////////
//     Function: Panda3D::create_instance
//       Access: Protected
//  Description: Uses the plugin API to create a new P3D instance to
//               play a particular .p3d file.  This instance is also
//               started if start_instance is true (which requires
//               that the named p3d file exists).
////////////////////////////////////////////////////////////////////
P3D_instance *Panda3D::
create_instance(const string &p3d, bool start_instance,
                int win_x, int win_y, int win_width, int win_height,
                char **args, int num_args) {
  // Check to see if the p3d filename we were given is a URL, or a
  // local file.
  Filename p3d_filename = Filename::from_os_specific(p3d);
  string os_p3d_filename = p3d;
  bool is_local = !is_url(p3d);
  if (is_local && start_instance) {
    if (!p3d_filename.exists()) {
      cerr << "No such file: " << p3d_filename << "\n";
      exit(1);
    }

    p3d_filename.make_absolute();
    os_p3d_filename = p3d_filename.to_os_specific();
  } 

  // Build up the token list.
  Tokens tokens = _tokens;
  P3D_token token;

  string log_basename;
  if (!_log_dirname.empty()) {
    // Generate output to a logfile.
    log_basename = p3d_filename.get_basename_wo_extension();
    token._keyword = "log_basename";
    token._value = log_basename.c_str();
    tokens.push_back(token);
  } else {
    // Send output to the console.
    token._keyword = "console_output";
    if (_console_environment) {
      token._value = "1";
    } else {
      token._value = "0";
    }
    tokens.push_back(token);
  }

  token._keyword = "auto_start";
  token._value = "1";
  tokens.push_back(token);

  P3D_token *tokens_p;
  size_t num_tokens = tokens.size();
  if (!tokens.empty()) {
    tokens_p = &tokens[0];
  }

  // Build up the argument list, beginning with the p3d_filename.
  pvector<const char *> argv;
  argv.push_back(os_p3d_filename.c_str());
  for (int i = 0; i < num_args; ++i) {
    argv.push_back(args[i]);
  }

  P3D_instance *inst = P3D_new_instance(NULL, tokens_p, num_tokens,
                                        argv.size(), &argv[0], NULL);

  if (inst != NULL) {
    if (start_instance) {
      // We call start() first, to give the core API a chance to
      // notice the "hidden" attrib before we set the window
      // parameters.
      P3D_instance_start(inst, is_local, os_p3d_filename.c_str());
    }

    P3D_instance_setup_window
      (inst, _window_type, _win_x, _win_y, _win_width, _win_height, _parent_window);
  }

  return inst;
}

////////////////////////////////////////////////////////////////////
//     Function: Panda3D::delete_instance
//       Access: Protected
//  Description: Deletes the indicated instance and removes it from
//               the internal structures.
////////////////////////////////////////////////////////////////////
void Panda3D::
delete_instance(P3D_instance *inst) {
  P3D_instance_finish(inst);
  _instances.erase(inst);

  // Make sure we also terminate any pending URLGetters associated
  // with this instance.
  URLGetters::iterator gi;
  gi = _url_getters.begin();
  while (gi != _url_getters.end()) {
    URLGetter *getter = (*gi);
    if (getter->get_instance() == inst) {
      URLGetters::iterator dgi = gi;
      ++gi;
      _url_getters.erase(dgi);
      delete getter;
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: Panda3D::usage
//       Access: Protected
//  Description: Reports the available command-line options.
////////////////////////////////////////////////////////////////////
void Panda3D::
usage() {
  cerr
    << "\nThis is panda3d version " 
    << P3D_PLUGIN_MAJOR_VERSION << "."
    << P3D_PLUGIN_MINOR_VERSION << "."
    << P3D_PLUGIN_SEQUENCE_VERSION;
#ifndef PANDA_OFFICIAL_VERSION
  cerr << "c";
#endif

  cerr
    << "\n\nUsage:\n"
    << "   panda3d [opts] file.p3d [args]\n"
    << "   panda3d -m [opts] file_a.p3d file_b.p3d [file_c.p3d ...]\n\n"
  
    << "This program is used to execute a Panda3D application bundle stored\n"
    << "in a .p3d file.  In the first form, without the -m option, it\n"
    << "executes one application; remaining arguments following the\n"
    << "application name are passed into the application.  In the second\n"
    << "form, with the -m option, it can execute multiple applications\n"
    << "simultaneously, though in this form arguments cannot be passed into\n"
    << "the applications.\n\n"

    << "Options:\n\n"

    << "  -m\n"
    << "    Indicates that multiple application filenames will be passed on\n"
    << "    the command line.  All applications will be run at the same\n"
    << "    time, but additional arguments may not be passed to any of the\n"
    << "    applictions.\n\n"

    << "  -t token=value\n"
    << "    Defines a web token or parameter to pass to the application(s).\n"
    << "    This simulates a <param> entry in an <object> tag.\n\n"

    << "  -w [toplevel|embedded|fullscreen|hidden]\n"
    << "    Specify the type of graphic window to create.  If you specify\n"
    << "    \"embedded\", a new window is created to be the parent.\n\n"

    << "  -s width,height\n"
    << "    Specify the size of the graphic window.\n\n"

    << "  -o x,y\n"
    << "    Specify the position (origin) of the graphic window on the\n"
    << "    screen, or on the parent window.\n\n"

    << "  -l log_dirname\n"
    << "    Specify the full path to the directory in which log files are\n"
    << "    to be written.  If this is not specified, the default is to send\n"
    << "    the application output to the console.\n\n"

    << "  -f\n"
    << "    Force an initial contact of the Panda3D download server, to check\n"
    << "    if a new version is available.  Normally, this is done only\n"
    << "    if contents.xml cannot be read.\n\n"

    << "  -i\n"
    << "    Runs the application interactively.  This requires that the application\n"
    << "    was built with -D on the packp3d command line.  If so, this option will\n"
    << "    create an interactive Python prompt after the application has loaded.\n"
    << "    It will also retain the PYTHONPATH environment variable from the user's\n"
    << "    environment, allowing Python files on disk to shadow the same-named\n"
    << "    Python files within the p3d file, for rapid iteration on the Python\n"
    << "    code.\n\n"

    << "  -S\n"
    << "    Runs the application with security enabled, as if it were embedded in\n"
    << "    a web page.\n\n"

    << "  -u url\n"

    << "    Specify the URL of the Panda3D download server.  This is the host\n"
    << "    from which the plugin itself will be downloaded if necessary.  The\n"
    << "    default is \"" << PANDA_PACKAGE_HOST_URL << "\" .\n\n"

    << "  -M super_mirror_url\n"
    << "    Specifies the \"super mirror\" URL, the special URL that is consulted\n"
    << "    first before downloading any package file referenced by a p3d file.\n"
    << "    This is primarily intended to support pre-installing a downloadable\n"
    << "    Panda3D tree on the local machine, to allow p3d applications to\n"
    << "    execute without requiring an internet connection.\n\n"

    << "  -p platform\n"
    << "    Specify the platform to masquerade as.  The default is \""
    << DTOOL_PLATFORM << "\" .\n\n";
}

////////////////////////////////////////////////////////////////////
//     Function: Panda3D::parse_token
//       Access: Protected
//  Description: Parses a web token of the form token=value, and
//               stores it in _tokens.  Returns true on success, false
//               on failure.
////////////////////////////////////////////////////////////////////
bool Panda3D::
parse_token(char *arg) {
  char *equals = strchr(arg, '=');
  if (equals == NULL) {
    return false;
  }

  // Directly munge the C string to truncate it at the equals sign.
  // Classic C tricks.
  *equals = '\0';
  P3D_token token;
  token._keyword = strdup(arg);
  token._value = strdup(equals + 1);
  *equals = '=';

  _tokens.push_back(token);

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: Panda3D::parse_int_pair
//       Access: Protected
//  Description: Parses a string into an x,y pair of integers.
//               Returns true on success, false on failure.
////////////////////////////////////////////////////////////////////
bool Panda3D::
parse_int_pair(char *arg, int &x, int &y) {
  char *endptr;
  x = strtol(arg, &endptr, 10);
  if (*endptr == ',') {
    y = strtol(endptr + 1, &endptr, 10);
    if (*endptr == '\0') {
      return true;
    }
  }

  // Some parse error on the string.
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: Panda3D::is_url
//       Access: Protected, Static
//  Description: Returns true if the indicated string appears to be a
//               URL, with a leading http:// or file:// or whatever,
//               or false if it must be a local filename instead.
////////////////////////////////////////////////////////////////////
bool Panda3D::
is_url(const string &param) {
  // We define a URL prefix as a sequence of at least two letters,
  // followed by a colon, followed by at least one slash.
  size_t p = 0;
  while (p < param.size() && isalpha(param[p])) {
    ++p;
  }
  if (p < 2) {
    // Not enough letters.
    return false;
  }
  if (p >= param.size() || param[p] != ':') {
    // No colon.
    return false;
  }
  ++p;
  if (p >= param.size() || param[p] != '/') {
    // No slash.
    return false;
  }

  // It matches the rules.
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: Panda3D::report_downloading_package
//       Access: Protected
//  Description: Tells the user we have to download a package.
////////////////////////////////////////////////////////////////////
void Panda3D::
report_downloading_package(P3D_instance *instance) {
  P3D_object *obj = P3D_instance_get_panda_script_object(instance);
  
  P3D_object *display_name = P3D_object_get_property(obj, "downloadPackageDisplayName");
  if (display_name == NULL) {
    cerr << "Installing package.\n";
    return;
  }

  int name_length = P3D_object_get_string(display_name, NULL, 0);
  char *name = new char[name_length + 1];
  P3D_object_get_string(display_name, name, name_length + 1);

  cerr << "Installing " << name << "\n";

  delete[] name;
  P3D_object_decref(display_name);
  _reporting_download = true;
}
 
////////////////////////////////////////////////////////////////////
//     Function: Panda3D::report_download_complete
//       Access: Protected
//  Description: Tells the user we're done downloading packages
////////////////////////////////////////////////////////////////////
void Panda3D::
report_download_complete(P3D_instance *instance) {
  if (_reporting_download) {
    cerr << "Install complete.\n";
  }
}

#ifdef __APPLE__
////////////////////////////////////////////////////////////////////
//     Function: Panda3D::st_timer_callback
//       Access: Protected, Static
//  Description: Installed as a timer on the event loop, so we can
//               process local events, in the Apple implementation.
////////////////////////////////////////////////////////////////////
pascal void Panda3D::
st_timer_callback(EventLoopTimerRef timer, void *user_data) {
  ((Panda3D *)user_data)->timer_callback(timer);
}
#endif  // __APPLE__

#ifdef __APPLE__
////////////////////////////////////////////////////////////////////
//     Function: Panda3D::timer_callback
//       Access: Protected
//  Description: Installed as a timer on the event loop, so we can
//               process local events, in the Apple implementation.
////////////////////////////////////////////////////////////////////
void Panda3D::
timer_callback(EventLoopTimerRef timer) {
  // Check for new requests from the Panda3D plugin.
  P3D_instance *inst = P3D_check_request(0.0);
  while (inst != (P3D_instance *)NULL) {
    P3D_request *request = P3D_instance_get_request(inst);
    if (request != (P3D_request *)NULL) {
      handle_request(request);
    }
    inst = P3D_check_request(0.0);
  }
  
  // Check the download tasks.
  run_getters();

  // If we're out of instances, exit the application.
  if (time_to_exit()) {
    QuitApplicationEventLoop();
  }
}
#endif  // __APPLE__

////////////////////////////////////////////////////////////////////
//     Function: Panda3D::URLGetter::Constructor
//       Access: Public
//  Description: 
////////////////////////////////////////////////////////////////////
Panda3D::URLGetter::
URLGetter(P3D_instance *instance, int unique_id,
          const URLSpec &url, const string &post_data) :
  _instance(instance),
  _unique_id(unique_id),
  _url(url),
  _post_data(post_data)
{
  HTTPClient *http = HTTPClient::get_global_ptr();

  _channel = http->make_channel(false);
  //  _channel->set_download_throttle(true);
  if (_post_data.empty()) {
    _channel->begin_get_document(_url);
  } else {
    _channel->begin_post_form(_url, _post_data);
  }

  _channel->download_to_ram(&_rf);
  _bytes_sent = 0;
}

////////////////////////////////////////////////////////////////////
//     Function: Panda3D::URLGetter::run
//       Access: Public
//  Description: Polls the URLGetter for new results.  Returns true if
//               the URL request is still in progress and run() should
//               be called again later, or false if the URL request
//               has been completed and run() should not be called
//               again.
////////////////////////////////////////////////////////////////////
bool Panda3D::URLGetter::
run() {
  if (_channel->run() || _rf.get_data_size() != 0) {
    if (_rf.get_data_size() != 0) {
      // Got some new data.
      bool download_ok = P3D_instance_feed_url_stream
        (_instance, _unique_id, P3D_RC_in_progress,
         _channel->get_status_code(),
         _channel->get_file_size(),
         (const unsigned char *)_rf.get_data().data(), _rf.get_data_size());
      _bytes_sent += _rf.get_data_size();
      _rf.clear();

      if (!download_ok) {
        // The plugin doesn't care any more.  Interrupt the download.
        cerr << "Download interrupted: " << _url 
             << ", after " << _bytes_sent << " of " << _channel->get_file_size()
             << " bytes.\n";
        return false;
      }
    }

    // Still more to come; call this method again later.
    return true;
  }

  // All done.
  P3D_result_code status = P3D_RC_done;
  if (!_channel->is_valid()) {
    if (_channel->get_status_code() != 0) {
      status = P3D_RC_http_error;
    } else {
      status = P3D_RC_generic_error;
    }
    cerr << "Error getting URL " << _url << "\n";
  }

  P3D_instance_feed_url_stream
    (_instance, _unique_id, status,
     _channel->get_status_code(),
     _bytes_sent, NULL, 0);
  return false;
}
