/****************************************************************************/
/*																			*/
/*								     WDC.h									*/
/*																			*/
/****************************************************************************/
/****************************************************************************/
/* This is a C language header file that defines the following routines		*/
/* for reading and writing WDC-format data files.							*/
/*																			*/
/*	-------------------  Read data from a WDC-format file	--------------  */
/*																			*/
/* long ReadWDC(char *FileName,NetworkPtr Network,char *StartTimeStr,		*/
/*				 char *EndTimeStr,char *StationList)						*/
/*		FileName		Name of the WDC-file containing the data.			*/
/*						If FileName is nil or zero length then standard		*/
/*						input is used.										*/
/*		Network			Structure into which the data is read.				*/
/*						Network_struct is defined in file MagnData.h.		*/
/*		StartTimeStr	String defining the first time to be read from		*/
/*						the file (String delimited with '\0').				*/
/*						String format : YYMMDDHH. If HH is missing then		*/
/*						00 is assumed. If StartTimeStr is nil or zero   	*/
/*						length then start of file is assumed.				*/
/*		EndTimeStr		String defining the time not to be read anymore 	*/
/*						Format as before YYMMDDHH. If nil or zero length	*/
/*						then data is read until end of file.				*/
/*		StationList		List of stations included in the data. The stations	*/
/*						must be specified with three letter codes (e.g.    	*/
/*						SOR) and must be separeted with a space in the list */
/*						(e.g. 'SOR MAS KEV'). If nil or zero length then 	*/
/*						all stations in the file are be included.			*/
/*																			*/
/*	-------------------  Write data into a WDC-format file	--------------  */
/*																			*/
/* long WriteWDC(char *FileName,NetworkPtr Network,char *StartTimeStr,		*/
/*				  char *EndTimeStr,char *StationList)						*/
/* 		FileName		Name of the WDC file. IF NULL or zero length then	*/
/*						stdout is used.										*/
/*		Network			A pointer to the Network structure whose data is to	*/
/*						be written into a file.								*/
/* 		StartTime		Start date string YYMMDDHHMM. If NULL then same as	*/
/*						the time of the first record of the first station	*/
/*						in the Network data structure.						*/
/* 		EndTime			End date string YYMMDDHHMM. This record is NOT		*/
/*						included anymore. If NULL then data till the end of	*/
/*						available data in Network structure will be written	*/
/*		StationList		String containing the 3-letter ID's of the stations	*/
/*						to be included. If NULL or 0-length then all the	*/
/*						stations will be written. The stations must be		*/
/*						separated by a space in the list (e.g. 'SOR MUO').	*/
/****************************************************************************/
/****************************************************************************/
/*							  Lasse Hakkinen								*/
/*					  Finnish Meteorological Institute						*/
/*						  Department of Geophysics							*/
/*								P.O.Box 503									*/
/*						FIN-00101, Helsinki, Finland						*/
/*						e-mail: Lasse.Hakkinen@fmi.fi						*/
/*						phone : (+358)-9-19294634							*/
/*						fax   : (+358)-9-19294603							*/
/*																			*/
/*						version 1.03		12.2.1998						*/
/****************************************************************************/
/****************************************************************************/
/*	Version history:														*/
/*	1.03 12.02.1998															*/
/*		- If WDC file is in DHZ-format then the unit of D is now 0.1 arcmin	*/
/*		  Previously the D values were read in with 0.01 arcmin accuracy.	*/
/*		  Also when writing WDC files with DHZ representation it is now		*/
/*		  assumed that in memory D is in units of 0.1 arcmin.				*/
/*  1.02 27.07.1996															*/
/*		- Fixed a bug where WriteWDC crashed is there is no data in the		*/
/*		  Network-structure													*/
/*	1.01  5.1.1996															*/
/*		- If WDC-file contained data for four components (e.g. F,X,Y,Z)		*/
/*		  program crashed. This was fixed by skipping all blocks which		*/
/*		  contain F component. So only three components are read.			*/
/*																			*/
/*	1.0  13.11.1995  First release											*/
/****************************************************************************/


#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define WDCstep 60			/* One-minute values. No other possibilities !	*/
#define MissingWDC 99999	/* Marker for missing data value (usually !)	*/

/*--------------------------------------------------------------------------*/
/* Define the offsets of various fields from the start of the WDC block.	*/
/* See the definition of IAGA record for the explanation of various fields.	*/
/*--------------------------------------------------------------------------*/

#define WDC_PolarDist	 0
#define WDC_Longitude	 6
#define WDC_Date		12
#define WDC_Component	18
#define WDC_Hour		19
#define WDC_Station		21
#define WDC_Data		34

#define CR 0x0D
#define NL 0x0A

/*--------------------------------------------------------------------------*/
/* Function prototypes														*/
/*--------------------------------------------------------------------------*/

static Time_sec GetWDCTime(char *Buffer);
static int ReadWDCBlock(FILE *WDCfile,char *Buff);
static void SetStationParamsWDC(StationPtr Station,char *Buff);
static long GetWDCFieldValue(char *p);
static void GetWDCData(StationPtr Station, char *Buff);
long ReadWDC(char *FileName,NetworkPtr Network,char *StartTimeStr,
			  char *EndTimeStr,char *StationList);

static void WriteWDCBlock(FILE *DataFile,StationPtr Station,Time_sec Time,
				   char Component);
long WriteWDC(char *FileName,NetworkPtr Network,char *StartTimeStr,
			  char *EndTimeStr,char *StationList);


/*==========================================================================*/
/*				Routines for reading WDC-format files						*/
/*==========================================================================*/


/*--------------------------------------------------------------------------*/
/*	Get the start time in seconds of a 400-character data block. In the		*/
/*  WDC file the date is in format YYMMDDXHH. Where X is the component		*/
/*	character (is there any logic here ?).									*/
/*--------------------------------------------------------------------------*/

static Time_sec GetWDCTime(char *Buffer)
{
	char dummyStr[10];
	
	strncpy(dummyStr,Buffer+WDC_Date,6);
	strncpy(dummyStr+6,Buffer+WDC_Hour,2);
	return (StrToSecs(dummyStr,8));
}


/*--------------------------------------------------------------------------*/
/* Read one block from the WDC-format file into the Buffer. This routine	*/
/* works also for 401 or 402 character blocks (i.e. terminated with			*/
/* linefeed and/or carriage returns).										*/
/* The routine will return 1 if the block was succesfully read, otherwise	*/
/* 0 is returned.															*/
/*--------------------------------------------------------------------------*/

static int ReadWDCBlock(FILE *WDCfile,char *Buffer)
{
	int c;
	
	/* Handle the first character */
	if ((c = getc(WDCfile)) == EOF) return(0);
	if (c < 32) {	/* Linefeed or carriage return */
		if ((c = getc(WDCfile)) == EOF) return(0);
		if (c < 32) {	/* Linefeed or carriage return */
			if ((c = getc(WDCfile)) == EOF) return(0);
		}
	}
	
	*Buffer = c;		/* Put the first char into Buffer */
	
	/* read the rest of the block */
	return(fread(Buffer+1,399,1,WDCfile));
}


/*--------------------------------------------------------------------------*/
/* Get the 6 character number from the Buffer. We could also use sscanf,	*/
/* but this is considerably faster because no checkings are made.			*/
/*--------------------------------------------------------------------------*/

static long GetWDCFieldValue(char *p)
{
	int k;
	int m = 0;
	long value = 0;
	long negative = 0;
	
	/* scan blankos */
	while (*p == ' ') {
		p++;
		m++;
	}
	
	for (k=m;k<6;k++) {
		if (*p == '-') negative = 1;
		else {
			if ((*p >= '0') && (*p <= '9'))
				value = 10*value+(*p-'0');
			else {
				return(MissingValue); /* Some illegal characters */
			}
		}
		p++;
	}

	if (negative) value = -value;
	if ((value == MissingWDC) || (value == 999999)) return(MissingValue);
	return (value);
}



/*--------------------------------------------------------------------------*/
/*	Fill some fields in the Station_struct by copying them from the WDC		*/
/*	buffer. AddNewStation procedure in the MagnData.h file will set other	*/
/*	station parameters.														*/
/*--------------------------------------------------------------------------*/

static void SetStationParamsWDC(StationPtr Station,char *Buff)
{
	long dummy;
	
	/* Set the station ID */
	strncpy(Station->StationID,Buff+WDC_Station,3);
	Station->StationID[3] = '\0';
	
	/* Set the location of the station */
	Station->Longitude = GetWDCFieldValue(Buff+WDC_Longitude)/10;
	Station->Latitude  = 9000 - GetWDCFieldValue(Buff+WDC_PolarDist)/10;

	/* Set the StartTime and TimeStep, GetWDCData-routine will set EndTime */
	Station->StartTime = GetWDCTime(Buff);
	Station->TimeStep  = WDCstep;
}



/*--------------------------------------------------------------------------*/
/* Copy data from the 400 character block into the proper OneDayBlock.		*/
/*--------------------------------------------------------------------------*/

static void GetWDCData(StationPtr Station, char *Buff)
{
	OneDay_struct *p;
	long *DataPtr,*TPtr;
	long i,Count,value;
	char *q;
	char Component;
	
	/*** Find the OneDayBlock which has room for new data ***/
	Component = *(Buff+WDC_Component);
	if (Component == 'E') Component = 'X';	/* Put AE index into X 	*/
	if (Component == 'L') Component = 'Y';	/* Put AL index into Y 	*/
	if (Component == 'U') Component = 'Z';	/* Put AU index into Z 	*/


	p = Station->FirstDay;
	do {
		switch (Component) {
			case 'H' : case 'X' : Count = p->XCount; break;
			case 'D' : case 'Y' : Count = p->YCount; break;
			case 'Z' :			  Count = p->ZCount; break;
		}
		if (Count < 86400L/(Station->TimeStep)) break; /* still room */
		p=p->NextDay;			/* day full was full, check the next one	*/
	}
	while (1);
	
	/* Set the temperature value */
	if ((Component == 'H') || (Component == 'X')) {
		TPtr  = (p->T)+(p->TCount);
		*TPtr = MissingValue;		/* There is no temperature value in WDC */
		p->TCount++;
	}
	
	/* Set the pointers and update counters */
	switch (Component) {
		case 'H' : case 'X' : DataPtr = (p->X)+(p->XCount);
							  p->XCount += 60; break;
		case 'D' : case 'Y' : DataPtr = (p->Y)+(p->YCount);
							  p->YCount += 60; break;
		case 'Z' :			  DataPtr = (p->Z)+(p->ZCount);
							  p->ZCount += 60; break;
	}

	/* Copy the data */
	q = Buff+WDC_Data;
	for (i=0;i<60;i++) {
		value = GetWDCFieldValue(q);
		if (Component == 'D')			/* Unit = 0.1 arcmin */
			*DataPtr++ = value;
		else
		{
			if (value == MissingValue)
				*DataPtr++ = MissingValue;
			else
				*DataPtr++ = 10*value;	/* Convert into 0.1 nT */
		}
		q += 6;
	}
	
	/* Set the last time */
	Station->EndTime = GetWDCTime(Buff)+60*Station->TimeStep;

	/* Set the component characters in the Station->Components */
	switch (Component) {
		case 'H' :	Station->Components[0] = 'H'; break;
		case 'X' :	Station->Components[0] = 'X'; break;
		case 'D' :	Station->Components[1] = 'D'; break;
		case 'Y' :	Station->Components[1] = 'Y'; break;
		case 'Z' :	Station->Components[2] = 'Z'; break;
	}
}


/*--------------------------------------------------------------------------*/
/*	Here is the routine for reading WDC-format data file into memory.		*/
/*	The data is read into a Network structure (see MagnData.h for details).	*/
/*	See the comments at the beginning of this file for more details about	*/
/*	the parameters for this routine.										*/
/*																			*/
/*	The rounite works by reading the data file one block (400 bytes) at a	*/
/*	time and adds that data in the particular stations data block. First	*/
/*	for each new station that is encountered space for one day is allocated.*/
/*	If the day becomes full a new one day block is allocated. At the end of	*/
/*	the routine all one day blocks are combined into a single data block.	*/
/*--------------------------------------------------------------------------*/

long ReadWDC(char *FileName,NetworkPtr Network,char *StartTimeStr,
			  char *EndTimeStr,char *StationList)
{
	FILE	*WDCfile;				/* The data file to be processed		*/
	char	Buffer[400];			/* Buffer for one WDC data block		*/
	Time_sec CurrTime;				/* Time of the current data block   	*/
	Time_sec StartTime;				/* Time of first record to be read		*/
	Time_sec EndTime;				/* Time of record not read anymore		*/
	StationPtr Station;				/* Pointer to the current station		*/


	InitNetwork(Network);			/* Initialize the fields of Network		*/

	/* --- Set the start and end times --- */
	if ((StartTimeStr == NULL) || (*StartTimeStr == '\0'))
		StartTime = MIN_TIME;
	else StartTime = StrToSecs(StartTimeStr,0);

	if ((EndTimeStr == NULL) || (*EndTimeStr == '\0'))
		EndTime = MAX_TIME-(Time_sec)86400;
	else EndTime = StrToSecs(EndTimeStr,0);

	/* --- Try to open the file --- */
	if ((FileName == NULL) || (*FileName == '\0')) WDCfile = stdin;
	else if ((WDCfile = fopen(FileName, "r")) == NULL) return FileError;

	/* --- Read the data into one day blocks ---*/ 
	while (ReadWDCBlock(WDCfile,Buffer) &&	/* 24 hours for each component */
		  ((CurrTime=GetWDCTime(Buffer)) < EndTime+(Time_sec)86400))
	{
	
		/* Change AE,AL,AU and AO index names so that they are not read as individual stations */
		
		if ((Buffer[WDC_Station] == 'A') && (Buffer[WDC_Station+2] == ' ')) {
			Buffer[WDC_Station+1] = 'E';
		}
		
		if ((CurrTime >= StartTime) && (CurrTime < EndTime)
			&& StationInList(Buffer+WDC_Station,StationList)
		    && (Buffer[WDC_Component] != 'F')
		    && (Buffer[WDC_Component] != 'O'))			/* skip F component and AO index */
		{
			Station = FindStation(Network,Buffer+WDC_Station);

			if (Station == NULL) {		/* First occurrence of this station */
				Station = AddNewStation(Network,WDCstep,60*WDCstep);
				if (Station != NULL)
					SetStationParamsWDC(Station,Buffer);
				else 		/* Unable to allocate memory for station data	*/
					return(OutOfMemory);
			}
			
			if (OneDayFull(Station,Buffer[WDC_Component])) {
				if (AddOneDay(Station))
					return(OutOfMemory); 	/* Unable to allocate memory	*/
			}
			GetWDCData(Station,Buffer);
		}
	}
	fclose(WDCfile);

	/* --- Combine one day blocks into one large block ---*/
	Station = Network->StationList;
	while (Station != NULL) {
		if (CombineData(Station) == OutOfMemory) return(OutOfMemory);
		Station = Station->Next;
	}

	return OK;
}


/*==========================================================================*/
/*				Routines for writing WDC-format files						*/
/*==========================================================================*/


/*--------------------------------------------------------------------------*/
/*	Write one 400 character block for given station and for given time		*/
/*	into specified datafile.												*/
/*--------------------------------------------------------------------------*/

static void WriteWDCBlock(FILE *DataFile,StationPtr Station,Time_sec Time,
						  char Component)
{
	long j,Ave;
	char DummyStr[14];
	Time_sec T = Time;
	
	/*** Write station info and time fields  ***/
	fprintf(DataFile,"%6d%6d",
				10*(9000-Station->Latitude),10*Station->Longitude);
	SecsToStr(Time,DummyStr);
/*	
	if (DummyStr[0] == '0') {
		DummyStr[0] = ' ';
		if (DummyStr[1] == '0') {
			DummyStr[1] = ' ';
		}
	}
*/
	fprintf(DataFile,"%.6s",DummyStr);
	fprintf(DataFile,"%c",Component);

/*
	if (DummyStr[6] == '0') {
		DummyStr[6] = ' ';
		if (DummyStr[7] == '0') {
			DummyStr[7] = ' ';
		}
	}
*/
	fprintf(DataFile,"%.2s",DummyStr+6);
	fprintf(DataFile,"%s",Station->StationID);
	fprintf(DataFile,"20D");		/* Mysterious origin character */
	fprintf(DataFile,"       ");	/* 7 spaces */
	
	/*** Write field values ***/
	for (j=0;j<60;j++) {
		Ave = ComputeAverage(Station,Component,T,60);
		if (Ave == MissingValue) Ave = MissingWDC;
		if ((Component == 'D') || (Ave == MissingWDC))		/* Unit = 0.1 arcmin */
			fprintf(DataFile,"% 6d",Ave);
		else
			fprintf(DataFile,"% 6d",RoundFloat(Ave/10.0));	/* Unit = 1 nT */
		T += 60;
	}

	/*** Write hour average ***/
	Ave = ComputeAverage(Station,Component,T-3600,3600);
	if (Ave != MissingValue)
		fprintf(DataFile,"% 6d",RoundFloat(Ave/10.0));
	else
		fprintf(DataFile,"% 6d",MissingWDC);
	
	 /*** The block end characters ***/
	fprintf(DataFile,"%c%c",CR,NL);
}


/*--------------------------------------------------------------------------*/
/*	Here is the routine for writing data into an WDC-format file. See the	*/
/*	comments at the beginning of this file for more details about the 		*/
/*	parameters for this routine.											*/
/*--------------------------------------------------------------------------*/

long WriteWDC(char *FileName,NetworkPtr Network,char *StartTimeStr,
			  char *EndTimeStr,char *StationList)
{
	FILE	*WDCfile;				/* The data file to be processed		*/
	Time_sec Time;					/* Time of the current data block   	*/
	Time_sec Time2;					/* Dummy time variable					*/
	Time_sec StartTime;				/* Time of first record to be read		*/
	Time_sec EndTime;				/* Time of record not read anymore		*/
	long	HourCount;				/* Length of data block to be written	*/
	long	Hour,Comp;				/* Dummy indices						*/
	StationPtr Station;				/* Pointer to the current station		*/
	Time_struct TimeStruct;
	
	/* --- Check there is some data in the Network --- */
	if (Network->StationList == NULL) return FAIL;
	
	/* --- Set the start and end times --- */
	if ((StartTimeStr == NULL) || (*StartTimeStr == '\0'))
		StartTime = Network->StationList[0].StartTime;
	else StartTime = StrToSecs(StartTimeStr,0);

	if ((EndTimeStr == NULL) || (*EndTimeStr == '\0'))
		EndTime = Network->StationList[0].EndTime;
	else EndTime = StrToSecs(EndTimeStr,0);

	/* --- Try to create the file --- */
	if ((FileName == NULL) || (*FileName == '\0')) WDCfile = stdout;
	else if ((WDCfile = fopen(FileName, "w")) == NULL) return FileError;

	/* ------------------- Write the data ------------------*/
	/* Data is written in full day (00UT-24UT) blocks. If 	*/
	/* start hour is not midnight (00UT) then data for all	*/
	/* components and for all stations till 24UT on the		*/
	/* first day will be written. After that full days.		*/
	/* -----------------------------------------------------*/

	Time = StartTime;
	while (Time < EndTime) {
		if ((EndTime-Time)<86400)
			HourCount = (EndTime-Time)/3600;	/* Last day ? */
		else {
			SecsToTm(Time,&TimeStruct);
			HourCount = 24-TimeStruct.tm_hour;
		}
	
		Station = Network->StationList;
		while (Station != NULL) {
			if (StationInList(Station->StationID,StationList)) {
				for (Comp=0;Comp<3;Comp++) {
					Time2 = Time;
					for (Hour=0;Hour<HourCount;Hour++) {
						WriteWDCBlock(WDCfile,Station,Time2,
									  Station->Components[Comp]);
						Time2 += 3600;
					}
				}
			}
			Station = Station->Next;
		}
		Time += HourCount*3600;
	}
	fclose(WDCfile);

	return OK;
}

