/*
 * winFlowJoWorkspace.hpp
 *
 *  Created on: May 15, 2012
 *      Author: wjiang2
 */

#ifndef WINFLOWJOWORKSPACE_HPP_
#define WINFLOWJOWORKSPACE_HPP_
#include "flowJoWorkspace.hpp"

namespace CytoML
{
class winFlowJoWorkspace:public flowJoWorkspace{
public:



	  winFlowJoWorkspace(xmlDoc * doc):flowJoWorkspace(doc){
			if(g_loglevel>=GATING_SET_LEVEL)
				COUT<<"windows version of flowJo workspace recognized."<<endl;
	  	//(unfortunately libxml2 doesn't support union query e.g.('*/(Population|NotNode)')
	  	nodePath.popNode="./*/*[name()='Population' or name()='NotNode' or name()='OrNode' or name()='AndNode']";//relative to sampleNode
	  	nodePath.gateDim="*[local-name()='dimension']";//relative to gateNode
	  	nodePath.gateParam="*[local-name()='parameter']";//relative to dimNode


	  }


	  string xPathSample(string sampleID){
	  			string xpath=nodePath.sample;
	  			xpath.append("/DataSet[@sampleID='");
	  			xpath.append(sampleID);
	  			xpath.append("']/..");
	  			return xpath;

	  }
	  /*
	   * choose the trans from global trans vector to attach to current sample
	   */
	  trans_local getTransformation(wsRootNode root,const compensation & comp, PARAM_VEC & transFlag, const trans_global_vec & gTrans, bool prefixed){

	  	trans_local res;
	  	unsigned sampleID=atoi(root.getProperty("sampleID").c_str());
	  	string sPrefix=comp.prefix;

	  	/*
	  	 *  save trans back to trans_local for each channel
	  	 */

	  	trans_map tp=res.getTransMap();
	  	for(trans_global_vec::const_iterator it=gTrans.begin();it!=gTrans.end();it++)
	  	{
	  		vector<int> sampleVec=it->getSampleIDs();
	  		vector<int>::iterator sampleRes=find(sampleVec.begin(),sampleVec.end(),sampleID);
	  		/*
	  		 * when sampleID matched, then get trans from current global trans group
	  		 */
	  		if(sampleRes!=sampleVec.end())
	  		{


	  			for(PARAM_VEC::iterator isTransIt=transFlag.begin();isTransIt!=transFlag.end();isTransIt++)
	  			{
	  				string curChName=isTransIt->param;
	  				if(isTransIt->log)
	  				{
	  					string curCmpChName = curChName;
	  					//TODO:check the logic here(compare with mac version)
	  					if(prefixed)
	  					    curCmpChName = sPrefix + curCmpChName;//append prefix
	  					trans_map curtp=it->getTransMap();
	  					trans_map::iterator resIt=curtp.find(curCmpChName);
	  					//try generic version if channel-specific is not found
	  					if(resIt==curtp.end())
	  						resIt = curtp.find("*");
	  					shared_ptr<transformation> curTrans;
	  					if(resIt!=curtp.end())
	  						curTrans=resIt->second;
	  					else{
	  						/*
	  						 * use default biexp trans if nothing matched
	  						 */
	  						curTrans.reset(new biexpTrans());

	  					}

	  					tp[curCmpChName]=curTrans;

	  					if(g_loglevel>=GATING_HIERARCHY_LEVEL)
  							COUT<<curCmpChName<<":"<<curTrans->getName()<<" "<<curTrans->getChannel()<<endl;

	  					/*
	  					 * calculate calibration table from the function
	  					 */
	  					if(!curTrans->computed())
	  					{
	  						if(g_loglevel>=GATING_HIERARCHY_LEVEL)
	  							COUT<<"computing calibration table..."<<endl;
	  						curTrans->computCalTbl();
	  					}

	  					if(!curTrans->isInterpolated())
	  					{
	  						if(g_loglevel>=GATING_HIERARCHY_LEVEL)
	  							COUT<<"spline interpolating..."<<endl;
	  						curTrans->interpolate();
	  					}

	  				}

	  			}

	  			/*
	  			 * assume this sampleID will not appear in any other global trans group
	  			 */
	  			break;
	  		}


	  	}

	  	res.setTransMap(tp);
	  	return res;
	  }

	  /*
	   *parsing transformations from CompensationEditor node and
	   *store in global container within gs
	   *
	   */
	  trans_global_vec getGlobalTrans(){


	  	trans_global_vec res;


	  	/*
	  	 * get CompensationEditor node
	  	 */

	  	string path="/Workspace/CompensationEditor";
	  	xmlXPathContextPtr context = xmlXPathNewContext(doc);
	  	xmlXPathObjectPtr CompEdres = xmlXPathEval((xmlChar *)path.c_str(), context);
	  	if(xmlXPathNodeSetIsEmpty(CompEdres->nodesetval))
	  	{
	  		COUT<<"no CompensationEditor found!"<<endl;
	  		xmlXPathFreeObject(CompEdres);
	  		xmlXPathFreeContext(context);
	  		return(res);
	  	}
	  	wsNode compEdNode(CompEdres->nodesetval->nodeTab[0]);

	  	/*
	  	 * parse all Compensation nodes and store them in global trans container
	  	 */
	  	path="/Workspace/CompensationEditor/Compensation";
	  	wsNode node(doc->children);
	  	xmlXPathObjectPtr compNodeRes =node.xpath(path);
	  	unsigned short nCompNodes=compNodeRes->nodesetval->nodeNr;
	  	if(nCompNodes<=0)
	  	{
	  		COUT<<"compensation not found!"<<endl;
	  		xmlXPathFreeObject(compNodeRes);
	  		return(res);
	  	}
	  	for(unsigned i =0;i<nCompNodes;i++)
	  	{
	  		wsNode compNode(compNodeRes->nodesetval->nodeTab[i]);
	  		string compName=compNode.getProperty("name");

	  		trans_global curTg;
	  		curTg.setGroupName(compName);

	  		if(g_loglevel>=GATING_SET_LEVEL)
	  			COUT<<"group:"<<compName<<endl;
	  		/*
	  		 * parse transformations for current compNode
	  		 */
	  		trans_map curTp=curTg.getTransMap();
	  		path=".//*[local-name()='logicle']";//get logicle(actually it is biexp)
	  		xmlXPathObjectPtr TransRes=compNode.xpathInNode(path);
	  		for(int j=0;j<TransRes->nodesetval->nodeNr;j++)
	  		{
	  			wsNode transNode(TransRes->nodesetval->nodeTab[j]);

	  			string pname=transNode.getProperty("parameter");
	  			/*
	  			 * when channel name is not specified
	  			 * try to parse it as the generic trans that is applied to channels
	  			 * that do not have channel-specific trans defined in workspace
	  			 * ,
	  			 */
	  			if(pname.empty())
	  				pname="*";

	  			string transType=(const char*)transNode.getNodePtr()->name;
	  			if(transType.compare("logicle")==0)
	  			{

	  				if(g_loglevel>=GATING_SET_LEVEL)
	  					COUT<<"logicle func:"<<pname<<endl;
	  				shared_ptr<biexpTrans> curTran (new biexpTrans());
	  				curTran->setName(compName);


	  				curTran->setChannel(pname);
	  				curTran->pos=atof(transNode.getProperty("T").c_str());
	  				curTran->neg=atof(transNode.getProperty("w").c_str());
	  				curTran->widthBasis=atof(transNode.getProperty("m").c_str());
	  				/*
	  				 * do the lazy calibration table calculation and interpolation
	  				 * when it gets saved in gh
	  				 */
	  				curTp[curTran->getChannel()]=curTran;
	  			}
	  			else
	  			{
	  				xmlXPathFreeObject(TransRes);
	  				xmlXPathFreeObject(compNodeRes);
	  				throw(domain_error("unknown tranformation type!" + transType));
	  			}


	  		}
	  		xmlXPathFreeObject(TransRes);
	  		/*
	  		 * parse sample list
	  		 */
	  		path="Samples/Sample";//get logicle(actually it is biexp)
	  		xmlXPathObjectPtr sampleRes=compNode.xpathInNode(path);
	  		unsigned nSample=sampleRes->nodesetval->nodeNr;
	  		vector<int> sampleIDs;
	  		for(unsigned j=0;j<nSample;j++)
	  		{
	  			wsNode curNode(sampleRes->nodesetval->nodeTab[j]);
	  			string curSampleID=curNode.getProperty("sampleID");
	  			sampleIDs.push_back(atoi(curSampleID.c_str()));
	  		}
	  		curTg.setSampleIDs(sampleIDs);
	  		xmlXPathFreeObject(sampleRes);
	  		curTg.setTransMap(curTp);
	  		/*
	  		 * push the tg object to global container
	  		 */
	  		res.push_back(curTg);
	  	}
	  	xmlXPathFreeObject(compNodeRes);


	  	return res;
	  }


	  compensation getCompensation(wsSampleNode sampleNode)
	  {
	  	compensation comp;

	  	xmlXPathObjectPtr res=sampleNode.xpathInNode("*[local-name()='spilloverMatrix']");
	  	if(res->nodesetval->nodeNr > 1)
	  	{
	  		xmlXPathFreeObject(res);
	  		throw(domain_error("not valid compensation node!"));
	  	}
	  	else if(res->nodesetval->nodeNr == 0)
	  	{
	  		 //no comp defined

	  		comp.cid="-2";
	  		comp.prefix="";
	  		comp.suffix="";
	  		comp.comment="none";
	  		comp.name="none";
	  	}
	  	else
	  	{

	  		wsNode node(res->nodesetval->nodeTab[0]);
	  		xmlXPathFreeObject(res);

	  		comp.cid=node.getProperty("id");
	  		comp.prefix=node.getProperty("prefix");
	  		comp.suffix=node.getProperty("suffix");
	  		/*
	  		 * -1:Acquisition-defined,to be computed from data
	  		 * -2:None
	  		 * empty:data is compensated already,spillover matrix can be read from keyword node or empty
	  		 * other:the spillover matrix is stored at special compensation node,
	  		 * 			and this cid serves as id to index that node. in pc version, we observe it is also stored at curent
	  		 * 			sampleNode,to keep the parsing consistent,we still look for it from the special compensation node within the context of xml root
	  		 */
	  		if(comp.cid.compare("-1")==0)
	  		{
	  			comp.comment="Acquisition-defined";
	  			comp.prefix="Comp-";
	  		}
	  		else if(comp.cid.compare("-2")==0)
	  			comp.comment="none";
	  		else if(comp.cid.empty())
	  			throw(domain_error("empty cid not supported yet!"));
	  		else
	  		{
	  			/*
	  			 * directly look for comp from spilloverMatrix node under sampleNode
	  			 */
	  			string path="*[local-name()='spillover']";
	  			xmlXPathObjectPtr resX=node.xpathInNode(path);

	  			/*
	  			 * deprecated:look for comp from global comp node.
	  			 * currently this is done by matching cid,yet it has been proved to be wrong,
	  			 * instead,should look for sampleNode to match sampleID
	  			 */
	  	//		string path="/Workspace/CompensationEditor/Compensation[@name='"+comp.cid+"']/*[local-name()='spilloverMatrix']/*[local-name()='spillover']";
	  	////			COUT<<path<<endl;
	  	//		xmlXPathObjectPtr resX=node.xpath(path);
	  			unsigned nX=resX->nodesetval->nodeNr;
	  			for(unsigned i=0;i<nX;i++)
	  			{
	  				wsNode curMarkerNode_X(resX->nodesetval->nodeTab[i]);
	  				comp.marker.push_back(curMarkerNode_X.getProperty("parameter"));
	  				xmlXPathObjectPtr resY=curMarkerNode_X.xpathInNode("*[local-name()='coefficient']");
	  				unsigned nY=resY->nodesetval->nodeNr;
	  				for(unsigned j=0;j<nY;j++)
	  				{
		  				wsNode curMarkerNode_Y(resY->nodesetval->nodeTab[j]);
		  				// On first pass through, add the detector names as well
	  					if(i == 0)
	  						comp.detector.push_back(curMarkerNode_Y.getProperty("parameter"));
		  				string sValue=curMarkerNode_Y.getProperty("value");
		  				comp.spillOver.push_back(atof(sValue.c_str()));
	  				}
	  				xmlXPathFreeObject(resY);
	  			}
	  			xmlXPathFreeObject(resX);

	  		}
	  	}
	  	return comp;
	  }

	  /*
	   * ellipsoidGate is a specialized ellipGate that is stored as transformed scale and
	   * scaled in 256 * 256 scale
	   */
	  gatePtr getGate(wsEllipseGateNode & node){
	  	/*
	  	 * using the same routine of polygon gate to parse 4 ellipse coordinates
	  	 */
	  	wsPolyGateNode pGNode(node.getNodePtr());
	  	auto pg=dynamic_pointer_cast<polygonGate>(getGate(pGNode, "*[local-name()='edge']/*[local-name()='vertex']"));
	  	vector<coordinate> v=pg->getParam().getVertices();


	  	/*
	  	 * copy four coordinates
	  	 */
	  	if(v.size()!=4)
	  		throw(domain_error("invalid number of antipode pionts of ellipse gate!"));

	  	unique_ptr<ellipsoidGate> gate(new ellipsoidGate(v, pg->getParam().getNameArray()));
	  	//Shift gets lost in ellipsoidGate constructor, so need to add it back in
	  	gate->setShift(getShift(node));
	  	return gatePtr(gate.release());

	  }

	  gatePtr getGate(wsCurlyQuadGateNode & node){
	  	//get intersection point
	  	xmlXPathObjectPtr resPara=node.xpathInNode(nodePath.gateDim);
	  	int nParam=resPara->nodesetval->nodeNr;
	  	if(nParam!=2)
	  		throw(logic_error("invalid number of dimensions for CurlyQuad gate!"));
	  	/*
	  	 * parse the parameters
	  	 */

	  	vector<string> dims;
	  	coordinate intersect;
	  	string quadPattern;
	  	for(int i=0;i<nParam;i++)
	  	{
	  		wsNode curPNode(resPara->nodesetval->nodeTab[i]);

	  		//get parameter name from the children node
	  		string dim;
	  		xmlXPathObjectPtr resPName=curPNode.xpathInNode(nodePath.gateParam);
	  		dim = wsNode(resPName->nodesetval->nodeTab[0]).getProperty("name");
	  		xmlXPathFreeObject(resPName);
	  		dims.push_back(dim);

	  		//get coordinates from properties

	  		string sVal=curPNode.getProperty("min");
	  		if(sVal.empty()){
	  			sVal = curPNode.getProperty("max");
	  			quadPattern.append("-");//note that it is opposite of the population sign
	  			if(sVal.empty())
	  				throw(logic_error("Can't find min or max property in dimension: " + dim));
	  		}else
	  			quadPattern.append("+");
	  		if(i == 0)
	  			intersect.x = boost::lexical_cast<double>(sVal);
	  		else
	  			intersect.y = boost::lexical_cast<double>(sVal);
	  	}
	  	QUAD quad;
	  	if(quadPattern == "-+")
	  		quad = Q1;
	  	else if(quadPattern == "++")
	  		quad = Q2;
	  	else if(quadPattern == "+-")
	  		quad = Q3;
	  	else if(quadPattern == "--")
	  		quad = Q4;
	  	else
	  		throw(domain_error("unrecognized quadPattern: " + quadPattern));
	  	//save it to param of polygonGate
	  	paramPoly pp;
	  	vector<coordinate> vert;
	  	vert.push_back(intersect);
	  	pp.setVertices(vert);
	  	pp.setName(dims);
	  	unique_ptr<CurlyQuadGate> gate(new CurlyQuadGate(pp, quad));
	  	//shift gets lost in CurlyQuadGate constructor, so need to add it back in
	  	gate->setShift(getShift(node));
	  	return gatePtr(gate.release());

	  }

	  /*
	   * The only difference of parsing between polygon and ellipsoid
	   * resides in vertexPath, default is "*[local-name()='vertex']", which is for polygon
	   * "*[local-name()='edge']/*[local-name()='vertex']" is for ellipsoidGate
	   */
	  gatePtr getGate(wsPolyGateNode & node, string vertexPath = "*[local-name()='vertex']"){
	  	/*
	  	 * not sure what is the criteria for using ellipseGate
	  	 * since it is of the same format of polygonGate
	  	 */

	  			unique_ptr<polygonGate> gate(new polygonGate());
	  			//get the negate flag
	  			gate->setNegate(node.getProperty("eventsInside")=="0");
	  			paramPoly p;
	  			vector<coordinate> v;
	  			vector<string> pn;

	  			//TODO:get parameter name(make sure the the order of x,y are correct)
	  			string polyGateDim=nodePath.gateDim+"/"+nodePath.gateParam;
	  			xmlXPathObjectPtr resPara=node.xpathInNode(polyGateDim);
	  			int nParam=resPara->nodesetval->nodeNr;
	  			if(nParam!=2)
	  			{
	  //				COUT<<"the dimension of the polygon gate:"<<nParam<<" is invalid!"<<endl;
	  				throw(domain_error("invalid dimension of the polygon gate!"));
	  			}
	  			for(int i=0;i<nParam;i++)
	  			{
	  				wsNode curPNode(resPara->nodesetval->nodeTab[i]);
	  				string curParam=curPNode.getProperty("name");
	  				pn.push_back(curParam);
	  			}
	  			xmlXPathFreeObject(resPara);

	  			//get vertices
	  			xmlXPathObjectPtr resVert=node.xpathInNode(vertexPath);
	  			for(int i=0;i<resVert->nodesetval->nodeNr;i++)
	  			{
	  				wsNode curVNode(resVert->nodesetval->nodeTab[i]);

	  				/*for each vertice node
	  				**get one pair of coordinates
	  				*/
	  				xmlXPathObjectPtr resCoord=curVNode.xpathInNode("*[local-name()='coordinate']");

	  				xmlNodeSetPtr nodeSet=resCoord->nodesetval;
	  				int nCoord=nodeSet->nodeNr;
	  				if(nCoord!=2)
	  				{
	  					xmlXPathFreeObject(resVert);
	  					xmlXPathFreeObject(resCoord);

	  					throw(domain_error("invalid  number of coordinates!"));
	  				}
	  				//get the coordinates values from the property of respective node
	  				coordinate pCoord;
	  				pCoord.x=atof(wsNode(nodeSet->nodeTab[0]).getProperty("value").c_str());
	  				pCoord.y=atof(wsNode(nodeSet->nodeTab[1]).getProperty("value").c_str());
	  				//and push to the vertices vector of the gate object
	  				v.push_back(pCoord);

	  				xmlXPathFreeObject(resCoord);
	  			}
	  			xmlXPathFreeObject(resVert);

	  			p.setName(pn);
	  			p.setVertices(v);
	  			gate->setParam(p);
	  			gate->setShift(getShift(node));
	  			return gatePtr(gate.release());
	  }

	  gatePtr getGate(wsRectGateNode & node){
		  	  gatePtr thisGate;
	  			//get parameter name
	  			xmlXPathObjectPtr resPara=node.xpathInNode(nodePath.gateDim);
	  			int nParam=resPara->nodesetval->nodeNr;
	  			/*
	  			 * parse the parameters
	  			 */
	  			vector<paramRange> r;
	  			string quad_pattern = "";
	  			for(int i=0;i<nParam;i++)
	  			{
	  				wsNode curPNode(resPara->nodesetval->nodeTab[i]);
	  				paramRange thisR;
	  				//get coordinates from properties
	  				/*
	  				 * be aware that numeric_limits<double>::min() return the minimum positive value
	  				 * instead of negative,so we have to use -max()
	  				 */
	  				string sMin=curPNode.getProperty("min");
	  				if(sMin.empty())
	  				{
	  					thisR.setMin(-numeric_limits<double>::infinity());
						quad_pattern += "-";
	  				}
	  				else
	  					thisR.setMin(atof(sMin.c_str()));

	  				string sMax=curPNode.getProperty("max");
	  				if(sMax.empty())
	  				{
	  					thisR.setMax(numeric_limits<double>::infinity());
						quad_pattern += "+";
	  				}
	  				else
	  					thisR.setMax(atof(sMax.c_str()));

	  				//get parameter name from the children node
	  				xmlXPathObjectPtr resPName=curPNode.xpathInNode(nodePath.gateParam);
	  				thisR.setName(wsNode(resPName->nodesetval->nodeTab[0]).getProperty("name"));
	  				xmlXPathFreeObject(resPName);
	  				r.push_back(thisR);

	  			}
	  			if(nParam==1){
	  				/*
	  				 * parse as rangeGate
	  				 */
	  				unique_ptr<rangeGate> g(new rangeGate());
	  				if(g_loglevel>=GATE_LEVEL)
	  					COUT<<"constructing rangeGate.."<<endl;
	  				//get the negate flag
	  				g->setNegate(node.getProperty("eventsInside")=="0");
	  				g->setParam(r.at(0));
	  				thisGate.reset(g.release());

	  			}else if(nParam==2){

	  				/*
	  				 * convert pRanges to polygon data structure
	  				 */
	  				paramPoly p;
	  				vector<coordinate> v;
	  				vector<string> pn;
	  				pn.push_back(r.at(0).getName());//x
	  				pn.push_back(r.at(1).getName());//y

	  				unique_ptr<polygonGate> g;
	  				auto uid = generate_uid();
	  				if(quad_pattern == "-+")
					{
						g.reset(new quadGate(p, uid, Q1));
						v.push_back({r[0].getMax(), r[1].getMin()});
						if(g_loglevel>=GATE_LEVEL)
							COUT<<"constructing quadGate..Q1"<<endl;
					}
	  				else if(quad_pattern == "++")
					{
						g.reset(new quadGate(p, uid, Q2));
						v.push_back({r[0].getMin(), r[1].getMin()});
						if(g_loglevel>=GATE_LEVEL)
							COUT<<"constructing quadGate..Q2"<<endl;
					}
	  				else if(quad_pattern == "+-")
					{
						g.reset(new quadGate(p, uid, Q3));
						v.push_back({r[0].getMin(), r[1].getMax()});
						if(g_loglevel>=GATE_LEVEL)
							COUT<<"constructing quadGate..Q3"<<endl;
					}
	  				else if(quad_pattern == "--")
					{
						g.reset(new quadGate(p, uid, Q4));
						v.push_back({r[0].getMax(), r[1].getMax()});
						if(g_loglevel>=GATE_LEVEL)
							COUT<<"constructing quadGate..Q4"<<endl;
					}
	  				else
					{
						g.reset(new rectGate());
						if(g_loglevel>=GATE_LEVEL)
							COUT<<"constructing rectGate.."<<endl;
						/*
						 * since rectangle gate in windows version defined differently from mac (simply as 4-point polygon)
						 * so make sure the order of vertices is correct during the conversion to polygon here
						 * lb->lt->rt->rb
						 */
						coordinate lb,lt,rb,rt;//left bottom,left top,right top,right top
						lb.x=r.at(0).getMin();
						lb.y=r.at(1).getMin();

						rt.x=r.at(0).getMax();
						rt.y=r.at(1).getMax();

						v.push_back(lb);
		  //				v.push_back(lt);
						v.push_back(rt);
		  //				v.push_back(rb);
					}

	  				p.setVertices(v);
	  				p.setName(pn);
	  				g->setParam(p);
	  				g->setNegate(node.getProperty("eventsInside")=="0");
					thisGate.reset(g.release());

	  			}else if(nParam!=2)
	  			{
	  				xmlXPathFreeObject(resPara);
	  				throw(domain_error("invalid  dimension of the rectangle gate!"));
	  			}


	  			xmlXPathFreeObject(resPara);


	  			thisGate->setShift(getShift(node));
	  			return thisGate;
	  }
	  gatePtr getGate(wsPopNode & node){

	  	if(node.getName()=="NotNode"){
	  		/*
	  		 * NotNode is a special population node that negate its dependent
	  		 * We parse it as a boolean gate
	  		 */
	  		xmlXPathObjectPtr resPaths=node.xpathInNode("Dependents/Dependent");
	  		if(resPaths->nodesetval->nodeNr != 1)
	  			throw(domain_error("'NotNode' must have one (and only one) 'Dependent'"));
	  		wsNode curGPNode(resPaths->nodesetval->nodeTab[0]);
	  		vector<string> gPaths;
	  		gPaths.push_back(curGPNode.getProperty("name"));
	  		unique_ptr<boolGate> gate(new boolGate());
	  		xmlXPathFreeObject(resPaths);
	  		gate->boolOpSpec = parseBooleanSpec("!G0", gPaths);
			return gatePtr(gate.release());
	  	}else if(node.getName()=="OrNode"||node.getName()=="AndNode"){
	  		/*
	  		 * NotNode is a special population node that negate its dependent
	  		 * We parse it as a boolean gate
	  		 */
	  		xmlXPathObjectPtr resPaths=node.xpathInNode("Dependents/Dependent");
	  		unsigned nDep = resPaths->nodesetval->nodeNr;
	  		if(nDep < 2)
	  			throw(domain_error("'OrNode' must have at least two 'Dependent' nodes"));
	  		vector<string> gPaths;
	  		std::stringstream specs;
	  		for(unsigned i =0; i < nDep; i++){
	  			wsNode curGPNode(resPaths->nodesetval->nodeTab[i]);
	  			gPaths.push_back(curGPNode.getProperty("name"));
	  			string op = node.getName()=="OrNode"?"|":"&";
	  			if(i == 0)
	  				op = "";
	  			specs << op << "G" << i ;
	  		}

	  		unique_ptr<boolGate> gate(new boolGate());
	  		xmlXPathFreeObject(resPaths);
	  		gate->boolOpSpec = parseBooleanSpec(specs.str(), gPaths);
			return gatePtr(gate.release());
	  	}else{


	  		xmlXPathObjectPtr resGate=node.xpathInNode("Gate/*");
	  		if(resGate->nodesetval->nodeNr!=1)
	  			throw(logic_error("invalid 'Gate' node!"));
	  		wsNode gNode(resGate->nodesetval->nodeTab[0]);
	  		xmlXPathFreeObject(resGate);
	  		const xmlChar * gateType=gNode.getNodePtr()->name;
	  		if(xmlStrEqual(gateType,(const xmlChar *)"PolygonGate"))
	  		{
	  			wsPolyGateNode pGNode(gNode.getNodePtr());
	  			if(g_loglevel>=GATE_LEVEL)
	  				COUT<<"parsing PolygonGate.."<<endl;
	  			return(getGate(pGNode));

	  		}
	  		else if(xmlStrEqual(gateType,(const xmlChar *)"RectangleGate"))
	  		{
	  			wsRectGateNode rGNode(gNode.getNodePtr());
	  			if(g_loglevel>=GATE_LEVEL)
	  				COUT<<"parsing RectangleGate.."<<endl;
	  			return(getGate(rGNode));
	  		}
	  		else if(xmlStrEqual(gateType,(const xmlChar *)"EllipsoidGate"))
	  		{
	  			wsEllipseGateNode eGNode(gNode.getNodePtr());
	  			if(g_loglevel>=GATE_LEVEL)
	  				COUT<<"parsing EllipsoidGate.."<<endl;
	  			return(getGate(eGNode));
	  		}
	  		else if(xmlStrEqual(gateType,(const xmlChar *)"CurlyQuad"))
	  		{
	  			wsCurlyQuadGateNode curlyQNode(gNode.getNodePtr());
	  			if(g_loglevel>=GATE_LEVEL)
	  				COUT<<"parsing CurlyQuad.."<<endl;
	  			return(getGate(curlyQNode));
	  		}
	  		else
	  		{
	  	//		COUT<<"gate type: "<<gateType<<" is not supported!"<<endl;
	  			throw(logic_error("invalid  gate type!"));
	  		}
	  	}
	  }

};

class xFlowJoWorkspace:public winFlowJoWorkspace{
public:
	xFlowJoWorkspace(xmlDoc * _doc):winFlowJoWorkspace(_doc){
		if(g_loglevel>=GATING_SET_LEVEL)
			COUT<<"version X"<<endl;
		nodePath.gateParam="*[local-name()='fcs-dimension']";
	}

	trans_global_vec getGlobalTrans(){trans_global_vec res;
										return res;
										};
	trans_local getTransformation(wsRootNode root,const compensation & comp, PARAM_VEC & transFlag, const trans_global_vec & gTrans, bool prefixed){

		trans_local res;

		/*
		 * get transformations node
		 */
		xmlXPathObjectPtr transParentNodeRes=root.xpathInNode("../Transformations");
		unsigned short nTransParentNodes=transParentNodeRes->nodesetval->nodeNr;
		if(nTransParentNodes<=0)
		{
			COUT<<"Transformation not found!"<<endl;
			xmlXPathFreeObject(transParentNodeRes);
			return(res);
		}else if(nTransParentNodes>1){
			throw(domain_error("More than one 'Transformations' node found!"));
		}

		wsNode transParentNode(transParentNodeRes->nodesetval->nodeTab[0]);

		/*
		 * parse each individual transformation
		 */
		xmlXPathObjectPtr transRes=transParentNode.xpathInNode("child::*");

		trans_map curTp=res.getTransMap();

		for(int j=0;j<transRes->nodesetval->nodeNr;j++)
		{
			wsNode transNode(transRes->nodesetval->nodeTab[j]);

			//parse the parameter name
			string pname;
			xmlXPathObjectPtr paramRes=transNode.xpathInNode("*[local-name()='parameter']");
			if(paramRes->nodesetval->nodeNr!=1)
				pname="";
			else{
				wsNode paramNode(paramRes->nodesetval->nodeTab[0]);
				pname=paramNode.getProperty("name");
				}


			xmlXPathFreeObject(paramRes);
			/*
			 * when channel name is not specified
			 * try to parse it as the generic trans that is applied to channels
			 * that do not have channel-specific trans defined in workspace
			 * ,
			 */
			if(pname.empty())
				pname="*";
			if(derived_params.find(pname)!=derived_params.end())
			{
				if(g_loglevel>=GATING_SET_LEVEL)
					COUT<<"skip parsing transformation for derived parameters:"<<pname<<endl;
				continue;
			}

			string transType=(const char*)transNode.getNodePtr()->name;
		    if(transType.compare("logicle")==0)
			{

					if(g_loglevel>=GATING_SET_LEVEL)
							COUT<<"logicle func:"<<pname<<endl;
					shared_ptr<biexpTrans> curTran(new biexpTrans());

					curTran->setName("");
					curTran->setChannel(pname);
					curTran->pos=atof(transNode.getProperty("M").c_str());
					curTran->neg=atof(transNode.getProperty("A").c_str());
					float w = atof(transNode.getProperty("W").c_str());
					curTran->widthBasis= -pow(10, 2*w);
					curTran->maxValue=atof(transNode.getProperty("T").c_str());
					unsigned short thisLen=atoi(transNode.getProperty("length").c_str());
					curTran->channelRange = thisLen;
//                      if(thisLen!=256)
//                              throw(domain_error("length is not 256 for biex transformation!"));
					/*
					 * do the lazy calibration table calculation and interpolation
					 * when it gets saved in gh
					 */
					curTp[curTran->getChannel()]=curTran;
			}
			else if(transType.compare("biex")==0)
			{

				if(g_loglevel>=GATING_SET_LEVEL)
					COUT<<"biex func:"<<pname<<endl;
				shared_ptr<biexpTrans> curTran(new biexpTrans());
				curTran->setName("");
				curTran->setChannel(pname);
				curTran->pos=atof(transNode.getProperty("pos").c_str());
				curTran->neg=atof(transNode.getProperty("neg").c_str());
				curTran->widthBasis=atof(transNode.getProperty("width").c_str());
				curTran->maxValue=atof(transNode.getProperty("maxRange").c_str());
				unsigned short thisLen=atoi(transNode.getProperty("length").c_str());
				curTran->channelRange = thisLen;
	//			if(thisLen!=256)
	//				throw(domain_error("length is not 256 for biex transformation!"));
				/*
				 * do the lazy calibration table calculation and interpolation
				 * when it gets saved in gh
				 */
				curTp[curTran->getChannel()]=curTran;
			}else if(transType.compare("linear")==0){
				//only meaningful for scaling ellipsoidGate from 256 back to raw
				if(g_loglevel>=GATING_SET_LEVEL)
					COUT<<"flin func:"<<pname<<endl;
				auto gain = transNode.getProperty("gain");
				
				if (boost::to_lower_copy(pname) == "time") {
				  if (gain !=
          "1") {  // cytolib::EVENT_DATA_TYPEmust skip adding it when gain == 1 since $TIMESTEP should
				    // be used for scaling time channel in that case
				    
				    auto scale_factor = stof(gain);
				    
				    std::shared_ptr<cytolib::scaleTrans> curTran(
				        new cytolib::scaleTrans(scale_factor));
				    curTran->setName("");
				    curTran->setChannel(pname);
				    curTran->setGateOnlyFlag(false);
				    curTran->setDataOnlyFlag(true);
				    curTp[pname] = curTran;
				  }
				} else {  // need it for ellipsoid gate rescaling
				  // and only meaningful for scaling cytolib::ellipsoidGate from 256 back
				  // minRange=atof(transNode.getProperty("minRange").c_str());to raw
				  cytolib::EVENT_DATA_TYPE maxRange =
				    atof(transNode.getProperty("maxRange").c_str());
				  if (maxRange == 0) maxRange = 1;
				  
				  std::shared_ptr<cytolib::scaleTrans> curTran(
				      new cytolib::scaleTrans(maxRange, maxRange));
				  
				  curTran->setName("");
				  curTran->setChannel(pname);
				  curTran->setGateOnlyFlag(true);
				  curTran->setDataOnlyFlag(false);
				  curTp[pname] = curTran;
				}
				
			}else if(transType.compare("log")==0){
				if(g_loglevel>=GATING_SET_LEVEL)
					COUT<<"flog func:"<<pname<<endl;
				double offset=atof(transNode.getProperty("offset").c_str());
				double decade=atof(transNode.getProperty("decades").c_str());
				// get top scale value from PnR stored in transFlag
				PARAM_VEC::const_iterator pit = findTransFlag(transFlag, pname, comp.prefix, comp.suffix);
				if(pit==transFlag.end())
					throw(domain_error(pname + " does not exist in transFlag vector!"));
				shared_ptr<logTrans> curTran(new logTrans(offset,decade,1,pit->range));

				curTran->setName("");
				curTran->setChannel(pname);

				curTp[curTran->getChannel()]=curTran;
			}else if(transType.compare("fasinh")==0){
				if(g_loglevel>=GATING_SET_LEVEL)
					COUT<<"fasinh func:"<<pname<<endl;
				double length=atof(transNode.getProperty("length").c_str());
				double maxRange=atof(transNode.getProperty("maxRange").c_str());
				double M=atof(transNode.getProperty("M").c_str());
				double T=atof(transNode.getProperty("T").c_str());
				double A=atof(transNode.getProperty("A").c_str());
				shared_ptr<fasinhTrans> curTran(new fasinhTrans(maxRange,length, T,A,M));

				curTran->setName("");
				curTran->setChannel(pname);

				curTp[curTran->getChannel()]=curTran;
			}
			else
				throw(domain_error(pname + ": unknown tranformation type!"+ transType));

		}
		xmlXPathFreeObject(transRes);
		xmlXPathFreeObject(transParentNodeRes);

		res.setTransMap(curTp);

		return res;
	}
	bool is_fix_slash_in_channel_name(){return true;};
};
};
#endif /* WINFLOWJOWORKSPACE_HPP_ */