/*************************************************************************
  FILE:  BddHiscProject.cpp
  DESCR: Processing Project file (.prj)
  AUTH:  Raoguang Song
  Supervisor: Dr. Ryan Leduc
  DATE:  (C) Jan, 2006
*************************************************************************/
#include "BddHiscProject.h"
#include <fstream>
#include "BddHiscErrmsg.h"
#include "BddHiscType.h"
#include "BddHiscPubfunc.h"
#include "BddHiscHighSub.h"
#include "BddHiscLowSub.h"
#include <iostream>
#include <string>
#include <cassert>
#include <cstdlib>

#include <QString>


#include "BddHisc.h"


#include "DesProject.h"
#include "ProjectEvent.h"
#include "DesTypes.h"
//#include "DesFlatProject.h"
#include "DesHierProject.h"
#include "Des.h"



using namespace std;

namespace BDDHISC
{

/**
 * DESCR:   Constructor
 * PARA:    None
 * RETURN:  None
 * ACCESS:  public
 */
CProject::CProject()
{
    InitPrj();
}

/**
 * DESCR:   Destructor
 * PARA:    None
 * RETURN:  None
 * ACCESS:  public
 */
CProject::~CProject()
{
    ClearPrj();
}

/**
 * DESCR:   Initialize data members
 * PARA:    None
 * RETURN:  None
 * ACCESS:  public
 */
void CProject::InitPrj()
{
    m_sPrjName.clear();
    m_iNumofLows = 0;
    
    m_pHighSub = NULL;
    m_pLowSub = NULL;

    m_AllEventsMap.clear();
    m_InvAllEventsMap.clear();
    
    m_iErrCode = 0;
    m_sErrMsg.clear();
}

/**
 * DESCR:   Release memory used by data members
 * PARA:    None
 * RETURN:  None
 * ACCESS:  public
 */
void CProject::ClearPrj()
{
    //can't change the order of deleting highsub and lowsub, see ~CSub()
    delete m_pHighSub;   
    m_pHighSub = NULL;
    
    if (m_pLowSub != NULL)
    {
        for (int i = 0; i < m_iNumofLows; i++)
        {
            delete m_pLowSub[i];
            m_pLowSub[i] = NULL;
        }
    }
    delete[] m_pLowSub;
    m_pLowSub = NULL;
}
 
/**
 * DESCR:   Load a project file
 * PARA:    vsPrjFile: project file name with path
 *          DESpotProj: HISC project in DESpot format
 * RETURN:  None
 * ACCESS:  public
 */
  void CProject::LoadPrj(DESpot::DesProject& DESpotProj)
{

   
  if (!DESpotProj.isValid()) 
    {
      close_hisc();
       throw EX("Project integrity test must pass before running BDD algorithms.");
    }


  //  this must never be called for a non hierarchical project.
  if(!(DESpotProj.getType() == DESpot::eHierProject))
    {
      close_hisc(); 
      throw EX("Can only run BddHisc algorithms on HISC projects.");
    }

  
  // bddhisc has a bug that it requires all uncontrollable events to
  // belong to at least one plant component. We need to ensure this.

  {
    DESpot::DesProject::EventIteratorPtr  PeventIt = DESpotProj.createProjEventIterator();
    DESpot::DesSubsystem::DesIteratorPtr PdesIt = DESpotProj.createDesIterator(DESpot::ePlantDes);
    bool uevFound = false;

      for(PeventIt->first(); PeventIt->isDone() == false; PeventIt->next())
	{
	  const DESpot::ProjectEvent& event = PeventIt->currentItem();

          /*   //Commented out to test LD support

	  // add check to exclude low  data events for now as BddHisc
	  // does not support this for verification.  Will still need
	  // for synthesis.
	  if (event.getType() == DESpot::eLDataEvent)
	    {
	      close_hisc();
	      std::wstring message = L"BDD algorithms do not currently support projects with Low Data events.";
	      throw message;
	    }

           */

	  if (!(event.isControllable()))
	    {
	      std::wstring eName = event.getName();
	      uevFound = false;

	      // check if in a plant DES
	      for(PdesIt->first(); PdesIt->notDone(); PdesIt->next())
		{
		  DESpot::Des& des = PdesIt->currentItem();
		  const DESpot::DesEvent* event;
		  if (des.findEvent(eName,event))
		    {
		      uevFound = true;
		      break;
		    }
		}

	      if(!uevFound) 
		{
		  close_hisc();
		  std::wstring message = L"BDD algorithms require that all uncontrollable events belong to at least one plant component.  Event \"";
		  message += eName;
		  message += L"\" does not.";
		  throw message;
		}
	    }

	}
  }


  // can I get rid of this try??
  try
    {

      // set project name 
      QString TmpString = QString::fromStdWString(DESpotProj.getName());
      m_sPrjName = TmpString.toStdString();

      //need to find out how many low-levels we have
      DESpot::DesHierProject* hierProject = dynamic_cast<DESpot::DesHierProject*>(&DESpotProj);

      int numLow = hierProject->getSubsystemCount() -1;
      // set the number of low levels for the project
      m_iNumofLows = numLow;

      //assign memory space for Low Sub array
      m_pLowSub = new CLowSub * [m_iNumofLows];
      for (int i = 0; i < m_iNumofLows; i++)
	m_pLowSub[i] = NULL;

      // add check for spaces Process the low levels
	int tmpNumLow = 0;
	DESpot::DesHierProject::SubsysIteratorPtr subsysIt = hierProject->createSubsysIterator();
	for(subsysIt->first(); subsysIt->notDone(); subsysIt->next())
	  {
	    const DESpot::DesSubsystem& lowSubsys = subsysIt->currentItem();
			
	    if (lowSubsys.isRoot() == false)
	      {
	   
		if (tmpNumLow >=m_iNumofLows)
		  {
		    close_hisc();
		    throw EX("Error: more subsystems present than recorded in subsystem count..");
		  }

		QString lowName = QString::fromStdWString(lowSubsys.getName());
		// remove spaces from name
		while (lowName.contains(" "))
		  {
		    lowName.remove(' ');
		  }
                // in Despot, sLowFile is not used anymore
		string sLowFile = lowName.toStdString();
		m_pLowSub[tmpNumLow] = new CLowSub(sLowFile, tmpNumLow + 1);
		if (m_pLowSub[tmpNumLow]->LoadSub(lowSubsys) < 0)
		  throw -1;
		tmpNumLow++;
	      }
	  }


	// Get the highlevel
	const DESpot::DesSubsystem& highSubsys = DESpotProj.getRootSubsys();
	// get high subsystem name
	QString highName = QString::fromStdWString(highSubsys.getName());

	// sHighFile is not used anymore in DESpot
	string sHighFile =  highName.toStdString();
	m_pHighSub = new CHighSub(sHighFile, 0);
	if (m_pHighSub->LoadSub(highSubsys) < 0)
	  throw -1;

    }
    catch (int iError)
    {
        return;
    }

    return;
}

/**
 * DESCR:   Set error msg and err code in this project
 * PARA:    vsvsErrMsg: Error message
 *          viErrCode: Error Code
 * RETURN:  None
 * ACCESS:  public
 */
void CProject::SetErr(const string & vsErrMsg, const int viErrCode)
{
    m_iErrCode = viErrCode;
    m_sErrMsg = vsErrMsg;
#ifdef  VERBOSE
    cout << "Error: " << m_sErrMsg << endl;
#endif
    return;
}

/**
 * DESCR:   Clear error msg and err code in this project
 * PARA:    None
 * RETURN:  None
 * ACCESS:  public
 */
void CProject::ClearErr()
{
    m_iErrCode = 0;
    m_sErrMsg.empty();
    return;
}

/*
 * DESCR:   Generate a sub file name with path (*.sub) from a prj file name 
 *          with path (.prj) and a sub file name without path.
 *          ex: vsSubFile = "/home/roger/sim.prj", vsDES = vsSubDir = "low1",
 *              will return "/home/roger/sim/abc/low1.sub"
 * PARA:    vsPrjFile: prj file name with path
 *          vsSubDir: sub file name without path
 * RETURN:  Generated sub file name with path
 * ACCESS:  private
 */
string CProject::GetSubFileFromPrjFile(const string & vsPrjFile, 
                                        const string &vsSubDir)
{
    assert(vsPrjFile.length() > 4);
    assert(vsPrjFile.substr(vsPrjFile.length() - 4) == ".prj");
    assert(vsSubDir.length() > 0);
    
    string sSubFile = vsSubDir + "/" + vsSubDir + ".sub";
    
    unsigned long iPos = vsPrjFile.find_last_of('/');
    
    if ( iPos == string::npos)
        return sSubFile;
    else
        return vsPrjFile.substr(0, iPos + 1) + sSubFile;
}

/**
 * DESCR:   Get level i  pointer.
 * PARA:    iSubIndex: level index (0: high-level, 1, 2,...: low-levels)
 * RETURN:  Level-i pointer
 * ACCESS:  public
 */
CSub* CProject::GetSub(const int iSubIndex)
{
    assert(iSubIndex <= m_iNumofLows);
    
    if (iSubIndex == 0)
    {
        assert(m_pHighSub != NULL);
        return m_pHighSub;
    }
    else
    {
        assert(m_pLowSub != NULL);
        assert(m_pLowSub[iSubIndex - 1] != NULL);
        return m_pLowSub[iSubIndex - 1];
    }
}

/**
 * DESCR:   Generate global event index from the event info in para 
 * PARA:    vEventSub(H/R/A/L/D, the first 4 bits), (input)
 *          viSubIndex(Sub index, highsub = 0, low sub start from 1. 
 *                     Next 12 bits), (input)
 *          vusiLocalEventIndex(local event index, odd: controllable, 
 *                              even:uncontrollab. The rest 16 bits) (input)
 * RETURN:  Generated global event index
 * ACCESS:  public
 */
int CProject::GenEventIndex(const EVENTSUB vEventSub, const int viSubIndex, 
                    const unsigned short vusiLocalEventIndex)
{
    assert(viSubIndex >= 0 && viSubIndex <= 4096);
    
    int iEventIndex = vEventSub;
    iEventIndex = iEventIndex << 28;
    
    int iSubIndex = 0;
    iSubIndex = viSubIndex;
    iSubIndex = iSubIndex << 16;
    iEventIndex += iSubIndex;
    
    iEventIndex += vusiLocalEventIndex;
    
    return iEventIndex;
}

/**
 * DESCR:   Generate event type(H/R/A/L/D), sub index and local event index from
 * global event index.
 * PARA:    viEventIndex: global event index (input)
 *          EventSub: H/R/A/L, see type.h (output) 
 *          viSubIndex(Sub index, highsub = 0, low sub start from 1) (output)
 *          vusiLocalEventIndex(local event index, odd: controllable, 
 *                              even:uncontrollable) (output)
 * RETURN:  Generated global event index
 * ACCESS:  public
 */
EVENTSUB CProject::GenEventInfo(const int viEventIndex, EVENTSUB & vEventSub, 
                            int & viSubIndex, 
                            unsigned short & vusiLocalEventIndex)
{
    int iEventSub = viEventIndex >> 28;
    assert(iEventSub <= 4);
    
    vEventSub = (EVENTSUB)iEventSub;
    
    viSubIndex = (viEventIndex & 0x0FFF0000) >> 16;
    vusiLocalEventIndex = (viEventIndex & 0x0000FFFF);
    
    return (EVENTSUB)iEventSub;
}

/*
 * DESCR:   Add an event to CProject event map
 *          If the event exists already exists in the map, the  it should have 
 *          same global index;  Otherwise the event sets are not disjoint
 * PARA:    vsEventName: Event name(input)
 *          viEventIndex: global event index (input)
 *          cEventSub: Event type ('H", 'L', 'R', 'A')
 *                     (output, only for new events)
 *          cControllable: Controllable? ('Y', 'N')(output)(only for new events)
 * RETURN:  0: success
 *          <0 the event sets are not disjoint.
 * ACCESS:  public
 */
int CProject::AddPrjEvent(const string & vsEventName, const int viEventIndex,
                          EVENTSUB &vEventSub, int & viSubIndex) 
{
    EVENTS::const_iterator citer;
    
    citer = m_AllEventsMap.find(vsEventName);
         
    if (citer != m_AllEventsMap.end())  //the event exists, check if the global 
                                        //event index is same.
    {
        if (citer->second != viEventIndex)
        {
            vEventSub = (EVENTSUB)(citer->second >> 28);
            viSubIndex = (citer->second & 0x0FFF0000) >> 16;
            return -1;
        }
    }
    else  //the event does not exist
    {
        m_AllEventsMap[vsEventName] = viEventIndex;
        m_InvAllEventsMap[viEventIndex] = vsEventName;
    }
    
    return 0;
}

/*
 * DESCR:   Search an event by its name
 * PARA:    vsEventName: Event name(input)
 * RETURN:  >0: Gloable event index
 *          <0: not found
 * ACCESS:  public
 */
int CProject::SearchPrjEvent(const string & vsEventName)
{
    EVENTS::const_iterator citer;
    
    citer = m_AllEventsMap.find(vsEventName);
         
    if (citer != m_AllEventsMap.end())  //the event exists
        return citer->second;
    else  //the event does not exist
        return -1;
}

/**
 * DESCR:   Save prj info in memory to a file (for checking)
 * PARA:    fout: output file stream
 * RETURN:  0: sucess -1: fail
 * ACCESS:  public
 */
int CProject::PrintPrj(ofstream& fout)
{
    try
    {
        fout << "# Project File." << endl << endl;
        
        fout << "[SYSTEM]" << endl;
        fout << m_sPrjName << endl;
        fout << endl;
        
        fout << "[HIGH]" << endl;
        fout << m_pHighSub->GetSubName() << endl;
        fout << endl;
        
        fout << "[LOW]" << endl;
        fout << m_iNumofLows << endl;
        for (int i = 0; i < m_iNumofLows; i++)
        {
            fout << m_pLowSub[i]->GetSubName() << endl;
        }
        fout << "################################################" << endl;
    }
    catch(...)
    {
        return -1;
    }
    return 0;
}

/**
 * DESCR:   Save all the sub files in this project to a text file for checking
 * PARA:    sFileName: output file name
 * RETURN:  0: sucess -1: fail
 * ACCESS:  public
 */
int CProject::PrintPrjAll(string sFileName)
{
    ofstream fout;
    try
    {
        fout.open(sFileName.data());
        if (!fout)
            throw -1;
        PrintPrj(fout);
        if (m_pHighSub->PrintSubAll(fout) < 0)
            throw -1;
        for (int i = 0; i < m_iNumofLows; i++)
        {
            if (m_pLowSub[i]->PrintSubAll(fout) < 0)
                throw -1;
        }
        fout.close();
    }
    catch(...)
    {
        if (fout.is_open())
            fout.close();
        SetErr(sFileName + ":Unable to create the print file.", 
                HISC_BAD_PRINT_FILE);
        return -1;
    }
    return 0;
}



} //end of namespace BDDHISC



