/*
 * PVVMUD a 3D MUD
 * Copyright (C) 1998-1999  Programvareverkstedet (pvv@pvv.org)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */
#include "server.H"
#include <iostream.h>
#include "mud.H"
//#include "cell.H"
//#include "area.H"
#include "srvgosmanager.H"
#include "option.H"

  
CMud::CMud(COption *option){
  TRACE_CREATE("Mud");

  m_option = option;
  m_userDB = new CUserDB(m_option->getString("userdb"));

  m_mudTime = 0;

  m_timeKeeper = new CTimeKeeper();

  m_markedClient = FALSE;
  m_markedGOS = FALSE;
  m_markedWorld = FALSE;

  m_clientList = new CObjectList();
  m_gosList = new CObjectList();
  m_worldList = new CObjectList();

  m_clientSocket = new CMudSrvSocket(this,option->getInt("port"),m_timeKeeper);
  m_GOSSocket = new CSrvGOSSrvSocket(this,option->getInt("gos_port"),
							m_timeKeeper);
  m_worldSocket =new CSrvWorldSrvSocket(this,option->getInt("world_port"),
							   m_timeKeeper);

  m_timeKeeper->addHeartBeat(SCHEDULE_INTERVAL,this);

  m_gos = NULL;
  m_geoCache = new CSrvGEOCache(this);
}

CMud::~CMud(){
  TRACE_DELETE("Mud");
  if (m_clientList != NULL) {
    m_clientList->deleteAll();
    delete m_clientList;
  }
  if (m_gosList != NULL) {
    m_gosList->deleteAll();
    delete m_gosList;
  }
  if (m_clientSocket != NULL) delete m_clientSocket;
  if (m_GOSSocket != NULL) delete m_GOSSocket;
  if (m_world != NULL) delete m_world;
  if (m_gos != NULL) delete m_gos;
}

CSrvWorld * CMud::getWorld(){
  return m_world;
}

COption * CMud::getOption(){
  return m_option;
}

CUserDB * CMud::getUserDB(){
  return m_userDB;
}

void CMud::addClient(CClientManager * client){
  m_clientList->addLast(client);
}

void CMud::removeClient(CClientManager * client){
  m_clientList->remove(client);
}

void CMud::checkMarkedClient(){
  CObjectListItem * item = m_clientList->getFirst();
  while (item != NULL){
    CClientManager * client = (CClientManager*)item->getObject();
    item = item->getNext();

    if (client->getQuit()){
	removeClient(client);
        delete client;
    }

  }
}

void CMud::addGOS(CSrvGOSManager * gos){
  m_gosList->addLast(gos);
}

void CMud::removeGOS(CSrvGOSManager * gos){
  m_gosList->remove(gos);
}

void CMud::checkMarkedGOS(){
  CObjectListItem * item = m_gosList->getFirst();
  while (item != NULL){
    CSrvGOSManager * gos = (CSrvGOSManager*)item->getObject();
    item = item->getNext();

    if (gos->getOnLine()){
      // First time this it true notify everybody that are waiting for a GOS.
      if (m_gos == NULL){
        try {
          m_gos = new CSrvGOS(this,m_timeKeeper,gos->getAddress());
        } catch (CException * e){
          cdebug << *e << "\nERROR: Failed to connect to gos: " 
               << gos->getAddress()->getAddressString() << "\n";
          delete e;
        }
      }

      gos->setRunning();

    }

    if (gos->getQuit()){
      cdebug << "Removing GOS from server\n";
      removeGOS(gos);
      delete gos;

      // Remove gos: Have to include a test to check if this is the gos
      //             that went down.
      if (m_gos != NULL){
        delete m_gos;
        m_gos = NULL;
      }
    }
  }
}

CInetAddress * CMud::getGOSAddress(){
  CSrvGOSManager * foundGOS = NULL;
  int foundNum; 
  CObjectListItem * item = m_gosList->getFirst();
  while (item != NULL){
    CSrvGOSManager * gos = (CSrvGOSManager*)item->getObject();
    item = item->getNext();

    if (gos->getRunning()){
      if ((foundGOS == NULL) || (gos->getNumClients() < foundNum)){
        foundNum = gos->getNumClients();
        foundGOS = gos;
      }
    }

  }
  if (foundGOS != NULL){
    foundGOS->addNumClients();
    return foundGOS->getAddress();
  }
  return NULL;
}

int CMud::freeGOSAddress(CInetAddress * address){
  CObjectListItem * item = m_gosList->getFirst();
  while (item != NULL){
    CSrvGOSManager * gos = (CSrvGOSManager*)item->getObject();
    item = item->getNext();

    if (gos->getAddress() == address){
      gos->addNumClients(-1);
      return TRUE;
    }
  }
  return FALSE;
}

void CMud::addWorld(CSrvWorldManager * worldSrv){
  m_worldList->addLast(worldSrv);
}

void CMud::removeWorld(CSrvWorldManager * worldSrv){
  m_worldList->remove(worldSrv);
  if (m_world != NULL) m_world->removeListener( worldSrv );
}

void CMud::checkMarkedWorld(){
  CObjectListItem * item = m_worldList->getFirst();
  while (item != NULL){
    CSrvWorldManager * worldSrv = (CSrvWorldManager*)item->getObject();
    item = item->getNext();

    if (worldSrv->getOnLine()){
      // First time this it true notify everybody that are waiting for a 
      // world server.

      if (m_world != NULL){
        m_world->addListener( worldSrv );
        m_world->sendWorld( worldSrv );
      }

      worldSrv->setRunning();

    }

    if (worldSrv->getQuit()){
        cdebug << "Removing WorldSrv from server\n";
	removeWorld(worldSrv);
        delete worldSrv;
    }
  }
}

CInetAddress * CMud::getWorldSrvAddress(){
  CSrvWorldManager * foundWorld = NULL;
  int foundNum; 
  CObjectListItem * item = m_worldList->getFirst();
  while (item != NULL){
    CSrvWorldManager * worldSrv = (CSrvWorldManager*)item->getObject();
    item = item->getNext();

    if (worldSrv->getRunning()){
      if ((foundWorld == NULL) || (worldSrv->getNumClients() < foundNum)){
        foundNum = worldSrv->getNumClients();
        foundWorld = worldSrv;
      }
    }

  }
  if (foundWorld != NULL){
    foundWorld->addNumClients();
    return foundWorld->getAddress();
  }
  return NULL;
}

int CMud::freeWorldAddress(CInetAddress * address){
  CObjectListItem * item = m_worldList->getFirst();
  while (item != NULL){
    CSrvWorldManager * worldSrv = (CSrvWorldManager*)item->getObject();
    item = item->getNext();

    if (worldSrv->getAddress() == address){
      worldSrv->addNumClients(-1);
      return TRUE;
    }
  }
  return FALSE;
}

int CMud::timeKeeperHB(){
  m_mudTime ++;

  // cdebug << "Schedule Mud Time = " << m_mudTime << " tics\n";

  if (m_markedClient){
    checkMarkedClient();
    m_markedClient = FALSE;
  }
 
  if (m_markedGOS){
    checkMarkedGOS();
    m_markedGOS = FALSE;
  }

  if (m_markedWorld){
    checkMarkedWorld();
    m_markedWorld = FALSE;
  }

//  m_world->animate( (double)SCHEDULE_INTERVAL / 1000.0 );
  m_world->animate( );

  return TRUE;
}

void CMud::loadWorld(){

  m_world = newWorld();
  createWorld(m_world);

  CObjectListItem * item = m_worldList->getFirst();
  while (item != NULL){
    CSrvWorldManager * worldSrv = (CSrvWorldManager*)item->getObject();
    item = item->getNext();
    if (worldSrv->getRunning())
      m_world->addListener(worldSrv);
  }
}

CSrvWorld * CMud::newWorld(){
  return new CSrvWorld(this);
}

void CMud::createWorld(CSrvWorld * world){
  cdebug << "CreateWorld not overloaded!\n";
}

void CMud::run(){
  loadWorld();
  m_timeKeeper->mainLoop();
}

void CMud::stop(){
  m_timeKeeper->stopLoop();
}

void CMud::request(int requestType,int requestId){
  if (m_gos != NULL) m_gos->request(requestType,requestId);
}

void CMud::error(){
}

void CMud::geometry(CGeometry * geometry){
  CGeometry * geo = (CGeometry*)m_geoCache->add(geometry->getId(),geometry);
  if (geo != NULL) delete geo;

//  char name[256];
//  sprintf(name,"tmp/%i.geo",geometry->getId());
//  FILE * geofile = fopen(name,"w");
//  geometry->writeBOG(geofile);
//  fclose(geofile);
}

void CMud::material(CMaterial * material){
  delete material;
}

void CMud::texture(CTexture * texture){
  delete texture;
}

CGeometry * CMud::getGeometry(DWORD geometryId){
  return (CGeometry*)m_geoCache->get(geometryId);
}



///////////////////////////////////////////////////////////////////////////////
// chatMessage
//   Send a message to every client connected
///////////////////////////////////////////////////////////////////////////////
void CMud::chatMessage(const char * message){
  CObjectListItem * item = m_clientList->getFirst();
  while (item != NULL){
    CClientManager * client = (CClientManager*)item->getObject();
    item = item->getNext();
    client->sendMsg( message );
  }
}