/* 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 <fstream>
#include "xstd/h/iomanip.h"

#include "xstd/rndDistrs.h"
#include "xstd/StrIdentifier.h"
#include "xstd/Ssl.h"
#include "base/RndPermut.h"
#include "base/OidGenStat.h"
#include "pgl/RobotSym.h"
#include "pgl/SessionSym.h"
#include "pgl/AclSym.h"
#include "pgl/GoalSym.h"
#include "pgl/PglStaticSemx.h"
#include "runtime/AddrMap.h"
#include "runtime/HostMap.h"
#include "runtime/PubWorld.h"
#include "runtime/PopModel.h"
#include "runtime/Goal.h"
#include "runtime/XactAbortCoord.h"
#include "runtime/LogComment.h"
#include "runtime/httpHdrs.h"
#include "csm/XmlTagIdentifier.h"
#include "client/WarmupPlan.h"
#include "client/ServerRep.h"
#include "client/RegExGroup.h"
#include "client/ForeignWorld.h"
#include "client/Client.h"
#include "client/CltCfg.h"
#include "client/CltOpts.h"

Memberships CltCfg::TheGlbMemberships;


CltCfg::CltCfg(): theRobot(0), 
	theOriginSel(0), theInterestSel(0), theReqTypeSel(0), theReqMethodSel(0),
	thePopModel(0),
	theBusyPeriod(0), theIdlePeriodDur(0), theProxyCycleCnt(0), 
	thePipelineDepth(0),
	theRecurRatio(-1), theEmbedRecurRatio(-1),
	theAbortProb(0), theAuthError(0),
	theWaitXactLmt(-1), theIcpPort(-1),
	theCredentialCycleCnt(0), theForeignWorld(0),
	theContainerTags(0),
	theAcceptedContentCodings(0), acceptingGzipContent(false),
	theCookiesKeepLimitSel(0),
	theWarmupPlan(0),
	genUniqUrls(false), didWarmup(false) {
}

CltCfg::~CltCfg() {
	while (theCredentials.count())
		delete theCredentials.dequeue();
	while (theProxies.count())
		delete theProxies.dequeue();
	delete theContainerTags;
	delete theAcceptedContentCodings;
	delete thePipelineDepth;
	delete theCookiesKeepLimitSel;
}

void CltCfg::configure(const RobotSym *aRobot) {
	AgentCfg::configure(aRobot);

	Assert(!theRobot && aRobot);
	theRobot = aRobot;

	theRobot->waitXactLimit(theWaitXactLmt);
	thePeerHttp = theRobot->peerHttp();
	thePeerIcp = theRobot->peerIcp();
	theRobot->icpPort(theIcpPort);

	theRobot->recurRatio(theRecurRatio);
	theRobot->embedRecurRatio(theEmbedRecurRatio);
	theRobot->abortProb(theAbortProb);
	theRobot->authError(theAuthError);
	theRobot->uniqueUrls(genUniqUrls);
	theUriThrower = theRobot->rawUriThrower();

	if (PopModelSym *pms = theRobot->popModel()) {
		thePopModel = new PopModel;
		thePopModel->configure(pms);
	}

	configureInterests();
	configureReqTypes();
	configureReqMethods();
	configureOrigins();
	configureProxies();
	configureCredentials();
	configureMemberships();
	configureContainerTags();
	configureAcceptedContentCodings();
	configurePipeDepth();

	if (AclSym *acl = theRobot->acl())
		theAcl.configure(*acl);

	if (const String fname = theRobot->foreignTrace())
		configureTrace(fname);

	if (SessionSym *ss = theRobot->session()) {
		if (GoalSym *bps = ss->busyPeriod()) {
			theBusyPeriod = new Goal;
			theBusyPeriod->configure(*bps);
		}
		theIdlePeriodDur = ss->idlePeriodDuration();
		ss->heartbeatGap(theSessionHeartbitGap);

		if (!theBusyPeriod != !theIdlePeriodDur) {
			cerr << theRobot->loc() << "both busy and idle periods "
				<< "should be specified (or not specified) for a robot session"
				<< endl << xexit;
		}
	}

	if (!thePopModel && theRecurRatio > 0)
		cerr << theRobot->loc() << "popularity model must be specified for"
			<< " positive recurrence ratio (robot " << theRobot->kind() << ')'
			<< endl << xexit;

	theCookiesKeepLimitSel = theRobot->cookiesKeepLimit();
}

int CltCfg::viservLimit() const {
	return TheHostMap->iterationCount();
}

void CltCfg::configureInterests() {
	static StrIdentifier sidf;
	if (!sidf.count()) {
		sidf.add("private", OidGenStat::intPrivate);
		sidf.add("public", OidGenStat::intPublic);
		sidf.add("foreign", OidGenStat::intForeign);
		sidf.optimize();
	}

	theInterestSel = theRobot->interests(sidf);
	if (!theInterestSel)
		theInterestSel = new ConstDistr(new RndGen, OidGenStat::intPrivate); // default
	theInterestSel->rndGen(LclRndGen("client_interests"));
}

void CltCfg::configureReqTypes() {
	static StrIdentifier sidf;
	if (!sidf.count()) {
		sidf.add("Basic", Client::rqtBasic);
		sidf.add("Ims200", Client::rqtIms200);
		sidf.add("Ims304", Client::rqtIms304);
		sidf.add("Reload", Client::rqtReload);
		sidf.optimize();
	}

	theReqTypeSel = theRobot->msgTypes(sidf);
	if (!theReqTypeSel)
		theReqTypeSel = new ConstDistr(new RndGen, Client::rqtBasic); // default
	theReqTypeSel->rndGen(LclRndGen("client_req_types"));
}

void CltCfg::configurePipeDepth() {
	thePipelineDepth = theRobot->pipelineDepth();
	if (thePipelineDepth)
		thePipelineDepth->rndGen(LclRndGen("client_pipe_depth"));
}

void CltCfg::configureReqMethods() {
	static StrIdentifier sidf;
	if (!sidf.count()) {
		sidf.add("GET", Client::rqmGet);
		sidf.add("HEAD", Client::rqmHead);
		sidf.add("POST", Client::rqmPost);
		sidf.add("PUT", Client::rqmPut);
		sidf.optimize();
	}

	theReqMethodSel = theRobot->reqMethods(sidf);
	if (!theReqMethodSel)
		theReqMethodSel = new ConstDistr(new RndGen, Client::rqmGet); // default
	theReqMethodSel->rndGen(LclRndGen("client_req_methods"));
}

void CltCfg::configureOrigins() {
	Array<NetAddr*> origNames;

	RndDistr *iad = 0;
	if (!theRobot->origins(origNames, theOriginSel) || !origNames.count()) {
		if (!theRobot->reqInterArrival(iad) || iad)
			cerr << theRobot->loc() << "no origin addresses specified for active robot " 
				<< theRobot->kind() << endl << xexit;
		return;
	}

	Assert(theOriginSel);
	theOriginSel->rndGen(LclRndGen("client_origins"));

	theViservs.stretch(origNames.count());
	for (int i = 0; i < origNames.count(); ++i)
		addOrigName(*origNames[i]);
}

// converts origin name into viserv idx
void CltCfg::addOrigName(const NetAddr &oname) {
	if (!TheAddrMap->has(oname)) {
		if (oname.isDomainName())
			cerr << here << "visible server name " << oname
				<< " is not found in address maps" << endl << xexit;
		TheAddrMap->add(oname);
	}

	int viserv = -1;
	if (!TheHostMap->find(oname, viserv))
		TheHostMap->addAt(viserv, oname);
	HostCfg *host = TheHostMap->at(viserv);
	if (!host->thePubWorld)
		PubWorld::Add(host, new PubWorld(UniqId::Create()));

	if (!host->theServerRep)
		host->theServerRep = new ServerRep(oname, viserv);

	// quit if an origin entry is repeated because it complicates
	// private world accounting (two origins would have one world)
	if (hasViserv(viserv)) {
		Comment << theRobot->loc() << "error: origin " << oname <<
			" is listed more than once in robot's origins" << 
			endc << xexit;
	}

	theViservs.append(viserv);

	checkTargets(viserv);
}

// checks content cfg and creates new server representative for viserv
void CltCfg::checkTargets(int viserv) {
	const NetAddr &visName = TheHostMap->at(viserv)->theAddr;
	int niamIdx; // name in AddrMap index
	Assert(TheAddrMap->find(visName, niamIdx));

	for (AddrMapAddrIter i = TheAddrMap->addrIter(niamIdx); i; ++i) {
		const NetAddr &addr = i.addr();
		int targetIdx = -1;
		if (!TheHostMap->find(addr, targetIdx) || 
			!TheHostMap->at(targetIdx)->theContent) {
			Comment << theRobot->loc() << "error: Robot cannot find"
				<< " configuration for server@" << addr;
			if (addr != i.name())
				Comment << " (visible as " << i.name() << ")";
			Comment << endc << xexit;
		}
	}
}

void CltCfg::configureProxies() {
	Array<NetAddr*> addrs;
	theRobot->proxies(addrs);
	theProxies.resize(addrs.count());
	while (addrs.count())
		theProxies.enqueue(addrs.pop());

	if (TheCltOpts.theProxyAddr && theProxies.count()) {
		Comment << theRobot->loc() << "--proxy option and "
			<< "Robot.proxies field are mutually exclusive" << endc << xexit;
	}
}

void CltCfg::configureCredentials() {
	Array<String*> creds;
	theRobot->credentials(creds);
	theCredentials.resize(creds.count());
	while (creds.count())
		theCredentials.enqueue(creds.pop());
}

void CltCfg::configureContainerTags() {
	Array<String*> tags;
	if (!theRobot->containerTags(tags))
		tags.append(new String("<embed src>")); // default
	theContainerTags = new XmlTagIdentifier();
	theContainerTags->configure(tags);
}

void CltCfg::configureAcceptedContentCodings() {
	Array<String*> codings;
	if (theRobot->acceptedContentCodings(codings)) {
		theAcceptedContentCodings = new String;
		for (int i = 0; i < codings.count(); ++i) {
			const String &coding = *codings[i];
			if (coding.startsWith("*") || coding.startsWith("gzip"))
				acceptingGzipContent = true;
			if (*theAcceptedContentCodings)
				*theAcceptedContentCodings += ", ";
			*theAcceptedContentCodings += coding;
			delete codings[i];
		}
	}
}

void CltCfg::configureMemberships() {
	// initialize global array once
	const Array<MembershipMapSym*> &syms = PglStaticSemx::TheMembershipsToUse;
	if (TheGlbMemberships.count() != syms.count()) {
		TheGlbMemberships.stretch(syms.count());
		for (int i = 0; i < syms.count(); ++i) {
			MembershipMap *m = new MembershipMap;
			m->configure(*syms[i], i+1);
			TheGlbMemberships.append(m);
		}
	}

	for (int i = 0; i < TheGlbMemberships.count(); ++i) {
		MembershipMap *g = TheGlbMemberships[i];
		bool belongs = false;
		for (int u = 0; !belongs && u < theCredentials.count(); ++u) {
			belongs = g->hasMember(*theCredentials.top(u));
		}
		if (belongs)
			theLclMemberships.append(g);
	}
}

void CltCfg::configureTrace(const String &fname) {
	theForeignWorld = new ForeignWorld;
	theForeignWorld->configure(fname);
}

bool CltCfg::hasViserv(int viserv) const {
	for (int origin = 0; origin < theViservs.count(); ++origin) {
		if (theViservs[origin] == viserv)
			return true;
	}
	return false;
}

bool CltCfg::selectCredentials(String &newCred) {
	if (theCredentials.empty())
		return false;

	// randomize in the beginning of each cycle
	if (theCredentialCycleCnt >= theCredentials.count())
		theCredentialCycleCnt = 0;
	if (!theCredentialCycleCnt) {
		static RndGen rng;
		theCredentials.randomize(rng);
	}
	theCredentialCycleCnt++;
		
	String *credential = theCredentials.dequeue();
	theCredentials.enqueue(credential);
	newCred = *credential;
	return true;
}

int CltCfg::selectInterest() {
	const int interest = (int)theInterestSel->trial();
	Assert(interest == OidGenStat::intPrivate ||
		interest == OidGenStat::intPublic ||
		interest == OidGenStat::intForeign);
	return interest;
}

int CltCfg::selectViserv() {
	if (theWarmupPlan) {
		const int viservIdx = theWarmupPlan->selectViserv();
		if (viservIdx >= 0)
			return viservIdx;
		stopWarmup();
	}

	const int viservIdx = (int)theOriginSel->trial();
	Assert(0 <= viservIdx && viservIdx < theViservs.count());
	return theViservs[viservIdx];
}

bool CltCfg::selectProxy(NetAddr &newAddr) {
	if (TheCltOpts.theProxyAddr) {
		newAddr = TheCltOpts.theProxyAddr;
		return true;
	}

	if (!theProxies.count())
		return false;
	
	// randomize in the beginning of each cycle
	if (theProxyCycleCnt >= theProxies.count())
		theProxyCycleCnt = 0;
	if (!theProxyCycleCnt) {
		static RndGen rng;
		theProxies.randomize(rng);
	}
	theProxyCycleCnt++;
	
	NetAddr *addr = theProxies.dequeue();
	theProxies.enqueue(addr);
	newAddr = *addr;
	return true;
}

// XXX: merge this with SrvCfg::selectAbortCoord()
void CltCfg::selectAbortCoord(XactAbortCoord &coord) {
	static RndGen rng1, rng2; // uncorrelated unless theAbortProb is 1
	if (rng1.event(theAbortProb)) {
		const int whether = rng2.state();
		(void)rng2.trial();
		coord.configure(rng2.state(), whether);
	} else {
		const int whether = rng1.state();
		(void)rng1.trial();
		coord.configure(whether, rng1.state());
	}
}

int CltCfg::findMemberships(const String &user, Memberships &groups) const {
	if (user) {
		for (int i = 0; i < theLclMemberships.count(); ++i) {
			MembershipMap *g = theLclMemberships[i];
			if (g->hasMember(user))
				groups.append(g);
		}
	}
	return groups.count();
}

bool CltCfg::followAllUris(const RepHdr &rep) const {
	if (theUriThrower.len() > 0) {
		return theUriThrower == "*" ||
			rep.theServer.startsWith(theUriThrower);
	} else {
		return false;
	}
}

// may be called multiple times
void CltCfg::startWarmup() {
	if (!theWarmupPlan && !didWarmup)
		theWarmupPlan = new WarmupPlan(theViservs);
}

void CltCfg::stopWarmup() {
	Assert(theWarmupPlan && !didWarmup);
	delete theWarmupPlan;
	theWarmupPlan = 0;
	didWarmup = true;
}

/* CltSharedCfgs */

CltCfg *CltSharedCfgs::getConfig(const RobotSym *rs) {
	for (int i = 0; i < count(); ++i) {
		if (item(i)->theRobot == rs)
			return item(i);
	}
	return addConfig(rs);
}

CltCfg *CltSharedCfgs::addConfig(const RobotSym *rs) {
	CltCfg *cfg = new CltCfg;
	cfg->configure(rs);
	append(cfg);
	return cfg;
}



syntax highlighted by Code2HTML, v. 0.9.1