// Filename: p3dMainObject.cxx
// Created by:  drose (10Jul09)
//
////////////////////////////////////////////////////////////////////
//
// 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 "p3dMainObject.h"
#include "p3dInstance.h"
#include "p3dSession.h"
#include "p3dStringObject.h"
#include "p3dInstanceManager.h"

////////////////////////////////////////////////////////////////////
//     Function: P3DMainObject::Constructor
//       Access: Public
//  Description: 
////////////////////////////////////////////////////////////////////
P3DMainObject::
P3DMainObject() :
  _pyobj(NULL)
{
  _unauth_play = false;
}

////////////////////////////////////////////////////////////////////
//     Function: P3DMainObject::Destructor
//       Access: Public, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
P3DMainObject::
~P3DMainObject() {
  set_pyobj(NULL);

  // Clear the local properties.
  Properties::const_iterator pi;
  for (pi = _properties.begin(); pi != _properties.end(); ++pi) {
    P3D_object *value = (*pi).second;
    P3D_OBJECT_DECREF(value);
  }
  _properties.clear();
}

////////////////////////////////////////////////////////////////////
//     Function: P3DMainObject::get_type
//       Access: Public, Virtual
//  Description: Returns the fundamental type of this kind of object.
////////////////////////////////////////////////////////////////////
P3D_object_type P3DMainObject::
get_type() {
  return P3D_OT_object;
}

////////////////////////////////////////////////////////////////////
//     Function: P3DMainObject::get_bool
//       Access: Public, Virtual
//  Description: Returns the object value coerced to a boolean, if
//               possible.
////////////////////////////////////////////////////////////////////
bool P3DMainObject::
get_bool() {
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: P3DMainObject::get_int
//       Access: Public, Virtual
//  Description: Returns the object value coerced to an integer, if
//               possible.
////////////////////////////////////////////////////////////////////
int P3DMainObject::
get_int() {
  return 0;
}

////////////////////////////////////////////////////////////////////
//     Function: P3DMainObject::get_float
//       Access: Public, Virtual
//  Description: Returns the object value coerced to a floating-point
//               value, if possible.
////////////////////////////////////////////////////////////////////
double P3DMainObject::
get_float() {
  return 0.0;
}

////////////////////////////////////////////////////////////////////
//     Function: P3DMainObject::make_string
//       Access: Public, Virtual
//  Description: Fills the indicated C++ string object with the value
//               of this object coerced to a string.
////////////////////////////////////////////////////////////////////
void P3DMainObject::
make_string(string &value) {
  if (_pyobj == NULL) {
    value = "P3DMainObject";
  } else {
    int size = P3D_OBJECT_GET_STRING(_pyobj, NULL, 0);
    char *buffer = new char[size];
    P3D_OBJECT_GET_STRING(_pyobj, buffer, size);
    value = string(buffer, size);
    delete[] buffer;
  }    
}

////////////////////////////////////////////////////////////////////
//     Function: P3DMainObject::get_property
//       Access: Public, Virtual
//  Description: Returns the named property element in the object.  The
//               return value is a new-reference P3D_object, or NULL
//               on error.
////////////////////////////////////////////////////////////////////
P3D_object *P3DMainObject::
get_property(const string &property) {
  if (_pyobj == NULL) {
    // Without a pyobj, we just report whatever's been stored locally.
    Properties::const_iterator pi;
    pi = _properties.find(property);
    if (pi != _properties.end()) {
      P3D_object *result = (*pi).second;
      P3D_OBJECT_INCREF(result);
      return result;
    }
    return NULL;
  }

  // With a pyobj, we pass the query down to it.
  return P3D_OBJECT_GET_PROPERTY(_pyobj, property.c_str());
}

////////////////////////////////////////////////////////////////////
//     Function: P3DMainObject::set_property
//       Access: Public, Virtual
//  Description: Modifies (or deletes, if value is NULL) the named
//               property element in the object.  Returns true on
//               success, false on failure.
////////////////////////////////////////////////////////////////////
bool P3DMainObject::
set_property(const string &property, bool needs_response, P3D_object *value) {
  // First, we set the property locally.
  if (value != NULL) {
    Properties::iterator pi;
    pi = _properties.insert(Properties::value_type(property, NULL)).first;
    assert(pi != _properties.end());
    P3D_object *orig_value = (*pi).second;
    if (orig_value != value) {
      P3D_OBJECT_XDECREF(orig_value);
      (*pi).second = value;
      P3D_OBJECT_INCREF(value);
    }
  } else {
    // (Or delete the property locally.)
    Properties::iterator pi;
    pi = _properties.find(property);
    if (pi != _properties.end()) {
      P3D_object *orig_value = (*pi).second;
      P3D_OBJECT_DECREF(orig_value);
      _properties.erase(pi);
    }
  }

  if (_pyobj == NULL) {
    // Without a pyobj, that's all we do.
    return true;
  }

  // With a pyobj, we also pass this request down.
  return P3D_OBJECT_SET_PROPERTY(_pyobj, property.c_str(), needs_response, value);
}

////////////////////////////////////////////////////////////////////
//     Function: P3DMainObject::has_method
//       Access: Public, Virtual
//  Description: Returns true if the named method exists on this
//               object, false otherwise.
////////////////////////////////////////////////////////////////////
bool P3DMainObject::
has_method(const string &method_name) {
  // Some special-case methods implemented in-place.
  if (method_name == "play") {
    return true;
  } else if (method_name == "read_game_log") {
    return true;
  } else if (method_name == "read_system_log") {
    return true;
  }

  if (_pyobj == NULL) {
    // No methods until we get our pyobj.
    return false;
  }

  return P3D_OBJECT_HAS_METHOD(_pyobj, method_name.c_str());
}

////////////////////////////////////////////////////////////////////
//     Function: P3DMainObject::call
//       Access: Public, Virtual
//  Description: Invokes the named method on the object, passing the
//               indicated parameters.  If the method name is empty,
//               invokes the object itself.
//
//               If needs_response is true, the return value is a
//               new-reference P3D_object on success, or NULL on
//               failure.  If needs_response is false, the return
//               value is always NULL, and there is no way to
//               determine success or failure.
////////////////////////////////////////////////////////////////////
P3D_object *P3DMainObject::
call(const string &method_name, bool needs_response,
     P3D_object *params[], int num_params) {
  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();

  if (method_name == "play") {
    return call_play(params, num_params);
  } else if (method_name == "read_game_log") {
    return call_read_game_log(params, num_params);
  } else if (method_name == "read_system_log") {
    return call_read_system_log(params, num_params);
  }

  if (_pyobj == NULL) {
    // No methods until we get our pyobj.
    return NULL;
  }

  return P3D_OBJECT_CALL(_pyobj, method_name.c_str(), needs_response,
                         params, num_params);
}

////////////////////////////////////////////////////////////////////
//     Function: P3DMainObject::output
//       Access: Public, Virtual
//  Description: Writes a formatted representation of the value to the
//               indicated string.  This is intended for developer
//               assistance.
////////////////////////////////////////////////////////////////////
void P3DMainObject::
output(ostream &out) {
  out << "P3DMainObject";
}

////////////////////////////////////////////////////////////////////
//     Function: P3DMainObject::set_pyobj
//       Access: Public
//  Description: Changes the internal pyobj pointer.  This is the
//               P3D_object that references the actual PyObject held
//               within the child process, corresponding to the true
//               main object there.  The new object's reference
//               count is incremented, and the previous object's is
//               decremented.
////////////////////////////////////////////////////////////////////
void P3DMainObject::
set_pyobj(P3D_object *pyobj) {
  if (_pyobj != pyobj) {
    P3D_OBJECT_XDECREF(_pyobj);
    _pyobj = pyobj;
    if (_pyobj != NULL) {
      P3D_OBJECT_INCREF(_pyobj);

      // Now that we have a pyobj, we have to transfer down all of the
      // properties we'd set locally.
      Properties::const_iterator pi;
      for (pi = _properties.begin(); pi != _properties.end(); ++pi) {
        const string &property_name = (*pi).first;
        P3D_object *value = (*pi).second;
        P3D_OBJECT_SET_PROPERTY(_pyobj, property_name.c_str(), false, value);
      }
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: P3DMainObject::get_pyobj
//       Access: Public
//  Description: Returns the internal pyobj pointer, or NULL if it has
//               not yet been set.
////////////////////////////////////////////////////////////////////
P3D_object *P3DMainObject::
get_pyobj() const {
  return _pyobj;
}

////////////////////////////////////////////////////////////////////
//     Function: P3DMainObject::set_instance
//       Access: Public
//  Description: Sets a callback pointer to the instance that owns
//               this object.  When this instance destructs, it clears
//               this pointer to NULL.
////////////////////////////////////////////////////////////////////
void P3DMainObject::
set_instance(P3DInstance *inst) {
  _inst = inst;
}

////////////////////////////////////////////////////////////////////
//     Function: P3DMainObject::call_play
//       Access: Private
//  Description: Starts the process remotely, as if the play button
//               had been clicked.  If the application has not yet
//               been validated, this pops up the validation dialog.
//
//               Only applicable if the application was in the ready
//               state, or the unauth state.  Returns true if the
//               application is now started, false otherwise.
//
//               This may be invoked from the unauth state only once.
//               If the user chooses not to authorize the plugin at
//               that time, it may not be invoked automatically again.
////////////////////////////////////////////////////////////////////
P3D_object *P3DMainObject::
call_play(P3D_object *params[], int num_params) {
  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
  if (_inst == NULL) {
    return inst_mgr->new_bool_object(false);
  }

  // I guess there's no harm in allowing JavaScript to call play(),
  // with or without explicit scripting authorization.

  if (!_inst->is_trusted()) {
    // Requires authorization.  We allow this only once; beyond that,
    // and you're only annoying the user.
    if (!_unauth_play) {
      _unauth_play = true;
      _inst->splash_button_clicked_main_thread();
    }

  } else if (!_inst->is_started()) {
    // We allow calling play() from a ready state without limit, but
    // probably only once will be necessary.
    _inst->splash_button_clicked_main_thread();
  }
   
  return inst_mgr->new_bool_object(_inst->is_started());
}

////////////////////////////////////////////////////////////////////
//     Function: P3DMainObject::call_read_game_log
//       Access: Private
//  Description: Reads the entire logfile as a string, and returns it
//               to the calling JavaScript process.
////////////////////////////////////////////////////////////////////
P3D_object *P3DMainObject::
call_read_game_log(P3D_object *params[], int num_params) {
  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
  if (_inst == NULL) {
    return inst_mgr->new_undefined_object();
  }

  if (!_inst->get_matches_script_origin()) {
    // If you're not allowed to be scripting us, you can't query the
    // game log either.  (But you can query the system log.)
    return inst_mgr->new_undefined_object();
  }

  P3DSession *session = _inst->get_session();
  if (session == NULL) {
    return inst_mgr->new_undefined_object();
  }

  string log_pathname = session->get_log_pathname();
  return read_log(log_pathname, params, num_params);
}

////////////////////////////////////////////////////////////////////
//     Function: P3DMainObject::call_read_system_log
//       Access: Private
//  Description: As above, but reads the system log, the logfile for
//               the installation process.
////////////////////////////////////////////////////////////////////
P3D_object *P3DMainObject::
call_read_system_log(P3D_object *params[], int num_params) {
  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();

  string log_pathname = inst_mgr->get_log_pathname();
  return read_log(log_pathname, params, num_params);
}

////////////////////////////////////////////////////////////////////
//     Function: P3DMainObject::call_read_log
//       Access: Private
//  Description: The generic log-reader function.
////////////////////////////////////////////////////////////////////
P3D_object *P3DMainObject::
read_log(const string &log_pathname, P3D_object *params[], int num_params) {
  ifstream log(log_pathname.c_str(), ios::in);

  // Check the parameter, if any--if specified, it specifies the last
  // n bytes to retrieve.
  int max_bytes = 0;
  if (num_params > 0) {
    max_bytes = P3D_OBJECT_GET_INT(params[0]);
  }

  // Get the size of the file.
  log.seekg(0, ios::end);
  size_t size = (size_t)log.tellg();

  if (max_bytes > 0 && max_bytes < (int)size) {
    // Apply the limit.
    log.seekg(size - max_bytes, ios::beg);
    size = (size_t)max_bytes;
  } else {
    // Read the entire file.
    log.seekg(0, ios::beg);
  }

  char *buffer = new char[size];
  if (buffer == NULL) {
    return NULL;
  }

  log.read(buffer, size);
  P3D_object *result = new P3DStringObject(buffer, log.gcount());
  delete[] buffer;

  return result;
}
