//configuration files for sessions and algorithms/collections

#include "CXEVCommunication.h" // the visitor analyzing the document trees built
#include "CCommunicationHandler.h"
#include <string>
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <stdlib.h>
#include "mrml_const.h" //mrml string constants newStart/EndMRMLElement

extern string gGIFTHome;// this variable contains the directory which 
                        //contains the configuration of the GIFT
extern bool asyncReadChar(int,
			  char*);
extern bool sendMessage(int,
			string,
			ostream&);

void newStartMRMLElement(void *inUserData, 
			 const char *inElementName, 
			 const char **inAttributes){

  cout << "STARTING:" << inElementName << endl;

  CXMLElement* lDocumentTree=(CXMLElement*)inUserData;
  lDocumentTree->addChild(inElementName,
			  inAttributes);
}
void newMRMLTextElement(void *inUserData, 
			const XML_Char *inText,
			const int inSize){

  CSelfDestroyPointer<char> lBuffer((char*)operator new(inSize+1));
  
  strncpy(lBuffer,inText,inSize);
  lBuffer[inSize]=(char)0;

  string lText(lBuffer);

  cout << inSize << ":--------------------TEXT_" << lText << "_" << endl;
  

//   bool lWhitespaceOnly(true);
//   for(char* i(lBuffer);
//       i!=((char*)lBuffer)+inSize;
//       i++){
//     if((*i!=' ')
//        && (*i!='\n')
//        && (*i!='\t')){
//       lWhitespaceOnly=false;
//     }
//   }


  //  if(!lWhitespaceOnly){
  CXMLElement* lDocumentTree=(CXMLElement*)inUserData;
  lDocumentTree->addChild(new CXMLElement(CXMLElement::cTextNode,
					  (char*)lBuffer));
  lDocumentTree->moveUp();
  //}else{
  //cout << "rejected: WHITESPACE ONLY"
  //     << endl;
  //}
}
void newEndMRMLElement(void *inUserData, 
		       const char *inElementName){
  cout << "ENDING:" << inElementName << endl;
  CXMLElement* lDocumentTree=(CXMLElement*)inUserData;

  lDocumentTree->moveUp();
  if(string(inElementName)==mrml_const::mrml){
    lDocumentTree->moveUp();
  }
}

//----------------------------------------
//communications:
//----------------------------------------
/// setting the communication socket for this session
void CCommunicationHandler::setSocket(int inSocket){
  mSocket=inSocket;
}
//----------------------------------------
// helpers for creating mrml messages
//----------------------------------------
//the preamble for a session
string CCommunicationHandler::preamble(){
  //ZORAN: I had to change this to something that is available all the time.
  //       BTW, this also preoduced a Socket Error, namely a connection refused,
  //       but was not linked to our problems.
  return string("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n "
		//"<!DOCTYPE mrml SYSTEM \"http://isrpc85.epfl.ch/Circus/code/mrml.dtd\">\n"
		//"<!DOCTYPE mrml SYSTEM \"http://isrpc85.epfl.ch/Circus/code/mrml.dtd\">\n"
		);
}


//Frame: this is for all messages the same
string CCommunicationHandler::frame(const string& inSession,
				    const string& inString){
  return string(preamble()+"<mrml session-id=\""+inSession+"\">\n"+inString+"\n</mrml>\n");
}
    
//----------------------------------------
//making attributes out of name/value pairs
string CCommunicationHandler::toAttribute(string inName,
					  string inString){
  return 
    inName
    +string("=\"")
    +inString
    +string("\"");
}
  
string CCommunicationHandler::toAttribute(string inName,
					  int inInt){
    
  char lBuffer[20];
  sprintf(lBuffer,"%d",inInt);
    
  string lString(lBuffer);
  return 
    inName
    +string("\"")
    +lBuffer
    +string("\" ");
}
  
string CCommunicationHandler::toAttribute(string inName,
					  double inFloat){
    
  char lBuffer[20];
  sprintf(lBuffer,"%lf",inFloat);
    
  string lString(lBuffer);
  return 
    inName
    +string("=\"")
    +lBuffer
    +string("\"");
}
// //----------------------------------------
// //turning a relevance level element into a string
// //(obsolete)
// string CCommunicationHandler::stringOfRelevanceLevelElement(const CRelevanceLevel& inRE,
// 							    double inUserRelevance=0.5){
//   return string("<sresultelement "
// 		+string(" ")
// 		+ toAttribute("calculatedsimilarity",inRE.getRelevanceLevel())
// 		+string(" ")
// 		+ toAttribute("imagelocation",inRE.getURL())
// 		+string(" ")
// 		+ toAttribute("userrelevance",inUserRelevance)
// 		+ " />\n");
// }
// string CCommunicationHandler::stringOfRelevanceLevelList(const CRelevanceLevelList& inRLL){
//   string lReturnValue("<sresultelementlist>\n");
    
//   for(CRelevanceLevelList::const_iterator i=inRLL.begin();
//       i!=inRLL.end();
//       i++){
//     lReturnValue+=stringOfRelevanceLevelElement(*i);
//   }
    
//   return lReturnValue+"</sresultelementlist>\n";
// }
  
//----------------------------------------
//Error message
int CCommunicationHandler::sendError(const string& inSession,
				     const string& inMessage){
  

  CXMLElement* lErrorElement=new CXMLElement("error",0);
  
  lErrorElement->addAttribute("message",inMessage);
}
//----------------------------------------
// making a new session
void CCommunicationHandler::openSession(const string& inUserName,
					const string& inSessionName){

  CXMLElement* lOpenedSession(mSessionManager.openSession(inUserName,
							  "",
							  inSessionName));
  //HACK to change the session of the multi response
  if(lOpenedSession){
    string lNewID(lOpenedSession->stringReadAttribute(mrml_const::session_id).second);

    cout << mMultiResponse->stringReadAttribute(mrml_const::session_id).second
	 << "-> new Session ID: " 
	 << lNewID
	 << endl;

    mMultiResponse->addAttribute(string(mrml_const::session_id),
				 lNewID);
    cout << "current state in multi reponse :"
	 << mMultiResponse->stringReadAttribute(mrml_const::session_id).second 
	 << endl;
  }
  addToMultiResponse(lOpenedSession);
}
// renaming the current session
void CCommunicationHandler::renameSession(const string& inSessionID,
					  const string& inName){
  addToMultiResponse(mSessionManager.renameSession(inSessionID,
						   inName));
  //FIXME : here we need to do SOMETHING
}
// renaming the current session
void CCommunicationHandler::deleteSession(const string& inSessionID){
  addToMultiResponse(mSessionManager.deleteSession(inSessionID));
}
// getting the property sheet for a given algorithm
void CCommunicationHandler::getPropertySheet(const string& inSessionID,
					     const string& inAlgorithmID){
  CXMLElement* lPropertySheet(mSessionManager.getPropertySheet(inSessionID,
							       inAlgorithmID));
  if(lPropertySheet){
    addToMultiResponse(lPropertySheet);
  }
}

//----------------------------------------
//the handshake message
int CCommunicationHandler::sendHandshake(const string& inUser){
    
  //at present this is only a dummy

  pair<string,string> 
    lSessionIDHandshakePair=mSessionManager.toXMLHandshake(inUser);

  string& lNewestSession(lSessionIDHandshakePair.first);
  string& lHandshake(lSessionIDHandshakePair.second);

  return sendMessage(mSocket,
		     frame(lNewestSession,
			   lHandshake).c_str(),
		     mLog);
}

//----------------------------------------
void CCommunicationHandler::getSessions(const string& inUser){
    
}
  
//----------------------------------------
void CCommunicationHandler::getCollections(){
  CXMLElement* lCollectionList(mSessionManager.getCollections());
  addToMultiResponse(lCollectionList);
}
//----------------------------------------
void CCommunicationHandler::getAlgorithms(){
  CXMLElement* lAlgorithms(mSessionManager.getAlgorithms());
  addToMultiResponse(lAlgorithms);
}
  
//----------------------------------------
//the result of a query
int CCommunicationHandler::sendResult(const string& inSession,
				      const CXMLElement& inRLL){
  assert("something wrong");
}
  
//----------------------------------------
//random images
int CCommunicationHandler::sendRandomImages(const string& inSession,
					    const string& inAlgorithm,
					    const string& inCollection,
					    const string& inNumberOfImages) {
  
  int lNumberOfImages=atoi(inNumberOfImages.c_str());
  
  CXMLElement* 
    lRLL=mSessionManager.getRandomImages(inSession,
					 inAlgorithm,
					 lNumberOfImages);
  
  cout << "SENDRANDOM" 
       << endl 
       << flush;
  
  addToMultiResponse(lRLL);
  return true;
};
  
  
//----------------------------------------
//setting properties of the query
void CCommunicationHandler::setResultSize(int inResultSize){
  mResultSize=inResultSize;
}

void CCommunicationHandler::setResultCutoff(const string& inCutoff){
  setResultCutoff(atof(inCutoff.c_str()));
}
void CCommunicationHandler::setResultCutoff(double inCutoff){
  mCutoff=inCutoff;
}

void CCommunicationHandler::setCollectionID(const string& inID){
  mCollection=inID;
}

void CCommunicationHandler::setAlgorithmID(const string& inID){
  mAlgorithm=inID;
}

//----------------------------------------
//parse XML using expat
//----------------------------------------
void CCommunicationHandler::parseString(const string& inMessage){
  bool lDone=false;
  do {
    if (!XML_Parse(mParser, 
		   inMessage.c_str(), 
		   inMessage.size(), 
		   lDone)) {
      cerr << "XML ERROR: "
	   << XML_ErrorString(XML_GetErrorCode(mParser))
	   << " at line "
	   << XML_GetCurrentLineNumber(mParser)
	   << endl;
      exit(1);
    }
  } while (!lDone);
}

//----------------------------------------
//parsing from a stream:
//read each character
//parse it
bool mParsingFinished;
void CCommunicationHandler::clearParsingFinished(){
  mParsingFinished=false;
};
void CCommunicationHandler::setParsingFinished(){
  mParsingFinished=true;
};
bool CCommunicationHandler::isParsingFinished()const{
  return mParsingFinished;
};


bool CCommunicationHandler::readAndParse(){
  clearParsingFinished();
  makeParser();
  bool lSuccess=false;
  char lBuffer[20]; 

  string lLogString;

  do {
      

    cout //<< "-" 
      << flush;
#ifdef _DEBUG
#endif 
      
      
    if(asyncReadChar(mSocket, lBuffer)) {
      cout 
	//<< "<"
	<< lBuffer[0] 
	//<< ">"
	<< flush;
#ifdef _DEBUG
#endif 
      lBuffer[1]=0;
      lLogString+=lBuffer;

      if (!XML_Parse(mParser, 
		     lBuffer, 
		     1, 
		     false)) {
	cerr << "XML ERROR: "
	     << XML_ErrorString(XML_GetErrorCode(mParser))
	     << " at line "
	     << XML_GetCurrentLineNumber(mParser)
	     << endl;
	lSuccess=false;
      }else
	lSuccess=true;// i read at least one character
    }else{
      cerr << "Stream broke down!"
	   << XML_ErrorString(XML_GetErrorCode(mParser))
	   << " at line "
	   << XML_GetCurrentLineNumber(mParser)
	   << endl;
      lSuccess=false;
    }	
  } while (lSuccess && 
	   //!isParsingFinished() ex
	   !(mDocumentRoot->isSubtreeFinished())
	   );
  XML_Parse(mParser, 
	    lBuffer, 
	    0, 
	    true);

  {
    ofstream lInLogFile((gGIFTHome
			 +
			 string("/gift-last-in-message.mrml")).c_str());
    lInLogFile << lLogString 
	       << flush
	       << endl;
  }


  if(lSuccess){
    mLog << lLogString 
	 << flush
	 << endl;
  }

  CXEVCommunication lVisitor(this);

  mDocumentRoot->traverse(lVisitor);

  return lSuccess;
}
  

void CCommunicationHandler::makeParser(){
  if(mParser)
    XML_ParserFree(mParser);
  mParser = XML_ParserCreate(NULL);//default encoding
  mDocumentRoot=new CXMLElement("__ROOT__",0);
  XML_SetUserData(mParser,
		  mDocumentRoot);//ex this
  XML_SetElementHandler(mParser, 
			newStartMRMLElement,//ex startMRMLElement
			newEndMRMLElement);//ex  endMRMLElement
  XML_SetCharacterDataHandler(mParser,
 			      newMRMLTextElement);

}



//----------------------------------------
/** 
    Clears the algorithmTree element
*/
//----------------------------------------
void CCommunicationHandler::clearAlgorithmElement(){
  
  mAlgorithmTree=0;
};
//----------------------------------------
/** start of 
    an element in the tree of configured
    algorithms.
*/
//----------------------------------------
void CCommunicationHandler::startAlgorithmElement(const char* inName,
						  const char* const* const inAttributes){
  if(!mAlgorithmTree){
    mAlgorithmTree=new CAlgorithm(inName,inAttributes);
  }else{
    mAlgorithmTree->addChild(inName,inAttributes);
  }
};
//----------------------------------------
/** end of 
    an element in the tree of configured
    algorithms                          */
//----------------------------------------
void CCommunicationHandler::endAlgorithmElement(){
  if(mAlgorithmTree){
    mAlgorithmTree->moveUp();
  }
};
//----------------------------------------
/** 
    clear the pointer to the algorithm tree
*/
//----------------------------------------
void CCommunicationHandler::initAlgorithmElement(){
  mAlgorithmTree=0;
};
//----------------------------------------
/** 
    read the pointer to the algorithm tree
*/
//----------------------------------------
CAlgorithm* CCommunicationHandler::readAlgorithmElement(){
  return mAlgorithmTree;
};

int CCommunicationHandler::getQueryAtRandomCount()const{
  return mQueryAtRandomCount;
}
void CCommunicationHandler::incrementQueryAtRandomCount(){
  mQueryAtRandomCount++;
}

//----------------------------------------
//Using the session manager which is a member
//of this for other purposes
//----------------------------------------
CSessionManager& CCommunicationHandler::getSessionManager(){
  return mSessionManager;
}
//----------------------------------------
/** Start building a tree by successive adding
    of XML elements */
void CCommunicationHandler::startTreeBuilding(const char* inElementName,
					      const char*const*const inAttributes){
  mCurrentTree=new CXMLElement(inElementName,
			       inAttributes);  
};
/** Start building a tree by successive adding
    of XML elements */
void CCommunicationHandler::addToCurrentTree(const char* inElementName,
					     const char*const* inAttributes){
  mCurrentTree->addChild(new CXMLElement(inElementName,
					 inAttributes));  
};
/** 
    move up in the tree
*/
void CCommunicationHandler::moveUpCurrentTree(){
  mCurrentTree->moveUp();
};
/** 
    is this building a tree at present?
*/
bool CCommunicationHandler::isBuildingTree()const{
  return (mCurrentTree && (!mCurrentTree->isSubtreeFinished()));
};
//----------------------------------------
//constructor/destructor
//----------------------------------------
CCommunicationHandler::CCommunicationHandler(const string& inBaseDir):
  mSessionManager(inBaseDir+"/gift-sessions.mrml",
		  inBaseDir+"/gift-config.mrml"),
  mLog(string(inBaseDir+"/gift-log.mrml").c_str(),
       ios::app),
  mQueryAtRandomCount(0),
  mCurrentTree(0){
  mResultSize=0;
  mCutoff=0.0;
  ///Constructing an expat parser
  mParser=0;
  makeParser();
}
CCommunicationHandler::~CCommunicationHandler(){
  //deleting the expat parser
  XML_ParserFree(mParser);
}

/** 
    
    If we process multiple queries which are part of one message,
    we have to first collect the answers from the requests, and then
    send the whole message.
    
    startMultiRequest and endMultiRequest
    
    are the functions which administer this process.
    
    startMultiRequest clears the message which is going to be built.
*/
void CCommunicationHandler::startMultiRequest(const string& inSessionID){
  mMultiResponse=new CXMLElement("mrml",0);
  mMultiResponse->addAttribute("session-id",
			       inSessionID);
};
/** sends the message which has been built*/
void CCommunicationHandler::endMultiRequest(){
  if(mMultiResponse){
    string lMessage;

    mMultiResponse->addAttribute("just-for-test",
				 "and-of-course-for-fun");

    mMultiResponse->toXML(lMessage);

    cout << "endMultiRequest: WRITING: "
	 << lMessage
	 << endl;

    sendMessage(mSocket,
		preamble()+"\n"+lMessage+"\n",
		mLog);
  }
};
/** 
    adds an XMLElement to the multi-response which is built
*/
void CCommunicationHandler::addToMultiResponse(CXMLElement* inElement){

  if(0){
    string lOutString;
    inElement->toXML(lOutString);
    cout << "CCommunicationHandler::addToMultiResponse: adding "
	 << lOutString
	 << endl;
  }

  assert(mMultiResponse);
  mMultiResponse->addChild(inElement);
  mMultiResponse->moveUp();
};



