
//OpenSCADA file: tprmtmpl.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 "tsys.h"
#include "tprmtmpl.h"


#define DEF_onlAttr	"0"


using namespace OSCADA;

//*************************************************
//* TPrmTempl                                     *
//*************************************************
TPrmTempl::TPrmTempl( const string &iid, const string &iname ) :
    TFunction("tmpl_"+iid), TConfig(&SYS->daq().at().elTmpl()),
    mId(cfg("ID")), mTimeStamp(cfg("TIMESTAMP").getId())
{
    mId = iid;
    setName(iname);
}

TPrmTempl::~TPrmTempl( )
{

}

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

    exclCopy(*src_n, "ID;");
    *(TFunction *)this = *(TFunction*)src_n;

    if(src_n->startStat() && !startStat()) setStart(true);

    return *this;
}

void TPrmTempl::postEnable( int flag )
{
    //Create default IOs
    if(flag&TCntrNode::NodeConnect) {
	ioIns(new IO("f_frq",trS("Frequency of calculation of the function, Hz"),IO::Real,TPrmTempl::LockAttr,"1000",false), 0);
	ioIns(new IO("f_start",trS("Function start flag"),IO::Boolean,TPrmTempl::LockAttr,"0",false), 1);
	ioIns(new IO("f_stop",trS("Function stop flag"),IO::Boolean,TPrmTempl::LockAttr,"0",false), 2);
	ioIns(new IO("f_err",trS("Function error"),IO::String,TPrmTempl::LockAttr,"0",false), 3);
    }
}

void TPrmTempl::postDisable( int flag )
{
    if(flag&NodeRemove) {
	TBDS::dataDel(owner().fullDB(), owner().owner().nodePath()+owner().tbl(), *this, TBDS::UseAllKeys);

	//Delete template's IO
	TConfig cfg(&owner().owner().elTmplIO());
	cfg.cfg("TMPL_ID").setS(id(), true);
	TBDS::dataDel(owner().fullDB()+"_io", owner().owner().nodePath()+owner().tbl()+"_io/", cfg);
    }
}

bool TPrmTempl::cfgChange( TCfg &co, const TVariant &pc )
{
    if(co.name() == "PR_TR") cfg("PROGRAM").setNoTransl(!progTr());

    if(co.getS() != pc.getS()) {
	if(co.name() == "PROGRAM" && startStat()) setStart(false);
	modif();
    }

    return true;
}

TPrmTmplLib &TPrmTempl::owner( ) const	{ return *(TPrmTmplLib*)nodePrev(); }

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

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

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

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

string TPrmTempl::stor( ) const		{ return owner().DB(); }

int TPrmTempl::maxCalcTm( )		{ return cfg("MAXCALCTM").getI(); }

void TPrmTempl::setMaxCalcTm( int vl )	{ cfg("MAXCALCTM").setI(vl); }

string TPrmTempl::progLang( )	{ return TSYS::strLine(cfg("PROGRAM").getS(),0); }

string TPrmTempl::prog( )
{
    string tPrg = cfg("PROGRAM").getS();
    size_t lngEnd = tPrg.find("\n");
    return tPrg.substr((lngEnd==string::npos)?0:lngEnd+1);
}

void TPrmTempl::setProgLang( const string &ilng ) { cfg("PROGRAM").setS(ilng+"\n"+prog()); }

void TPrmTempl::setProg( const string &iprg ) { cfg("PROGRAM").setS(progLang()+"\n"+iprg); }

void TPrmTempl::setStart( bool vl )
{
    if(startStat() == vl) return;
    if(vl) {
	//Check for doubled attributes clear
	std::map<string,bool> ioIds;
	for(int id = 0; id < ioSize(); )
	    if(ioIds.find(io(id)->id()) != ioIds.end()) { ioDel(id); modif(); }
	    else { ioIds[io(id)->id()] = true; id++; }

	//Compile the new function
	if(prog().size())
	    work_prog = SYS->daq().at().at(TSYS::strSepParse(progLang(),0,'.')).at().
					compileFunc(TSYS::strSepParse(progLang(),1,'.'),*this,prog(),"",maxCalcTm());
    }
    TFunction::setStart(vl);
}

AutoHD<TFunction> TPrmTempl::func( )
{
    if(!startStat())	throw err_sys(_("Template is off."));
    if(!prog().size())	return AutoHD<TFunction>(this);
    try { return SYS->nodeAt(work_prog); }
    catch(TError &err) {
	//Template restart try
	setStart(false);
	setStart(true);
	return SYS->nodeAt(work_prog);
    }
}

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

    if(icfg) *(TConfig*)this = *icfg;
    else {
	cfg("PROGRAM").setExtVal(true);
	TBDS::dataGet(owner().fullDB(), owner().owner().nodePath()+owner().tbl(), *this);
    }
    if(!progTr()) cfg("PROGRAM").setExtVal(false, true);

    //Load IO
    vector<string> u_pos;
    TConfig ioCfg(&owner().owner().elTmplIO());
    ioCfg.cfg("TMPL_ID").setS(id(), true);
    for(int ioCnt = 0; TBDS::dataSeek(owner().fullDB()+"_io",owner().owner().nodePath()+owner().tbl()+"_io",ioCnt++,ioCfg,TBDS::UseCache); ) {
	string sid = ioCfg.cfg("ID").getS();

	// Position storing
	int pos = ioCfg.cfg("POS").getI();
	while((int)u_pos.size() <= pos)	u_pos.push_back("");
	u_pos[pos] = sid;

	int iid = ioId(sid);
	if(iid < 0)
	    ioIns(new IO(sid.c_str(),ioCfg.cfg("NAME").getS().c_str(),(IO::Type)ioCfg.cfg("TYPE").getI(),ioCfg.cfg("FLAGS").getI(),
			ioCfg.cfg("VALUE").getS().c_str(),false), pos);
	else {
	    io(iid)->setName(ioCfg.cfg("NAME").getS());
	    io(iid)->setType((IO::Type)ioCfg.cfg("TYPE").getI());
	    io(iid)->setFlg(ioCfg.cfg("FLAGS").getI());
	    io(iid)->setDef(ioCfg.cfg("VALUE").getS());
	}
    }

    //Remove holes
    for(unsigned iP = 0; iP < u_pos.size(); )
	if(u_pos[iP].empty()) u_pos.erase(u_pos.begin()+iP);
	else iP++;

    //Position fixing
    for(int iP = 0; iP < (int)u_pos.size(); iP++) {
	int iid = ioId(u_pos[iP]);
	if(iid != iP) try{ ioMove(iid,iP); } catch(...){ }
    }
}

void TPrmTempl::save_( )
{
    string w_db = owner().fullDB();
    string w_cfgpath = owner().owner().nodePath()+owner().tbl();

    //Self save
    mTimeStamp = SYS->sysTm();
    TBDS::dataSet(w_db, w_cfgpath, *this);

    //Save IO
    TConfig cfg(&owner().owner().elTmplIO());
    cfg.cfg("TMPL_ID").setS(id(),true);
    for(int iIO = 0; iIO < ioSize(); iIO++) {
	if(io(iIO)->flg()&TPrmTempl::LockAttr) continue;
	cfg.cfg("ID").setS(io(iIO)->id());
	cfg.cfg("NAME").setS(io(iIO)->name());
	cfg.cfg("TYPE").setI(io(iIO)->type());
	cfg.cfg("FLAGS").setI(io(iIO)->flg());
	cfg.cfg("VALUE").setNoTransl(!((io(iIO)->type() == IO::String && io(iIO)->flg()&IO::TransltText) ||
					io(iIO)->flg()&(TPrmTempl::CfgLink|IO::Selectable)));
	cfg.cfg("VALUE").setS(io(iIO)->def());
	cfg.cfg("POS").setI(iIO);
	TBDS::dataSet(w_db+"_io", w_cfgpath+"_io", cfg);
    }
    //Clear IO
    cfg.cfgViewAll(false);
    for(int fld_cnt = 0; TBDS::dataSeek(w_db+"_io",w_cfgpath+"_io",fld_cnt++,cfg); ) {
	string sio = cfg.cfg("ID").getS();
	if(ioId(sio) < 0 || io(ioId(sio))->flg()&TPrmTempl::LockAttr) {
	    if(!TBDS::dataDel(w_db+"_io",w_cfgpath+"_io",cfg,TBDS::UseAllKeys|TBDS::NoException)) break;
	    fld_cnt--;
	}
    }
}

TVariant TPrmTempl::objFuncCall( const string &iid, vector<TVariant> &prms, const string &user_lang )
{
    //Configuration functions call
    TVariant cfRez = objFunc(iid, prms, user_lang, RWRWR_, "root:" SDAQ_ID);
    if(!cfRez.isNull()) return cfRez;

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

void TPrmTempl::cntrCmdProc( XMLNode *opt )
{
    //Get page info
    if(opt->name() == "info") {
	TCntrNode::cntrCmdProc(opt);
	ctrMkNode3("oscada_cntr",opt,-1,"/",_("Parameter template: ")+trD(name()),RWRWR_, "doc","User_API|Documents/User_API");
	if(ctrMkNode3("area",opt,-1,"/tmpl",_("Template"),R_R_R_)) {
	    if(ctrMkNode3("area",opt,-1,"/tmpl/st",_("State"),R_R_R_)) {
		ctrMkNode3("fld",opt,-1,"/tmpl/st/st",_("Accessible"),RWRWR_, "tp","bool");
		ctrMkNode3("fld",opt,-1,"/tmpl/st/use",_("Used"),R_R_R_, "tp","dec");
		ctrMkNode3("fld",opt,-1,"/tmpl/st/timestamp",_("Date of modification"),R_R_R_, "tp","time");
	    }
	    if(ctrMkNode3("area",opt,-1,"/tmpl/cfg",_("Configuration"),SEC_RD)) {
		ctrMkNode3("fld",opt,-1,"/tmpl/cfg/ID",_("Identifier"),SEC_RD, "tp","str");
		ctrMkNode3("fld",opt,-1,"/tmpl/cfg/NAME",_("Name"),SEC_RD|SEC_WR, "tp","str", "len",i2s(limObjNm_SZ).c_str());
		ctrMkNode3("fld",opt,-1,"/tmpl/cfg/DESCR",_("Description"),SEC_RD|SEC_WR,
		    "tp","str", "cols","100", "rows","4", "SnthHgl","1");
		ctrMkNode3("fld",opt,-1,"/tmpl/cfg/MAXCALCTM",_("Maximum calculate time, seconds"),SEC_RD|(startStat()?0:SEC_WR),
		    "tp","dec", "min","0", "max","3600");
	    }
	}
	if(ctrMkNode3("area",opt,-1,"/io",_("IO"),R_R_R_)) {
	    if(ctrMkNode3("table",opt,-1,"/io/io",_("IO"),RWRWR_,"s_com","add,ins,move,del","rows","5")) {
		ctrMkNode3("list",opt,-1,"/io/io/0",_("Identifier"),RWRWR_, "tp","str");
		ctrMkNode3("list",opt,-1,"/io/io/1",_("Name"),RWRWR_, "tp","str",
		    "help",_("The name's rows after the first one treat as help."));
		ctrMkNode3("list",opt,-1,"/io/io/2",_("Type"),RWRWR_, "tp","dec", "idm","1", "dest","select",
		    "sel_id",TSYS::strMess("%d;%d;%d;%d;%d;%d;%d;%d;%d;%d;%d",
			IO::Real,IO::Integer,IO::Boolean,IO::String,IO::String|(IO::TransltText<<8),IO::String|(IO::FullText<<8),IO::String|((IO::FullText|IO::TransltText)<<8),
			IO::Object,IO::Integer|(IO::Selectable<<8),IO::Real|(IO::Selectable<<8),IO::String|(IO::Selectable<<8)).c_str(),
		    "sel_list",_("Real;Integer;Boolean;String;String (translate);Text;Text (translate);Object;Integer numbers selection;Real numbers selection;Strings selection"));
		ctrMkNode3("list",opt,-1,"/io/io/3",_("Mode"),RWRWR_, "tp","dec", "idm","1", "dest","select",
		    "sel_id",TSYS::strMess("%d;%d",IO::Default,IO::Output).c_str(),"sel_list",_("Input;Output"));
		ctrMkNode3("list",opt,-1,"/io/io/4",_("Attribute"),RWRWR_, "tp","dec", "idm","1", "dest","select",
		    "sel_id",TSYS::strMess("%d;%d;%d",IO::Default,TPrmTempl::AttrRead,TPrmTempl::AttrFull).c_str(),
		    "sel_list",_("Not attribute;Read only;Full access"));
		ctrMkNode3("list",opt,-1,"/io/io/5",_("Configuration"),RWRWR_, "tp","dec", "idm","1", "dest","select",
		    "sel_id",TSYS::strMess("%d;%d;%d",IO::Default,TPrmTempl::CfgConst,TPrmTempl::CfgLink).c_str(),
		    "sel_list",_("Variable;Constant;Link"));
		ctrMkNode3("list",opt,-1,"/io/io/6",_("Value"),RWRWR_, "tp","str");
	    }
	    ctrMkNode3("fld",opt,-1,"/io/prog_lang",_("Program language"),RWRW__, "tp","str", "dest","sel_ed", "select","/plang/list");
	    if(progTr())
		ctrMkNode3("fld",opt,-1,"/io/prog_tr",_(cfg("PR_TR").fld().descr()),RWRW__, "tp","bool");
	    ctrMkNode3("fld",opt,-1,"/io/prog",_(cfg("PROGRAM").fld().descr()),RWRW__, "tp","str", "rows","10", "SnthHgl","1");
	}
	return;
    }

    //Process command to page
    vector<string> list;
    string a_path = opt->attr("path");
    if(a_path == "/tmpl/st/st") {
	if(ctrChkNode2(opt,"get",SEC_RD))	opt->setText(startStat()?"1":"0");
	if(ctrChkNode2(opt,"set",SEC_WR))	setStart(s2i(opt->text()));
    }
    else if(a_path == "/tmpl/st/use" && ctrChkNode3(opt,"get",R_R_R_,SEC_RD)) opt->setText(i2s(startStat()?func().at().use():0));
    else if(a_path == "/tmpl/st/timestamp" && ctrChkNode3(opt,"get",R_R_R_,SEC_RD)) opt->setText(i2s(timeStamp()));
    else if(a_path == "/tmpl/cfg/ID" && ctrChkNode2(opt,"get",SEC_RD))	opt->setText(id());
    else if(a_path == "/tmpl/cfg/NAME") {
	if(ctrChkNode2(opt,"get",SEC_RD))	opt->setText(trD(name()));
	if(ctrChkNode2(opt,"set",SEC_WR))	setName(trDSet(name(),opt->text()));
    }
    else if(a_path == "/tmpl/cfg/DESCR") {
	if(ctrChkNode2(opt,"get",SEC_RD))	opt->setText(trD(descr()));
	if(ctrChkNode2(opt,"set",SEC_WR))	setDescr(trDSet(descr(),opt->text()));
	if(ctrChkNode2(opt,"SnthHgl",SEC_RD))	nodeLoadACLSnthHgl(*opt);
    }
    else if(a_path == "/tmpl/cfg/MAXCALCTM") {
	if(ctrChkNode2(opt,"get",SEC_RD))	opt->setText(i2s(maxCalcTm()));
	if(ctrChkNode2(opt,"set",SEC_WR))	setMaxCalcTm(s2i(opt->text()));
    }
    else if(a_path == "/io/io") {
	if(ctrChkNode3(opt,"get",RWRWR_,SEC_RD)) {
	    XMLNode *n_id   = ctrMkNode3("list",opt,-1,"/io/io/0","",RWRWR_);
	    XMLNode *n_nm   = ctrMkNode3("list",opt,-1,"/io/io/1","",RWRWR_);
	    XMLNode *n_type = ctrMkNode3("list",opt,-1,"/io/io/2","",RWRWR_);
	    XMLNode *n_mode = ctrMkNode3("list",opt,-1,"/io/io/3","",RWRWR_);
	    XMLNode *n_attr = ctrMkNode3("list",opt,-1,"/io/io/4","",RWRWR_);
	    XMLNode *n_accs = ctrMkNode3("list",opt,-1,"/io/io/5","",RWRWR_);
	    XMLNode *n_val  = ctrMkNode3("list",opt,-1,"/io/io/6","",RWRWR_);

	    for(int id = 0; id < ioSize(); id++) {
		if(n_id)	n_id->childAdd("el")->setText(io(id)->id());
		if(n_nm)	n_nm->childAdd("el")->setText(trD(io(id)->name()));
		if(n_type)	n_type->childAdd("el")->setText(i2s(io(id)->type()|((io(id)->flg()&(IO::FullText|IO::TransltText|IO::Selectable))<<8)));
		if(n_mode)	n_mode->childAdd("el")->setText(i2s(io(id)->flg()&(IO::Output|IO::Return)));
		if(n_attr)	n_attr->childAdd("el")->setText(i2s(io(id)->flg()&(TPrmTempl::AttrRead|TPrmTempl::AttrFull)));
		if(n_accs)	n_accs->childAdd("el")->setText(i2s(io(id)->flg()&(TPrmTempl::CfgConst|TPrmTempl::CfgLink)));
		if(n_val)	n_val->childAdd("el")->setText(((io(id)->type()==IO::String && (io(id)->flg()&IO::TransltText)) ||
								io(id)->flg()&(TPrmTempl::CfgLink|IO::Selectable))
								    ? trD(io(id)->def())
								    : io(id)->def());
	    }
	}
	if(ctrChkNode3(opt,"add",RWRWR_,SEC_WR)) {
	    IO *ioPrev = ioSize() ? io(ioSize()-1) : NULL;
	    if(ioPrev) {
		string ioID = TSYS::strLabEnum(ioPrev->id());
		while(ioId(ioID) >= 0) ioID = TSYS::strLabEnum(ioID);
		ioAdd(new IO(ioID.c_str(),TSYS::strLabEnum(trD(ioPrev->name())).c_str(),
				ioPrev->type(),ioPrev->flg()&(~LockAttr),trD(ioPrev->def()).c_str()));
	    } else ioAdd(new IO("new",trDSet("",_("New IO")),IO::Real,IO::Default));
	    modif();
	}
	if(ctrChkNode3(opt,"ins",RWRWR_,SEC_WR)) {
	    int row = s2i(opt->attr("row"));
	    IO *ioPrev = row ? io(row-1) : NULL;
	    if(ioPrev) {
		string ioID = TSYS::strLabEnum(ioPrev->id());
		while(ioId(ioID) >= 0) ioID = TSYS::strLabEnum(ioID);
		ioIns(new IO(ioID.c_str(),TSYS::strLabEnum(trD(ioPrev->name())).c_str(),
				ioPrev->type(),ioPrev->flg()&(~LockAttr),trD(ioPrev->def()).c_str()), row);
	    } else ioIns(new IO("new",trDSet("",_("New IO")),IO::Real,IO::Default), row);
	    modif();
	}
	if(ctrChkNode3(opt,"del",RWRWR_,SEC_WR)) {
	    int row = s2i(opt->attr("row"));
	    if(io(row)->flg()&TPrmTempl::LockAttr)
		throw err_sys(_("Deleting a locked attribute is not allowed."));
	    ioDel(row);
	    modif();
	}
	if(ctrChkNode3(opt,"move",RWRWR_,SEC_WR)) { ioMove(s2i(opt->attr("row")), s2i(opt->attr("to"))); modif(); }
	if(ctrChkNode3(opt,"set",RWRWR_,SEC_WR)) {
	    int row = s2i(opt->attr("row"));
	    int col = s2i(opt->attr("col"));
	    if(io(row)->flg()&TPrmTempl::LockAttr)	throw err_sys(_("Changing locked attribute is not allowed."));
	    if((col == 0 || (col == 1 && !Mess->translDyn())) && !opt->text().size())
		throw err_sys(_("Empty value is not allowed."));
	    modif();
	    switch(col) {
		case 0:
		    opt->setText(TSYS::strEncode(sTrm(opt->text()),TSYS::Limit,
					i2s(owner().owner().elTmplIO().fldAt(owner().owner().elTmplIO().fldId("ID")).len())));
		    io(row)->setId(opt->text());
		    break;
		case 1:	io(row)->setName(trDSet(io(row)->name(),sTrm(opt->text())));	break;
		case 2:
		    io(row)->setType((IO::Type)(s2i(opt->text())&0xFF));
		    io(row)->setFlg(io(row)->flg()^((io(row)->flg()^(s2i(opt->text())>>8))&(IO::FullText|IO::TransltText|IO::Selectable)));
		    break;
		case 3:	io(row)->setFlg(io(row)->flg()^((io(row)->flg()^s2i(opt->text()))&(IO::Output|IO::Return)));		break;
		case 4:	io(row)->setFlg(io(row)->flg()^((io(row)->flg()^s2i(opt->text()))&(TPrmTempl::AttrRead|TPrmTempl::AttrFull)));		break;
		case 5:	io(row)->setFlg(io(row)->flg()^((io(row)->flg()^s2i(opt->text()))&(TPrmTempl::CfgConst|TPrmTempl::CfgLink)));	break;
		case 6:	io(row)->setDef(((io(row)->type()==IO::String && io(row)->flg()&IO::TransltText) ||
					    io(row)->flg()&(TPrmTempl::CfgLink|IO::Selectable)) ? trDSet(io(row)->def(),sTrm(opt->text())) : opt->text());
		    setStart(false);
		    break;
	    }
	}
    }
    else if(a_path == "/io/prog_lang") {
	if(ctrChkNode3(opt,"get",RWRW__,SEC_RD)) opt->setText(progLang());
	if(ctrChkNode3(opt,"set",RWRW__,SEC_WR)) setProgLang(opt->text());
    }
    else if(a_path == "/io/prog_tr") {
	if(ctrChkNode3(opt,"get",RWRW__,SEC_RD)) opt->setText(i2s(progTr()));
	if(ctrChkNode3(opt,"set",RWRW__,SEC_WR)) setProgTr(s2i(opt->text()));
    }
    else if(a_path == "/io/prog") {
	if(ctrChkNode3(opt,"get",RWRW__,SEC_RD)) opt->setText(prog());
	if(ctrChkNode3(opt,"set",RWRW__,SEC_WR)) setProg(opt->text());
	if(ctrChkNode3(opt,"SnthHgl",RWRW__,SEC_RD))
	    try {
		SYS->daq().at().at(TSYS::strParse(progLang(),0,".")).at().
				compileFuncSnthHgl(TSYS::strParse(progLang(),1,"."),*opt);
	    } catch(...){ }
    }
    else TCntrNode::cntrCmdProc(opt);
}

TPrmTempl::Impl::Impl( TCntrNode *iobj, const string &iname, bool blked ) : TValFunc(iname.c_str(),NULL,blked), obj(iobj)
{

}

int  TPrmTempl::Impl::lnkId( const string &nm )
{
    MtxAlloc res(lnkRes, true);
    if(!func()) return -1;	//!!!! Due to the execution context can be cleared already
    for(int iIO = 0; iIO < func()->ioSize(); iIO++)
	if(func()->io(iIO)->id() == nm)
	    return iIO;
    return -1;
}

bool TPrmTempl::Impl::lnkPresent( int num )
{
    MtxAlloc res(lnkRes, true);
    map<int,SLnk>::iterator it = lnks.find(num);
    return (it != lnks.end());
}

void TPrmTempl::Impl::lnkList( vector<int> &ls ) const
{
    MtxAlloc res(const_cast<ResMtx&>(lnkRes), true);
    for(map<int,SLnk>::const_iterator iL = lnks.begin(); iL != lnks.end(); ++iL)
	ls.push_back(iL->first);
}

void TPrmTempl::Impl::lnkAdd( int num, const SLnk &l )
{
    lnkRes.lock();
    lnks[num] = l;
    lnkRes.unlock();
}

string TPrmTempl::Impl::lnkAddr( int num, bool spec ) const
{
    MtxAlloc res(const_cast<ResMtx&>(lnkRes), true);
    map<int,SLnk>::const_iterator it = lnks.find(num);
    if(it == lnks.end()) throw TError(obj->nodePath(), _("Error of parameter ID."));
    return spec ? it->second.addrSpec : it->second.addr;
}

void TPrmTempl::Impl::lnkAddrSet( int num, const string &vl, bool spec )
{
    MtxAlloc res(lnkRes, true);
    map<int,SLnk>::iterator it = lnks.find(num);
    if(it == lnks.end()) throw TError(obj->nodePath(), _("Error of parameter ID."));
    if(spec)	it->second.addrSpec = vl;
    else if(it->second.addr != vl) {
	it->second.addr = vl;
	it->second.con.free(); it->second.addrSpec = "";	//for reconnect
    }
}

bool TPrmTempl::Impl::lnkInit( int num, bool toRecnt )
{
    MtxAlloc res(lnkRes, true);
    map<int,SLnk>::iterator it = lnks.find(num);
    if(it == lnks.end()) return false;
    if(toRecnt) { it->second.con.free(); it->second.addrSpec = ""; }

    bool toCheckRes = true;

retry:
    if(it->second.addr.empty() || it->second.addr.find("val:") == 0)	toCheckRes = false;
    // Try to relink
    else if(it->second.con.freeStat()) {
	string addr = it->second.addr, aAddr;
	res.unlock();

	AutoHD<TVal> con;
	int objOff = 0;

	try {
	    bool isPath = (addr.find("prm:") == 0);
	    if(isPath) addr = addr.substr(4);

	    con = SYS->daq().at().attrAt((aAddr=TSYS::strParse(addr,0,"#",&objOff)), isPath?0:'.', true, obj);
	    if(!con.freeStat()) {
		if(con.at().fld().type() == TFld::Object && objOff < (int)addr.size())
		    setS(num, con.at().getO().at().propGet(addr.substr(objOff),isPath?0:'.'));
		else setS(num, con.at().getS());
		toCheckRes = false;
	    }
	    //!!!! For relative attribute links like "../test" with presence this one as "test" parameter also,
	    //     there is better to relink try for parameters without attributes in this case
	    else if(isPath && addr.find("..") != string::npos) ;
	    else if(!SYS->daq().at().prmAt(aAddr,isPath?0:'.',true,obj).freeStat()) toCheckRes = false;	//just a parameter
	    if(isPath) addr = "prm:" + addr;
	} catch(TError &err) { }

	//Setting the connection result
	res.lock();
	// Relink after going behind the lock
	if((it=lnks.find(num)) == lnks.end())	return false;
	if(it->second.addr != addr) goto retry;			//the item address changed

	it->second.con = con;
	it->second.objOff = objOff;
    }

    return toCheckRes;
}

bool TPrmTempl::Impl::lnkActive( int num )
{
    MtxAlloc res(lnkRes, true);
    map<int,SLnk>::iterator it = lnks.find(num);
    return (it != lnks.end() && !it->second.con.freeStat());
}

TVariant TPrmTempl::Impl::lnkInput( int num )
{
    MtxAlloc res(lnkRes, true);
    map<int,SLnk>::iterator it = lnks.find(num);

    if(it == lnks.end())	return EVAL_REAL;
    if(it->second.addr.find("val:") == 0) return it->second.addr.substr(4);
    if(it->second.con.freeStat())	  return EVAL_REAL;
    if(it->second.hops > RECURS_DET_HOPS) {
	it->second.addr = ""; it->second.con.free();
	it->second.hops = 0;
	mess_warning(obj->nodePath().c_str(), _("Detected of the link recursion."));
	return EVAL_REAL;
    }
    it->second.hops++;

    AutoHD<TVal> con = it->second.con;
    int objOff = it->second.objOff;
    string addr = it->second.addr;
    res.unlock();

    TVariant rVal = EVAL_REAL;

    try {
	if(con.at().fld().type() == TFld::Object && objOff < (int)addr.size())
	    rVal = con.at().getO().at().propGet(addr.substr(objOff), '.');
	else rVal = con.at().get();
    } catch(TError&) { }

    it->second.hops = 0;

    return rVal;
}

bool TPrmTempl::Impl::lnkOutput( int num, const TVariant &vl )
{
    if(vl.isEVal()) return false;
    MtxAlloc res(lnkRes, true);
    map<int,SLnk>::iterator it = lnks.find(num);
    if(it == lnks.end() || it->second.con.freeStat() || (it->second.con.at().fld().flg()&TFld::NoWrite) ||
	    !(ioFlg(num)&(IO::Output|IO::Return)))
	return false;
    if(it->second.hops > RECURS_DET_HOPS) {
	it->second.addr = ""; it->second.con.free();
	it->second.hops = 0;
	mess_warning(obj->nodePath().c_str(), _("Detected of the link recursion."));
	return false;
    }
    it->second.hops++;

    AutoHD<TVal> con = it->second.con;
    int objOff = it->second.objOff;
    string addr = it->second.addr;
    res.unlock();

    try {
	if(con.at().fld().type() == TFld::Object && objOff < (int)addr.size()) {
	    con.at().getO().at().propSet(addr.substr(objOff), '.', vl);
	    con.at().setO(con.at().getO());	//For the modifying object sign
	}
	else {
	    //Disabling the attributes translation at change
	    if(con.at().isTransl()) con.at().setNoTransl(true);

	    con.at().set(vl);
	}
    } catch(TError&) { }

    it->second.hops = 0;

    return true;
}

void TPrmTempl::Impl::addLinksAttrs( TElem *attrsCntr )
{
    map<string, bool> als;

    //MtxAlloc res(lnkRes, true);	//!!!! There is not a direct access to links
    for(int iIO = 0; iIO < func()->ioSize(); iIO++) {
	if((func()->io(iIO)->flg()&TPrmTempl::CfgLink) && !lnkPresent(iIO)) {
	    lnkAdd(iIO, TPrmTempl::Impl::SLnk());
	    setS(iIO, EVAL_STR);	//|| setS(iIO,"0"); on DAQ.ModBus
	}
	if(attrsCntr && (func()->io(iIO)->flg()&(TPrmTempl::AttrRead|TPrmTempl::AttrFull))) {
	    unsigned fId = 0;
	    unsigned flg = TVal::DirWrite|TVal::DirRead;
	    if(func()->io(iIO)->flg()&IO::FullText)		flg |= TFld::FullText;
	    if(func()->io(iIO)->flg()&IO::TransltText)		flg |= TFld::TransltText;
	    string selVals, selNms;
	    if(func()->io(iIO)->flg()&IO::Selectable) {
		flg |= TFld::Selectable;
		selVals = TSYS::strLine((selNms=trD(func()->io(iIO)->def())), 1);
		selNms = TSYS::strLine(selNms, 2);
	    }
	    if(func()->io(iIO)->flg()&TPrmTempl::AttrRead)	flg |= TFld::NoWrite;
	    TFld::Type tp = TFld::type(ioType(iIO));
	    if((fId=attrsCntr->fldId(func()->io(iIO)->id(),true)) < attrsCntr->fldSize()) {
		if(attrsCntr->fldAt(fId).type() != tp)
		    try{ attrsCntr->fldDel(fId); }
		    catch(TError &err){ mess_warning(err.cat.c_str(), "%s", err.mess.c_str()); }
		else {
		    attrsCntr->fldAt(fId).setFlg(flg);
		    attrsCntr->fldAt(fId).setDescr(func()->io(iIO)->name()/*.c_str()*/);
		    if((flg&TFld::Selectable)) {
			attrsCntr->fldAt(fId).setValues(selVals);
			attrsCntr->fldAt(fId).setSelNames(selNms);
		    }
		}
	    }

	    if(!attrsCntr->fldPresent(func()->io(iIO)->id()))
		attrsCntr->fldAdd(new TFld(func()->io(iIO)->id().c_str(),func()->io(iIO)->name()/*.c_str()*/,tp,flg,"","",selVals,selNms));

	    als[func()->io(iIO)->id()] = true;
	}
    }

    //Check for removing
    for(int iP = 0; attrsCntr && iP < (int)attrsCntr->fldSize(); iP++)
	if(als.find(attrsCntr->fldAt(iP).name()) == als.end())
	    try{ attrsCntr->fldDel(iP); iP--; }
	    catch(TError &err) { mess_warning(err.cat.c_str(), "%s", err.mess.c_str()); }
}

bool TPrmTempl::Impl::initLnks( bool toRecnt )
{
    vector<int> ls;
    bool chkLnkNeed = false;

    lnkList(ls);
    for(int iL = 0; iL < (int)ls.size(); ++iL)
	if(lnkInit(ls[iL],toRecnt)) chkLnkNeed = true;

    return chkLnkNeed;
}

void TPrmTempl::Impl::cleanLnks( bool andFunc )
{
    lnkRes.lock();
    lnks.clear();
    if(andFunc) setFunc(NULL);
    lnkRes.unlock();
}

void TPrmTempl::Impl::inputLinks( )
{
    vector<int> ls;
    lnkList(ls);
    for(int iL = 0; iL < (int)ls.size(); ++iL)
	set(ls[iL], lnkInput(ls[iL]));
}

void TPrmTempl::Impl::outputLinks( )
{
    vector<int> ls;
    lnkList(ls);
    for(int iL = 0; iL < (int)ls.size(); ++iL)
	if(ioMdf(ls[iL])) lnkOutput(ls[iL], get(ls[iL]));
}

void TPrmTempl::Impl::archAttrs( TValue *vl, int64_t tm )
{
    if(!vl) return;

    int idIO = -1, idLnk = -1;
    AutoHD<TVal> pVal;
    vector<string> ls;

    vl->vlList(ls);
    for(unsigned iEl = 0; iEl < ls.size(); iEl++)
	if(!(pVal=vl->vlAt(ls[iEl])).at().isCfg() && !(pVal.at().fld().flg()&TVal::Dynamic) && (idIO=ioId(ls[iEl])) >= 0)
	    pVal.at().set(((idLnk=lnkId(ls[iEl])) >= 0 && lnkActive(idLnk)) ? lnkInput(idLnk) : get(idIO), tm, true);
}

bool TPrmTempl::Impl::cntrCmdProc( XMLNode *opt, const string &pref )
{
    XMLNode *selA = NULL;
    TParamContr *pC = dynamic_cast<TParamContr*>(obj);

    MtxAlloc res(lnkRes, true);
    //Get page info
    if(opt->name() == "info" && obj->ctrMkNode3("area",opt,-1,pref.c_str(),_("Template configuration"),SEC_RD)) {
	vector<string> list;
	obj->ctrMkNode3("fld",opt,-1,(pref+"/attr_only").c_str(),_("Show attributes"),SEC_RD|SEC_WR, "tp","bool");
	if(obj->ctrMkNode3("area",opt,-1,(pref+"/prm").c_str(),_("Parameters"),SEC_RD))
	    for(int iIO = 0; iIO < ioSize(); iIO++) {
		//Updating the selection items in the dynamic translation mode
		if(func()->io(iIO)->flg()&IO::Selectable && Mess->translDyn() &&
		    (selA=ctrId(opt->childGet(0),"/val/"+func()->io(iIO)->id(),true)))
		{
		    string selVals, selNms = trD(func()->io(iIO)->def());
		    selVals = TSYS::strLine(selNms, 1), selNms = TSYS::strLine(selNms, 2);
		    if(selNms.empty())	selA->setAttr("sel_list", selVals);
		    else selA->setAttr("sel_id", selVals)->setAttr("sel_list", selNms);
		}

		//Same template properties
		if(!(func()->io(iIO)->flg()&(TPrmTempl::CfgLink|TPrmTempl::CfgConst)))
		    continue;
		// Check the selected param
		bool is_lnk = func()->io(iIO)->flg()&TPrmTempl::CfgLink;
		if(is_lnk && TSYS::strLine(trD(func()->io(iIO)->def()),0).size() &&
		    !s2i(TBDS::genPrmGet((pC?pC->owner().nodePath():obj->nodePath())+"onlAttr",DEF_onlAttr,opt->attr("user"))))
		{
		    string nprm = TSYS::strLine(TSYS::strSepParse(TSYS::strLine(trD(func()->io(iIO)->def()),0),0,'|'), 0);

		    // Checking for already present parameters
		    bool f_ok = false;
		    for(unsigned iL = 0; iL < list.size() && !f_ok; iL++)
			if(list[iL] == nprm) f_ok = true;
		    if(!f_ok) {
			obj->ctrMkNode3("fld",opt,-1,(pref+"/prm/pr_"+i2s(iIO)).c_str(),nprm,SEC_RD|SEC_WR,
			    "tp","str", "dest","sel_ed", "select",(pref+"/prm/pl_"+i2s(iIO)).c_str(), "help",lnkHelp().c_str());
			list.push_back(nprm);
		    }
		}
		else {
		    string nprm = trD(func()->io(iIO)->name());
		    int nOff = 0; string nprm1 = TSYS::strLine(nprm, 0, &nOff);

		    const char *tip = "str";
		    bool fullTxt = false;
		    if(!is_lnk)
			switch(ioType(iIO)) {
			    case IO::Integer:	tip = "dec";	break;
			    case IO::Real:	tip = "real";	break;
			    case IO::Boolean:	tip = "bool";	break;
			    case IO::String:
				if(func()->io(iIO)->flg()&IO::FullText) fullTxt = true;
				break;
			    case IO::Object:	fullTxt = true;	break;
			}
		    XMLNode *wn = obj->ctrMkNode3("fld",opt,-1,(pref+"/prm/el_"+i2s(iIO)).c_str(),nprm1,SEC_RD|SEC_WR, "tp",tip);
		    if(wn) {
			if(nOff < (int)nprm.size()) wn->setAttr("help",nprm.substr(nOff));
			if(is_lnk) wn->setAttr("dest","sel_ed")->setAttr("select",pref+"/prm/ls_"+i2s(iIO))->
				       setAttr("help",(wn->attr("help").size()?wn->attr("help")+"\n\n":"")+lnkHelp());
			else if(func()->io(iIO)->flg()&IO::Selectable) {
			    string selVals, selNms = trD(func()->io(iIO)->def());
			    selVals = TSYS::strLine(selNms, 1), selNms = TSYS::strLine(selNms, 2);
			    if(selNms.empty()) wn->setAttr("dest","select")->setAttr("sel_list", selVals);
			    else wn->setAttr("dest","select")->setAttr("sel_id", selVals)->setAttr("sel_list", selNms);
			}
			if(fullTxt) wn/*->setAttr("cols","100")*/->setAttr("rows","4");
		    }
		}
	    }
	return true;
    }

    //Process command to page
    string a_path = opt->attr("path");
    if(a_path.find(pref) != 0)	return false;
    a_path = a_path.substr(pref.size());
    if(a_path == "/attr_only") {
	if(obj->ctrChkNode2(opt,"get",SEC_RD))
	    opt->setText(TBDS::genPrmGet((pC?pC->owner().nodePath():obj->nodePath())+"onlAttr",DEF_onlAttr,opt->attr("user")));
	if(obj->ctrChkNode2(opt,"set",SEC_WR))
	    TBDS::genPrmSet((pC?pC->owner().nodePath():obj->nodePath())+"onlAttr",opt->text(),opt->attr("user"));
    }
    else if(a_path.find("/prm/pr_") == 0) {
	if(obj->ctrChkNode2(opt,"get",SEC_RD)) {
	    string lnk_val = lnks[s2i(a_path.substr(8))].addr;
	    bool isPath = (lnk_val.find("prm:") == 0);
	    if(!SYS->daq().at().attrAt(TSYS::strParse(isPath?lnk_val.substr(4):lnk_val,0,"#"),isPath?0:'.',true,obj).freeStat()) {
		opt->setText(lnk_val.substr(0,lnk_val.rfind(isPath?"/":".")));
		opt->setText(opt->text()+" (+)");
	    }
	    else opt->setText(lnk_val);
	}
	if(obj->ctrChkNode2(opt,"set",SEC_WR)) {
	    string p_nm = TSYS::strSepParse(TSYS::strLine(trD(func()->io(s2i(a_path.substr(8)))->def()),0),0,'|');
	    string p_vl = TSYS::strParse(opt->text(), 0, " ");
	    bool isPath = (p_vl.find("prm:") == 0);
	    if(pC && p_vl == (isPath?TSYS::path2sepstr(pC->DAQPath(),'.'):pC->DAQPath()))
		throw TError(obj->nodePath(),_("Error, recursive linking."));
	    AutoHD<TValue> prm = SYS->daq().at().prmAt(isPath?p_vl.substr(4):p_vl, isPath?0:'.', true, obj);

	    for(map<int,SLnk>::iterator iL = lnks.begin(); iL != lnks.end(); ++iL)
		if(p_nm == TSYS::strSepParse(TSYS::strLine(trD(func()->io(iL->first)->def()),0),0,'|')) {
		    lnkAddrSet(iL->first, p_vl);
		    string p_attr = TSYS::strSepParse(TSYS::strLine(trD(func()->io(iL->first)->def()),0),1,'|');
		    if(!prm.freeStat() && prm.at().vlPresent(p_attr))
			lnkAddrSet(iL->first, p_vl+(isPath?"/":".")+p_attr);
		}
	    initLnks();
	    obj->modif();
	}
    }
    else if((a_path.find("/prm/pl_") == 0 || a_path.find("/prm/ls_") == 0) && obj->ctrChkNode3(opt,"get",R_R_R_,SEC_RD)) {
	bool is_pl = (a_path.find("/prm/pl_") == 0);
	string m_prm = lnks[s2i(a_path.substr(8))].addr;
	bool isVal = (m_prm.find("val:") == 0);
	bool isPath = (m_prm.find("prm:") == 0);
	if(is_pl && !SYS->daq().at().attrAt(m_prm,isPath?0:'.',true,obj).freeStat())
	    m_prm = m_prm.substr(0, m_prm.rfind(isPath?"/":"."));
	if(m_prm.size() && !isVal)
	    SYS->daq().at().ctrListPrmAttr(opt, m_prm, is_pl, isPath?0:'.', isPath?"prm:":"", obj);
	else if(m_prm.empty()) {
	    SYS->daq().at().ctrListPrmAttr(opt, m_prm, is_pl, '.');
	    SYS->daq().at().ctrListPrmAttr(opt, m_prm, is_pl, 0, "prm:", obj);
	    if(!is_pl) opt->childAdd("el")->setText(_("val:Constant value"));
	}
    }
    else if(a_path.find("/prm/el_") == 0) {
	if(obj->ctrChkNode2(opt,"get",SEC_RD)) {
	    int iIO = s2i(a_path.substr(8));
	    if(func()->io(iIO)->flg()&TPrmTempl::CfgLink) {
		opt->setText(lnks[iIO].addr);
		bool isPath = (opt->text().find("prm:") == 0);
		if(!SYS->daq().at().attrAt(TSYS::strParse(isPath?opt->text().substr(4):opt->text(),0,"#"),isPath?0:'.',true,obj).freeStat())
		    opt->setText(opt->text()+" (+)");
	    }
	    else if(func()->io(iIO)->flg()&TPrmTempl::CfgConst)
		opt->setText((func()->io(iIO)->type()==IO::Real) ? r2s(getR(iIO), 6) : getS(iIO));
	}
	if(obj->ctrChkNode2(opt,"set",SEC_WR)) {
	    int iIO = s2i(a_path.substr(8));
	    if(func()->io(iIO)->flg()&TPrmTempl::CfgLink) {
		string a_vl = opt->text().find("val:") == 0 ? opt->text() : TSYS::strParse(opt->text(), 0, " ");
		//if(TSYS::strSepParse(a_vl,0,'.') == owner().owner().modId() &&
		//	TSYS::strSepParse(a_vl,1,'.') == owner().id() &&
		//	TSYS::strSepParse(a_vl,2,'.') == id())
		//    throw TError(nodePath(),_("Error, recursive linking."));
		lnkAddrSet(iIO, a_vl);
		initLnks();
	    }
	    else if(func()->io(iIO)->flg()&TPrmTempl::CfgConst) setS(iIO, opt->text());
	    obj->modif();
	}
    }
    else return false;

    return true;
}

string TPrmTempl::Impl::lnkHelp( )
{
    return _("Address to the DAQ attribute is written in the forms:\n"
	"  \"{Type}.{Cntr}.{Prm}[.{PrmN}].{Attr}\";\n"
	"  \"prm:/{Type}/{Cntr}/{Prm}[/{PrmN}]/{Attr}\";\n"
	"  \"val:Constant value\".\n"
	"Where:\n"
	"  {Type},{Cntr},{Prm},{Attr} - DAQ type, controller object, parameter and attribute;\n"
	"  \"prm:\" - prefix of the ordinal form path where you can use the relativity by '.' and '..';\n"
	"  \"val:\" - prefix of the constant value in links.\n\n"
	"Link's template, of the column \"Value\" on the template forming side, writes in the form \"{Parameter}|{attribute}\", where:\n"
	"  {Parameter} - specifies the parameter's name as the attribute's container;\n"
	"  {attribute} - attributes with the equal value <Parameter> will be grouped and will be appointed only by the indication of the attributes' container, "
	"and the individual attributes will be associated with the attributes of the container in accordance with this field.\n");
}

//*************************************************
//* TPrmTmplLib                                   *
//*************************************************
TPrmTmplLib::TPrmTmplLib( const string &id, const string &name, const string &lib_db ) :
    TConfig(&SYS->daq().at().elLib()), runSt(false), mId(cfg("ID")), mDB(lib_db)
{
    mId = id;
    setName(name);
    cfg("DB").setS("tmplib_"+id);	//????[v1.0] Remove, saved for the new libraries work on old OpenSCADA versions
    m_ptmpl = grpAdd("tmpl_");
}

TPrmTmplLib::~TPrmTmplLib()
{

}

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

    //Configuration copy
    exclCopy(*src_n, "ID;DB;");
    setDB(src_n->DB());

    //Templates copy
    vector<string> ls;
    src_n->list(ls);
    for(unsigned iP = 0; iP < ls.size(); iP++) {
	if(!present(ls[iP])) add(ls[iP].c_str());
	(TCntrNode&)at(ls[iP]).at() = (TCntrNode&)src_n->at(ls[iP]).at();
    }
    if(src_n->startStat() && !startStat()) start(true);

    return *this;
}

void TPrmTmplLib::preDisable( int flag )
{
    start(false);
}

void TPrmTmplLib::postDisable( int flag )
{
    if(flag&(NodeRemove|NodeRemoveOnlyStor)) {
	//Delete libraries record
	TBDS::dataDel(DB(flag&NodeRemoveOnlyStor)+"."+owner().tmplLibTable(), owner().nodePath()+"tmplib", *this, TBDS::UseAllKeys);

	//Delete temlate librarie's DBs
	TBDS::dataDelTbl(fullDB(flag&NodeRemoveOnlyStor), owner().nodePath()+tbl());
	TBDS::dataDelTbl(fullDB(flag&NodeRemoveOnlyStor)+"_io", owner().nodePath()+tbl()+"_io/");

	if(flag&NodeRemoveOnlyStor) { setStorage(mDB, "", true); return; }
    }
}

TDAQS &TPrmTmplLib::owner( ) const	{ return *(TDAQS*)nodePrev(); }

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

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

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

void TPrmTmplLib::setDescr( const string &vl )	{ cfg("DESCR").setS(vl); nodeLoadACL(vl); }

void TPrmTmplLib::setFullDB( const string &vl )
{
    int off = vl.size();
    cfg("DB").setS(TSYS::strParseEnd(vl,0,".",&off));
    setDB(vl.substr(0,off+1));
}

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

    if(icfg) *(TConfig*)this = *icfg;
    else TBDS::dataGet(DB()+"."+owner().tmplLibTable(), owner().nodePath()+"tmplib", *this);

    //Load templates
    map<string, bool>	itReg;
    TConfig cEl(&owner().elTmpl());
    cEl.cfg("PROGRAM").setExtVal(true);
    //cEl.cfgViewAll(false);
    for(int fldCnt = 0; TBDS::dataSeek(fullDB(),owner().nodePath()+tbl(),fldCnt++,cEl,TBDS::UseCache); ) {
	string fId = cEl.cfg("ID").getS();
	if(!present(fId)) add(fId);
	at(fId).at().load(&cEl);
	itReg[fId] = true;
    }

    //Check for remove items removed from 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]);
    }
}

void TPrmTmplLib::save_( )
{
    TBDS::dataSet(DB()+"."+owner().tmplLibTable(), owner().nodePath()+"tmplib", *this);
    setDB(DB(), true);
}

void TPrmTmplLib::start( bool val )
{
    bool isErr = false;
    vector<string> lst;
    list(lst);
    for(unsigned iF = 0; iF < lst.size(); iF++)
	try{ at(lst[iF]).at().setStart(val); }
	catch(TError &err) {
	    mess_err(err.cat.c_str(), "%s", err.mess.c_str());
	    mess_sys(TMess::Error, _("Error starting the template '%s'."), lst[iF].c_str());
	    isErr = true;
	}

    runSt = val;

    if(isErr)	throw err_sys(_("Error starting some templates."));
}

void TPrmTmplLib::add( const string &id, const string &name )
{
    chldAdd(m_ptmpl, new TPrmTempl(id,name));
}

TVariant TPrmTmplLib::objFuncCall( const string &iid, vector<TVariant> &prms, const string &user_lang )
{
    //Configuration functions call
    TVariant cfRez = objFunc(iid, prms, user_lang, RWRWR_, "root:" SDAQ_ID);
    if(!cfRez.isNull()) return cfRez;

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

void TPrmTmplLib::cntrCmdProc( XMLNode *opt )
{
    //Get page info
    if(opt->name() == "info") {
	TCntrNode::cntrCmdProc(opt);
	XMLNode *nd = ctrMkNode3("oscada_cntr",opt,-1,"/",_("Parameter templates library: ")+id(),RWRWR_);
	if(nd)	nd->setAttr("doc", TUIS::docKeyGet(descr()));
	if(ctrMkNode3("branches",opt,-1,"/br","",R_R_R_))
	    ctrMkNode3("grp",opt,-1,"/br/tmpl_",_("Template"),RWRWR_,
		"idm",i2s(limObjNm_SZ).c_str(), "idSz",i2s(limObjID_SZ).c_str());
	if(ctrMkNode3("area",opt,-1,"/lib",_("Library"),R_R_R_)) {
	    if(ctrMkNode3("area",opt,-1,"/lib/st",_("State"),R_R_R_)) {
		ctrMkNode3("fld",opt,-1,"/lib/st/st",_("Accessible"),RWRWR_, "tp","bool");
		if(isStdStorAddr())
		    ctrMkNode3("fld",opt,-1,"/lib/st/db",_("Storage"),SEC_RD|SEC_WR,
			"tp","str", "dest","select", "select","/db/list", "help",TMess::labStor().c_str());
		else ctrMkNode3("fld",opt,-1,"/lib/st/db",_("Library DB"),SEC_RD|SEC_WR,
			"tp","str", "dest","sel_ed", "select",("/db/tblList:tmplib_"+id()).c_str(),
			"help",TSYS::strMess(_("Storage address in the format \"{DB module}.{DB name}.{Table name}\".\nTo use %s, set '%s.{Table name}'."),
					TMess::labStorFromCode(DB_GEN).c_str(),DB_GEN).c_str());
		if(DB(true).size())
		    ctrMkNode3("comm",opt,-1,"/lib/st/removeFromDB",TSYS::strMess(_("Remove from '%s'"),
			TMess::labStorFromCode(DB(true)).c_str()).c_str(),SEC_RD|SEC_WR, "help",TMess::labStorRem(mDB).c_str());
		ctrMkNode3("fld",opt,-1,"/lib/st/timestamp",_("Date of modification"),R_R_R_, "tp","time");
	    }
	    if(ctrMkNode3("area",opt,-1,"/lib/cfg",_("Configuration"),SEC_RD)) {
		ctrMkNode3("fld",opt,-1,"/lib/cfg/ID",_("Identifier"),SEC_RD, "tp","str");
		ctrMkNode3("fld",opt,-1,"/lib/cfg/NAME",_("Name"),SEC_RD|SEC_WR, "tp","str", "len",i2s(limObjNm_SZ).c_str());
		ctrMkNode3("fld",opt,-1,"/lib/cfg/DESCR",_("Description"),SEC_RD|SEC_WR,
		    "tp","str", "cols","100", "rows","3", "SnthHgl","1");
	    }
	}
	if(ctrMkNode3("area",opt,-1,"/tmpl",_("Parameter templates"),R_R_R_))
	    ctrMkNode3("list",opt,-1,"/tmpl/tmpl",_("Templates"),RWRWR_,
		"tp","br", "idm",i2s(limObjNm_SZ).c_str(), "s_com","add,del", "br_pref","tmpl_", "idSz",i2s(limObjID_SZ).c_str());
	return;
    }

    //Process command to page
    string a_path = opt->attr("path");
    if(a_path == "/lib/st/st") {
	if(ctrChkNode3(opt,"get",RWRWR_,SEC_RD)) opt->setText(startStat() ? "1" : "0");
	if(ctrChkNode3(opt,"set",RWRWR_,SEC_WR)) start(s2i(opt->text()));
    }
    else if(a_path == "/lib/st/db") {
	if(ctrChkNode2(opt,"get",SEC_RD)) opt->setText(isStdStorAddr()?DB():fullDB());
	if(ctrChkNode2(opt,"set",SEC_WR)) isStdStorAddr() ? setDB(opt->text()) : setFullDB(opt->text());
    }
    else if(a_path == "/lib/st/removeFromDB" && ctrChkNode2(opt,"set",SEC_WR))
	postDisable(NodeRemoveOnlyStor);
    else if(a_path == "/lib/st/timestamp" && ctrChkNode3(opt,"get",R_R_R_,SEC_RD)) {
	vector<string> tls;
	list(tls);
	time_t maxTm = 0;
	for(unsigned iT = 0; iT < tls.size(); iT++) maxTm = vmax(maxTm, at(tls[iT]).at().timeStamp());
	opt->setText(i2s(maxTm));
    }
    else if(a_path == "/lib/cfg/ID" && ctrChkNode2(opt,"get",SEC_RD))	opt->setText(id());
    else if(a_path == "/lib/cfg/NAME") {
	if(ctrChkNode2(opt,"get",SEC_RD))	opt->setText(trD(name()));
	if(ctrChkNode2(opt,"set",SEC_WR))	setName(trDSet(name(),opt->text()));
    }
    else if(a_path == "/lib/cfg/DESCR") {
	if(ctrChkNode2(opt,"get",SEC_RD))	opt->setText(trD(descr()));
	if(ctrChkNode2(opt,"set",SEC_WR))	setDescr(trDSet(descr(),opt->text()));
	if(ctrChkNode2(opt,"SnthHgl",SEC_RD))	{ nodeLoadACLSnthHgl(*opt); nodeDocSnthHgl(*opt); }
    }
    else if(a_path == "/br/tmpl_" || a_path == "/tmpl/tmpl") {
	if(ctrChkNode3(opt,"get",RWRWR_,SEC_RD)) {
	    vector<string> lst;
	    list(lst);
	    for(unsigned iF = 0; iF < lst.size(); iF++)
		opt->childAdd("el")->setAttr("id",lst[iF])->setText(trD(at(lst[iF]).at().name()));
	}
	if(ctrChkNode3(opt,"add",RWRWR_,SEC_WR)) add(TSYS::strEncode(opt->attr("id"),TSYS::oscdID).c_str(),opt->text().c_str());
	if(ctrChkNode3(opt,"del",RWRWR_,SEC_WR)) del(opt->attr("id").c_str(),true);
    }
    else TCntrNode::cntrCmdProc(opt);
}
