/*
 */

/* Class definition */
#include "aimsun_ds.h"
#include "tools.h"

// TODO: reference additional headers your program requires here
#include <process.h>
#include <strsafe.h>
#include <shlwapi.h>
#include <io.h>

//#include <atlbase.h>

#define MAX_PATH_BYTES	(MAX_PATH*sizeof(TCHAR))
#define MAX_EXE_BYTES	(MAX_EXE_PATH*sizeof(TCHAR))

/* Contoller list is hardwired for Aimsun scenario `zlicin_495_601.sce` */
#define NUM_CONTROLLERS	2
const TCHAR * ControllerList[NUM_CONTROLLERS] = { TEXT("K_5_495"), TEXT("K_5_601") };
const TCHAR * WindowStr[NUM_CONTROLLERS]      = { TEXT("els3@5.495"), TEXT("els3@5.601") };
const int IntersectionIDs[] = { 495, 601 }; 
const int NumIntersections = sizeof ( IntersectionIDs );

/* Entrance sections are hardwired for Aimsun scenario `zlicin_495_601.sce` */
const int EntranceSections[] = { 1, 42, 45, 287, 26 };
const int NumEntranceSections = sizeof(EntranceSections)/sizeof(int);

/* Identifiers of sections that Aimsun shall collect statistics for are also
   hardwired for the scenario `zlicin_495_601.sce`. */
const int StatIds[] = {
	1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
	26, 27, 35, 28, 29, 30, 31, 32, 33, 34, 39, 36, 37, 38, 21, 22, 23, 24, 25, 40,
	42, 43, 44, 45, 49, 46, 47, 50, 51, 287, 288, 54, 55, 56, 57, 58, 59, 60, 61,
	62, 63, 64, 65, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77 };
const int NumStatIds = sizeof(StatIds)/sizeof(int);


const TCHAR * PatternsELS3[] = { TEXT("GetramELS3.dll"), TEXT("GetramELS3d.dll") };
const TCHAR * PatternsVGS[] = { TEXT("GetramVGS.dll"), TEXT("GetramVGSd.dll") };

/* Offsets of `dt` components (dt is the vector returned by AimsunDS, it contains composite
   values of all measurements for all intersections in the system). */
const int SignalPlanOffsets[] = {  0, 36 };
const int MeasurementOffsets[]  = {  6, 42 };

AimsunDS::AimsunDS () : DS()
{
  /* Description of the channels of the data vector. */
  Drv = RV ( "{"
    "495_VA  495_VB   495_VC   495_VD   495_VE   495_VF "
    "495_DVA 495_DVB  495_DVA1 495_DVB1 495_DVC  495_DVD  495_DVE  495_DVF  495_DVF1 "
    "495_S1  495_S2   495_S3   495_S4   495_S5   495_S5a "
    "601_VA  601_VB   601_VC   601_VD   601_VE   601_SE "
	"601_DVA 601_DVAa 601_DVB  601_DVBa 601_DVB1 601_DVC  601_DVD  601_DVD1 601_DSE 601_DVE 601_DSE1 601_DVE1 "
    "601_S6  601_S6a  601_S7   601_S8   601_S9   601_S9a "
    "495_Q1 495_Q2 "
    "}",
	"    1,      1,      1,      1,      1,       1,      "
	"    2,      2,      2,      2,      2,       2,      2,      2,      2,"
	"    2,      2,      2,      2,      2,       2,"
	"    1,      1,      1,      1,      1,       1,      "
	"    2,      2,      2,      2,      2,       2,       2,       2,       2,       2,       2,       2,"
	"    2,      2,      2,      2,      2,       2"
	"    1,      1"
	);
  /* Remember the size of the data vector. */
  dtsize = Drv._dsize();

  /* Description of the channels of the input vector. */
  Urv = RV ( "{"
	  "Tc"
	  "495_offset"
	  "495_VA  495_VB   495_VC   495_VD   495_VE   495_VF "
	  "601_offset"
	  "601_VA  601_VB   601_VC   601_VD   601_VE   601_SE "
	  "}" );
  /* Remember the size of the input vector. */
  utsize = Urv._dsize();

  /* Initialise the pointer to received data. */
  p_rsp = NULL;
}

void AimsunDS::from_setting ( const Setting &cfg )
{
	/* Check the `stop time` field in configuration. */
	UI::get ( stopTime, cfg, "stop_time" );

	/* Query the configuration file for the name of the file containsing
	   vehicle entrances. */
	UI::get ( entranceFileName, cfg, "entrances" );
}

void AimsunDS::validate ()
{
	HKEY  hKey;
	TCHAR szBasePath[MAX_PATH];		/**< Base path of the whole application, root of the tree. */
	TCHAR szDllPath[MAX_PATH];		/**< Directory where all DLLs reside. Will be added to PATH. */
	TCHAR szSceDllPath[MAX_PATH];	/**< Temporary storage for DLL path rewritten in the scenario file. */
	TCHAR szSceDir[MAX_PATH];		/**< Directory of the simulated scenario. */
	TCHAR szScePath[MAX_PATH];		/**< Full path to the Aimsun scenario file. The file resides in `szSceDir`. */
	TCHAR szScePathA[MAX_PATH];
	TCHAR szScePathB[MAX_PATH];
	TCHAR szScePathC[MAX_PATH];
	TCHAR szNetPath[MAX_PATH];
	TCHAR szHomeDir[MAX_PATH];		/**< Installation directory of Aimsun 4.2.x */
	TCHAR szELS3Path[MAX_PATH];		/**< Full path of the simulator of ELS3 controller. */
	TCHAR szExePath[MAX_EXE_PATH];	/**< Command line when staring ELS3 controllers. */
	TCHAR szEntrancePath[MAX_PATH];	/**< Points to the CSV file with vehicle entrances for the simulation. */
	DWORD dwBufLen = MAX_PATH_BYTES;
	LONG  res;

	intptr_t hPid;

	int verIndex = 1; //@TODO@: 0 is for release, 1 for debug build of the DLL libraries.

	/* The root directory of the simulator tree is defined externally
	   in "CMakeLists.txt". */
	StringCbCopy ( szBasePath, MAX_PATH_BYTES, BASE_PATH );

	/* Create the path to DLL directory. The path is defined externally
	   in  "CMakeLists.txt".
	   The path will be used to inject the appropriate location of
	   Getram extensions into the selected Aimsun scenario and also
	   to modify PATH variable of executed processes in order to
	   provide access to API DLLs. */
	StringCbCopy ( szDllPath, MAX_PATH_BYTES, DLL_PATH );

	/* Add the DLL directory to the PATH used by this process.
	   The PATH will be passed to all other processes started
	   by this one and hence they will be able to find API
	   DLLs. */
	_addpath ( szDllPath );

#ifdef LATER_OR_NEVER
	GetPrivateProfileString (
		TEXT("paths"),
		TEXT("base"),
		NULL,
		szBasePath,
		MAX_PATH,
		TEXT(".\\simulate.ini")
		);
#endif

	/* Find the location of Aimsun executable. It is stored in registry tree as
	   \\HKEY_LOCAL_MACHINE\SOFTWARE\TSS-Transport Simulation Systems\GETRAM\4.2.0 */
	res = RegOpenKeyEx (
		HKEY_LOCAL_MACHINE,
		TEXT("SOFTWARE\\TSS-Transport Simulation Systems\\GETRAM\\4.2.0"),
		0,
		KEY_QUERY_VALUE,
		&hKey
		);
	if ( res != ERROR_SUCCESS )
	{
		perror ( "Cannot get handle to Aimsun registry entry" );
		return;
	}

	/* The executable location is `HomeDir` key. */
	res = RegQueryValueEx (
		hKey,
		TEXT("HomeDir"),
        NULL, NULL,
		(LPBYTE) szHomeDir,
		&dwBufLen
		);
	RegCloseKey( hKey );
    if (( res != ERROR_SUCCESS ) || ( dwBufLen > MAX_PATH_BYTES ))
	{
		perror ( "Cannot read the Aimsun home directory from registry" );
        return;
	}

	/* Concatenate the home path with the executable name. */
	StringCbCat ( szHomeDir, MAX_PATH_BYTES, TEXT("\\aimsun42.exe") );

	/* Create the version of home path including the quotation marks. */
	StringCbCopy ( szExePath, MAX_EXE_PATH, TEXT("\"") );
	StringCbCat ( szExePath, MAX_EXE_PATH, szHomeDir );
	StringCbCat ( szExePath, MAX_EXE_PATH, TEXT("\"") );

	/* Create the path to ELS3 executable. */
	StringCbCopy ( szELS3Path, MAX_PATH_BYTES, TEXT("\"") );
	StringCbCat ( szELS3Path, MAX_PATH_BYTES, szBasePath );
	StringCbCat ( szELS3Path, MAX_PATH_BYTES, TEXT("\\els3\\els3sample.exe\"") );

	/* Aimsun scenario modification.
	   We have to take care of items that are specified by absolute
	   paths in the scenario file. These items include:
	   - network path (section #NETWORK),
	   - ELS3 Getram extension DLL (section #EXTENSIONS),
	   - VGS Getram extension DLL (section #EXTENSIONS).

	   We will start with constructing the new network path.*/
	StringCbCopy ( szSceDir, MAX_EXE_PATH, szBasePath );
	StringCbCat ( szSceDir, MAX_EXE_PATH, TEXT("\\scenarios\\zlicin_495_601") );
	StringCbCopy ( szNetPath, MAX_EXE_PATH, szSceDir );
	StringCbCat ( szNetPath, MAX_EXE_PATH, TEXT("\\zlicin_495_601") );

	/* Our convention is that the scenario file name corresponds
	   to the network path name. */
	StringCbCopy ( szScePath, MAX_EXE_PATH, szNetPath );
	StringCbCat ( szScePath, MAX_EXE_PATH, TEXT(".sce") );

	/* First modification step: Replace the location of ELS3 Getram
	   extension.
	   Start with constructing full path to the extension DLL.*/
	StringCbCopy ( szSceDllPath, MAX_EXE_PATH, szDllPath );
	StringCbCat ( szSceDllPath, MAX_EXE_PATH, TEXT("\\") );
	StringCbCat ( szSceDllPath, MAX_EXE_PATH, PatternsELS3[verIndex] );
	/* We are not allowed to modify scenario in place (it is part
	   of SVN snapshot and we do not want our local changes to
	   propagate to the trunk with every commit). Therefore we
	   need an alternative scenario name that will be constructed
	   now. */
	StringCbCopy ( szScePathA, MAX_EXE_PATH, szScePath );
	StringCbCat ( szScePathA, MAX_EXE_PATH, TEXT(".a") );
	/* And do the replacements. */
	replace_in_scenario (
		szScePath, szScePathA,
		TEXT("#EXTENSIONS"),
		PatternsELS3,
		2,
		szSceDllPath
		);

	/* Second modification step.
	   Replace the location of VGS Getram extension. */
	StringCbCopy ( szSceDllPath, MAX_EXE_PATH, szDllPath );
	StringCbCat ( szSceDllPath, MAX_EXE_PATH, TEXT("\\") );
	StringCbCat ( szSceDllPath, MAX_EXE_PATH, PatternsVGS[verIndex] );
	StringCbCopy ( szScePathB, MAX_EXE_PATH, szScePath );
	StringCbCat ( szScePathB, MAX_EXE_PATH, TEXT(".b") );
	replace_in_scenario (
		szScePathA, szScePathB,
		TEXT("#EXTENSIONS"),
		PatternsVGS,
		2,
		szSceDllPath
		);

	const TCHAR * pattern_net[] = { TEXT("zlicin_495_601") };

	/* Third modification step.
	   Replace the network path. */
	StringCbCopy ( szScePathC, MAX_EXE_PATH, szScePath );
	StringCbCat ( szScePathC, MAX_EXE_PATH, TEXT(".c") );
	replace_in_scenario (
		szScePathB, szScePathC,
		TEXT("#NETWORK"),
		pattern_net,
		1,
		szNetPath
		);

	/* Fourth modification step.
	   Replace the `stop time` field of RunTime information. */
	replace_stoptime (
		szNetPath,
		stopTime.c_str()
		);

	//???
	//StringCbCat ( szScePath, MAX_EXE_PATH, TEXT("\"") );
	//StringCbCat ( szScePath, MAX_EXE_PATH, TEXT("\"") );

	/* Spawn the process. */
	hPid = _tspawnl (
		_P_NOWAIT,
		szHomeDir,
		szExePath,
		TEXT("-run"),
		szScePathC,
		NULL
		);

	if ( hPid < 0 )
	{
		perror ( "Cannot start Aimsun by _spawnl()" );
		return;
	}

	/* Now that Aimsun is running, we need to start also the controllers. */
	for ( int c = 0 ; c < NUM_CONTROLLERS ; c++ )
	{
		TCHAR szELS3Config[MAX_PATH];
		szELS3Config[0] = 0;
		StringCbCat ( szELS3Config, MAX_PATH_BYTES, TEXT("\"") );
		StringCbCat ( szELS3Config, MAX_PATH_BYTES, szBasePath );
		StringCbCat ( szELS3Config, MAX_PATH_BYTES, TEXT("\\els3\\configs\\") );
		StringCbCat ( szELS3Config, MAX_PATH_BYTES, ControllerList[c] );
		StringCbCat ( szELS3Config, MAX_PATH_BYTES, TEXT(".ini\"") );

		/* Create the command line for the controller. */
		szExePath[0] = 0;
		StringCbCat ( szExePath, MAX_EXE_PATH, TEXT("start \"") );
		StringCbCat ( szExePath, MAX_EXE_PATH, WindowStr[c] );
		StringCbCat ( szExePath, MAX_EXE_PATH, TEXT("\" ") );
		StringCbCat ( szExePath, MAX_EXE_PATH, szELS3Path );
		StringCbCat ( szExePath, MAX_EXE_PATH, TEXT(" -l frm,det,spl") );
		StringCbCat ( szExePath, MAX_EXE_PATH, TEXT(" -c 12.12.2007 00:00:00") );
		StringCbCat ( szExePath, MAX_EXE_PATH, TEXT(" -f ") );
		StringCbCat ( szExePath, MAX_EXE_PATH, szELS3Config );

		/* Spawn the process. */
		_tsystem ( szExePath );
	}

	/* Aaaaggggrrrrrhhhh!
	   C++ is much much faster than Matlab and we have a synchronisation
	   issue with els3 controllers starting too late. */
	Sleep ( 5000 );


	/* Prepare full path for vehicle input data. */
	StringCbCopy ( szEntrancePath, MAX_EXE_PATH, szSceDir );
	StringCbCat  ( szEntrancePath, MAX_EXE_PATH, TEXT("\\") );
	StringCbCat  ( szEntrancePath, MAX_EXE_PATH, entranceFileName.c_str() );

	/* Initialise and start the vehicle input.
	   The function call refers to delay-loaded DLL, so expect possible
	   complainst in case that the DLL canot be found in PATH. */
	initVGS (
		HEADWAY_UNIFORM,
		szEntrancePath,
		TEXT("sect_stats.csv"),
		TEXT("glob_stats.csv")
		);

}

void AimsunDS::initVGS (
	const vgs_headway_mode headway,
	const string &entrancePath,
	const string &sectionStatsPath,
	const string &globalStatsPath
	)
{
    int res;
	
	/* Pass information about entrance sections to VGS API (and so to the
       GetramVGS extension that will be generating vehicles for these
	   entrances). */
    vgs_set_entrance_sections ( EntranceSections, NumEntranceSections );
    
    /* Check the validity of the headway mode. */
    res = vgs_is_valid_headway_mode ( headway );
    if ( !res )
	{
        fprintf ( stderr, "Invalid headway mode '%d'\n", headway );
		exit ( -1 );
	}
    
    /* Specify where to find the CSV with vehicle entrances and which
	   headway mode to use for generating vehicle flows. */
	vgs_generate_vehicles ( entrancePath.c_str(), headway );
    
    /* Pass to VGS API information about section identifiers we want to
       collect statistics for (and VGS will pass it to the GetramVGS
       extension). */ 
	vgs_set_stats_sections ( StatIds, NumStatIds );
    
    /* Specify where to store CSV files with statistical data. */
	vgs_set_stats_file_names ( sectionStatsPath.c_str(), globalStatsPath.c_str() );
    
    /* Specify the maximum queuing speed in kmph. */
    vgs_set_max_qspeed ( 3.6f );
    
    /* Unlock the semaphore blocking Aimsun from execution. */
    vgs_unlock_aimsun();
}

void AimsunDS::getdata ( vec &dt ) const
{
	/* Return the last data. */
	printf ( "\n\tAimsunDS::getdata() starting\n" );

	if ( p_rsp == NULL )
	{
		fprintf ( stderr, "ERROR: AimsunDS::getdata() - no data available yet!\n" );
		return;
	}

	/* Loop over all controllers in the measurement set. */
	for ( int i = 0; i < p_rsp->count; i++)
	{
		det_stats * ds_ptr  = p_rsp->dets + i;
		sp_stats *  sp_ptr  = p_rsp->sps  + i;

		printf ( "\tRealised signal plans:\n" );

		/* Loop over all measurement sets. We will deliver just
		   the last one. */
		for ( int j = 0; j < sp_ptr->count ; j++ )
		{
			sp_stattab * dat_ptr = sp_ptr->splans + j;
			int          pos     = _search_index ( IntersectionIDs, NumIntersections, dat_ptr->its_id );
			int          offset  = SignalPlanOffsets[pos];

			printf ( "\t[%3d] t=%6ds ", j, dat_ptr->global_time );

			/* Copy particular signals. */
			for ( int k = 0 ; k < dat_ptr->num_siggroups ; k++ )
			{
				dt[k+offset] = dat_ptr->green_time[k];
				printf ( "%3d ", dat_ptr->green_time[k] );
			}
			printf ( "\n" );
		}

		printf ( "\n\tDetector data:\n" );

		/* Loop over all mesurement records with intensities and
		   occupancies. */
		for ( int j = 0 ; j < ds_ptr->count ; j++ )
		{
			det_aggreg_table * dat_ptr = ds_ptr->stats + j;
			int  pos     = _search_index ( IntersectionIDs, NumIntersections, dat_ptr->its_id );
			int  moffset = MeasurementOffsets[pos];

			printf ( "\t[%3d] t=%6ds ", j, dat_ptr->global_time );

			for ( int k = 0 ; k < dat_ptr->num_det ; k++ )
			{
				dt[2*k+moffset]   = dat_ptr->dt_intensity[k];
				dt[2*k+moffset+1] = dat_ptr->dt_occupancy[k];
				printf ( "{%2d/%2d} ",
					dat_ptr->dt_intensity[k], dat_ptr->dt_occupancy[k] );
			}
			printf ( "\n" );
		}
	}

	printf ( "\tAimsunDS::getdata() finished\n" );
	cout << dt ;
}


void AimsunDS::write ( vec &ut )
{
    eh_res     sp_res;   /* Result code from write operation. */
	eh_hrdels3 hrdsp[2]; /* Signal plan for two intersections. */
    
    /* Check that the length of the vector is correct. */
    if ( ut.size() != utsize )
    {
      fprintf ( stderr, "Incorrect length of data vector (should be %d)", utsize );
      exit(-1);
    }
    /* Check that the phase lengths sum to correct cycle time. */
    /*
	if ( ut[0]+ut[1]+ut[2] != 80 )
    {
      fprintf ( stderr, "Intersection 495: F1+F2+F3 != Tc (should be 80)" );
      exit(-1);
    }
    if ( ut[3]+ut[4]+ut[5] != 80 )
    {
      fprintf ( stderr, "Intersection 601: F1+F2+F3 != Tc (should be 80)" );
      exit(-1);
    }*/

    hrdsp[0].id = 495;
    hrdsp[0].sp.cycle_time   = int ( ut[0] );
	hrdsp[0].sp.offset       = int ( ut[1] );
	hrdsp[0].sp.phase_len[0] = int ( ut[2] );
    hrdsp[0].sp.phase_len[1] = int ( ut[3] );
    hrdsp[0].sp.phase_len[2] = int ( ut[4] );
    hrdsp[0].sp.phase_len[3] = -1;

    hrdsp[1].id = 601;
    hrdsp[1].sp.cycle_time   = int ( ut[ 0] );
    hrdsp[1].sp.offset       = int ( ut[ 8] );
    hrdsp[1].sp.phase_len[0] = int ( ut[ 9] );
    hrdsp[1].sp.phase_len[1] = int ( ut[10] );
    hrdsp[1].sp.phase_len[2] = int ( ut[11] );
    hrdsp[1].sp.phase_len[3] = -1;

	/* Write new signal plan to the shared memory of active controllers. */
    sp_res = eh_write_hrd_data( hrdsp, 2 );

	if ( sp_res != EH_RES_OK )
	{
		printf ( "eh_write_hrd_data(): " );
		if ( sp_res == EH_RES_WCNT )
		{
			printf ( "Wrong count of ELS3...\n" );
		}
		else if ( sp_res == EH_RES_WRID )
		{
			printf ( "Wrong intersection ID...\n" );
		}
		else
		{
			printf ( "Unknown error [%d] ...\n", sp_res );
		}
	}
}


void AimsunDS::step()
{
	eh_wres      wsp_res;   /* Result code of the wait operation. */
    int          count;		/* Count of measurement sets. */
    
    /* Wait for 100 seconds until data from all ELS3 controllers arrive. */
	wsp_res = eh_wait_on_rsp_complete( 100000 );

	if ( wsp_res != EH_WRES_OK )
	{
		printf ( "eh_wait_on_rsp_complete(): " );

		if ( wsp_res == EH_WRES_TIMEOUT )
		{
			printf ( "Timeout elapsed ...\n" );
		}
		else if ( wsp_res == EH_WRES_EXIT )
		{
			printf ( "Some ELS3 has exited ...\n" );
		}
		else
		{
			printf ( "Unknown error [%d] ...\n", wsp_res );
		}
		exit(-1);
	}

	/* This will copy the detector data out. It is needed by the
	   Matlab HRDS version due to its internal structure - realised
	   signal plans are read asynchronously and later. The value
       of `count` reports the number of data sets that will be
       returned by the call to `eh_read_realised_sp()`. This coount
       will be at least 1 but it may be higher in cases where the
       controller reports measurements in short intervals but we are
       reading them less frequently. */
	count = eh_copy_out_measurements ();

	printf ( "got %d measurements from controllers ...", count );

	/* Read realised signal plan from shared memory. */
	if ( eh_read_realised_sp( &p_rsp ) == EH_RES_OK )
	{
		printf("\nsuccesfully read data from controllers\n" );
	}
	else
	{
		printf(" but got no data ???\n");
	}
}
