
//OpenSCADA file: tfunction.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 <math.h>

#include <tsys.h>
#include <tmess.h>
#include "tfunction.h"

#define DEF_ntCalc	"1"

using namespace OSCADA;

//*************************************************
//* Function abstract object                      *
//*************************************************
TFunction::TFunction( const string &iid, const char *igrp, const string &istor ) :
    mId(iid), mStor(istor), runSt(false), beStart(false), mTVal(NULL), grp(igrp)
{
    if(mess_lev() == TMess::Debug) SYS->cntrIter(objName(), 1);
}

TFunction::TFunction( const TFunction &src ) : runSt(false), beStart(false), mTVal(NULL), grp(NULL)
{
    operator=(src);

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

TFunction::~TFunction( )
{
    for(unsigned iIO = 0; iIO < mIO.size(); iIO++) delete mIO[iIO];

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

TFunction &TFunction::operator=( const TFunction &func )
{
    //Copy IO
    // Clear no present IO
    for(int iIO = 0; iIO < ioSize(); )
	if(func.ioId(io(iIO)->id()) < 0) ioDel(iIO);
	else iIO++;
    // Update present and create new IO
    for(int iIO = 0; iIO < func.ioSize(); iIO++) {
	int dst_io = ioId(func.io(iIO)->id());
	if(dst_io < 0)
	    dst_io = ioIns(new IO(func.io(iIO)->id().c_str(),func.io(iIO)->name(),func.io(iIO)->type(),func.io(iIO)->flg(),
		func.io(iIO)->def(),func.io(iIO)->hide(),func.io(iIO)->rez().c_str()), iIO);
	else *io(dst_io) = *func.io(iIO);
	if(dst_io != iIO && !use()) {
	    if(iIO >= ioSize())
		throw err_sys(_("A duple IO '%s' is detected in position %d after %d. If you do not see the duple IO then you created a special IO, remove or rename it!"),
		    func.io(dst_io)->id().c_str(), iIO, dst_io);
	    else ioMove(dst_io, iIO);
	}
    }

    if(mId.empty()) mId = func.id();
    mStor = func.stor();

    return *this;
}

void TFunction::preDisable( int flag )
{
    if(mTVal) { delete mTVal; mTVal = NULL; }
    if(used.size()) {
	string mess;
	for(unsigned i = 0; i < used.size(); i++)
	    mess += used[i]->vfName()+", ";
	throw err_sys(_("Function used: %s"), mess.c_str());
    }
}

void TFunction::setId( const string &vl )
{
    if(!nodePrev(true)) mId = vl;
}

int TFunction::ioSize( ) const	{ return mIO.size(); }

IO *TFunction::io( int iid ) const
{
    if(iid >= (int)mIO.size()) throw err_sys(_("Index %d is broken!"), iid);
    return mIO[iid];
}

int TFunction::ioId( const string &id ) const
{
    for(int iIO = 0; iIO < (int)mIO.size(); iIO++)
	if(mIO[iIO]->id() == id) return iIO;

    return -1;
}

void TFunction::setStart( bool val )
{
    if(!val && mTVal) { delete mTVal; mTVal = NULL; }

    runSt = val;
}

void TFunction::ioList( vector<string> &list )
{
    for(unsigned iIO = 0; iIO < mIO.size(); iIO++)
	list.push_back(mIO[iIO]->id());
}

int TFunction::ioAdd( IO *io )
{
    preIOCfgChange();
    mIO.push_back(io);
    io->owner = this;
    postIOCfgChange();
    modif();

    return mIO.size()-1;
}

int TFunction::ioIns( IO *io, int pos )
{
    if(pos < 0 || pos > (int)mIO.size()) pos = mIO.size();

    preIOCfgChange();
    mIO.insert(mIO.begin()+pos,io);
    io->owner = this;
    postIOCfgChange();
    modif();

    return pos;
}

void TFunction::ioDel( int pos )
{
    if(pos < 0 || pos >= (int)mIO.size()) throw err_sys(_("Error removing IO %d."), pos);

    preIOCfgChange();
    mIO.erase(mIO.begin()+pos);
    postIOCfgChange();
    modif();
}

void TFunction::ioMove( int pos, int to )
{
    if(pos < 0 || pos >= (int)mIO.size() || to < 0 || to >= (int)mIO.size())
	throw err_sys(_("Error moving IO from %d to %d."), pos, to);

    preIOCfgChange();
    IO *io = mIO[to];
    mIO[to] = mIO[pos];
    mIO[pos] = io;
    postIOCfgChange();
    modif();
}

void TFunction::preIOCfgChange( )
{
    //Previous stop
    beStart = startStat();
    if(beStart) {
	setStart(false);
	if(mTVal) { delete mTVal; mTVal = NULL; }
    }

    //Main process
    string blk_lst;
    for(unsigned i = 0; i < used.size(); i++)
	if(used[i]->blk()) blk_lst += used[i]->vfName()+",";
    if(blk_lst.size())
	throw err_sys(_("Changes are not possible because the function is used: %s"), blk_lst.c_str());

    for(unsigned i = 0; i < used.size(); i++) used[i]->preIOCfgChange();
}

void TFunction::postIOCfgChange( )
{
    //Start for restore
    if(beStart) setStart(true);

    for(unsigned i = 0; i < used.size(); i++) used[i]->postIOCfgChange();
}

void TFunction::valAtt( TValFunc *vfnc )
{
    MtxAlloc res(dataRes(), true);
    for(unsigned i = 0; i < used.size(); i++)
	if(used[i] == vfnc)
	    throw err_sys(_("The value '%s' is already connected!"), vfnc->vfName().c_str());
    used.push_back(vfnc);
}

void TFunction::valDet( TValFunc *vfnc )
{
    dataRes().lock();
    for(unsigned i = 0; i < used.size() ;i++)
	if(used[i] == vfnc)
	{ used.erase(used.begin()+i); break; }
    dataRes().unlock();
}

TVariant TFunction::objFuncCall( const string &iid, vector<TVariant> &prms, const string &user_lang )
{
    // ElTp call(ElTp prm1, ...) - the function call
    //  prm{N} - {N} parameter to the function.
    if(iid == "call") {
	if(!startStat()) return EVAL_REAL;
	TValFunc vfnc("JavaLikeObjFuncCalc",this);

	//  Get return position
	int rPos, iP, pP;
	for(rPos = 0; rPos < vfnc.func()->ioSize(); rPos++)
	    if(vfnc.ioFlg(rPos)&IO::Return) break;
	//  Process parameters
	for(iP = pP = 0; true; iP++) {
	    pP = (iP>=rPos) ? iP+1 : iP;
	    if(pP >= vfnc.func()->ioSize()) break;
	    //   Set default value
	    if(iP >= (int)prms.size()) { vfnc.setS(pP,vfnc.func()->io(pP)->def()); continue; }
	    vfnc.set(pP,prms[iP]);
	}
	//  Make calc
	vfnc.calc(TSYS::strLine(user_lang,0));
	//  Process outputs
	for(iP = 0; iP < (int)prms.size(); iP++) {
	    pP = (iP>=rPos) ? iP+1 : iP;
	    if(pP >= vfnc.func()->ioSize()) break;
	    if(!(vfnc.ioFlg(pP)&IO::Output))   continue;
	    prms[iP] = vfnc.get(pP);
	    prms[iP].setModify();
	}
	//  Set return
	if(rPos < vfnc.func()->ioSize()) return vfnc.get(rPos);
	return EVAL_REAL;
    }

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

void TFunction::cntrCmdProc( XMLNode *opt )
{
    //Get page info
    if(opt->name() == "info") {
	TCntrNode::cntrCmdProc(opt);
	ctrMkNode("oscada_cntr",opt,-1,"/",_("Function: ")+name(),R_R_R_,"root",grp);
	if(ctrMkNode("area",opt,-1,"/func",_("Function"))) {
	    if(ctrMkNode("area",opt,-1,"/func/st",_("State"))) {
		ctrMkNode("fld",opt,-1,"/func/st/st",_("Accessible"),RWRWR_,"root",grp,1,"tp","bool");
		ctrMkNode("fld",opt,-1,"/func/st/use",_("Used"),R_R_R_,"root",grp,1,"tp","dec");
	    }
	    if(ctrMkNode("area",opt,-1,"/func/cfg",_("Configuration"))) {
		ctrMkNode("fld",opt,-1,"/func/cfg/ID",_("Identifier"),R_R_R_,"root",grp,1,"tp","str");
		ctrMkNode("fld",opt,-1,"/func/cfg/NAME",_("Name"),R_R_R_,"root",grp,1,"tp","str");
		ctrMkNode("fld",opt,-1,"/func/cfg/DESCR",_("Description"),R_R_R_,"root",grp,3,"tp","str","cols","90","rows","5");
	    }
	}
	if(ctrMkNode("area",opt,-1,"/io",_("IO")))
	    if(ctrMkNode("table",opt,-1,"/io/io",_("IO"),R_R_R_,"root",grp,1,"rows","5")) {
		ctrMkNode("list",opt,-1,"/io/io/0",_("Identifier"),R_R_R_,"root",grp,1,"tp","str");
		ctrMkNode("list",opt,-1,"/io/io/1",_("Name"),R_R_R_,"root",grp,1,"tp","str");
		ctrMkNode("list",opt,-1,"/io/io/2",_("Type"),R_R_R_,"root",grp,1,"tp","str");
		ctrMkNode("list",opt,-1,"/io/io/3",_("Mode"),R_R_R_,"root",grp,1,"tp","str");
		ctrMkNode("list",opt,-1,"/io/io/4",_("Hide"),R_R_R_,"root",grp,1,"tp","bool");
		ctrMkNode("list",opt,-1,"/io/io/5",_("Default"),R_R_R_,"root",grp,1,"tp","str");
	    }
	if(ctrMkNode("area",opt,-1,"/exec",_("Execute"),RWRW__,"root",grp)) {
	    ctrMkNode("fld",opt,-1,"/exec/en",_("Enabled"),RWRW__,"root",grp,1,"tp","bool");
	    // Adding the test form
	    if(mTVal) {
		if(ctrMkNode("area",opt,-1,"/exec/io",_("IO")))
		    for(int iIO = 0; iIO < ioSize(); iIO++) {
			if(mIO[iIO]->hide()) continue;
			string nprm = _(io(iIO)->name());
			int nOff = 0; string nprm1 = TSYS::strLine(nprm, 0, &nOff);

			XMLNode *nd = ctrMkNode("fld",opt,-1,("/exec/io/"+io(iIO)->id()).c_str(),
					nprm1,((io(iIO)->flg()&IO::Return)?R_R_R_:RWRW__),"root",grp);
			if(nd) {
			    if(nOff < (int)nprm.size()) nd->setAttr("help",nprm.substr(nOff));
			    switch(io(iIO)->type()) {
				case IO::String:
				    nd->setAttr("tp","str");
				    if(io(iIO)->flg()&IO::FullText) nd->setAttr("cols","100")->setAttr("rows","4");
				    break;
				case IO::Integer:	nd->setAttr("tp","dec");	break;
				case IO::Real:		nd->setAttr("tp","real");	break;
				case IO::Boolean:	nd->setAttr("tp","bool");	break;
				case IO::Object:	nd->setAttr("tp","str")->setAttr("cols","100")->setAttr("rows","4");	break;
			    }
			}
		    }
		// Adding the calc button and the calc time field
		ctrMkNode("comm",opt,-1,"/exec/calc",_("Execute"),RWRW__,"root",grp);
		ctrMkNode("fld",opt,-1,"/exec/n_clc",_("Executions, spent time"),RWRW__,"root",grp,1,"tp","dec");
		ctrMkNode("fld",opt,-1,"/exec/tm","",R_R___,"root",grp,1,"tp","str");

	    }
	}
        return;
    }

    //Process command to page
    string a_path = opt->attr("path");
    if(a_path == "/func/st/st") {
	if(ctrChkNode(opt,"get",RWRWR_,"root",grp,SEC_RD))	opt->setText(runSt?"1":"0");
	if(ctrChkNode(opt,"set",RWRWR_,"root",grp,SEC_WR))	setStart(s2i(opt->text()));
    }
    else if(a_path == "/func/st/use" && ctrChkNode(opt))	opt->setText(i2s(used.size()));
    else if(a_path == "/func/cfg/ID" && ctrChkNode(opt))	opt->setText(id());
    else if(a_path == "/func/cfg/NAME" && ctrChkNode(opt))	opt->setText(name());
    else if(a_path == "/func/cfg/DESCR" && ctrChkNode(opt))	opt->setText(descr());
    else if(a_path == "/io/io" && ctrChkNode(opt,"get",R_R_R_,"root",grp,SEC_RD)) {
	XMLNode *n_id	= ctrMkNode("list",opt,-1,"/io/io/0","",R_R_R_,"root",grp);
	XMLNode *n_nm	= ctrMkNode("list",opt,-1,"/io/io/1","",R_R_R_,"root",grp);
	XMLNode *n_type	= ctrMkNode("list",opt,-1,"/io/io/2","",R_R_R_,"root",grp);
	XMLNode *n_mode	= ctrMkNode("list",opt,-1,"/io/io/3","",R_R_R_,"root",grp);
	XMLNode *n_hide	= ctrMkNode("list",opt,-1,"/io/io/4","",R_R_R_,"root",grp);
	XMLNode *n_def	= ctrMkNode("list",opt,-1,"/io/io/5","",R_R_R_,"root",grp);
	//XMLNode *n_vect	= ctrId(opt,"6");
	for(int iIO = 0; iIO < ioSize(); iIO++) {
	    string tmp_str;
	    if(n_id)	n_id->childAdd("el")->setText(io(iIO)->id());
	    if(n_nm)	n_nm->childAdd("el")->setText(_(io(iIO)->name()));
	    if(n_type) {
		switch(io(iIO)->type()) {
		    case IO::String:
			tmp_str = (io(iIO)->flg()&IO::FullText) ? _("Text") : _("String");
			if(io(iIO)->flg()&IO::TransltText) tmp_str = tmp_str + " "+_("(translate)");
			break;
		    case IO::Integer:	tmp_str = _("Integer");	break;
		    case IO::Real:	tmp_str = _("Real");	break;
		    case IO::Boolean:	tmp_str = _("Bool");	break;
		    case IO::Object:	tmp_str = _("Object");	break;
		}
		n_type->childAdd("el")->setText(tmp_str);
	    }
	    if(n_mode) {
		if(io(iIO)->flg()&IO::Return)		tmp_str = _("Return");
		else if(io(iIO)->flg()&IO::Output)	tmp_str = _("Output");
		else					tmp_str = _("Input");
		n_mode->childAdd("el")->setText(tmp_str);
	    }
	    if(n_hide)	n_hide->childAdd("el")->setText(io(iIO)->hide()?"1":"0");
	    if(n_def)	n_def->childAdd("el")->setText(io(iIO)->def());
	}
    }
    else if(a_path == "/exec/en") {
	if(ctrChkNode(opt,"get",RWRW__,"root",grp,SEC_RD))	opt->setText(mTVal?"1":"0");
	if(ctrChkNode(opt,"set",RWRW__,"root",grp,SEC_WR)) {
	    bool to_en_exec = s2i(opt->text());
	    if(to_en_exec && !mTVal) {
		if(!startStat()) setStart(true);
		mTVal = new TValFunc(id()+"_exec",this);
		SYS->cntrSet(nodePath('.'), 0); SYS->cntrSet(nodePath('.')+".min", 0); SYS->cntrSet(nodePath('.')+".max", 0);
	    }
	    if(!to_en_exec && mTVal)	{ delete mTVal; mTVal = NULL; }
	}
    }
    else if(a_path == "/exec/n_clc" && mTVal) {
	if(ctrChkNode(opt,"get",RWRW__,"root",grp,SEC_RD)) opt->setText(TBDS::genPrmGet(nodePath()+"ntCalc",DEF_ntCalc,opt->attr("user")));
	if(ctrChkNode(opt,"set",RWRW__,"root",grp,SEC_WR)) {
	    TBDS::genPrmSet(nodePath()+"ntCalc",opt->text(),opt->attr("user"));
	    SYS->cntrSet(nodePath('.'), 0); SYS->cntrSet(nodePath('.')+".min", 0); SYS->cntrSet(nodePath('.')+".max", 0);
	}
    }
    else if(a_path == "/exec/tm" && mTVal && ctrChkNode(opt,"get",R_R___,"root",grp,SEC_RD))
	opt->setText(tm2s(1e-6*SYS->cntrGet(nodePath('.')))+
		" ["+tm2s(1e-6*SYS->cntrGet(nodePath('.')+".min"))+"-"+tm2s(1e-6*SYS->cntrGet(nodePath('.')+".max"))+"]");
    else if(a_path.find("/exec/io") == 0 && mTVal) {
	string io_id = TSYS::pathLev(a_path, 2);
	for(unsigned iIO = 0; iIO < mIO.size(); iIO++)
	    if(io_id == io(iIO)->id()) {
		if(ctrChkNode(opt,"get",RWRW__,"root",grp,SEC_RD))
		    opt->setText((mTVal->ioType(iIO)==IO::Real) ? r2s(mTVal->getR(iIO),6) : mTVal->getS(iIO));
		if(ctrChkNode(opt,"set",RWRW__,"root",grp,SEC_WR))
		    mTVal->setS(iIO,opt->text());
		break;
	    }
    }
    else if(a_path == "/exec/calc" && mTVal && ctrChkNode(opt,"set",RWRW__,"root",grp,SEC_WR)) {
	int n_tcalc = s2i(TBDS::genPrmGet(nodePath()+"ntCalc",DEF_ntCalc,opt->attr("user")));
	string wuser = opt->attr("user");
	time_t tm_lim = SYS->sysTm()+prmWait_TM+1;
	int64_t t_cnt = TSYS::curTime(), t_tmp = 0;
	for(int iC = 0; iC < n_tcalc && SYS->sysTm() < tm_lim; iC++)
	    mTVal->calc(wuser);
	SYS->cntrSet(nodePath('.'), t_cnt=(TSYS::curTime()-t_cnt));
	SYS->cntrSet(nodePath('.')+".min", (t_tmp=SYS->cntrGet(nodePath('.')+".min")) ? vmin(t_tmp,t_cnt) : t_cnt);
	SYS->cntrSet(nodePath('.')+".max", (t_tmp=SYS->cntrGet(nodePath('.')+".max")) ? vmax(t_tmp,t_cnt) : t_cnt);
    }
    else TCntrNode::cntrCmdProc(opt);
}

//*************************************************
//* IO                                            *
//*************************************************
IO::IO( const char *iid, const string &iname, IO::Type itype,  unsigned iflgs, const string &idef, bool ihide, const char *irez ) :
    mId(iid), mName(iname), mType(itype), mFlg(iflgs), mDef(idef), mHide(ihide), mRez(irez), owner(NULL)
{

}

IO::IO( const IO &src ) : mType(Real), mFlg(0), mHide(false), owner(NULL)
{
    operator=(src);
}

IO &IO::operator=( const IO &iio )
{
    setId(iio.id());
    setName(iio.name());
    setType(iio.type());
    setFlg(iio.flg());
    setDef(iio.def());
    setHide(iio.hide());
    setRez(iio.rez());

    return *this;
}

void IO::setId( const string &val )
{
    if(mId == val) return;
    owner->preIOCfgChange();
    mId = val;
    owner->postIOCfgChange();
}

void IO::setName( const string &val )	{ mName = val; }

void IO::setType( Type val )
{
    if(mType == val) return;
    owner->preIOCfgChange();
    mType = val;
    owner->postIOCfgChange();
}

void IO::setFlg( unsigned val )
{
    if(mFlg == val) return;
    owner->preIOCfgChange();
    mFlg = val;
    owner->postIOCfgChange();
}

void IO::setDef( const string &val )
{
    if(mDef == val) return;
    //owner->preIOCfgChange();
    mDef = val;
    //owner->postIOCfgChange();
}

void IO::setHide( bool val )
{
    if(mHide == val) return;
    //owner->preIOCfgChange();
    mHide = val;
    //owner->postIOCfgChange();
}

void IO::setRez( const string &val )
{
    if(mRez == val) return;
    mRez = val;
}

//*************************************************
//* TValFunc                                      *
//*************************************************
TValFunc::TValFunc( const string &iname, TFunction *ifunc, bool iblk, const string &iuser ) :
    exCtx(NULL), mName(iname), mUser(iuser), mBlk(iblk), mMdfChk(false), mPrgCh(false), mCalc(false), mFunc(NULL)
{
    setFunc(ifunc);
}

TValFunc::~TValFunc( )
{
    if(mFunc) funcDisConnect();
}

void TValFunc::setFunc( TFunction *ifunc, bool att_det )
{
    if(mFunc)	funcDisConnect(att_det);
    if(ifunc) {
	mFunc = ifunc;
	if(att_det) {
	    mFunc->AHDConnect();
	    mFunc->valAtt(this);
	}
	for(int iVl = 0; iVl < mFunc->ioSize(); iVl++) {
	    SVl val;
	    val.tp = mFunc->io(iVl)->type();
	    val.mdf = false;
	    string def = mFunc->io(iVl)->def();
	    if(mFunc->io(iVl)->flg()&IO::Selectable)	def = TSYS::strLine(def, 0);
	    switch(val.tp) {
		case IO::String:	val.val.s = new string(def);	break;
		case IO::Integer:	val.val.i = s2ll(def);		break;
		case IO::Real:		val.val.r = s2r(def);		break;
		case IO::Boolean:	val.val.b = s2i(def);		break;
		case IO::Object:	val.val.o = new AutoHD<TVarObj>(new TVarObj);	break;
	    }
	    mVal.push_back(val);
	}
    }
}

void TValFunc::funcDisConnect( bool det )
{
    ctxClear();

    if(mFunc) {
	for(unsigned iVl = 0; iVl < mVal.size(); iVl++)
	    switch( mVal[iVl].tp ) {
		case IO::String: delete mVal[iVl].val.s;	break;
		case IO::Object: delete mVal[iVl].val.o;	break;
	    }

	mVal.clear();
	if(det) {
	    mFunc->valDet(this);
	    mFunc->AHDDisConnect();
	    mFunc = NULL;
	}
    }
}

int TValFunc::ioId( const string &iid )
{
    if(!mFunc)	throw TError("ValFnc", _("IO '%s' is not present!"), iid.c_str());

    return mFunc->ioId(iid);
}

void TValFunc::ioList( vector<string> &list )
{
    if(!mFunc)	throw TError("ValFnc", _("Function is not attached!"));

    return mFunc->ioList(list);
}

int TValFunc::ioSize( ) const
{
    if(!mFunc)	throw TError("ValFnc", _("Function is not attached!"));

    return mFunc->ioSize();
}

TVariant TValFunc::get( unsigned id )
{
    if(id >= mVal.size())	throw TError("ValFnc", _("%s: Error with ID or IO %d!"), "getS()", id);
    switch(mVal[id].tp) {
	case IO::Integer:	return getI(id);
	case IO::Real:		return getR(id);
	case IO::Boolean:	return getB(id);
	case IO::String:	return getS(id);
	case IO::Object:	return getO(id);
    }

    return EVAL_STR;
}

string TValFunc::getS( unsigned id )
{
    if(id >= mVal.size()) throw TError("ValFnc", _("%s: Error with ID or IO %d!"),"getS()", id);
    switch(mVal[id].tp) {
	case IO::Integer: { int64_t tvl = getI(id);return (tvl!=EVAL_INT) ? ll2s(tvl) : EVAL_STR; }
	case IO::Real:	  { double tvl = getR(id); return (tvl!=EVAL_REAL) ? r2s(tvl) : EVAL_STR; }
	case IO::Boolean: { char tvl = getB(id);   return (tvl!=EVAL_BOOL) ? i2s((bool)tvl) : EVAL_STR; }
	case IO::Object:  { AutoHD<TVarObj> tvl = getO(id); return (tvl.at().objName() == "EVAL") ? EVAL_STR : tvl.at().getStr(); }
	//case IO::Object:  return getO(id).at().getStr();
	case IO::String: {
	    mRes.lock();
	    string tvl(mVal[id].val.s->data(), mVal[id].val.s->size());
	    mRes.unlock();
	    return tvl;
	}
    }

    return EVAL_STR;
}

int64_t TValFunc::getI( unsigned id )
{
    if(id >= mVal.size()) throw TError("ValFnc", _("%s: Error with ID or IO %d!"), "getI()", id);
    switch(mVal[id].tp) {
	case IO::String:  { string tvl = getS(id); return (tvl!=EVAL_STR) ? s2ll(tvl) : EVAL_INT; }
	case IO::Real:	  { double tvl = getR(id); return (tvl!=EVAL_REAL) ? (int64_t)tvl : EVAL_INT; }
	case IO::Boolean: { char tvl = getB(id);   return (tvl!=EVAL_BOOL) ? (bool)tvl : EVAL_INT; }
	case IO::Object:  { AutoHD<TVarObj> tvl = getO(id); return (tvl.at().objName() == "EVAL") ? EVAL_INT : 1; }
	case IO::Integer: return mVal[id].val.i;
    }

    return EVAL_INT;
}

double TValFunc::getR( unsigned id )
{
    if(id >= mVal.size()) throw TError("ValFnc", _("%s: Error with ID or IO %d!"), "getR()", id);
    switch(mVal[id].tp) {
	case IO::String:  { string tvl = getS(id); return (tvl!=EVAL_STR) ? s2r(tvl) : EVAL_REAL; }
	case IO::Integer: { int64_t tvl = getI(id);return (tvl!=EVAL_INT) ? tvl : EVAL_REAL; }
	case IO::Boolean: { char tvl = getB(id);   return (tvl!=EVAL_BOOL) ? (bool)tvl : EVAL_REAL; }
	case IO::Object:  { AutoHD<TVarObj> tvl = getO(id); return (tvl.at().objName() == "EVAL") ? EVAL_REAL : 1; }
	case IO::Real:    return mVal[id].val.r;
    }

    return EVAL_REAL;
}

char TValFunc::getB( unsigned id )
{
    if(id >= mVal.size()) throw TError("ValFnc", _("%s: Error with ID or IO %d!"), "getB()", id);
    switch(mVal[id].tp) {
	case IO::String:  { string tvl = getS(id); return (tvl!=EVAL_STR) ? (bool)s2i(tvl) : EVAL_BOOL; }
	case IO::Integer: { int64_t tvl = getI(id);return (tvl!=EVAL_INT) ? (bool)tvl : EVAL_BOOL; }
	case IO::Real:    { double tvl = getR(id); return (tvl!=EVAL_REAL) ? (bool)tvl : EVAL_BOOL; }
	case IO::Object:  { AutoHD<TVarObj> tvl = getO(id); return (tvl.at().objName() == "EVAL") ? EVAL_BOOL : true; }
	case IO::Boolean:  return mVal[id].val.b;
    }

    return EVAL_BOOL;
}

AutoHD<TVarObj> TValFunc::getO( unsigned id )
{
    if(id >= mVal.size()) throw TError("ValFnc", _("%s: Error with ID or IO %d!"), "getO()", id);
    if(mVal[id].tp != IO::Object) throw TError("ValFnc", _("Error getting object from non-object IO %d!"), id);
    mRes.lock();
    AutoHD<TVarObj> rez = *mVal[id].val.o;
    mRes.unlock();

    return rez;
}

void TValFunc::set( unsigned id, const TVariant &val )
{
    if(id >= mVal.size())	throw TError("ValFnc", _("%s: Error with ID or IO %d!"), "setS()", id);
    switch(mVal[id].tp) {
	case IO::Integer:	setI(id, val.getI());	break;
	case IO::Real:		setR(id, val.getR());	break;
	case IO::Boolean:	setB(id, val.getB());	break;
	case IO::String:	setS(id, val.getS());	break;
	case IO::Object:	setO(id, val.getO());	break;
    }
}

void TValFunc::setS( unsigned id, const string &val )
{
    if(id >= mVal.size())	throw TError("ValFnc", _("%s: Error with ID or IO %d!"), "setS()", id);
    switch(mVal[id].tp) {
	case IO::Integer:	setI(id, (val!=EVAL_STR) ? s2ll(val) : EVAL_INT);	break;
	case IO::Real:		setR(id, (val!=EVAL_STR) ? s2r(val) : EVAL_REAL);	break;
	case IO::Boolean:	setB(id, (val!=EVAL_STR) ? (bool)s2i(val) : EVAL_BOOL);	break;
	case IO::Object:
	    setO(id, (val!=EVAL_STR) ? TVarObj::parseStr(val,*mVal[id].val.o) : new TEValObj());
	    break;
	case IO::String:
	    mRes.lock();
	    if(mdfChk() && val != *mVal[id].val.s) mVal[id].mdf = true;
	    mVal[id].val.s->assign(val.data(), val.size());;
	    mRes.unlock();
	    break;
    }
}

void TValFunc::setI( unsigned id, int64_t val )
{
    if(id >= mVal.size())	throw TError("ValFnc", _("%s: Error with ID or IO %d!"), "setI()", id);
    switch(mVal[id].tp) {
	case IO::String:	setS(id, (val!=EVAL_INT) ? ll2s(val) : EVAL_STR);	break;
	case IO::Real:		setR(id, (val!=EVAL_INT) ? val : EVAL_REAL);		break;
	case IO::Boolean:	setB(id, (val!=EVAL_INT) ? (bool)val : EVAL_BOOL);	break;
	case IO::Object:	if(val == EVAL_INT) setO(id, new TEValObj());		break;
	case IO::Integer:
	    if(mdfChk() && val != mVal[id].val.i) mVal[id].mdf = true;
	    mVal[id].val.i = val;
	    break;
    }
}

void TValFunc::setR( unsigned id, double val )
{
    if(id >= mVal.size())	throw TError("ValFnc", _("%s: Error with ID or IO %d!"), "setR()", id);
    switch(mVal[id].tp) {
	case IO::String:	setS(id, (val!=EVAL_REAL) ? r2s(val) : EVAL_STR);	break;
	case IO::Integer:	setI(id, (val!=EVAL_REAL) ? (int64_t)val : EVAL_INT);	break;
	case IO::Boolean:	setB(id, (val!=EVAL_REAL) ? (bool)val : EVAL_BOOL);	break;
	case IO::Object:	if(val == EVAL_REAL) setO(id, new TEValObj());		break;
	case IO::Real:
	    if(isnan(val)) val = 0;	//Check for 'Not a Number'
	    if(mdfChk() && val != mVal[id].val.r) mVal[id].mdf = true;
	    mVal[id].val.r = val;
	    break;
    }
}

void TValFunc::setB( unsigned id, char val )
{
    if(id >= mVal.size())	throw TError("ValFnc", _("%s: Error with ID or IO %d!"), "setB()", id);
    switch(mVal[id].tp) {
	case IO::String:	setS(id, (val!=EVAL_BOOL) ? i2s((bool)val) : EVAL_STR);	break;
	case IO::Integer:	setI(id, (val!=EVAL_BOOL) ? (bool)val : EVAL_INT);	break;
	case IO::Real:		setR(id, (val!=EVAL_BOOL) ? (bool)val : EVAL_REAL);	break;
	case IO::Object:	if(val == EVAL_BOOL) setO(id, new TEValObj());		break;
	case IO::Boolean:
	    if(mdfChk() && val != mVal[id].val.b) mVal[id].mdf = true;
	    mVal[id].val.b = val;
	    break;
    }
}

void TValFunc::setO( unsigned id, AutoHD<TVarObj> val )
{
    if(id >= mVal.size()) throw TError("ValFnc", _("%s: Error with ID or IO %d!"), "setO()", id);
    switch(mVal[id].tp) {
	case IO::String:	setS(id, val.at().getStr());	break;
	case IO::Integer: case IO::Real: case IO::Boolean:
				setB(id, true);			break;
	case IO::Object:
	    mRes.lock();
	    if(mdfChk() && !(val == *mVal[id].val.o)) mVal[id].mdf = true;
	    *mVal[id].val.o = val;
	    mRes.unlock();
	    return;
    }
}

void TValFunc::setMdfChk( bool set )
{
    mMdfChk = set;
    if(set)
	for(unsigned i_v = 0; i_v < mVal.size(); i_v++)
	    mVal[i_v].mdf = false;
}

void TValFunc::calc( const string &user )
{
    if(!mFunc) return;
    if(!user.empty()) mUser = user;
    mCalc = true;
    try { mFunc->calc(this); }
    catch(TError &err) { mess_warning(err.cat.c_str(), err.mess.c_str()); }
    mCalc = false;
}

void TValFunc::preIOCfgChange( )	{ setFunc(NULL, false); }

void TValFunc::postIOCfgChange( )	{ setFunc(mFunc, false); }

TValFunc *TValFunc::ctxGet( int key )
{
    map<int,TValFunc* >::iterator vc = vctx.find(key);

    return (vc == vctx.end()) ? NULL : vc->second;
}

void TValFunc::ctxSet( int key, TValFunc *val )
{
    map<int,TValFunc* >::iterator vc = vctx.find(key);
    if(vc == vctx.end()) vctx[key] = val;
    else {
	if(vc->second) delete vc->second;
	vc->second = val;
    }
}

void TValFunc::ctxClear( )
{
    for(map<int,TValFunc* >::iterator ivcx = vctx.begin(); ivcx != vctx.end(); ivcx++)
	delete ivcx->second;
    vctx.clear();
}

//***********************************************************
//* TFuncArgObj                                             *
//*   The function arguments access object.                 *
//***********************************************************
TVariant TFuncArgsObj::propGet( const string &id )
{
    int apos = -1;

    if(!vf.func())	return EVAL_REAL;
    if(id == "length")	return vf.ioSize();
    if(id.size() && isdigit(id[0])) apos = s2i(id);
    if(apos < 0 || apos >= vf.ioSize()) apos = vf.ioId(id);
    if(apos != -1)	return vf.get(apos);
    return EVAL_REAL;
}

void TFuncArgsObj::propSet( const string &id, TVariant val )
{
    int apos = -1;

    if(!vf.func()) return;
    if(id.size() && isdigit(id[0])) apos = s2i(id);
    if(apos < 0 || apos >= vf.ioSize()) apos = vf.ioId(id);
    if(apos != -1) vf.set(apos, val);
}

string TFuncArgsObj::getStrXML( const string &oid )
{
    string nd("<TFuncArgsObj");
    if(!oid.empty()) nd = nd + " p='" + oid + "'";
    nd = nd + ">\n";
    for(int iIO = 0; vf.func() && iIO < vf.ioSize(); iIO++)
	switch(vf.ioType(iIO)) {
	    case IO::String:	nd += "<str id='"+vf.func()->io(iIO)->id()+"'>"+TSYS::strEncode(vf.getS(iIO),TSYS::Html)+"</str>\n"; break;
	    case IO::Integer:	nd += "<int id='"+vf.func()->io(iIO)->id()+"'>"+vf.getS(iIO)+"</int>\n"; break;
	    case IO::Real:	nd += "<real id='"+vf.func()->io(iIO)->id()+"'>"+vf.getS(iIO)+"</real>\n"; break;
	    case IO::Boolean:	nd += "<bool id='"+vf.func()->io(iIO)->id()+"'>"+vf.getS(iIO)+"</bool>\n"; break;
	    case IO::Object:	nd += vf.getO(iIO).at().getStrXML(vf.func()->io(iIO)->id()); break;
	}
    nd += "</TFuncArgsObj>\n";

    return nd;
}
