	FUNCTION WRITE_CIF, START_DATE=START, END_DATE=END_DATE,	$
		OBSERVER=OBSERVER, COMMENT=COMMENT, ERRMSG=ERRMSG,	$
		FILENAME=FILE, SUMMARY=SUMMARY
;+
; Project     :	SOHO - CDS
;
; Name        :	WRITE_CIF()
;
; Purpose     :	Write a Catalog Output File for the SOHO Catalog
;
; Category    :	Class3, Operations, Catalog
;
; Explanation :	Writes a file containing the CDS contribution to the SOHO-wide
;		catalog system.
;
; Syntax      :	Result = WRITE_CIF()
;
; Examples    :	
;
; Inputs      :	None.
;
; Opt. Inputs :	None.
;
; Outputs     :	A file is written to the directory CDS_CIF_W
;
; Opt. Outputs:	None.
;
; Keywords    :	START_DATE = The start date to use.  If not passed, then
;			     yesterday is used.
;
;		END_DATE   = The end date to use.  The default depends on
;			     whether /SUMMARY is used.  The main purpose of the
;			     END_DATE keyword is to be able to limit the size
;			     of the output files when processing large time
;			     ranges.  For example, one could choose to process
;			     the data a month at a time.
;
;		OBSERVER   = Name of observer to place in file header.  If not
;			     passed, then the name of the current account is
;			     used instead.
;
;		COMMENT	   = A string array of comments to place in the header.
;
;		ERRMSG	   = If defined and passed, then any error messages
;			     will
;			     be returned to the user in this parameter rather
;			     than depending on the MESSAGE routine in IDL.  In
;			     order to use this feature, ERRMSG must be defined
;			     first, e.g.
;
;				ERRMSG = ''
;				Result = WRITE_CIF(ERRMSG=ERRMSG, ...)
;				IF ERRMSG NE '' THEN ...
;
;		FILE	   = Returns the name of the output IAP file.
;
;		SUMMARY	   = If set, then a summary file is written out.  This
;			     differs from the regular CIF file in three
;			     important ways:
;
;				1.  Only the MAIN entries are written out.
;				2.  The file is written to a different
;				    directory ($CDS_SUMMARY_DATA_W) with a
;				    different naming convention.
;				3.  The search is done on observation date
;				    rather than modification date.
;
; Calls       :	GET_UTC, ANYTIM2UTC, CONCAT_DIR, LIST_MAIN, GET_STUDY,
;		LIST_EXPER, LOAD_WAVECAL, GET_DATAWIN, LIST_EXP_COMM
;
; Common      :	None.
;
; Restrictions:	The environment variable CDS_CIF_W must be defined.
;
; Side effects:	None.
;
; Prev. Hist. :	None.
;
; History     :	Version 1, 03-Apr-1996, William Thompson, GSFC
;		Version 2, 09-Apr-1996, William Thompson, GSFC
;			Removed unused keyword.  Document all keywords.
;		Version 3, 20-May-1996, William Thompson, GSFC
;			Added /SUMMARY keyword.
;		Version 4, 21-May-1996, William Thompson, GSFC
;			End file with two blank lines
;		Version 4, 10-Mar-1997, William Thompson, GSFC
;			Fixed bug with background windows being included in
;			data window lists.
;		Version 5, 12-Mar-1997, William Thompson, GSFC
;			Fixed bug where CMP_NO was not being written out.
;			Added keyword END_DATE.
;		Version 6, 08-Apr-1997, William Thompson, GSFC
;			Added PUBLIC keyword for certain data.
;		Version 7, 02-Jun-1997, William Thompson, GSFC
;			Scan both current and old CIF directories.
;		Version 8, 12-Sep-1997, William Thompson, GSFC
;			Fix bug introduced in version 7.
;
; Contact     :	WTHOMPSON
;-
;
	ON_ERROR, 2
;
;  Initialize the message to be returned.  Also initialize the RESULT to 0.  If
;  the routine is successful, this parameter will be updated below.
;
	MESSAGE = ''
	RESULT = 0
	YESNO = ['N','Y']
;
;  Get the start and stop times.  If not passed, then use yesterday.  Always
;  deal in entire days.
;
	GET_UTC, UTC
	IF N_ELEMENTS(START) EQ 0 THEN BEGIN
		START_TIME = ANYTIM2UTC(UTC)
		START_TIME.MJD = START_TIME.MJD - 1
	END ELSE BEGIN
		START_TIME = ANYTIM2UTC(START)
	ENDELSE
	START_TIME.TIME = 0
;
	IF N_ELEMENTS(END_DATE) EQ 1 THEN BEGIN
		END_TIME = ANYTIM2UTC(END_DATE)
		END_TIME.MJD = END_TIME.MJD + 1
		END_TIME.TIME = 0
	END ELSE IF KEYWORD_SET(SUMMARY) THEN BEGIN
		END_TIME = START_TIME
		END_TIME.MJD = END_TIME.MJD + 1
	END ELSE BEGIN
		END_TIME = ANYTIM2UTC(UTC)
		IF END_TIME.TIME NE 0 THEN BEGIN
			END_TIME.MJD = END_TIME.MJD + 1
			END_TIME.TIME = 0
		ENDIF
	ENDELSE
;
;  Form the first part of the filename.
;
	IF KEYWORD_SET(SUMMARY) THEN DATE = START_TIME ELSE DATE = UTC
	DATE = ANYTIM2UTC(DATE, /EXTERNAL)
	NAME = 'CDS' + TRIM(DATE.YEAR) +		$
		STRMID(TRIM(100+DATE.MONTH),1,2) +	$
		STRMID(TRIM(100+DATE.DAY),1,2)
;
;  Find all the CIF files with names starting with those characters.  Get the
;  highest version number and add one to it.
;
	IF KEYWORD_SET(SUMMARY) THEN BEGIN
	    PATH = '$CDS_SUMMARY_DATA_W'
	    FOUND = FINDFILE(CONCAT_DIR('$CDS_SUMMARY_DATA_W',		$
		    NAME + '*.CIF'), COUNT=COUNT)
	END ELSE BEGIN
	    PATH = '$CDS_CIF_W'
	    FOUND1 = FINDFILE(CONCAT_DIR('$CDS_CIF_W', NAME + '*.CIF'),	$
		    COUNT=COUNT1)
	    FOUND2 = FINDFILE(CONCAT_DIR('$CDS_OLD_CIF', NAME + '*.CIF'), $
		    COUNT=COUNT2)
	    COUNT = COUNT1 + COUNT2
	    IF COUNT1 EQ 0 THEN BEGIN
		FOUND = FOUND2
	    END ELSE IF COUNT2 EQ 0 THEN BEGIN
		FOUND = FOUND1
	    END ELSE FOUND = [FOUND1, FOUND2]
	ENDELSE
;
	IF COUNT EQ 0 THEN VERSION = 1 ELSE BEGIN
		FOUND = FOUND(SORT(FOUND))
		BREAK_FILE, FOUND(COUNT-1), DISK, DIR, FILENAME, EXT
		VERSION = FIX(STRMID(FILENAME,11,3)) + 1
	ENDELSE
	NAME = NAME + STRMID(TRIM(1000+VERSION),1,3) + '.CIF'
;
;  Open the output file, and write the header.  Use ".CIF" as the default
;  extension.
;
	FILE = CONCAT_DIR(PATH, NAME)
	ON_IOERROR, ERROR_POINT
	OPENW, UNIT, FILE, /GET_LUN
	IF KEYWORD_SET(SUMMARY) THEN DATATYPE = 'SUMMARY DATA' ELSE	$
		DATATYPE = 'CATALOG'
	PRINTF, UNIT, 'DATATYPE = INSTRUMENTER INPUT TO THE SOHO ' + DATATYPE
	PRINTF, UNIT, 'FILENAME = ' + NAME
	PRINTF, UNIT, 'INSTRUME = CDS'
	SPAWN, 'hostname', HOSTNAME
	PRINTF, UNIT, 'ORIG_ID  = ' + GETENV('LOGNAME') + '@' + HOSTNAME
;
;  If OBSERVER was not passed, then use "CDS Staff"
;
	IF N_ELEMENTS(OBSERVER) EQ 0 THEN OBSERVER = 'CDS Staff'
	PRINTF, UNIT, 'OBSERVER = ' + OBSERVER
	GET_UTC, UTC, /ECS, /TRUNCATE
	PRINTF, UNIT, 'DATE_CRE = ' + UTC
	PRINTF, UNIT, 'STARTIME = ' + ANYTIM2UTC(START_TIME, /ECS, /TRUNCATE)
	PRINTF, UNIT, 'ENDTIME  = ' + ANYTIM2UTC(END_TIME,   /ECS, /TRUNCATE)
;
;  If COMMENT was passed, then write out the comments.
;
	FOR I = 0, N_ELEMENTS(COMMENT)-1 DO	$
		PRINTF, UNIT, 'COMMENT  = ' + COMMENT(I)
	PRINTF, UNIT, 'END'
;
;  Find all the entries in the MAIN database that have been modified since the
;  starting date.
;
	IF KEYWORD_SET(SUMMARY) THEN DATE_MOD=0 ELSE DATE_MOD=1
	LIST_MAIN, START_TIME, END_TIME, OBS, N_FOUND, DATE_MOD=DATE_MOD
;
;  For each entry, get the study information.  If the study can't be found,
;  then don't write out the entry.
;
	FOR I = 0, N_FOUND-1 DO BEGIN
	    GET_STUDY, OBS(I).STUDY_ID, OBS(I).STUDYVAR, STUDY
	    IF STUDY.STUDY_ID GE 0 THEN BEGIN
		PRINTF, UNIT, ''
		PRINTF, UNIT, 'MAIN'
		PRINTF, UNIT, '	PROG_NUM = ' + TRIM(OBS(I).PROG_NUM)
		PRINTF, UNIT, '	PROG_ID  = ' + TRIM(OBS(I).PROG_ID)
		PRINTF, UNIT, '	OBS_PROG = ' + TRIM(STUDY.OBS_PROG)
;
;  SCI_OBJ must not be blank.
;
		SCI_OBJ = TRIM(OBS(I).SCI_OBJ)
		IF SCI_OBJ EQ '' THEN SCI_OBJ = 'Unknown'
		PRINTF, UNIT, '	SCI_OBJ  = ' + SCI_OBJ
;
		PRINTF, UNIT, '	SCI_SPEC = ' + TRIM(OBS(I).SCI_SPEC)
		PRINTF, UNIT, '	CMP_NO   = ' + TRIM(OBS(I).CMP_NO>0)
		PRINTF, UNIT, '	OBJECT   = ' + TRIM(OBS(I).OBJECT)
		PRINTF, UNIT, '	OBJ_ID   = ' + TRIM(OBS(I).OBJ_ID)
		PRINTF, UNIT, '	DATE_OBS = ' + TAI2UTC(OBS(I).DATE_OBS, /CCSDS)
		PRINTF, UNIT, '	DATE_END = ' + TAI2UTC(OBS(I).DATE_END, /CCSDS)
		PRINTF, UNIT, '	XCEN     = ' + TRIM(OBS(I).XCEN)
		PRINTF, UNIT, '	YCEN     = ' + TRIM(OBS(I).YCEN)
		PRINTF, UNIT, '	ANGLE    = ' + TRIM(OBS(I).ANGLE)
		PRINTF, UNIT, '	IXWIDTH  = ' + TRIM(OBS(I).IXWIDTH)
		PRINTF, UNIT, '	IYWIDTH  = ' + TRIM(OBS(I).IYWIDTH)
		PRINTF, UNIT, '	SEQ_FROM = ' + TRIM(OBS(I).SEQ_FROM)
		PRINTF, UNIT, '	SEQ_TO   = ' + TRIM(OBS(I).SEQ_TO)
		PRINTF, UNIT, '	COMMENTS = ' + TRIM(OBS(I).COMMENTS)
		PRINTF, UNIT, '	DATE_MOD = ' + TAI2UTC(OBS(I).DATE_MOD, /CCSDS)
	    ENDIF
	ENDFOR
;
;  If the /SUMMARY keyword was set, then we're done.
;
	IF KEYWORD_SET(SUMMARY) THEN GOTO, EXIT_POINT
;
;  Save a list of all data window IDs already written into the file.
;
	DW_IDS = -1
;
;  Find all the entries in the EXPERIMENT database that have been modified
;  since the starting date.
;
	LIST_EXPER, START_TIME, END_TIME, OBS, N_FOUND, /DATE_MOD
;
;  For each entry, get the raster information.  If the raster can't be found,
;  then don't write out the entry.
;
	FOR I = 0, N_FOUND-1 DO BEGIN
	    GET_RASTER, OBS(I).RAS_ID, OBS(I).RAS_VAR, RASTER
	    IF RASTER.RAS_ID GT 0 THEN BEGIN
;
;  Get the data window information, and write out the entries for the data
;  windows.
;
		TEST = LOAD_WAVECAL(OBS(I).DETECTOR, OBS(I).DATE_OBS,	$
			GSET_ID=OBS(I).GSET_ID)
		GET_DATAWIN, RASTER.DW_ID, DW
;
;  Check to see if the data window list has already been written out.  If it
;  has, then don't bother to write it again.
;
		W = WHERE(RASTER.DW_ID EQ DW_IDS, COUNT)
		IF COUNT EQ 0 THEN BEGIN
		    DW_IDS = [DW_IDS, RASTER.DW_ID]
		    FOR J = 0, N_ELEMENTS(DW.WINS)-1 DO BEGIN
			IF STRTRIM(DW.WINS(J).WIN_NAME,2) NE 'Background' $
				THEN BEGIN
			    PRINTF, UNIT, ''
			    PRINTF, UNIT, 'LINELIST'
			    PRINTF, UNIT, '	WAVE_ID  = ' + TRIM(DW.DW_ID)
			    PRINTF, UNIT, '	WAVE_IND = ' + TRIM(J+1)
;
;  Calculate the minimum and maximum wavelength values.
;
			    WIN_DEF = DW.WINS(J).WIN_DEF
			    PIX = WIN_DEF(0) + [0, WIN_DEF(2)-1]
			    IF DW.DETECTOR EQ 'N' THEN BEGIN
				BAND = 2 - WIN_DEF(1) / 512
			    END ELSE BEGIN
				BAND = 1 + WIN_DEF(0) / 2048
				PIX = PIX MOD 2048
			    ENDELSE
			    WAVE = PIX2WAVE(DW.DETECTOR+TRIM(BAND), PIX)
			    PRINTF, UNIT, '	WAVEMIN  = ' + TRIM(WAVE(0))
			    PRINTF, UNIT, '	WAVEMAX  = ' + TRIM(WAVE(1))
			    PRINTF, UNIT, '	DATE_MOD = ' +	$
				    ANYTIM2UTC(UTC, /CCSDS)
		    	ENDIF
		    ENDFOR
		ENDIF
;
;  Print out the entry for the experiment.
;
		PRINTF, UNIT, ''
		PRINTF, UNIT, 'EXPERIMENT'
		PRINTF, UNIT, '	PROG_NUM = ' + TRIM(OBS(I).PROG_NUM)
		PRINTF, UNIT, '	SEQ_NUM  = ' + TRIM(OBS(I).SEQ_NUM)
		PRINTF, UNIT, '	FILENAME = ' + TRIM(OBS(I).FILENAME)
;
;  OBS_SEQ must not be blank.
;
		OBS_SEQ = TRIM(OBS(I).OBS_SEQ)
		IF OBS_SEQ EQ '' THEN OBS_SEQ = 'R' + TRIM(OBS(I).RAS_ID) + $
			'V' + TRIM(OBS(I).RAS_VAR)
		PRINTF, UNIT, '	OBS_SEQ  = ' + OBS_SEQ
;
;  OBS_MODE must not be blank.
;
		OBS_MODE = TRIM(OBS(I).OBS_MODE)
		IF OBS_MODE EQ '' THEN OBS_MODE = 'Unknown'
		PRINTF, UNIT, '	OBS_MODE = ' + OBS_MODE
;
		PRINTF, UNIT, '	DATE_OBS = ' + TAI2UTC(OBS(I).DATE_OBS, /CCSDS)
		PRINTF, UNIT, '	DATE_END = ' + TAI2UTC(OBS(I).DATE_END, /CCSDS)
		PRINTF, UNIT, '	COUNT    = ' + TRIM(OBS(I).COUNT)
		PRINTF, UNIT, '	XCEN     = ' + TRIM(OBS(I).XCEN)
		PRINTF, UNIT, '	YCEN     = ' + TRIM(OBS(I).YCEN)
		PRINTF, UNIT, '	ANGLE    = ' + TRIM(OBS(I).ANGLE)
		PRINTF, UNIT, '	IXWIDTH  = ' + TRIM(OBS(I).IXWIDTH)
		PRINTF, UNIT, '	IYWIDTH  = ' + TRIM(OBS(I).IYWIDTH)
		PRINTF, UNIT, '	SEQVALID = ' + YESNO(OBS(I).SEQVALID)
;
;  Expand out the detector to the full name.
;
		IF OBS(I).DETECTOR EQ 'N' THEN DETECTOR = 'NIS' ELSE	$
			DETECTOR = 'GIS'
		PRINTF, UNIT, '	DETECTOR = ' + TRIM(DETECTOR)
;
		PRINTF, UNIT, '	EXPTIME  = ' + TRIM(OBS(I).EXPTIME)
;
;  Determine the number of exposures from the raster definition.
;
		EXPCOUNT = (RASTER.NX > 1) * (RASTER.NY > 1)
		PRINTF, UNIT, '	EXPCOUNT = ' + TRIM(EXPCOUNT)
;
		PRINTF, UNIT, '	WAVE_ID  = ' + TRIM(RASTER.DW_ID)
		PRINTF, UNIT, '	COMMENTS = ' + TRIM(OBS(I).COMMENTS)
		PRINTF, UNIT, '	DATE_MOD = ' + TAI2UTC(OBS(I).DATE_MOD,/CCSDS)
;
;  If the study is either NISAT_S, SPECT_1, or SYNOP_F, then mark the data as
;  being in the public domain.
;
		TEST = TRIM(OBS(I).OBS_PROG)
		IF (TEST EQ 'NISAT_S') OR (TEST EQ 'SPECT_1') OR	$
			(TEST EQ 'SYNOP_F') THEN			$
			PRINTF, UNIT, '	PUBLIC   = Y'
	    ENDIF
	ENDFOR
;
;  List any comments associated with the observations.
;
	LIST_EXP_COMM, START_TIME, COMM, N_FOUND, /DATE_MOD, END_DATE=END_DATE
;
;  Write out the comments one-by-one.
;
	FOR I = 0, N_FOUND-1 DO BEGIN
;
;  For each entry, get the raster information.  If the raster can't be found,
;  then don't write out the entry.
;
	    GET_EXPER, COMM(I).SEQ_NUM, EXPER
	    GET_RASTER, EXPER.RAS_ID, EXPER.RAS_VAR, RASTER
	    IF RASTER.RAS_ID GT 0 THEN BEGIN
		PRINTF, UNIT, ''
		PRINTF, UNIT, 'EXPERIMENT_COMMENT'
		PRINTF, UNIT, '	SEQ_NUM  = ' + TRIM(COMM(I).SEQ_NUM)
		PRINTF, UNIT, '	COMM_NO  = ' + TRIM(COMM(I).COMM_NO)
		PRINTF, UNIT, '	COMMENT  = ' + TRIM(COMM(I).COMMENT)
		PRINTF, UNIT, '	DATE_MOD = ' + TAI2UTC(COMM(I).DATE_MOD,/CCSDS)
	    ENDIF
	ENDFOR
;
;  End the file with two blank lines.
;
	PRINTF, UNIT, ''
	PRINTF, UNIT, ''
;
;  Print out a message stating that the file was written.
;
	FILE_STATUS = FSTAT(UNIT)
	MESSAGE = 'Wrote file ' + FILE_STATUS.NAME
	IF N_ELEMENTS(ERRMSG) EQ 0 THEN PRINT, MESSAGE
	RESULT = 1
	GOTO, EXIT_POINT
;
;  If you reach this point, you've hit an error.
;
ERROR_POINT:
	ON_IOERROR, NULL
	MESSAGE = 'Error writing file ' + FILE
;
;  This is the error handling point, where the message is either printed
;  immediately, or returned as a keyword.
;
HANDLE_ERROR:
	IF N_ELEMENTS(ERRMSG) EQ 0 THEN MESSAGE, MESSAGE, /CONTINUE
;
;  Whether you hit an error or not, you will reach this point.  Close the file
;  and return.
;
EXIT_POINT:
	IF N_ELEMENTS(UNIT) NE 0 THEN FREE_LUN, UNIT
	IF N_ELEMENTS(ERRMSG) NE 0 THEN ERRMSG = MESSAGE
	RETURN, RESULT
	END
