
//OpenSCADA file: tparamcontr.cpp
/***************************************************************************
 *   Copyright (C) 2003-2026 by Roman Savochenko, <roman@oscada.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; version 2 of the License.               *
 *                                                                         *
 *   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 <algorithm>

#include "tsys.h"
#include "tdaqs.h"
#include "tcontroller.h"
#include "ttypedaq.h"
#include "ttypeparam.h"
#include "tparamcontr.h"

using namespace OSCADA;

//*************************************************
//* TParamContr                                   *
//*************************************************
TParamContr::TParamContr( const string &name, TTypeParam *tpprm ) :
    TConfig(tpprm), mRdPrcTm(0), mPrm(-1), mEn(false), tpParm(tpprm), mJustCreated(true)
{
    cfg("SHIFR") = mId = name;	//!! To prevent the ID location changing on the parameter type change

    setName(name);
    if(type().isPrmCntr) mPrm = grpAdd("prm_");
    //type().create(this);

    if(mess_lev() == TMess::Debug) SYS->cntrIter(objName(), 1);
}

TParamContr::~TParamContr( )
{
    //type().destroy(this);
    nodeDelAll();

    if(mess_lev() == TMess::Debug) SYS->cntrIter(objName(), -1);
}

string TParamContr::objName( )	{ return TValue::objName() + ":TParamContr"; }

string TParamContr::DAQPath( )
{
    string oPath = ownerPath();
    return owner().DAQPath()+(oPath.size()?".":"") + oPath + "." + id();
}

string TParamContr::ownerPath( bool inclSelf )
{
    string rez;

    if(inclSelf) rez = nodeName();
    for(TCntrNode *own = nodePrev(); dynamic_cast<TParamContr*>(own); own = own->nodePrev())
	rez = rez.empty() ? string(own->nodeName()) : (string(own->nodeName())+"."+rez);

    return rez;
}

TCntrNode &TParamContr::operator=( const TCntrNode &node )
{
    TParamContr *src_n = const_cast<TParamContr*>(dynamic_cast<const TParamContr*>(&node));
    if(!src_n) return *this;

    if(enableStat()) disable();

    //Check for parameter type and change it if different and alow
    if(type().name != src_n->type().name && owner().owner().tpPrmToId(src_n->type().name) >= 0)
	setType(src_n->type().name);

    //Configuration copy
    exclCopy(*src_n, "SHIFR;");

    //Enable new parameter
    if(src_n->enableStat() && toEnable() && !enableStat()) {
	enable();

	//Copying dynamic attributes
	if(dynElCntr() && src_n->dynElCntr()) {
	    MtxAlloc res(dynElCntr()->resEl(), true);
	    MtxAlloc resSrc(src_n->dynElCntr()->resEl(), true);
	    for(unsigned aId = 0; aId < src_n->dynElCntr()->fldSize(); ++aId) {
		string aIdNm = src_n->dynElCntr()->fldAt(aId).name();
		if(vlPresent(aIdNm) || src_n->vlAt(aIdNm).at().arch().freeStat()) continue;
		dynElCntr()->fldAdd(new TFld(src_n->dynElCntr()->fldAt(aId)));
	    }
	}

	//Archives creation and copy
	vector<string> aLs;
	vlList(aLs);
	for(unsigned iA = 0; iA < aLs.size(); iA++) {
	    if(!src_n->vlPresent(aLs[iA]) || src_n->vlAt(aLs[iA]).at().arch().freeStat()) continue;

	    try {
		vlAt(aLs[iA]).at().setArch();
		(TCntrNode&)vlAt(aLs[iA]).at().arch().at() = (TCntrNode&)src_n->vlAt(aLs[iA]).at().arch().at();
	    } catch(TError &err) { if(err.cod != TError::Arch_Val_DblVSrc) throw; }
	}

	//Included parameters copy
	if(mPrm >= 0) {
	    vector<string> prmLs;
	    src_n->list(prmLs);
	    for(unsigned iP = 0; iP < prmLs.size(); iP++) {
		if(!owner().owner().tpPrmPresent(src_n->at(prmLs[iP]).at().type().name)) continue;
		if(!present(prmLs[iP])) add(prmLs[iP], owner().owner().tpPrmToId(src_n->at(prmLs[iP]).at().type().name));
		(TCntrNode&)at(prmLs[iP]).at() = (TCntrNode&)src_n->at(prmLs[iP]).at();
		//if(toEnable() && !enableStat()) enable();
	    }
	}
    }

    return *this;
}

TController &TParamContr::owner( ) const
{
    TCntrNode *own = nodePrev();
    while(!dynamic_cast<TController*>(own)) own = own->nodePrev();

    return *(TController*)own;
}

string TParamContr::name( )			{ string nm = cfg("NAME").getS(); return nm.size() ? nm : id(); }

void TParamContr::setName( const string &inm )	{ cfg("NAME").setS(inm); }

string TParamContr::descr( )			{ return cfg("DESCR").getS(); }

int64_t TParamContr::timeStamp( )
{
    int64_t mTimeStamp = cfg("TIMESTAMP").getI();

    vector<string> ls;
    list(ls);
    for(unsigned iL = 0; iL < ls.size(); ++iL)
	mTimeStamp = vmax(mTimeStamp, at(ls[iL]).at().timeStamp());

    return mTimeStamp;
}

bool TParamContr::dataActive( )			{ return owner().startStat(); }

void TParamContr::setDescr( const string &idsc ){ cfg("DESCR").setS(idsc); }

void TParamContr::list( vector<string> &list ) const
{
    if(mPrm < 0) return;
    chldList(mPrm, list);
}

bool TParamContr::present( const string &iid ) const
{
    if(mPrm < 0) return false;
    return chldPresent(mPrm, iid);
}

string TParamContr::add( const string &iid, unsigned type )
{
    return (mPrm < 0) ? "" : chldAdd(mPrm, owner().ParamAttach(TSYS::strEncode(sTrm(iid),TSYS::oscdID),type));
}

void TParamContr::del( const string &iid, int flags )
{
    if(mPrm < 0) return;
    chldDel(mPrm, iid, -1, flags);
}

AutoHD<TParamContr> TParamContr::at( const string &iid, const string &who ) const
{
    if(mPrm < 0) return AutoHD<TParamContr>();
    return chldAt(mPrm, iid);
}

void TParamContr::LoadParmCfg( )
{
    if(mPrm < 0) return;

    map<string, bool>	itReg;

    //Search and create new parameters
    for(unsigned iTp = 0; iTp < owner().owner().tpPrmSize(); iTp++) {
	if(owner().owner().tpPrmAt(iTp).DB(&owner()).empty()) continue;
	try {
	    TConfig cEl(&owner().owner().tpPrmAt(iTp));
	    //cEl.cfgViewAll(false);
	    cEl.cfg("OWNER").setS(ownerPath(true), TCfg::ForceUse);

	    // Search new one in DB and Config-file
	    for(int fld_cnt = 0; TBDS::dataSeek(owner().DB()+"."+owner().owner().tpPrmAt(iTp).DB(&owner()),
		    owner().owner().nodePath()+owner().owner().tpPrmAt(iTp).DB(&owner()),fld_cnt++,cEl,TBDS::UseCache); )
	    {
		try {
		    string shfr = cEl.cfg("SHIFR").getS();
		    if(!present(shfr))	add(shfr, iTp);
		    at(shfr).at().load(&cEl);
		    itReg[shfr] = true;
		} catch(TError &err) {
		    mess_err(err.cat.c_str(), "%s", err.mess.c_str());
		    mess_sys(TMess::Error, _("Error adding parameter '%s'."), cEl.cfg("SHIFR").getS().c_str());
		}
	    }
	} catch(TError &err) {
	    mess_err(err.cat.c_str(), "%s", err.mess.c_str());
	    mess_sys(TMess::Error, _("Error finding and creating new parameters."));
	}
    }

    //Check for remove items removed from the DB
    if(SYS->chkSelDB(SYS->selDB(),true)) {
	vector<string> itLs;
	list(itLs);
	for(unsigned iIt = 0; iIt < itLs.size(); iIt++)
	    if(itReg.find(itLs[iIt]) == itReg.end())
		del(itLs[iIt]);
    }

    //Force loading the available parameters
    vector<string> prmLs;
    list(prmLs);
    for(unsigned iP = 0; iP < prmLs.size(); iP++) {
	at(prmLs[iP]).at().modifG();
	at(prmLs[iP]).at().load();
    }
}

void TParamContr::postEnable( int flag )
{
    TValue::postEnable(flag);

    if(!vlCfg()) setVlCfg(this);
    if(!vlElemPresent(&SYS->daq().at().elErr()))
	vlElemAtt(&SYS->daq().at().elErr());

    type().create(this);
}

void TParamContr::preDisable( int flag )
{
    type().destroy(this);

    //Delete or stop the archives
    vector<string> aLs;
    vlList(aLs);
    for(unsigned iA = 0; iA < aLs.size(); iA++)
	if(!vlAt(aLs[iA]).at().arch().freeStat()) {
	    string arh_id = vlAt(aLs[iA]).at().arch().at().id();
	    if(flag&NodeRemove_NoArch)
		SYS->archive().at().valAt(arh_id).at().stop();
	    else SYS->archive().at().valDel(arh_id, true);
	}

    if(enableStat())	disable();
}

void TParamContr::postDisable( int flag )
{
    if(flag&NodeRemove) {
	//Delete the parameter from DB
	cfg("OWNER") = ownerPath();
	TBDS::dataDel(owner().DB()+"."+owner().tbl(type()), owner().owner().nodePath()+owner().tbl(type()), *this, TBDS::UseAllKeys);
    }
}

void TParamContr::load_( TConfig *icfg )
{
    if(!SYS->chkSelDB(owner().DB())) throw TError();

    if(icfg) *(TConfig*)this = *icfg;
    else {
	//Checking for need to change the parameter type at loading from the configuration context
	if(SYS->cfgCtx() && !SYS->cfgCtx()->childGet("prmTp",type().name,true))
	    for(unsigned iTp = 0; iTp < owner().owner().tpPrmSize(); ++iTp)
		if(SYS->cfgCtx()->childGet("prmTp",owner().owner().tpPrmAt(iTp).name,true)) {
		    setType(owner().owner().tpPrmAt(iTp).name);
		    break;
		}

	//cfgViewAll(true);
	TValue::setNoTransl(false);
	cfg("OWNER") = ownerPath();
	TBDS::dataGet(owner().DB()+"."+owner().tbl(type()), owner().owner().nodePath()+owner().tbl(type()), *this);
    }

    LoadParmCfg();

    mJustCreated = false;
}

void TParamContr::save_( )
{
    cfg("OWNER") = ownerPath();
    cfg("TIMESTAMP") = (int64_t)SYS->sysTm();
    TBDS::dataSet(owner().DB()+"."+owner().tbl(type()), owner().owner().nodePath()+owner().tbl(type()), *this);
    if(SYS->cfgCtx(true)) SYS->cfgCtx(true)->setAttr("prmTp", type().name);

    mJustCreated = false;

    //Save archives
    vector<string> aLs;
    vlList(aLs);
    for(unsigned iA = 0; iA < aLs.size(); iA++)
	if(!vlAt(aLs[iA]).at().arch().freeStat())
	    vlAt(aLs[iA]).at().arch().at().save();

    //Control of saving-storing the containers
    TParamContr *ownPrm = dynamic_cast<TParamContr*>(nodePrev());
    if(ownPrm && ownPrm->justCreated())	ownPrm->save();
    else if(!ownPrm && owner().justCreated())	owner().save();
}

bool TParamContr::cfgChange( TCfg &co, const TVariant &pc )
{
    if(co.getS() != pc.getS()) {
	if(co.name() == "DESCR") nodeLoadACL(co.getS());

	modif();
    }

    return type().cfgChange(this, co);
}

void TParamContr::enable( )
{
    //Checking for enabling the parents
    TParamContr *ownPrm = dynamic_cast<TParamContr*>(nodePrev());
    if((ownPrm && !ownPrm->enableStat()) || (!ownPrm && !owner().enableStat()))
	throw err_sys(_("Enable parent nodes before children!"));

    type().enable(this);

    mEn = true;

    bool enErr = false;
    //Enable the parameters
    vector<string> prm_list;
    list(prm_list);
    for(unsigned iPrm = 0; iPrm < prm_list.size(); iPrm++)
	if(at(prm_list[iPrm]).at().toEnable())
	    try{ at(prm_list[iPrm]).at().enable(); }
	    catch(TError &err) {
		mess_warning(err.cat.c_str(), "%s", err.mess.c_str());
		mess_sys(TMess::Warning, _("Error turning on the parameter '%s'."), prm_list[iPrm].c_str());
		enErr = true;
	    }

    if(enErr) throw err_sys(_("Error turning on some parameters."));
}

void TParamContr::disable( )
{
    //Disable parameters
    vector<string> prm_list;
    list(prm_list);
    for(unsigned iPrm = 0; iPrm < prm_list.size(); iPrm++)
	if(at(prm_list[iPrm]).at().enableStat())
	    try{ at(prm_list[iPrm]).at().disable(); }
	    catch(TError &err) {
		mess_warning(err.cat.c_str(), "%s", err.mess.c_str());
		mess_sys(TMess::Warning, _("Error turning off the parameter '%s'."), prm_list[iPrm].c_str());
	    }

    type().disable(this);
    mEn = false;
}

void TParamContr::vlGet( TVal &val )
{
    if(val.name() == "err") {
	if(!enableStat()) val.setS(_("1:Parameter disabled."), 0, true);
	else if(!owner().startStat()) val.setS(_("2:Acquisition stopped."), 0, true);
	else val.setS("0", 0, true);
    }

    type().vlGet(this, val);
}

void TParamContr::vlSet( TVal &vo, const TVariant &vl, const TVariant &pvl )
{
    type().vlSet(this, vo, vl, pvl);
}

bool TParamContr::vlSetRednt( TVal &vo, const TVariant &vl, const TVariant &pvl )
{
    if(!owner().redntUse()) return false;
    if(vl == pvl) return true;

    XMLNode req("set");
    req.setAttr("path", nodePath(0,true)+"/%2fserv%2fattr")->setAttr("reforwardRedundOff", "1")
	->childAdd("el")->setAttr("id", vo.name())->setText(vl.getS());
    SYS->daq().at().rdStRequest(owner().workId(), req);

    return true;
}

void TParamContr::vlArchMake( TVal &val )
{
    if(!val.arch().freeStat())	val.arch().at().setDB(owner().DB());
    type().vlArchMake(this, val);
}

void TParamContr::setType( const string &tpId )
{
    if(enableStat() || tpId == type().name || !owner().owner().tpPrmPresent(tpId))	return;

    //Remove all included parameters
    if(mPrm >= 0 && !owner().owner().tpPrmAt(owner().owner().tpPrmToId(tpId)).isPrmCntr) {
	vector<string> pls;
	list(pls);
	for(unsigned iP = 0; iP < pls.size(); iP++) del(pls[iP], NodeRemove);
	grpDel(mPrm); mPrm = -1;
    }

    type().destroy(this);
    setNodeMode(TCntrNode::Disabled);

    try {
	//Wait for disconnect other
	while(nodeUse(true) > 1) TSYS::sysSleep(1e-3);
	//Remove from DB
	postDisable(NodeRemove);

	//Create temporary structure
	TConfig tCfg(&type());
	tCfg = *(TConfig*)this;

	//Set new config structure
	tpParm = &owner().owner().tpPrmAt(owner().owner().tpPrmToId(tpId));
	setElem(tpParm);

	//Restore configurations
	*(TConfig*)this = tCfg;
    } catch(...) { }

    if(mPrm < 0 && tpParm->isPrmCntr) mPrm = grpAdd("prm_");

    setNodeMode(TCntrNode::Enabled);

    setVlCfg(this);

    type().create(this);

    modif();
}

TVariant TParamContr::objFuncCall( const string &iid, vector<TVariant> &prms, const string &user_lang )
{
    // TCntrNodeObj cntr() - get the controller node
    if(iid == "cntr")	return new TCntrNodeObj(AutoHD<TCntrNode>(&owner()), user_lang);
    // bool messSet( string mess, int lev, string type2Code = "OP", string cat = "") -
    //		sets of the DAQ-sourced message <mess> with the level <lev>, for the parameter.
    if(iid == "messSet" && prms.size() >= 2) {
	string	mess = prms[0].getS(), pName, pNm;

	//  Getting whole parameter name with included parameters of not empty names and with checking for dublicate in the message
	TRegExp re("^MessName:(.*?)$", "gm");
	for(TParamContr *prmO = this; prmO; prmO = dynamic_cast<TParamContr*>(prmO->nodePrev())) {
	    TArrayObj *messNm = re.match(trD(prmO->descr()));
	    bool isMessName = (messNm && messNm->arSize() >= 2);
	    pNm = isMessName ? sTrm(messNm->arGet(1).getS()) : "";
	    if(messNm) delete messNm;
	    if(!isMessName && prmO->cfg("NAME").getS().size() && mess.find(trD(prmO->name())) == string::npos)
		pNm = trD(prmO->name());
	    if(pNm.size()) pName = pNm + (pName.size()?" > ":"") + pName;
	}

	//  Same messaging
	owner().messSet(mess, prms[1].getI(), ((prms.size()>=3)?prms[2].getS():"OP"),
	    ownerPath(true)+"\n"+pName, ((prms.size()>=4)?prms[3].getS():""));

	return true;
    }
    // bool alarmSet( string mess, int lev = -5, bool force = false ) -
    //		sets/removes of the violation <mess> with the level <lev> (negative to set otherwise to remove) for the parameter.
    if(iid == "alarmSet" && prms.size() >= 1) {
	string	mess = prms[0].getS(), pName, pNm;

	//  Getting whole parameter name with included parameters of not empty names and with checking for dublicate in the message
	TRegExp re("^MessName:(.*?)$", "gm");
	for(TParamContr *prmO = this; prmO; prmO = dynamic_cast<TParamContr*>(prmO->nodePrev())) {
	    TArrayObj *messNm = re.match(trD(prmO->descr()));
	    bool isMessName = (messNm && messNm->arSize() >= 2);
	    pNm = isMessName ? TRegExp(":","g").replace(sTrm(messNm->arGet(1).getS()),"=") : "";
	    if(messNm) delete messNm;
	    if(!isMessName && prmO->cfg("NAME").getS().size() && mess.find(trD(prmO->name())) == string::npos)
		pNm = TRegExp(":","g").replace(trD(prmO->name()), "=");
	    if(pNm.size()) pName = pNm + (pName.size()?" > ":"") + pName;
	}

	//  Same messaging
	owner().alarmSet(mess, (prms.size() >= 2) ? prms[1].getI() : -TMess::Crit,
	    ownerPath(true)+"\n"+pName, (prms.size() >= 3) ? prms[2].getB() : false);

	return true;
    }
    //bool attrAdd( string id, string name, string tp = "real", string selValsNms = "" ) - attribute <id> and <name> for type <tp> add.
    //  id, name - new attribute id and name;
    //  tp - attribute type [bool[ean] | int[eger] | real | str[ing] | text | obj[ect] ] + selection mode [sel | seled] + read only [ro];
    //  selValsNms - two lines with values in first and it's names in first (separated by ";").
    if(iid == "attrAdd" && prms.size() >= 1 && dynElCntr()) {
	if(!enableStat()) return false;
	TFld::Type tp = TFld::Real;
	string stp, stp_ = (prms.size() >= 3) ? prms[2].getS() : "real";
	stp.resize(stp_.length());
	std::transform(stp_.begin(), stp_.end(), stp.begin(), ::tolower);
	if(stp.find("bool") != string::npos)		tp = TFld::Boolean;
	else if(stp.find("int") != string::npos)	tp = TFld::Integer;
	else if(stp.find("real") != string::npos)	tp = TFld::Real;
	else if(stp.find("str") != string::npos ||
		stp.find("text") != string::npos)	tp = TFld::String;
	else if(stp.find("obj") != string::npos)	tp = TFld::Object;

	unsigned flg = TVal::Dynamic;
	if(stp.find("sel") != string::npos)	flg |= TFld::Selectable;
	if(stp.find("seled") != string::npos)	flg |= TFld::SelEdit;
	if(stp.find("text") != string::npos)	flg |= TFld::FullText;
	if(stp.find("ro") != string::npos)	flg |= TFld::NoWrite;

	string	sVals = (prms.size() >= 4) ? prms[3].getS() : "";
	string	sNms = TSYS::strLine(sVals, 1);
	sVals = TSYS::strLine(sVals, 0);

	MtxAlloc res(dynElCntr()->resEl(), true);
	unsigned aId = dynElCntr()->fldId(prms[0].getS(), true);
	if(aId < dynElCntr()->fldSize()) {
	    if(prms.size() >= 2 && prms[1].getS().size()) dynElCntr()->fldAt(aId).setDescr(prms[1].getS());
	    dynElCntr()->fldAt(aId).setFlg(dynElCntr()->fldAt(aId).flg()^((dynElCntr()->fldAt(aId).flg()^flg)&(TFld::Selectable|TFld::SelEdit|TFld::FullText|TFld::NoWrite)));
	    dynElCntr()->fldAt(aId).setValues(sVals);
	    dynElCntr()->fldAt(aId).setSelNames(sNms);
	    dynElCntr()->fldAt(aId).setLen(SYS->sysTm());
	}
	else if(!vlPresent(prms[0].getS()))
	    dynElCntr()->fldAdd(new TFld(prms[0].getS().c_str(),prms[(prms.size()>=2)?1:0].getS(),tp,flg,i2s(SYS->sysTm()).c_str(),"",sVals,sNms));

	loadVal(prms[0].getS());

	return true;
    }
    //bool attrDel( string id ) - attribute <id> remove.
    if(iid == "attrDel" && prms.size() >= 1 && dynElCntr()) {
	if(!enableStat()) return false;
	MtxAlloc res(dynElCntr()->resEl(), true);
	unsigned aId = dynElCntr()->fldId(prms[0].getS(), true);
	if(aId == dynElCntr()->fldSize()) return false;
	try { dynElCntr()->fldDel(aId); } catch(TError&) { return false; }
	return true;
    }

    //Configuration functions call
    TVariant cfRez = objFunc(iid, prms, user_lang, RWRWR_, "root:" SDAQ_ID);
    if(!cfRez.isNull()) return cfRez;

    return TValue::objFuncCall(iid, prms, user_lang);
}

void TParamContr::cntrCmdProc( XMLNode *opt )
{
    string a_path = opt->attr("path");

    //Service commands process
    if(a_path.find("/serv/") == 0)	{ TValue::cntrCmdProc(opt); return; }

    //Get page info
    if(opt->name() == "info") {
	TValue::cntrCmdProc(opt);
	ctrMkNode3("oscada_cntr",opt,-1,"/",_("Parameter: ")+trD(name()),RWRWR_);
	ctrMkNode3("branches",opt,-1,"/br","",R_R_R_);
	if(ctrMkNode3("area",opt,0,"/prm",_("Parameter"),R_R_R_)) {
	    if(ctrMkNode3("area",opt,-1,"/prm/st",_("State"),R_R_R_)) {
		ctrMkNode3("fld",opt,-1,"/prm/st/type",_("Type"),((!enableStat()&&owner().owner().tpPrmSize()>1)?RWRWR_:R_R_R_),
		    "tp","str", "dest","select", "select","/prm/tpLst",
		    "help",_("The type changing leads to lose some data of the specific configurations."));
		/*if(!enableStat() && owner().owner().tpPrmSize() > 1)
		    ctrMkNode("fld",opt,-1,"/prm/st/type",_("Type"),RWRWR_,"root",SDAQ_ID,4,"tp","str","dest","select","select","/prm/tpLst",
			"help",_("The type changing leads to lose some data of the specific configurations."));
		else ctrMkNode("fld",opt,-1,"/prm/st/type",_("Type"),R_R_R_,"root",SDAQ_ID,1,"tp","str");*/
		if(owner().enableStat())
		    ctrMkNode3("fld",opt,-1,"/prm/st/en",_("Enabled"),SEC_RD|SEC_WR, "tp","bool");
		ctrMkNode3("fld",opt,-1,"/prm/st/timestamp",_("Date of modification"),R_R_R_, "tp","time");
	    }
	    if(ctrMkNode3("area",opt,-1,"/prm/cfg",_("Configuration"),SEC_RD)) {
		TConfig::cntrCmdMake(this,opt,"/prm/cfg",0,"","",SEC_RD|SEC_WR);
		ctrMkNode3("fld",opt,-1,"/prm/cfg/DESCR",EVAL_STR,SEC_RD|SEC_WR, "SnthHgl","1");
		ctrRemoveNode(opt,"/prm/cfg/OWNER");
		ctrRemoveNode(opt,"/prm/cfg/TIMESTAMP");
	    }
	}

	type().cntrCmdProc(this, opt);

	if(mPrm >= 0) {
	    ctrMkNode3("grp",opt,-1,"/br/prm_",_("Parameter"),RWRWR_, "idm",i2s(limObjNm_SZ).c_str(), "idSz",i2s(limObjID_SZ).c_str());
	    if(ctrMkNode3("area",opt,-1,"/iPrms",_("Inclusion"),R_R_R_)) {
		ctrMkNode3("fld",opt,-1,"/iPrms/nmb",_("Number"),R_R_R_, "tp","str");
		ctrMkNode3("list",opt,-1,"/iPrms/prm",_("Parameters"),RWRWR_,
		    "tp","br", "idm",i2s(limObjNm_SZ).c_str(), "s_com","add,del", "br_pref","prm_", "idSz",i2s(limObjID_SZ).c_str());
	    }
	}

	return;
    }
    //Process command to page
    if(a_path == "/prm/st/type") {
	if(ctrChkNode3(opt,"get",RWRWR_,SEC_RD)) opt->setText(type().name);
	if(ctrChkNode3(opt,"set",RWRWR_,SEC_WR)) setType(opt->text());
    }
    else if(a_path == "/prm/st/en") {
	if(ctrChkNode2(opt,"get",SEC_RD)) opt->setText(enableStat()?"1":"0");
	if(ctrChkNode2(opt,"set",SEC_WR)) {
	    if(!owner().enableStat())	throw err_sys(_("Controller is not running!"));
	    else s2i(opt->text()) ? enable() : disable();
	}
    }
    else if(a_path == "/prm/st/timestamp" && ctrChkNode3(opt,"get",R_R_R_,SEC_RD)) opt->setText(i2s(timeStamp()));
    else if(mPrm >= 0 && a_path == "/iPrms/nmb" && ctrChkNode3(opt,"get",R_R_R_,SEC_RD)) {
	vector<string> c_list;
	list(c_list);
	unsigned eC = 0;
	for(unsigned iA = 0; iA < c_list.size(); iA++)
	    if(at(c_list[iA]).at().enableStat()) eC++;
	opt->setText(TSYS::strMess(_("All: %d; Enabled: %d"),c_list.size(),eC));
    }
    else if((a_path == "/br/prm_" || a_path == "/iPrms/prm")) {
	if(ctrChkNode3(opt,"get",RWRWR_,SEC_RD)) {
	    vector<string> c_list;
	    list(c_list);
	    for(unsigned iA = 0; iA < c_list.size(); iA++) {
	        XMLNode *cN = opt->childAdd("el")->setAttr("id",c_list[iA])->setText(trD(at(c_list[iA]).at().name()));
		if(!s2i(opt->attr("recurs"))) continue;
		cN->setName(opt->name())->setAttr("path",TSYS::strEncode(opt->attr("path"),TSYS::PathEl))->setAttr("recurs","1");
		at(c_list[iA]).at().cntrCmd(cN);
		cN->setName("el")->setAttr("path","")->setAttr("rez","")->setAttr("recurs","")->setText("");
	    }
	}
	if(ctrChkNode3(opt,"add",RWRWR_,SEC_WR)) { opt->setAttr("id", add(opt->attr("id"))); at(opt->attr("id")).at().setName(opt->text()); }
	if(ctrChkNode3(opt,"del",RWRWR_,SEC_WR)) del(opt->attr("id"), NodeRemove);
    }
    else if(type().cntrCmdProc(this, opt)) /* Process OK */;
    else if(a_path == "/prm/cfg/DESCR" && ctrChkNode2(opt,"SnthHgl",SEC_RD)) { nodeLoadACLSnthHgl(*opt); nodeMessSnthHgl(*opt); }
    else if(a_path.find("/prm/cfg") == 0) TConfig::cntrCmdProc(this, opt, TSYS::pathLev(a_path,2), "", "", -1);
    else if(a_path == "/prm/tmplList" && ctrChkNode3(opt,"get",R_R_R_,SEC_RD)) {
	opt->childAdd("el")->setText("");
	vector<string> lls, ls;
	SYS->daq().at().tmplLibList(lls);
	for(unsigned iL = 0; iL < lls.size(); iL++) {
	    SYS->daq().at().tmplLibAt(lls[iL]).at().list(ls);
	    for(unsigned iT = 0; iT < ls.size(); iT++)
		opt->childAdd("el")->setText(lls[iL]+"."+ls[iT]);
	}
    }
    else if(a_path == "/prm/tpLst" && ctrChkNode3(opt,"get",R_R_R_,SEC_RD))
	for(unsigned iTp = 0; iTp < owner().owner().tpPrmSize(); iTp++)
	    opt->childAdd("el")->setAttr("id",owner().owner().tpPrmAt(iTp).name)->setText(owner().owner().tpPrmAt(iTp).descr);
    else TValue::cntrCmdProc(opt);
}
