/* Web Polygraph       http://www.web-polygraph.org/
 * (C) 2003-2006 The Measurement Factory
 * Licensed under the Apache License, Version 2.0 */

#include "base/polygraph.h"

#include <ctype.h>
#include "xstd/h/iostream.h"
#include "xstd/h/iomanip.h"

#include "xstd/BitMask.h"
#include "xstd/Ssl.h"
#include "base/ObjId.h"
#include "base/RndPermut.h"
#include "base/BStream.h"
#include "base/AddrParsers.h"
#include "base/polyLogCats.h"
#include "base/polyLogTags.h"
#include "runtime/AddrMap.h"
#include "runtime/HostMap.h"
#include "runtime/PubWorld.h"
#include "runtime/HttpCookies.h"
#include "runtime/StatPhase.h"
#include "runtime/StatPhaseMgr.h"
#include "runtime/LogComment.h"
#include "runtime/ErrorMgr.h"
#include "runtime/EphPortMgr.h"
#include "runtime/ExpPortMgr.h"
#include "runtime/PersistWorkSetMgr.h"
#include "runtime/PopModel.h"
#include "runtime/polyBcastChannels.h"
#include "runtime/polyErrors.h"
#include "runtime/StatPhaseMgr.h"
#include "runtime/globals.h"
#include "runtime/SslWrap.h"
#include "runtime/SslWraps.h"
#include "csm/ContentSel.h"
#include "csm/ContentCfg.h"
#include "csm/ContentMgr.h"
#include "csm/oid2Url.h"
#include "pgl/RobotSym.h"
#include "dns/DnsMgr.h"
#include "client/CltConnMgr.h"
#include "client/CltOpts.h"
#include "client/CltXact.h"
#include "client/ForeignWorld.h"
#include "client/IcpCltXact.h"
#include "client/SessionMgr.h"
#include "client/PrivWorlds.h"
#include "client/PrivCache.h"
#include "client/UserCred.h"
#include "client/CltCfg.h"
#include "client/Client.h"


class IcpAgentCfg;

struct SharedCfgItem { const RobotSym *sym; CltCfg *cfg; };
static Array<SharedCfgItem> SharedCfgs;

XactFarm<CltXact> *Client::TheXacts = 0;
ObjFarm<IcpCltXact> Client::TheIcpXacts;

CltSharedCfgs *Client::TheSharedCfgs = new CltSharedCfgs;
Array<PortMgr*> Client::ThePortMgrs;


Client::Client(): thePrivCache(0), theAuthOrigins(0), theCfg(0), 
	theConnMgr(0), theDnsMgr(0), theSessionMgr(0), theIcpClient(0),
	theCcXactLvl(0), theExtraLaunchLvl(0), theCookiesKeepLimit(0),
	authProxy(false), isIdle(false) {

	theChannels.append(TheInfoChannel);
	theChannels.append(TheLogCfgChannel);
	theChannels.append(TheLogStateChannel);
	startListen();
}

Client::~Client() {
	delete theConnMgr;
	delete theDnsMgr;
	delete theSessionMgr;
	delete thePrivCache;
	delete theAuthOrigins;
}

void Client::configure(const RobotSym *cfg, const NetAddr &aHost) {
	Assert(TheXacts);
	Assert(aHost.port() < 0); // remove later

	SockOpt opt;
	Agent::configure(cfg, aHost, opt);

	theCfg = TheSharedCfgs->getConfig(cfg);

	configurePrivWorlds();

	int pcCap = 0;
	if (cfg->privCache(pcCap))
		thePrivCache = new PrivCache(pcCap);

	theCfg->selectProxy(theProxyAddr); // sticky selection

	theConnMgr = new CltConnMgr;
	theConnMgr->configure(opt, cfg, theProxyAddr ? 1 : theCfg->viservLimit());
	theConnMgr->portMgr(getPortMgr(true));
	theConnMgr->idleTimeout(cfg->idlePconnTimeout());

	const SslWrap *sslWrap = 0;
	if (theCfg->selectSslWrap(sslWrap)) { // sticky selection
		theSslCtx = sslWrap->makeClientCtx(theHost);
		theConnMgr->configureSsl(theSslCtx, sslWrap);
	}

	theDnsMgr = new DnsMgr(this);
	theDnsMgr->configure(cfg->dnsResolver());

	if (theCfg->theBusyPeriod) {
		theSessionMgr = new SessionMgr(this);
		theSessionMgr->configure(theCfg);
	}

	if (theCfg->thePeerHttp && !theCfg->thePeerIcp) {
		cerr << cfg->loc() << "the HTTP peer is at " << theCfg->thePeerHttp << ", but where is the ICP peer?" << endl;
		exit(-3);
	}

	isCookieSender = theCfg->selectCookieSenderStatus();
	if (isCookieSender) {
		theCookiesKeepLimit = theCfg->theCookiesKeepLimitSel ?
			(int)theCfg->theCookiesKeepLimitSel->trial() : 4;
	}

	TheXacts->limit(1024); // magic, no good way to estimate
}

void Client::icpClient(IcpClient *anIcpClient) {
	Assert(!theIcpClient);
	theIcpClient = anIcpClient;
}

void Client::start() {
	Assert(theConnMgr);
	Assert(theDnsMgr);

	Agent::start();

	theDnsMgr->start();

	theCfg->startWarmup(); // warmup plan created if needed

	if (theSessionMgr)
		theSessionMgr->start();
	else
		becomeBusy();
}

void Client::stop() {
	if (theSessionMgr)
		theSessionMgr->stop();
	else
	if (!isIdle)
		becomeIdle();

	theDnsMgr->stop();
	Agent::stop();
}

void Client::becomeBusy() {
	isIdle = false;

	selectHttpVersion(*theCfg);

	theMemberships.reset();
	theCfg->selectCredentials(theCredentials);
	theCfg->findMemberships(theCredentials, theMemberships);

	Broadcast(TheSessionBegChannel, this);
	scheduleLaunch(TheClock);
}

void Client::continueSession() {
	Broadcast(TheSessionCntChannel, this);
}

void Client::becomeIdle() {
	isIdle = true;

	if (thePrivCache)
		thePrivCache->clear();

	if (theAuthOrigins)
		theAuthOrigins->clear();
	authProxy = false;

	while (theLaunchDebts.count())
		launchCanceled(dequeSuspXact());
	theExtraLaunchLvl = 0;

	theDnsMgr->clearCache();
	theConnMgr->closeAllIdle();

	Broadcast(TheSessionEndChannel, this);
}

void Client::describe(ostream &os) const {
	Agent::describe(os);
	if (theProxyAddr)
		os << " via " << theProxyAddr;
}

void Client::noteInfoEvent(BcastChannel *ch, InfoEvent ev) {
	Assert(ch == TheInfoChannel);
	if (ev == ieWssFreeze && !PrivWorld::Frozen()) {
		for (PrivWorldIterator p(this); !p.atEnd(); ++p)
			p.privWorld().freezeWss();

		if (PrivWorld::Frozen()) {
			Comment(6) << "fyi: all private working sets are now frozen" << endc;
			ReportWss(5);
		}
	}
}

void Client::noteLogEvent(BcastChannel *ch, OLog &log) {
	if (ch == TheLogCfgChannel) {
		log << bege(lgCltCfg, lgcCltSide);
		// XXX: implement (CltCfgRec)
		log << ende;
	} else
	if (ch == TheLogStateChannel) {
		log << bege(lgCltState, lgcCltSide)
			<< theSeqvId 
			<< thePrivWorlds
			<< ende;
	}
}

void Client::noteXactDone(CltXact *x) {
	Assert(x);
	if (!Should(x->conn()))
		return; // XXX: fix me

	const ObjId oid(x->oid());

	// XXX: request type may be different this time
	CltXact *retry = shouldRetry(x) ? genXact(oid, x) : 0;

	if (theSessionMgr)
		theSessionMgr->noteXactDone(x);

	if (!Should(x->conn()))
		return; // XXX: fix me
	if (isIdle)
		x->conn()->lastUse(true); // close all connections if we are idling

	theConnMgr->put(x->conn());
	putXact(x);
	theCcXactLvl--;
	Assert(theCcXactLvl >= 0);

	// no xaction should restart when we are idle
	if (isIdle) {
		Should(!retry);
		return;
	}

	if (retry && tryLaunch(retry))
		return;
		
	// push waiting xactions forward
	if (theLaunchDebts.count() && !theConnMgr->atHardConnLimit()) {
		resumeXact();
		return;
	}

	if (theCcXactLvl == 0)
		loneXactFollowup();
}

void Client::loneXactFollowup() {
}


CltXact *Client::fetch(const ObjId &oid, DistrPoint *dp) {
	Assert(oid);
	CltXact *x = genXact(oid, 0);
	x->cacheDistrPoint(dp);
	if (tryLaunch(x))
		return x;
	return 0;
}

// must not be called directly but rather through tryLaunch
bool Client::launch(CltXact *x) {
	Assert(x);

	Connection *conn = x->conn(); // non-zero if pipelining

	if (!conn) {
		Assert(!theConnMgr->atHardConnLimit());

		const NetAddr &connAddr = x->nextHop();
		Assert(connAddr);
		Assert(!connAddr.isDomainName());

		const NetAddr destAddr = Oid2UrlHost(x->oid());

		conn = theConnMgr->get(connAddr, destAddr);

		if (!conn && ReportError(errConnectEstb)) {
			Comment(1) << theHost << " failed to connect to " << connAddr <<
			endc;
		}
	}

	if (conn) {
		theCcXactLvl++;
		x->exec(this, conn);
		return true;
	}

	launchFailed(x);
	return false;
}

// tries to generate and launch a transaction
// returns true if the xaction will be launched [eventually]
bool Client::tryLaunch() {
	if (theExtraLaunchLvl) {
		--theExtraLaunchLvl;
		return false;
	}
	
	return tryLaunch(genXact());
}

// will launch the xaction (if possible) or postpone it (if not)
// returns true if the xaction will be launched [eventually]
bool Client::tryLaunch(CltXact *x) {
	Assert(x);

	if (isIdle)
		return launchCanceled(x);

	if (!x->nextHop()) {

		// should we ask peers for the best server?
		// XXX: remove askedPeer; the x->nextHop() should be enough?
		if (theCfg->thePeerIcp && !x->askedPeer()) {
			askPeer(theCfg->thePeerIcp, x);
			return true;
		}

		// next hop address should be known at this time 
		// (but my not be resolved yet)
		if (!setNextHopAddr(x))
			return false;

		// should we lookup the next hop address?
		if (theDnsMgr->needsLookup(x->nextHop())) {
			// async call unless fails immediately
			if (lookupAddr(x))
				return true;
			launchFailed(x);
			return false;
		}
	}

	// check if we should postpone the xaction
	if (!x->conn() && theConnMgr->atHardConnLimit())
		return suspendXact(x);

	return launch(x);
}

bool Client::suspendXact(CltXact *x) {
	if (theCfg->theWaitXactLmt < 0 || theLaunchDebts.count() < theCfg->theWaitXactLmt) {
		theLaunchDebts.append(x);
		Broadcast(TheWaitBegChannel, x);
		return true;
	}

	if (ReportError(errTooManyWaitXact)) {
		Comment(3) << "xactions active: " << theCcXactLvl 
			<< " waiting: " << theLaunchDebts.count() 
			<< " limit: " << theCfg->theWaitXactLmt << endc;
	}
	launchFailed(x);
	return false;
}

void Client::resumeXact() {
	launch(dequeSuspXact());
}

CltXact *Client::dequeSuspXact() {
	CltXact *x = theLaunchDebts.pop();
	Broadcast(TheWaitEndChannel, x);
	return x;
}

bool Client::launchCanceled(CltXact *x) {
	Assert(x);
	x->noteAbort();
	putXact(x);
	return false;
}

bool Client::launchFailed(CltXact *x) {
	Assert(x);
	if (!isIdle)
		x->countFailure();
	return launchCanceled(x);
}

void Client::putXact(CltXact *x) {
	// recycle x and xactions that caused it
	// stop if a xaction still has kids
	while (x && x->finished() && x->childCount() == 0) {
		CltXact *cause = x->cause();
		if (cause)
			cause->noteChildGone(x);
		TheXacts->put(x);
		x = cause;
	}
}

CltXact *Client::genXact(const ObjId &oid, CltXact *cause) {
	CltXact *x = TheXacts->get();
	x->oid(oid);
	if (cause) {
		cause->noteChildNew(x);
		x->cause(cause);
	}
	return x;
}


CltXact *Client::genXact() {
	ObjId oid;
	genOid(oid);
	return genXact(oid, 0);
}

void Client::genOid(ObjId &oid) {
	const int interest = theCfg->selectInterest();
	if (interest == OidGenStat::intForeign) {
		selectForeignObj(oid);
	} else {
		selectViserv(oid);
		selectTarget(oid);
		selectObj(oid, interest);
		selectContType(oid);
	}
	selectReqType(oid);
	selectReqMethod(oid);

	if (theCfg->genUniqUrls)
		oid.world(UniqId::Create()); // changes every time
}

void Client::selectViserv(ObjId &oid) {
	const int viserv = theCfg->selectViserv();
	HostCfg *host = TheHostMap->at(viserv);
	Assert(host);
	Assert(host->thePubWorld);
	Assert(host->theServerRep);
	oid.viserv(viserv);

	int limit = 0;
	if (doCookies(limit) && !host->theCookies)
		host->theCookies = new HttpCookies(limit);
}

void Client::selectTarget(ObjId &oid) {
	const NetAddr &visName = TheHostMap->at(oid.viserv())->theAddr;
	int niamIdx; // name in AddrMap index
	Assert(TheAddrMap->find(visName, niamIdx));
	if (oid.type() < 0)
		selectAnyTarget(oid, niamIdx);
	else
		selectTypedTarget(oid, niamIdx);

	// sanity checks
	const HostCfg *host = TheHostMap->at(oid.target());
	Assert(host);
	Assert(host->theContent);
}

void Client::selectObj(ObjId &oid, int interest) {
	static RndGen rng;

	OidGenStat &oidGenStat = TheStatPhaseMgr->oidGenStat();

	const bool needPub = interest == OidGenStat::intPublic;
	const bool needRepeat = rng.event(theCfg->theRecurRatio*
		TheStatPhaseMgr->recurFactor().current());
	oidGenStat.recordNeed(needRepeat, interest);

	PrivWorld &privWorld = thePrivWorlds[oid.viserv()];
	PubWorld &pubWorld = *TheHostMap->at(oid.viserv())->thePubWorld;

	const bool privCanRep = privWorld.canRepeat();
	const bool privCanProd = privWorld.canProduce();

	const bool pubCanRep = pubWorld.canRepeat();
	const bool pubCanProd = pubWorld.canProduce();

	const bool canRep = privCanRep || pubCanRep;
	const bool canProd = privCanProd || pubCanProd;

	// the logic gives priority to repeatOid goal rather than to genPubOid
	if (canRep && (needRepeat || !canProd)) {
		if (pubCanRep && (needPub || !privCanRep)) {
			pubWorld.repeat(oid, theCfg->thePopModel);
			oidGenStat.recordGen(true, OidGenStat::intPublic);
			return;
		}

		Assert(privCanRep); 
		privWorld.repeat(oid, theCfg->thePopModel);
		oidGenStat.recordGen(true, OidGenStat::intPrivate);
		return;
	}

	// new public
	if (pubCanProd && (needPub || !privCanProd)) {
		pubWorld.produce(oid, rng);
		oidGenStat.recordGen(false, OidGenStat::intPublic);
		return;
	}

	// new private object (last resort, never fails)
	privWorld.produce(oid, rng);
	oidGenStat.recordGen(false, OidGenStat::intPrivate);
}

void Client::selectContType(ObjId &oid) {
	Assert(oid.type() < 0); // we do not overwrite existing setting
	const HostCfg *hcfg = TheHostMap->at(oid.target());
	Assert(hcfg);
	Assert(hcfg->theContent);
	const ContentCfg *ccfg = hcfg->theContent->getDir(oid);
	oid.type(ccfg->id());
}

void Client::selectReqType(ObjId &oid) {
	static RndGen rng;
	const int reqType =
		rng.event(TheStatPhaseMgr->specialMsgFactor().current()) ?
		(int)theCfg->theReqTypeSel->trial() : rqtBasic;
	oid.ims200(reqType == rqtIms200);
	oid.ims304(reqType == rqtIms304);
	oid.reload(reqType == rqtReload);
	oid.rediredReq(false);
}

void Client::selectReqMethod(ObjId &oid) {
	static RndGen rng;
	const int reqMethod =
		rng.event(TheStatPhaseMgr->specialMsgFactor().current()) ?
		(int)theCfg->theReqMethodSel->trial() : rqmGet;
	oid.get(reqMethod == rqmGet);
	oid.post(reqMethod == rqmPost);
	oid.head(reqMethod == rqmHead);
	oid.put(reqMethod == rqmPut);
}

// find any target behind a visible name
void Client::selectAnyTarget(ObjId &oid, int niamIdx) {
	const NetAddr &targetAddr = TheAddrMap->selectAddr(niamIdx);
	int targetIdx = -1;
	Assert(TheHostMap->find(targetAddr, targetIdx));
	oid.target(targetIdx);
}

// find a target that has requested oid type
void Client::selectTypedTarget(ObjId &oid, int niamIdx) {
	Assert(oid.type() > 0);
	for (AddrMapAddrIter i = TheAddrMap->addrIter(niamIdx); i; ++i) {
		int targetIdx = -1;
		Assert(TheHostMap->find(i.addr(), targetIdx));
		const HostCfg *hcfg = TheHostMap->at(targetIdx);
		Assert(hcfg && hcfg->theContent);
		if (hcfg->theContent->hasContType(oid.type())) {
			oid.target(targetIdx);
			return;
		}
	}

	// we failed to find a target that has the right content
	if (ReportError(errUnreachContType)) {
		static const String strUndefined = "undefined";
		const NetAddr &visName = TheHostMap->at(oid.viserv())->theAddr;
		const String kind = TheContentMgr.get(oid.type())->kind() ?
			TheContentMgr.get(oid.type())->kind() : strUndefined;
		Comment << "robot at " << host() << " cannot find content of '"
			<< kind << "' kind on server(s) visible as " << visName << endc;
	}

	selectAnyTarget(oid, niamIdx);
}

void Client::selectForeignObj(ObjId &oid) {
	Assert(theCfg->foreignWorld());
	ForeignWorld &foreignWorld = *theCfg->foreignWorld();

	static RndGen rng;

	OidGenStat &oidGenStat = TheStatPhaseMgr->oidGenStat();

	const bool needRepeat = rng.event(theCfg->theRecurRatio*
		TheStatPhaseMgr->recurFactor().current());
	oidGenStat.recordNeed(needRepeat, OidGenStat::intForeign);

	const bool canRep = foreignWorld.canRepeat();
	const bool canProd = foreignWorld.canProduce();

	if (canRep && (needRepeat || !canProd)) {
		foreignWorld.repeat(oid, theCfg->thePopModel);
		oidGenStat.recordGen(true, OidGenStat::intForeign);
		return;
	}

	foreignWorld.produce(oid, rng);
	oidGenStat.recordGen(false, OidGenStat::intForeign);
}

bool Client::credsForOrigin(const ObjId &oid, UserCred &cred) const {
	if (!theAuthOrigins || !theAuthOrigins->isSet(oid.viserv()))
		return false;
	return genCredentials(cred);
}

bool Client::credsForProxy(const ObjId &, UserCred &cred) const {
	if (!authProxy)
		return false;
	return genCredentials(cred);
}

bool Client::genCredentials(UserCred &cred) const {
	cred = UserCred(theCredentials);
	static RndGen rng;
	if (cred.image() && rng.event(theCfg->theAuthError))
		cred.invalidate();
	return cred.image();
}

bool Client::doCookies(int &limit) const {
	if (isCookieSender && theCookiesKeepLimit > 0) {
		limit = theCookiesKeepLimit;
		return true;
	} else {
		limit = 0;
		return false;
	}
}

bool Client::shouldRetry(const CltXact *x) const {
	// do not retry if we became idle while trying
	if (isIdle)
		return false;

	// count the number of consequitive retries
	int rcount = 0;
	while (x && x->needRetry()) {
		rcount++;
		x = x->cause();
	}

	if (rcount > 10) {
		ReportError(errManyRetries);
		return false;
	}

	return rcount > 0;
}

void Client::noteOriginAuthReq(CltXact *cause) {
	if (!theAuthOrigins)
		theAuthOrigins = new BitMask(theCfg->viservLimit());
	theAuthOrigins->beSet(cause->oid().viserv());
}

void Client::noteProxyAuthReq(CltXact *cause) {
	authProxy = true;
}

void Client::noteRedirect(CltXact *cause, const ObjId &oid) {
	Assert(oid.rediredReq());
	if (tryLaunch(genXact(oid, cause)))
		theExtraLaunchLvl++;
}

void Client::noteEmbedded(CltXact *parent, const ObjId &oid) {
	static RndGen rng;
	if (!rng.event(theCfg->theEmbedRecurRatio))
		return;

	if (thePrivCache && thePrivCache->loadOid(oid))
		return; // hit, no need to request oid

	CltXact *child = genXact(oid, parent);

	child->page(parent->page());

	// simulate cached DNS responses for embedded objects
	if (!oid.foreignUrl() && oid.viserv() == parent->oid().viserv())
		child->nextHopVar() = parent->nextHop();

	// pipeline if possible
	if (child->nextHop() && child->nextHop() == parent->nextHop()) {
		if (CltXactMgr *mgr = parent->getPipeline())
			child->pipeline(mgr);
	}

	if (tryLaunch(child))
		theExtraLaunchLvl++;
}

void Client::askPeer(const NetAddr &addr, CltXact *x) {
	IcpCltXact *q = TheIcpXacts.get();
	q->reason(x);
	q->exec(this, addr);
}

void Client::notePeerAsked(IcpCltXact *q) {
	Assert(q);

	CltXact *x = q->reason(this);
	x->usePeer(q->hit());
	/* pass ICP stats here ... */

	TheIcpXacts.put(q);

	tryLaunch(x);
}

bool Client::lookupAddr(CltXact *x) {
	return theDnsMgr->lookup(x->nextHop(), x);
}

void Client::noteAddrLookup(const NetAddr &addr, CltXact *x) {
	Assert(x);

	// successful lookup?
	if (addr) {
		x->nextHopVar() = addr; // update
		tryLaunch(x);
	} else {
		launchFailed(x);
	}
}

// select the address for the next hop connection
bool Client::setNextHopAddr(CltXact *x) const {
	NetAddr &addr = x->nextHopVar();

	if (x->usePeer() && theCfg->thePeerHttp)
		return addr = theCfg->thePeerHttp;

	if (theProxyAddr)
		return addr = theProxyAddr;

	if (x->oid().foreignUrl()) {
		const char *uri = x->oid().foreignUrl().cstr();
		if (!SkipHostInUri(uri, uri+x->oid().foreignUrl().len(), addr)) {
			if (ReportError(errHostlessForeignUrl))
				Comment << "foreign URL: " << uri << endc;
			return false;
		}
		return addr;
	}

	return addr = TheHostMap->at(x->oid().viserv())->theAddr;
}

PortMgr *Client::getPortMgr(bool bind2iface) {
	const NetAddr addr = bind2iface ? theHost : NetAddr();

	// check if we already have a port mgr for host
	for (int i = 0; i < ThePortMgrs.count(); ++i) {
		if (ThePortMgrs[i]->addr() == addr)
			return ThePortMgrs[i];
	}

	// create new port manager
	PortMgr *mgr = TheCltOpts.thePorts.set() ?
		(PortMgr*) new ExpPortMgr(addr, TheCltOpts.thePorts.lo(), TheCltOpts.thePorts.hi()) :
		(PortMgr*) new EphPortMgr(addr);
	ThePortMgrs.append(mgr);
	return mgr;
}

// configure private worlds, one for each [unique] origin
void Client::configurePrivWorlds() {
	thePrivWorlds.stretch(theCfg->viservLimit());
	thePrivWorlds.count(theCfg->viservLimit());
	for (PrivWorldIterator p(this); !p.atEnd(); ++p) {
		if (!p.privWorld()) {
			p.privWorld() = PrivWorld(theId);
			PrivWorld::TheTotalCount++;
		}
	}
}

void Client::loadWorkingSet(IBStream &is) {
	static bool loadedWss = false;
	if (!loadedWss) {
		is >> PrivWorld::TheWss;
		PrivWorld::TheFrozenCount = 0;
		loadedWss = true;
	}

	Agent::loadWorkingSet(is);

	const int cfgCount = PrivWorldIterator(this).count();
	const int storedCount = is.geti();
	ThePersistWorkSetMgr.checkInput();

	if (storedCount != cfgCount) {
		Comment << "warning: robot #" << theSeqvId << " used to talk to " <<
			storedCount << " origin servers when working set was " <<
			"stored but is configured to talk to " << cfgCount <<
			" (possibly different) servers now" << endc;
	}

	// load private worlds, one at a time
	for (int i = 0; i < storedCount; ++i) {
		NetAddr visName;
		PrivWorld privWorld;
		is >> visName >> privWorld;
		ThePersistWorkSetMgr.checkInput();
		int viserv = -1;
		if (TheHostMap->find(visName, viserv) && theCfg->hasViserv(viserv)) {
			if (!Should(viserv < thePrivWorlds.count())) {
				thePrivWorlds.stretch(viserv+1);
				thePrivWorlds.count(viserv+1);
			}
			if (!Should(thePrivWorlds[viserv]))
				PrivWorld::TheTotalCount++;
			thePrivWorlds.put(privWorld, viserv);
			// update frozen counter, assume default worlds were not frozen
			if (privWorld.wss() >= 0)
				PrivWorld::TheFrozenCount++;
		} else {
			Comment << "error: visible server " << visName << " in the " <<
				"private working set (being loaded for robot " << host() <<
				"from " << is.name() << ") is not in current robot's " <<
				"origins configuration, skipping" << endc;
		}
	}
}

void Client::storeWorkingSet(OBStream &os) {
	static bool storedWss = false;
	if (!storedWss) {
		os << PrivWorld::TheWss;
		storedWss = true;
	}

	Agent::storeWorkingSet(os);

	PrivWorldIterator p(this);
	os << p.count();
	for (; !p.atEnd(); ++p)
		os << p.addr() << p.privWorld();
}

void Client::missWorkingSet() {
	// should we freeze all private worlds?
}

int Client::logCat() const {
	return lgcCltSide;
}

void Client::ReportWss(int commentLvl) {
	const BigSize meanFillSz = 
		(*TheStatPhaseMgr && TheStatPhaseMgr->fillCnt()) ?
		TheStatPhaseMgr->fillSz()/TheStatPhaseMgr->fillCnt() :
		BigSize(0);

	ostream &os = Comment(commentLvl) << 
		"fyi: min 'direct' objects in working set:" << endl;

	int pubFrozenCount = 0;
	int pubTotalCount = 0;
	const int pubWss = PubWorld::CurrentWss(pubFrozenCount, pubTotalCount);
	os << "\tglobal public: " << pubWss << " (";
	if (meanFillSz > 0)
		os << "~" << (meanFillSz*pubWss) << " size, ";
	os << pubFrozenCount << '/' << pubTotalCount << '=' << 
		Percent(pubFrozenCount, pubTotalCount) << "% frozen slices)" <<
		endl;

	const int privFrozenCount = PrivWorld::TheFrozenCount;
	const int privTotalCount = PrivWorld::TheTotalCount;
	const int privWss = PrivWorld::TheWss;
	os << "\tlocal private: " << privWss << " (";
	if (meanFillSz > 0)
		os << "~" << (meanFillSz*privWss) << " size, ";
	os << privFrozenCount << '/' << privTotalCount << '=' << 
		Percent(privFrozenCount, privTotalCount) << "% frozen slices)"
		;

	os << endc;
}

void Client::Farm(XactFarm<CltXact> *aFarm) {
	Assert(!TheXacts && aFarm);
	TheXacts = aFarm;
}

void Client::LogState(OLog &log) {
	log << bege(lgSrvRepState, lgcCltSide);
	// XXX: remove state logging?
	// OLogStorePtrs(log, ThePubWorlds);
	log << ende;
}


syntax highlighted by Code2HTML, v. 0.9.1