/******************************************************************
  
  Module:  registry.cpp
  
  Author: Sean Craig
  
  Copyright 2005 Sony Online Entertainment.  All rights reserved.
  
*******************************************************************/

//-------------------------------------------------------- Includes
#include "registry.h"
#include "tinyxml.h"
#include "titan.h"
#include "SyScene.h"
#include "SyLoader.h"
#include "SyESFParse.h"       
#include "SyStr.h"
#include "stats.h"
#include "inventory.h"
#include "TitanPeeringNetwork.h"
#include "graphic.h"
#include "SyDebug.h"
#include "database.h"
//#include "script_pawn.h"
#include "levelobj.h"
#include "gameerror.h"

#ifdef SNTUNER
#include "libsntuner.h"
#endif

//---------------------------------------------- Class Declarations

class cGameSaveDescObject : public SyPropObject
{
public:
  cGameSaveDescObject();

  static int           InitPropClass();
  static SyPropObject* Creator();

  static const SyPropClassID mCLASSID     =  0xBEAF;
  static const SyPropID PropId_Desc       = 0x0000;
  static const SyPropID PropId_Timestamp  = 0x0001;
  static const SyPropID PropId_NumLocalPlayers = 0x0002;
  static const SyPropID PropId_NumNetworkPlayers = 0x0003;

  SyString mDesc;
  SyTime mTimestamp;
  int mNumLocalPlayers;
  int mNumNetworkPlayers;
};

cGameSaveDescObject::cGameSaveDescObject()
: mTimestamp(0),
  mNumLocalPlayers(0),
  mNumNetworkPlayers(0)
{
  InitPropObject(mCLASSID);
}

int cGameSaveDescObject::InitPropClass()
{
  AddClass( mCLASSID, 
            "cGameSaveDescObject", 
            Creator,     
            mCLASSID, 
            0 ); 

  AddStringProperty(mCLASSID,PropId_Desc,SyMemberOffset(cGameSaveDescObject,mDesc),"mDesc");
  AddTimeProperty(mCLASSID,PropId_Timestamp,SyMemberOffset(cGameSaveDescObject,mTimestamp),"mTimestamp");
  AddInt32Property(mCLASSID,PropId_NumLocalPlayers,SyMemberOffset(cGameSaveDescObject,mNumLocalPlayers),"mNumLocalPlayers");
  AddInt32Property(mCLASSID,PropId_NumNetworkPlayers,SyMemberOffset(cGameSaveDescObject,mNumNetworkPlayers),"mNumNetworkPlayers");

  return 0;
}

SyPropObject* cGameSaveDescObject::Creator()
{
  SyPropObject *pObject;

  pObject = SyNew cGameSaveDescObject();
  if (pObject == NULL)
  {
    SyAssert(0);
    return(NULL);
  }

  return(pObject);
}

//--------------------------------------------------------- Globals

//----------------------------------------- Functions Declarations
//------------------------------------ Member Functions Definitions


//------------------------------------ cGameObjRegistry

cGameObjectRegistry::cGameObjectRegistry()
: mpLevelObject(NULL),
  mNumSaveGames(0),
  mLastSaveGameIndex(-1)
{
  InitPropObject( GAMEOBJECTREGISTRY_CLASSID );

  for (int ii=0;ii<cGameObject::NUM_OBJ_TYPES;++ii)
  {
      mTypeLists[ii] =new SyEmbeddedList<cGameObject, &cGameObject::mTypeLink>();
  }
}


cGameObjectRegistry::~cGameObjectRegistry()
{
  Clear();

  for (int ii=0;ii<cGameObject::NUM_OBJ_TYPES;++ii)
  {
    delete mTypeLists[ii];
  }
}

void cGameObjectRegistry::Init(Titan *parent)
{
  mpTitan = parent;

  ReadSaveGames();
} 

void
cGameObjectRegistry::Clear()
{
  cGameObject *cur = mIDHashTable.First();

  while (cur != NULL)
  {
    cGameObject *dead = cur;
    cur = mIDHashTable.Next(cur);

    dead->Exit();

    if (dead->IsLocal())
    {
      mpTitan->GetPeeringNetwork()->ObjectDestroy(dead->GetID());
    }

    mIDHashTable.Remove(dead);
    mNameHashTable.Remove(dead);
    mTypeLists[dead->GetType()]->Remove(dead);
    mXMin.Remove(dead);
    mXMax.Remove(dead);
    delete dead;
  }

  mObjects.Clear();

  if (mpLevelObject)
  {
    mpLevelObject->Exit();

    if (mpLevelObject->IsLocal())
    {
      mpTitan->GetPeeringNetwork()->ObjectDestroy(mpLevelObject->GetID());
    }

    delete mpLevelObject;
    mpLevelObject = NULL;
  }

#if 0       // not needed here? - JEFFP
  mpTitan->GetTitanUI()->SceneClearAndInit();       
#endif
}

void
cGameObjectRegistry::ClearNonplayer()
{
  cGameObject *cur = mIDHashTable.First();

  while (cur != NULL)
  {
    cGameObject *dead = cur;;
    cur = mIDHashTable.Next(cur);

    if (dead->GetType() != cGameObject::OBJ_PLAYER && !dead->IsRemote())
    {
      Delete(dead->GetID());
    }
  }


  if (!mpLevelObject->IsRemote())
  {
    Delete(mpLevelObject->GetID());
  }

#if 0       // not needed here? - JEFFP
  mpTitan->GetTitanUI()->SceneClearAndInit();
#endif

  cur = mIDHashTable.First();

  while (cur != NULL)
  {
    if (cur->GetType() == cGameObject::OBJ_PLAYER)
    {
      cur->Reload(); // reload model
    }
    else
    {
      cur->GetGraphic()->SceneClearAndInit();
    }

    cur = mIDHashTable.Next(cur);
  }
}

int           
cGameObjectRegistry::InitPropClass()
{
  SyPropClassID ClassID = GAMEOBJECTREGISTRY_CLASSID;

/* Add the class */

  AddClass( ClassID, 
           "cGameObjectRegistry", 
            Creator, 
            ClassID, 
            0 ); 

  AddSubObjectPtrMapProperty<tGameObjectID,cGameObject *>(ClassID,
                                                          0x0000,
                                                          SyMemberOffset(cGameObjectRegistry,mObjects),
                                                          "mObjects",
                                                           SYPROPTYPE_INT32);

  AddSubObjectPtrProperty(ClassID,
                       0x0002,
                       SyMemberOffset(cGameObjectRegistry,mpLevelObject),
                       "mpLevelObject");
  return 0;
}

SyPropObject* 
cGameObjectRegistry::Creator()
{
  SyPropObject *pObject;

  pObject = SyNew cGameObjectRegistry();
  if (pObject == NULL)
  {
    SyAssert(0);
    return(NULL);
  }

  return(pObject);
}


int cGameObjectRegistry::GetNumRemotePlayers()
{
  int count = 0;
  cGameObject* pPlayer = BeginType(cGameObject::OBJ_PLAYER);

  while (pPlayer)
  {
    if (pPlayer->IsRemote())
    {
      ++count;
    }

    pPlayer = NextType(pPlayer);
  }

  return count;
}

void              
cGameObjectRegistry::LoadLevelXML(const char *xml_filename)  // call before InitGameObjects
{
  const char *l_ObjectTypenames[] = 
  {
    "Player",
    "NPC",
    "Item",
    "Marker",
    "Projectile",
    "Prop"
  };

  static const int NUM_TYPENAMES = (sizeof(l_ObjectTypenames) / sizeof (l_ObjectTypenames[0]));

  SyAssertf(NUM_TYPENAMES==cGameObject::NUM_OBJ_TYPES,"Missing type names in xml parser");

  char buffer[512];
  sprintf(buffer,"data/level/%s",xml_filename);
	TiXmlDocument doc( buffer );
	bool loadOkay = doc.LoadFile();

	if ( !loadOkay )
	{
    SyAssertf(0, "Unable to load XML file '%s'",buffer);
    return;
	}
  
	TiXmlNode* node = 0;
	TiXmlElement* element = 0;

  node = doc.FirstChild( "designPlacement" );
  SyAssert(node);         
	node = node->FirstChild( "level" );
	SyAssert( node );
  element = node->ToElement();

  SyAssertf(element != NULL,"Error reading xml: element expected?");

  delete mpLevelObject;
  mpLevelObject = new cLevelObject;

  // parse level model name
  const char *model = element->Attribute("model"); 
  const char *path = "game_assets/art/levels/";
  char ModelName[512];
  sprintf(ModelName,"%s%s",path,model);
  mpLevelObject->SetModelName(ModelName);

  TiXmlNode* camera_node = node->FirstChild("camera");
  SyAssertf(camera_node != NULL, "Camera File Not Defined in Level");
  const char *camera_name = camera_node->FirstChild()->Value();
  char CameraName[512];
  sprintf(CameraName,"%s%s",path,camera_name);
  mpLevelObject->SetCameraName(CameraName);

	node = node->FirstChild( "object" );
	SyAssert( node );

  while (node != NULL)                  
  {
    TiXmlNode* data_node = node->FirstChild("Type");
    const char *type = data_node->FirstChild()->Value();

    cGameObject::tObjectType objType;
    if (type == NULL || type[0] == '\0')
    {
      objType = cGameObject::OBJ_MARKER;
    }
    else
    {
      int enumVal = 0;
      bool result = ChooseEnum(type,l_ObjectTypenames,NUM_TYPENAMES, enumVal);
      if (result)
      {
        objType = (cGameObject::tObjectType)enumVal;
      }
      else
      {
        SyAssertf(0,"Unknown Object Type");
        return;
      }
    }

  
    tGameObjectID id = Create(objType);
    cGameObject *obj = Fetch(id);

    // set name (if there is one)
    data_node = node->FirstChild("Name");
    if (data_node != NULL)
    {
      const char *name = data_node->FirstChild()->Value();
      Name(obj,name);
    }

    // find position

    SyVect3 position;

    TiXmlNode *position_node = node->FirstChild("Position");
    const char *position_str = position_node->FirstChild()->Value();

    position_str += SyStr::GetNextToken(position_str,&position.X);
    position_str += SyStr::GetNextToken(position_str,&position.Y);
    position_str += SyStr::GetNextToken(position_str,&position.Z);
    //sscanf(position_str,"%f,%f,%f",&position.X,&position.Y,&position.Z);

    obj->SetLocation(position);

    // find orientation
    SyVect3 orientation;

    TiXmlNode *orient_node = node->FirstChild("Orientation");
    const char *orientation_str = orient_node->FirstChild()->Value();
    orientation_str += SyStr::GetNextToken(orientation_str,&orientation.X);
    orientation_str += SyStr::GetNextToken(orientation_str,&orientation.Y);
    orientation_str += SyStr::GetNextToken(orientation_str,&orientation.Z);
    //sscanf(orientation_str,"%f,%f,%f",&orientation.X,&orientation.Y,&orientation.Z);

    obj->SetHPR(orientation);
   
    TiXmlNode *master_node = node->FirstChild("Master");
    if (master_node != NULL)
    {
      const char *master_str = master_node->FirstChild()->Value();
      obj->GetStats()->SetMaster(master_str);
    }

    TiXmlNode *item = node->FirstChild("Item");
    while (item != NULL)
    {
      TiXmlNode *master_node = item->FirstChild("Master");
      const char *master_str = master_node->FirstChild()->Value();
      TiXmlNode *quantity_node = item->FirstChild("Quantity");
      int quantity = 1;
      if (quantity_node != NULL)
      {
        quantity = atoi(quantity_node->FirstChild()->Value());
      }

      obj->GetInventory()->Add(SyHashResourceID(master_str),quantity);
      item = item->NextSibling();
    }
    
    InitObject(obj->GetID(),false);

    node = node->NextSibling();
  }

  
  SyAssertf(mpLevelObject!=NULL,"No Level Object???");
  InitObject(cLevelObject::LEVEL_OBJECT_GAME_ID,false);

  if (mpLevelObject)
  {
    mpLevelObject->SetNumGameObjects(GetNumGameObjects());
  }
}


void              
cGameObjectRegistry::LoadLevelBinary(const char *filename)   // call before InitGameObjects
{
  char buffer[512];
  sprintf(buffer,"game_assets/design/level/%s",filename);
  SyObjFile file;
  if (file.Open(buffer,SyObjFile::ROnly)<0)
  {
    SyAssertf(0,"Unable To Open File %s",buffer);
    return;
  }

  Clear(); 

  /*
   * If we're doing wad, 
   */
  if (mpTitan->GetUseWadFile() == true)
  {
    SyPathname pathName (filename);
    sprintf(buffer,"programming/wad/%s.wad", pathName.Base().AsChar()); 

    SyESFParse tParser = mpTitan->GetTitanUI()->GetESFParser();
    SyScene * tScene = mpTitan->GetScene();
    int result = tParser.Parse (buffer, *tScene);
    if (result == -1)
    {
      GAME_ASSERT(ERROR_ART, false , "Wad file not found: '%s'.", buffer);
      mpTitan->SetUseWadFile (false);
    }
  }

  mpLevelObject = (cLevelObject*)cLevelObject::ReadObject(file);
  mpLevelObject->SetLevelName(filename);

  cGameObject *obj = (cGameObject*) cGameObject::ReadObject(file);

  while (obj != NULL)
  {
    obj->SetID(mpTitan->GetPeeringNetwork()->ObjectReserveId());
    obj->FixUp(this);
    Add(obj);
    obj = (cGameObject*) cGameObject::ReadObject(file);
  }
  file.Close();

  SyAssertf(mpLevelObject!=NULL,"No Level Object???");
  InitObject(cLevelObject::LEVEL_OBJECT_GAME_ID,false);

  cGameObject *cur = mIDHashTable.First();
  while (cur != NULL)
  {
    InitObject(cur->GetID(),false);
    cur = mIDHashTable.Next(cur);
  }

  mpLevelObject->SetNumGameObjects(GetNumGameObjects());
}

// only use this to jar all of the props to start them on their path to havok stabilization
void
cGameObjectRegistry::NudgeProps()
{
  cGameObject *cur = mIDHashTable.First();
  while (cur != NULL)
  {
    if (cur->GetType() == cGameObject::OBJ_PROP)
    {
     cur->Nudge();
    }
    cur = mIDHashTable.Next(cur);
  }
}

void
cGameObjectRegistry::SaveLevelBinary(const char *filename)
{
  char buffer[512];
  sprintf(buffer,"game_assets/design/level/%s",filename);
  SyObjFile file;
  if (file.Open(buffer,SyObjFile::WOnlyCreate)<0)
  {
    SyAssertf(0,"Unable To Open File %s",buffer);
    return;
  }

  mpLevelObject->SaveScriptData();
  mpLevelObject->WriteObject(file);
  mpLevelObject->ClearScriptData();
  cGameObject *cur = mIDHashTable.First();
  while (cur != NULL)
  {
    if (cur->GetType() != cGameObject::OBJ_PLAYER)
    {
      cInventoryCharacter * inventoryPtr = (cInventoryCharacter*)cur->GetInventory();
      inventoryPtr->Clear();
      cur->WriteObject(file);
    }
    cur = mIDHashTable.Next(cur);
  }
  file.Close();
}

void              
cGameObjectRegistry::TransitionLevel(const char *filename)   // call before InitGameObjects
{
  bool level_object_local = !mpLevelObject->IsRemote();

  // delete ALL nonplayer objects we own
  ClearNonplayer();

  if (level_object_local)
  {
    char buffer[512];
    sprintf(buffer,"game_assets/design/level/%s",filename);
    SyObjFile file;

    if (file.Open(buffer,SyObjFile::ROnly)<0)
    {
      SyAssertf(0,"Unable To Open File %s",buffer);
      return;
    }
    mpLevelObject = (cLevelObject*)cLevelObject::ReadObject(file);
    mpLevelObject->SetLevelName(filename);

    cGameObject *obj = (cGameObject*) cGameObject::ReadObject(file);

    while (obj != NULL)
    {
      obj->SetID(mpTitan->GetPeeringNetwork()->ObjectReserveId());
      obj->FixUp(this);
      Add(obj);
      obj = (cGameObject*) cGameObject::ReadObject(file);
    }
    file.Close();

    SyAssertf(mpLevelObject!=NULL,"No Level Object???");
    InitObject(cLevelObject::LEVEL_OBJECT_GAME_ID,false);

    cGameObject *cur = mIDHashTable.First();
    while (cur != NULL)
    {
      if (cur->IsLocal())
      {
        InitObject(cur->GetID(),false);
      }
      else
      {
        if (cur->GetName() != NULL && 
          cur->GetName()[0] != '\0')
        {
          mNameHashTable.Insert(cur, cur->GetNameID());
        }
      }

      cur = mIDHashTable.Next(cur);
    }

    mpLevelObject->SetNumGameObjects(GetNumGameObjects());
  }
  mpTitan->SetInTransition();

}


void
cGameObjectRegistry::InitObject(tGameObjectID id,bool ownerLifetime)
{
  if (cLevelObject::LEVEL_OBJECT_GAME_ID == id)
  {
    static const int MAX_LEVEL_OBJECT_BUFFER_SIZE = 30*1028;
    char buffer[MAX_LEVEL_OBJECT_BUFFER_SIZE];

    mpLevelObject->SetRemote(false);
    mpLevelObject->Init(mpTitan);
    int bufferSize = NetworkGetState(id,buffer,MAX_LEVEL_OBJECT_BUFFER_SIZE);
    mpTitan->GetPeeringNetwork()->ObjectCreate(cGameObject::OBJ_NPC,cLevelObject::LEVEL_OBJECT_GAME_ID,ownerLifetime,buffer,bufferSize);
  }
  else
  {
    static const int MAX_GAMEOBJECT_BUFFER_SIZE = 10*1028;
    char buffer[MAX_GAMEOBJECT_BUFFER_SIZE];

    cGameObject *obj= Fetch(id);
    obj->Init();
    if (obj->GetName() != NULL && 
        obj->GetName()[0] != '\0')
    {
      if (mNameHashTable.FindFirst(obj->GetNameID())==NULL)
      {
        mNameHashTable.Insert(obj, obj->GetNameID());
      }
    }
    int bufferSize = NetworkGetState(id,buffer,MAX_GAMEOBJECT_BUFFER_SIZE);
    mpTitan->GetPeeringNetwork()->ObjectCreate(obj->GetType(),obj->GetID(),ownerLifetime,buffer,bufferSize);
    obj->PostInit();
  }
}


void 
cGameObjectRegistry::Update(float time)
{
#ifdef SNTUNER
  snStartMarker(21, "mpLevelObject->Update(delta_time)");
#endif
  mpLevelObject->Update(time);
#ifdef SNTUNER
  snStopMarker(21);
#endif

#ifdef SNTUNER
  snStartMarker(16, "UpdateBeforePhysics");
#endif
  /* first UpdateBeforePhysics */
  cGameObject *cur = mIDHashTable.First();
  while (cur != NULL)
  {
    cur->UpdateBeforePhysics(time);
    cur = mIDHashTable.Next(cur);
  }
#ifdef SNTUNER
  snStopMarker(16);
#endif

#ifdef SNTUNER
  snStartMarker(17, "mpTitan->GetTitanUI()->UpdatePhysics");
#endif
  /* call update physics for the game Physics object */
  mpTitan->GetTitanUI()->UpdatePhysics( time );
#ifdef SNTUNER
  snStopMarker(17);
#endif

#ifdef SNTUNER
  snStartMarker(18, "cGameObjectRegistry::UpdatePhysics");
#endif
  /* UpdatePhysics */
  cur = mIDHashTable.First();
  while (cur != NULL)
  {
    cur->UpdatePhysics(time);
    cur = mIDHashTable.Next(cur);
  }
#ifdef SNTUNER
  snStopMarker(18);
#endif

#ifdef SNTUNER
  snStartMarker(19, "cGameObjectRegistry::UpdateAfterPhysics");
#endif
  /* UpdateAfterPhysics */
  cur = mIDHashTable.First();
  while (cur != NULL)
  {
    cur->UpdateAfterPhysics(time);
    cur = mIDHashTable.Next(cur);
  }
#ifdef SNTUNER
  snStopMarker(19);
#endif

#ifdef SNTUNER
  snStartMarker(29, "cGameObjectRegistry::CheckForDelete");
#endif
  CheckForDelete();
#ifdef SNTUNER
  snStopMarker(29);
#endif
}

// for use with havok stabilization
void
cGameObjectRegistry::UpdateJustProps( float time )
{
  /* first UpdateBeforePhysics */
  cGameObject *cur = mIDHashTable.First();
  while (cur != NULL)
  {
    if (cur->GetType()==cGameObject::OBJ_PROP)
    {
      cur->UpdateBeforePhysics(time);
    }
    cur = mIDHashTable.Next(cur);
  }

  /* call update physics for the game Physics object */
  mpTitan->GetTitanUI()->UpdatePhysics( time );

  /* UpdatePhysics */
  cur = mIDHashTable.First();
  while (cur != NULL)
  {
    if (cur->GetType()==cGameObject::OBJ_PROP)
    {
      cur->UpdatePhysics(time);
    }
    cur = mIDHashTable.Next(cur);
  }

  /* UpdateAfterPhysics */
  cur = mIDHashTable.First();
  while (cur != NULL)
  {
    if (cur->GetType()==cGameObject::OBJ_PROP)
    {
      cur->UpdateAfterPhysics(time);
    }
    cur = mIDHashTable.Next(cur);
  }
}

void 
cGameObjectRegistry::CheckForDelete()
{
  cGameObject *cur = mIDHashTable.First();
  while (cur != NULL)
  {
    cGameObject *dead = cur;
    cur = mIDHashTable.Next(cur);
    if (dead->CheckForDelete())
    {
      Delete(dead->GetID());
    }

  }
}

void 
cGameObjectRegistry::Prerender()
{
  cGameObject *cur = mIDHashTable.First();
  while (cur != NULL)
  {
    cur->Prerender();
    cur = mIDHashTable.Next(cur);
  };
}

tGameObjectID 
cGameObjectRegistry::Create(cGameObject::tObjectType newtype)
{
  cGameObject *newobj = new cGameObject();

  tGameObjectID id = mpTitan->GetPeeringNetwork()->ObjectReserveId();
  newobj->Create(this,id,newtype);
 
  if (Add(newobj))
  {
    if (mpLevelObject)
    {
      mpLevelObject->SetNumGameObjects(GetNumGameObjects());
    }

    return id;
  }

  return ID_NONE;
}


tGameObjectID     
cGameObjectRegistry::NetworkCreate(cGameObject::tObjectType newtype,tGameObjectID id,const char *state,int statelen)
{
  if (cLevelObject::LEVEL_OBJECT_GAME_ID == id)
  {
    SyObjFile file;
    if (file.Open((void *)state,statelen,statelen,SyObjFile::ROnly)<0)
    {
        SyAssertf(0,"Unable To Open File.");
        return ID_NONE;
    }

    uint16 Type;
    uint16 Version;
    file.ReadBegin(Type,Version);
    delete mpLevelObject;
    mpLevelObject = (cLevelObject*)cLevelObject::ReadObject(file);
    SyAssert(mpLevelObject!=NULL);
    if (mpLevelObject)
    {
      mpLevelObject->SetRemote(true);
      mpLevelObject->Init(mpTitan);
    }
    file.ReadEnd();
    file.Close();
  }
  else
  {
    cGameObject *newobj = Fetch(id);
    
    bool bFound = false;

    if (newobj)
    {
      bFound = true; // create an object from a network save game that already exists here
    }
    else 
    {
      newobj = new cGameObject();
      newobj->Create(this,id,newtype);

      if (!Add(newobj))
      {
        SyAssertf(0,"Unable to add new object");
        return 0;
      }
    }
 
    NetworkSetState(id, state, statelen); // calls init on the object

    if (!bFound)
    {
      cGameObject *obj = Fetch(id);
      SyAssertf(obj!=NULL,"Unable to insert new object???");
      if (obj != NULL && obj->GetName() != NULL && obj->GetName()[0] != '\0')
      {
        mNameHashTable.Insert(obj, obj->GetNameID());
      }
    }

    if (mpLevelObject)
    {
      mpLevelObject->SetNumGameObjects(GetNumGameObjects());
    }
  }

  return id;
}

void              
cGameObjectRegistry::NetworkDelete(tGameObjectID id)
{
  if (id ==cLevelObject::LEVEL_OBJECT_GAME_ID )
  {
    // must be transitioning levels...
    return;
  }

  cGameObject *obj = mIDHashTable.FindFirst(id);
  if (obj != NULL)
  { 
    obj->Exit();
    mNameHashTable.Remove(obj);
    SyAssertf(mNameHashTable.FindFirst(obj->GetNameID())!=obj,"Remove from hash table failed.");
    mIDHashTable.Remove(obj);
    mTypeLists[obj->GetType()]->Remove(obj);
    mXMin.Remove(obj);
    mXMax.Remove(obj);
    mOpen.Remove(obj);

    if (mpLevelObject)
    {
      mpLevelObject->SetNumGameObjects(GetNumGameObjects());
    }

    delete obj;
  }
}


void     
cGameObjectRegistry::NetworkSetState(tGameObjectID id,const char *state,int statelen)
{
  SyObjFile file;
  if (file.Open((void *)state,statelen,statelen,SyObjFile::ROnly)<0)
  {
    SyAssertf(0,"Unable To Open File.");
    return;
  }

  uint16 Type;
  uint16 Version;
  file.ReadBegin(Type,Version);
  if (id ==cLevelObject::LEVEL_OBJECT_GAME_ID )
  {
    delete mpLevelObject;
    mpLevelObject = (cLevelObject*)cLevelObject::ReadObject(file);
    SyAssert(mpLevelObject!=NULL);
    if (mpLevelObject)
    {
      mpLevelObject->Init(mpTitan);
    }
  }
  else
  {
    cGameObject *obj = Fetch(id);
    int result = obj->Reset(file);
    SyAssertf(result >= 0, "GameObject failed to set network state (buffer not big enough?)");
    obj->FixUp(this);
    obj->SetRemote(!mpTitan->GetPeeringNetwork()->ObjectIsOwner(id));
    obj->Init();
    obj->PostInit();
  }
  file.ReadEnd();
  file.Close();
}

unsigned int               
cGameObjectRegistry::NetworkGetState(tGameObjectID id,char *state, int maxlen)
{
  int curLoc = 0;
  SyObjFile file;
  if (file.Open(state,maxlen,curLoc,SyObjFile::WOnlyCreate)<0)
  {
    SyAssertf(0,"Unable To Open File.");
    return (unsigned)-1;
  }

  uint16 Type = 0x0001;
  uint16 Version = 0x0002;
  file.WriteBegin( Type,Version );
  if (id ==cLevelObject::LEVEL_OBJECT_GAME_ID )
  {
    mpLevelObject->WriteObject(file);
  }
  else
  {
    cGameObject *obj = Fetch(id);
    if (obj == NULL)
    {
      SyAssertf(0,"Asking for state of object i don't have.");
      return 0;
    }
    obj->GetState(file);
  }
  uint64 Size;
  file.WriteEnd(); // Size includes the object header
  Size=file.FileSize();
  file.Close();
  return (unsigned int)Size;
}

unsigned int      
cGameObjectRegistry::NetworkReceiveBroadcast(tGameObjectID id,const char *state, int maxlen)
{
  if (cLevelObject::LEVEL_OBJECT_GAME_ID == id)
  {
    SyAssertf(mpLevelObject!=NULL,"No Level while receiving net broadcast");

    if (mpLevelObject != NULL)
    {
      mpLevelObject->NetworkReceiveBroadcast(state, maxlen);
    }
  }
  else
  {
    cGameObject *obj_ptr = Fetch(id);
    SyAssertf(obj_ptr!=NULL,"Unknown object");
    if (obj_ptr != NULL)
    {
      obj_ptr->NetworkReceiveBroadcast(state,maxlen);
    }
  }

  return true;
}

unsigned int      
cGameObjectRegistry::NetworkReceiveMessage(tGameObjectID id,const char *state, int maxlen)
{
  if (cLevelObject::LEVEL_OBJECT_GAME_ID == id)
  {
    SyAssertf(mpLevelObject!=NULL,"No Level while receiving net msg");

    if (mpLevelObject != NULL)
    {
      mpLevelObject->NetworkReceiveMessage(state, maxlen);
    }
  }
  else
  {
    cGameObject *obj_ptr = Fetch(id);
    // quite possible to receive message for object that's been deleted.
    if (obj_ptr != NULL)
    {
      obj_ptr->NetworkReceiveMessage(state,maxlen);
    }
  }
  return true;
}

unsigned int      
cGameObjectRegistry::NetworkTakeOwnership(tGameObjectID id)
{
  if (id == cLevelObject::LEVEL_OBJECT_GAME_ID)
  {
    SyAssertf(mpLevelObject!=NULL,"cGameObjectRegistry::NetworkTakeOwnership - no level");
    if (mpLevelObject)
    {
      mpLevelObject->SetRemote(false);
    }

    return true;
  }
  cGameObject *obj_ptr = Fetch(id);
  SyAssertf(obj_ptr!=NULL,"Unknown object");
  if (obj_ptr != NULL)
  {
    SyAssertf(obj_ptr->IsRemote(),"Taking ownership of object i already own???");
    obj_ptr->SetRemote(false); 
  }
  return true;
}

void
cGameObjectRegistry::NetworkRequestOwnership(tGameObjectID id, int requestingPeerId)
{
  // mark this object as being no longer owned - if the transfer fails we will get 
  // ownership transferred back through the peering api
  if (id != cLevelObject::LEVEL_OBJECT_GAME_ID)
  {
    cGameObject *obj_ptr = Fetch(id);
    SyAssertf(obj_ptr!=NULL,"Unknown object");
    if (obj_ptr != NULL)
    {
      SyAssertf(!obj_ptr->IsRemote(),"Taking ownership of object i already own???");
      obj_ptr->SetRemote(true); 
    }
  }

  // the next notification you will get after calling ObjectTransferOwnership is an 
  // NetworkSetState callback which is the full state of the object under
  // the new ownership 

  // somebody is requesting ownership of the specified object (which we own), we should generally transfer ownership

    mpTitan->GetPeeringNetwork()->ObjectTransferOwnership(id, requestingPeerId);
}

bool
cGameObjectRegistry::Name(cGameObject *obj, const char *name)
{
  if (obj == NULL)
  {
    return false;
  }

  // make sure object is registered
  if (Fetch(obj->GetID()) == NULL)
  {
    SyAssertf(false, "Cannot name unregistered object!");
    return false;
  }

  // check to see if we're already named
  if (obj->GetName() != NULL)
  {
    cGameObject *found= mNameHashTable.FindFirst(obj->GetNameID());
    if (found != NULL)
    {
      if (found==obj)
      {
        // changed name
        mNameHashTable.Remove(found);
      }
      else
      {
        // name already registered to someone else!
        SyAssertf(0,"Duplicate name registration");
        return false;
      }
    }
  }

  obj->SetName(name);
  // SyAssertf(mNameHashTable.FindFirst(obj->GetNameID())==NULL,"Already in hash table?");
  mNameHashTable.Insert(obj, obj->GetNameID());

  return true;
}

int
cGameObjectRegistry::Delete(tGameObjectID erased)
{
  if (erased == cLevelObject::LEVEL_OBJECT_GAME_ID)
  {
    if (mpLevelObject == NULL) {return mObjects.End();};
    mpLevelObject->Exit();
    mpTitan->GetPeeringNetwork()->ObjectDestroy(erased);
    delete mpLevelObject;
    mpLevelObject = NULL;
    return 1;
  }

  cGameObject *obj = mIDHashTable.FindFirst(erased);
  if (obj != NULL)
  { 
    obj->Exit();
    mNameHashTable.Remove(obj);
    SyAssertf(mNameHashTable.FindFirst(obj->GetNameID())!=obj,"Remove from hash table failed.");
    mpTitan->GetPeeringNetwork()->ObjectDestroy(erased);
    mIDHashTable.Remove(obj);
    mTypeLists[obj->GetType()]->Remove(obj);
    mXMin.Remove(obj);
    mXMax.Remove(obj);
    mOpen.Remove(obj);

    if (mpLevelObject)
    {
      mpLevelObject->SetNumGameObjects(GetNumGameObjects());
    }

    delete obj;

    return 1;
  }  
  return 0;
}
  
bool
cGameObjectRegistry::Add(cGameObject *obj)
{
  if (obj == NULL)
  {
    return false;
  }

  mTypeLists[obj->GetType()]->InsertTail(obj);
  mIDHashTable.InsertTail(obj, obj->GetID());

  if (obj->GetType() != cGameObject::OBJ_MARKER)
  {
    mXMin.InsertTail(obj);
    mXMax.InsertTail(obj);
    obj->UpdateExtents();
  }
  return true;
}


cGameObject *  
cGameObjectRegistry::Fetch(tGameObjectID id)
{
  return mIDHashTable.FindFirst(id);
}

cGameObject *  
cGameObjectRegistry::Fetch(const char *name)
{
  tGameID id = SyHashResourceID(name);
  return mNameHashTable.FindFirst(id);
}

cGameObject *     
cGameObjectRegistry::FetchByActorHandle(SyActorHandle handle)
{
  int32 id = mpTitan->GetScene()->GetActorID(handle);

  // check for unitialized actor id or bad gameobject id
  if (-1 == id || ID_NONE == id)
  {
    return NULL;
  }

  return Fetch((tGameObjectID)id);
}

bool cGameObjectRegistry::SaveGameGetInfo(int index, TitanSave* pInfo)
{
  if (index < 0 || index >= mNumSaveGames)
  {
    return false;
  }

  SyObjFile file;
  char filename[32];

  SyStr::snprintf(filename, 31, "game%03d.sav", index);

  cGameSaveDescObject* pSaveGame;

  if (file.Open(filename, SyObjFile::ROnly) == -1)
  {
    return false;
  }

  pSaveGame = (cGameSaveDescObject*)cGameSaveDescObject::ReadObject(file);
  pInfo->mName = pSaveGame->mDesc;
  pInfo->mTimestamp = pSaveGame->mTimestamp;
  pInfo->mNumNetworkPlayers = GetNumRemotePlayers();
  pInfo->mNumLocalPlayers = pSaveGame->mNumLocalPlayers;

  delete pSaveGame;
  return true;
}

struct RemotePlayerSaveData
{
  tGameObjectID mPlayerID;
  int           mPeerID;
};

bool cGameObjectRegistry::SaveGameLoad(int index)
{
  SyObjFile file;
  char filename[32];

  SyStr::snprintf(filename, 31, "game%03d.sav", index);

  if (file.Open(filename,SyObjFile::ROnly)<0)
  {
    SyAssertf(0,"Unable To Open File");
    return false;
  }

  SyVector<RemotePlayerSaveData> remotePlayers;

  cGameObject* pPlayer = BeginType(cGameObject::OBJ_PLAYER);
  while (pPlayer)
  {
    if (pPlayer->IsRemote())
    {
      RemotePlayerSaveData remPlayer;
      remPlayer.mPlayerID = pPlayer->GetID();
      remPlayer.mPeerID = mpTitan->GetPeeringNetwork()->GetObjectPeerId(pPlayer->GetID());
      remotePlayers.Add(remPlayer);
    }

    pPlayer = NextType(pPlayer);
  }

  Clear();

#if 0       // not needed here? - JEFFP
  mpTitan->GetTitanUI()->SceneClearAndInit();
#endif

  cGameSaveDescObject* pSaveGame = (cGameSaveDescObject*)cGameSaveDescObject::ReadObject(file);
  delete pSaveGame;

  mpLevelObject = (cLevelObject*)cLevelObject::ReadObject(file);

  cGameObject *pObj = (cGameObject*) cGameObject::ReadObject(file);

  while (pObj != NULL)
  {
    pObj->FixUp(this);
    Add(pObj);
    pObj = (cGameObject*) cGameObject::ReadObject(file);
  }

  file.Close();

  InitObject(cLevelObject::LEVEL_OBJECT_GAME_ID, false);

  pObj = mIDHashTable.First();
  while (pObj != NULL)
  {
    InitObject(pObj->GetID(), pObj->GetType()==cGameObject::OBJ_PLAYER);
    pObj = mIDHashTable.Next(pObj);
  }  

  mLastSaveGameIndex = index;

  mpLevelObject->SetNumGameObjects(GetNumGameObjects());

  // restore network players' ownership
  pPlayer = BeginType(cGameObject::OBJ_PLAYER);
  while (pPlayer)
  {
    for (int iRemote=0;iRemote<remotePlayers.Size(); ++iRemote)
    {
      if (remotePlayers(iRemote).mPlayerID == pPlayer->GetID())
      {
        NetworkRequestOwnership(remotePlayers(iRemote).mPlayerID, remotePlayers(iRemote).mPeerID);
      }
    }

    pPlayer = NextType(pPlayer);
  }

  return true;
}

bool cGameObjectRegistry::SaveGameOverwrite(int index, const SyString& desc)
{
  SyAssert(index >= 0 && index < mNumSaveGames);

  SyObjFile file;
  char filename[32];

  SyStr::snprintf(filename, 31, "game%03d.sav", index);

  if (file.Open(filename, SyObjFile::WOnlyCreate) == -1)
  {
    return false;
  }

  cGameSaveDescObject* pSaveGame = SyNew cGameSaveDescObject(); 
  pSaveGame->mDesc = desc;
  pSaveGame->mTimestamp = SyTimer::GetTime();
  pSaveGame->mNumLocalPlayers = mpTitan->GetNumLocalPlayers();
  pSaveGame->mNumNetworkPlayers = GetNumRemotePlayers();

  if (pSaveGame->WriteObject(file) == -1)
  {
    file.Close();
    delete pSaveGame;
    return false;
  }

  if (!WriteSaveGameData(file))
  {
    file.Close();
    delete pSaveGame;
    return false;
  }

  if (file.Close() == -1)
  {
    delete pSaveGame;
    return false;
  }

  mLastSaveGameIndex = index;
  delete pSaveGame;

  return true;
}

bool cGameObjectRegistry::SaveGameCreate(const SyString& desc)
{
  SyObjFile file;
  char filename[32];

  SyStr::snprintf(filename, 31, "game%03d.sav", mNumSaveGames);

  if (file.Open(filename, SyObjFile::WOnlyCreate) == -1)
  {
    return false;
  }

  cGameSaveDescObject* pSaveGame = SyNew cGameSaveDescObject(); 
  pSaveGame->mDesc = desc;
  pSaveGame->mTimestamp = SyTimer::GetTime();
  pSaveGame->mNumLocalPlayers = mpTitan->GetNumLocalPlayers();
  pSaveGame->mNumNetworkPlayers = GetNumRemotePlayers();

  if (pSaveGame->WriteObject(file) == -1)
  {
    file.Close();
    delete pSaveGame;
    return false;
  }

  if (!WriteSaveGameData(file))
  {
    file.Close();
    delete pSaveGame;
    return false;
  }

  if (file.Close() == -1)
  {
    delete pSaveGame;
    return false;
  }

  mLastSaveGameIndex = mNumSaveGames++;
  delete pSaveGame;

  return true;
}

bool cGameObjectRegistry::SaveGameLoadLast()
{
  if (mLastSaveGameIndex == -1)
  {
    return false;
  }

  SyAssert(mLastSaveGameIndex < mNumSaveGames);

  return SaveGameLoad(mLastSaveGameIndex);
}

bool cGameObjectRegistry::SaveGameOverwriteLast()
{
  SyString desc("QuickSave");

  if (mLastSaveGameIndex == -1)
  {
    return SaveGameCreate(desc);
  }

  SyAssert(mLastSaveGameIndex >= 0 && mLastSaveGameIndex < mNumSaveGames);

  return SaveGameOverwrite(mLastSaveGameIndex, desc);
}

void cGameObjectRegistry::ReadSaveGames()
{
  SyObjFile file;
  
  char filename[32];

  mNumSaveGames = 0;

  SyStr::snprintf(filename, 31, "game%03d.sav", mNumSaveGames);

  while (file.Open(filename, SyObjFile::ROnly) != -1)
  {
    file.Close();

    ++mNumSaveGames;
    SyStr::snprintf(filename, 31, "game%03d.sav", mNumSaveGames);
  }
}

bool cGameObjectRegistry::WriteSaveGameData(SyObjFile& file)
{
  mpLevelObject->SaveScriptData();

  if (mpLevelObject->WriteObject(file) == -1)
  {
    mpLevelObject->ClearScriptData();
    return false;
  }

  mpLevelObject->ClearScriptData();

  cGameObject *cur = mIDHashTable.First();
  while (cur != NULL)
  {
    if (cur->WriteObject(file) == -1)
    {
      return false;
    }

    cur = mIDHashTable.Next(cur);
  }

  return true;
}

void              
cGameObjectRegistry::ReinsertAfterMin(cGameObject *before,cGameObject *after)
{
  mXMin.Remove(after);
  mXMin.InsertAfter(after,before);
};

void              
cGameObjectRegistry::ReinsertAfterMax(cGameObject *before,cGameObject *after)
{
  mXMax.Remove(after);
  mXMax.InsertAfter(after,before);
};

//////////////////////////////////////////////////////////////////////////////////////////// Global Funcs

void 
RegPropClasses_Registry()
{
  cGameObjectRegistry::InitPropClass();
  cGameSaveDescObject::InitPropClass();
  RegPropClasses_GameObj();
  RegPropClasses_LevelObj();
}
// EOF
