
//OpenSCADA module DAQ.System file: da_ups.cpp
/***************************************************************************
 *   Copyright (C) 2014-2025 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 "os_contr.h"
#include "da_ups.h"

using namespace SystemCntr;

//*************************************************
//* UPS                                           *
//*************************************************
UPS::UPS( ) : tTr("Sockets"), nTr("sys_UPS"), reqRes(true)	{ }

UPS::~UPS( )	{ }

void UPS::init( TMdPrm *prm, bool update )
{
    DA::init(prm, update);

    if(!update) {
	TCfg &cSubt = prm->cfg("SUBT");
	cSubt.fld().setDescr(_("UPS"));
	cSubt.fld().setFlg(cSubt.fld().flg()|TFld::SelEdit);
	cSubt.setS("localhost:3493");
    }
}

void UPS::deInit( TMdPrm *prm )
{
    DA::deInit(prm);

    TCfg &cSubt = prm->cfg("SUBT");
    cSubt.fld().setFlg(cSubt.fld().flg()&~(TFld::SelEdit));
}

void UPS::cfgChange( TMdPrm *prm, TCfg &co, const TVariant &pc )
{
    if(co.name() == "SUBT") {
	string dls = upsList(co.getS());
	co.fld().setValues("");
	co.fld().setSelNames(dls);
    }
}

string UPS::upsList( const string &addr )
{
    string rez, host, val, c_el;
    char name[51] = "", dscr[256] = "";

    try {
	MtxAlloc res(reqRes, true);
	if((host=TSYS::strParse(TSYS::strParse(addr,0," "),1,"@")).empty()) host = TSYS::strParse(addr,0," ");
	val = reqUPS(host, "LIST UPS\x0A", (mess_lev()==TMess::Debug)?mod->nodePath():"");
	for(int off = 0, lstSec = false; (c_el=TSYS::strLine(val,0,&off)).size(); )
	    if(c_el == "BEGIN LIST UPS") lstSec = true;
	    else if(c_el == "END LIST UPS")	break;
	    else if(lstSec)
		if(sscanf(c_el.c_str(),"UPS %50s \"%255[^\"]s\"",name,dscr) == 2)
		    rez = rez + name+"@"+host+" ("+dscr+");";
    } catch(TError &err) { /*mess_err(err.cat.c_str(),"%s",err.mess.c_str());*/ }

    return rez;
}

void UPS::getVal( TMdPrm *prm )
{
    vector<string> als;
    try {
	string c_el, addr = TSYS::strParse(prm->cfg("SUBT").getS(),0," ");
	int aOff = 0;
	string UPS = TSYS::strParse(addr, 0, "@", &aOff);
	char var[51] = "", vVal[256] = "";
	if(aOff < (int)addr.size()) {
	    MtxAlloc res(reqRes, true);

	    //Vars list and values obtain
	    string val = reqUPS(addr, "LIST VAR "+UPS+"\x0A", (prm->owner().messLev()==TMess::Debug)?prm->nodePath():"");
	    for(int off = 0, lstSec = false; (c_el=TSYS::strLine(val,0,&off)).size(); )
		if(c_el == "BEGIN LIST VAR "+UPS) lstSec = true;
		else if(c_el == "END LIST VAR "+UPS)	break;
		else if(lstSec) {
		    if(sscanf(c_el.c_str(),"VAR %*s %50s \"%255[^\"]s\"",var,vVal) != 2) continue;
		    string vid = var;
		    string aid = TSYS::strEncode(vid, TSYS::oscdID);
		    string aVal = vVal;
		    if(!prm->vlPresent(aid)) {
			// Description request
			string descr = reqUPS(addr, "GET DESC "+UPS+" "+var+"\x0A", (prm->owner().messLev()==TMess::Debug)?prm->nodePath():"");
			if(sscanf(descr.c_str(),"DESC %*s %*s \"%255[^\"]s\"",vVal) == 1 && strcmp(vVal,"Description unavailable") != 0)
			    descr = vVal;
			else descr = var;
			// Type request
			string stp = reqUPS(addr, "GET TYPE "+UPS+" "+var+"\x0A", (prm->owner().messLev()==TMess::Debug)?prm->nodePath():"");
			string vLen, selLs;
			size_t fPos = 0;
			unsigned flg = (stp.rfind("RW") != string::npos) ? (unsigned)(TVal::DirWrite|TVal::NoSave) : (unsigned)TFld::NoWrite;
			if((fPos=stp.rfind("STRING:")) != string::npos) vLen = atoi(stp.c_str()+fPos+7);
			if(stp.rfind("ENUM") != string::npos) {
			    string enS = reqUPS(addr, "LIST ENUM "+UPS+" "+var+"\x0A", (prm->owner().messLev()==TMess::Debug)?prm->nodePath():"");
			    string enSel;
			    for(int offEn = 0, lstSecEn = false; (enSel=TSYS::strLine(enS,0,&offEn)).size(); )
				if(enSel == "BEGIN LIST ENUM "+UPS+" "+var)	lstSecEn = true;
				else if(enSel == "END LIST ENUM "+UPS+" "+var)	break;
				else if(lstSecEn && sscanf(enSel.c_str(),"ENUM %*s %*s \"%255[^\"]s\"",vVal) == 1) selLs = selLs + vVal + ";";

			    flg |= TFld::Selectable;
			}
			// Create
			((TElem*)prm->daData)->fldAdd(new TFld(aid.c_str(),descr,TFld::String,flg,vLen.c_str(),"",selLs,selLs,vid.c_str()));
		    }
		    prm->vlAt(aid).at().setS(aVal, 0, true);
		    als.push_back(aid);
		}

	    //Commands list obtain
	    val = reqUPS(addr, "LIST CMD "+UPS+"\x0A", (prm->owner().messLev()==TMess::Debug)?prm->nodePath():"");
	    for(int off = 0, lstSec = false; (c_el=TSYS::strLine(val,0,&off)).size(); )
		if(c_el == "BEGIN LIST CMD "+UPS) lstSec = true;
		else if(c_el == "END LIST CMD "+UPS)	break;
		else if(lstSec) {
		    if(sscanf(c_el.c_str(),"CMD %*s %50s",var) != 1) continue;
		    string vid = var;
		    string aid = TSYS::strEncode(vid,TSYS::oscdID);
		    if(!prm->vlPresent(aid)) {
			// Description request
			string descr = reqUPS(addr, "GET CMDDESC "+UPS+" "+vid+"\x0A", (prm->owner().messLev()==TMess::Debug)?prm->nodePath():"");
			if(sscanf(descr.c_str(),"CMDDESC %*s %*s \"%255[^\"]s\"",vVal) == 1 && strcmp(vVal,"Description unavailable") != 0)
			    descr = vVal;
			else descr = var;
			// Create
			((TElem*)prm->daData)->fldAdd(new TFld(aid.c_str(),descr.c_str(),TFld::Boolean,TVal::DirWrite|TVal::NoSave,"","","","",vid.c_str()));
		    }
		    prm->vlAt(aid).at().setB(false, 0, true);
		    als.push_back(aid);
		}
	}
    } catch(TError &err) { /*mess_err(err.cat.c_str(),"%s",err.mess.c_str());*/ }

    if(als.size()) {
	prm->daErr = "";
	//Checking to delete DAQ parameter's attributes
	for(int iP = 0; iP < (int)fldSize(); ++iP) {
	    unsigned iL;
	    for(iL = 0; iL < als.size(); ++iL)
		if(fldAt(iP).name() == als[iL])
		    break;
	    if(iL >= als.size())
		try{ fldDel(iP); --iP; }
		catch(TError &err) { mess_warning(err.cat.c_str(), err.mess.c_str()); }
	}
    }
    else if(!prm->daErr.getVal().size()) {
	prm->setEval();
	prm->daErr = _("10:Device is not available.");
    }
}

void UPS::vlSet( TMdPrm *p, TVal &vo, const TVariant &vl, const TVariant &pvl )
{
    string vId = vo.fld().reserve();
    string addr = TSYS::strParse(p->cfg("SUBT").getS(),0," ");
    int aOff = 0;
    string UPS = TSYS::strParse(addr, 0, "@", &aOff);
    if(vId.empty() || aOff >= (int)addr.size())	return;

    MtxAlloc res(reqRes, true);

    bool OK = true;
    OK = OK && reqUPS(addr,"USERNAME "+p->addPrm("USER")+"\x0A",(p->owner().messLev()==TMess::Debug)?p->nodePath():"").compare(0,2,"OK") == 0;
    OK = OK && reqUPS(addr,"PASSWORD "+p->addPrm("PASS")+"\x0A",(p->owner().messLev()==TMess::Debug)?p->nodePath():"").compare(0,2,"OK") == 0;

    if(OK) {
	//Instant commands
	if(vo.fld().type() == TFld::Boolean) {
	    if(vl.getB()) reqUPS(addr, "INSTCMD "+UPS+" "+vId+"\x0A", (p->owner().messLev()==TMess::Debug)?p->nodePath():"");
	}
	//RW variable set
	else
	    reqUPS(addr, "SET VAR "+UPS+" "+vId+" \""+vl.getS()+"\"\x0A", (p->owner().messLev()==TMess::Debug)?p->nodePath():"");
    }

    OK = OK && reqUPS(addr, "LOGOUT\x0A", (p->owner().messLev()==TMess::Debug)?p->nodePath():"").compare(0,2,"OK") == 0;
}

string UPS::reqUPS( const string &addr, const string &req, const string &debCat )
{
    string val, host;
    char buf[1024];

    if((host=TSYS::strParse(TSYS::strParse(addr,0," "),1,"@")).empty()) host = TSYS::strParse(addr,0," ");

    //Check connect and start
    AutoHD<TTransportOut> tr;
    if(!SYS->transport().at().at(tTr).at().outPresent(nTr)) {
	SYS->transport().at().at(tTr).at().outAdd(nTr, "");
	tr = SYS->transport().at().at(tTr).at().outAt(nTr);
	tr.at().setName(_("UPS"));
	tr.at().setTimings("3:0.1");
	tr.at().modifClr();
    }
    else tr = SYS->transport().at().at(tTr).at().outAt(nTr);

    MtxAlloc resN(tr.at().reqRes(), true);
    if(tr.at().addr() != host) { tr.at().setAddr(host); tr.at().modifClr(); }
    if(!tr.at().startStat()) tr.at().start();

    if(debCat.size()) mess_debug(debCat.c_str(), _("REQ -> '%s'"), req.c_str());

    //Request
    int resp_len = tr.at().messIO(req.data(), req.size(), buf, sizeof(buf));
    val.assign(buf, resp_len);

    //Wait tail
    while(resp_len) {
	try{ resp_len = tr.at().messIO(NULL, 0, buf, sizeof(buf)); } catch(TError &err) { break; }
	val.append(buf, resp_len);
    }

    if(debCat.size()) mess_debug(debCat.c_str(), _("RESP -> '%s'"), val.c_str());

    return val;
}

bool UPS::cntrCmdProc( TMdPrm *p, XMLNode *opt )
{
    if(opt->name() == "info") {
	p->ctrMkNode3("fld",opt,-1,"/prm/cfg/user",_("User"),SEC_RD|SEC_WR, "tp","str");
	p->ctrMkNode3("fld",opt,-1,"/prm/cfg/pass",_("Password"),SEC_RD|SEC_WR, "tp","str");
	return true;
    }

    //Process command to page
    string a_path = opt->attr("path");
    if(a_path == "/prm/cfg/user") {
	if(p->ctrChkNode2(opt,"get",SEC_RD)) opt->setText(p->addPrm("USER"));
	if(p->ctrChkNode2(opt,"set",SEC_WR)) p->setAddPrm("USER", opt->text());
    }
    else if(a_path == "/prm/cfg/pass") {
	if(p->ctrChkNode2(opt,"get",SEC_RD)) opt->setText(string(p->addPrm("PASS").size(),'*'));
	if(p->ctrChkNode2(opt,"set",SEC_WR)) p->setAddPrm("PASS", opt->text());
    }
    else return false;

    return true;
}

void UPS::dList( vector<string> &list, TMdPrm *prm )
{
    string dls = upsList(prm?prm->cfg("SUBT").getS():"localhost:3493"), tVl;
    for(int off = 0; (tVl=TSYS::strParse(dls,0,";",&off)).size() || off < (int)dls.size(); )
	list.push_back(tVl);
}
