SUBROUTINE TCI_MERGE( NMER, MERID, OUTID, TBASE, TOFFS, STATUS )
*+
*  Name:
*     TCI_MERGE

*  Purpose:
*     Merges timing information from one timing structure into another

*  Language:
*     Starlink Fortran

*  Invocation:
*     CALL TCI_MERGE( NMER, MERID, OUTID, TBASE, TOFFS, STATUS )

*  Description:
*     Merging timing description consists of the following steps,
*
*     1) Merge the observation start times. The earliest is chosen
*     2) Merge exposure times. These are simply added
*     3) Merge the observation lengths. The merged observation length
*        is the sum of the inputs
*     4) Merge the live time slots

*  Arguments:
*     NMER = INTEGER (given)
*        Number of timing structures to merge
*     MERID[] = INTEGER (given)
*        ADI identifiers of the timing structures to merge
*     OUTID = INTEGER (returned)
*        ADI identifier of the timing structure to merge into
*     TBASE = INTEGER (returned)
*        Number of input which formed reference time
*     TOFFS[] = DOUBLE PRECISION (returned)
*        The timing offset of each input wrt the new timing origin (secs)
*     STATUS = INTEGER (given and returned)
*        The global status.

*  Examples:
*     {routine_example_text}
*        {routine_example_description}

*  Pitfalls:
*     {pitfall_description}...

*  Notes:
*     {routine_notes}...

*  Prior Requirements:
*     {routine_prior_requirements}...

*  Side Effects:
*     {routine_side_effects}...

*  Algorithm:
*     {algorithm_description}...

*  Accuracy:
*     {routine_accuracy}

*  Timing:
*     {routine_timing}

*  External Routines Used:
*     {name_of_facility_or_package}:
*        {routine_used}...

*  Implementation Deficiencies:
*     {routine_deficiencies}...

*  References:
*     TCI Subroutine Guide : http://www.sr.bham.ac.uk/asterix-docs/Programmer/Guides/tci.html

*  Keywords:
*     package:tci, usage:public

*  Copyright:
*     Copyright (C) University of Birmingham, 1995

*  Authors:
*     DJA: David J. Allan (Jet-X, University of Birmingham)
*     {enter_new_authors_here}

*  History:
*     5 Sep 1995 (DJA):
*        Original version.
*     {enter_changes_here}

*  Bugs:
*     {note_any_bugs_here}

*-
      
*  Type Definitions:
      IMPLICIT NONE              ! No implicit typing

*  Global Constants:
      INCLUDE 'SAE_PAR'          ! Standard SAE constants
      INCLUDE 'PRM_PAR'

*  Arguments Given:
      INTEGER			NMER, MERID(*)

*  Arguments Returned:
      INTEGER			TBASE, OUTID
      DOUBLE PRECISION		TOFFS(*)

*  Status:
      INTEGER 			STATUS             	! Global status

*  External References:
      EXTERNAL			TCI_MERGE_BADLIVE
        LOGICAL			TCI_MERGE_BADLIVE

*  Local Variables:
      DOUBLE PRECISION		BASE_TAI		! Base TAI of all i/ps
      DOUBLE PRECISION	        DVAL			! Value from input id
      DOUBLE PRECISION		MJD			! Base date
      DOUBLE PRECISION		OBL			! Max obs length
      DOUBLE PRECISION		TAI			! Base atomic time
      DOUBLE PRECISION		TEFF			! Effective exposure
      DOUBLE PRECISION		TEXP			! Exposure time
      DOUBLE PRECISION		TOFFSET			! Offset for live time

      INTEGER			I			! Loop over datasets
      INTEGER			ILEN			! Input live time len
      INTEGER			IONPTR, IOFPTR, IDPTR	! Input live time data
      INTEGER			IMJD			! Index of earliest MJD
      INTEGER			ITAI			! Index of earliest TAI
      INTEGER			IVAL			! Value from input
      INTEGER			NLSLOT			! Max # o/p live slots
      INTEGER			ONPTR, OFPTR, ODPTR	! Live time workspace
      INTEGER			ONLSLOT			! Actual # o/p live slots
      INTEGER			START			! Copy origin

      LOGICAL			BADLIVE			! Found duff live times
      LOGICAL			MERDUR			! Merge live time dur'n
      LOGICAL			MEREFF			! Merge eff exposure
      LOGICAL			MEREXP			! Merge exposure time
      LOGICAL			MERLIV			! Merge live times
      LOGICAL			MERMJD			! Merge base dates
      LOGICAL			MEROBL			! Merge obs length
      LOGICAL			MERTAI			! Merge base TAIs
      LOGICAL			OK			! Validity check
*.

*  Check inherited global status.
      IF ( STATUS .NE. SAI__OK ) RETURN

*  Initialise
      BASE_TAI = 0D0
      MERDUR = .TRUE.
      MEREXP = .TRUE.
      MEREFF = .TRUE.
      MERLIV = .TRUE.
      MERMJD = .TRUE.
      MEROBL = .TRUE.
      MERTAI = .TRUE.
      TEXP = 0D0
      TEFF = 0D0
      MJD = VAL__MAXD
      TAI = VAL__MAXD
      OBL = 0D0
      NLSLOT = 0

*  Scan inputs checking to see which stuff we'll merge
      DO I = 1, NMER

*    Simple exposure time
        CALL ADI_THERE( MERID(I), 'Exposure', OK, STATUS )
        IF ( OK ) THEN
          CALL ADI_CGET0D( MERID(I), 'Exposure', DVAL, STATUS )
          TEXP = TEXP + DVAL
        ELSE
          MEREXP = .FALSE.
        END IF

*    Effective exposure time
        CALL ADI_THERE( MERID(I), 'EffExposure', OK, STATUS )
        IF ( OK ) THEN
          CALL ADI_CGET0D( MERID(I), 'EffExposure', DVAL, STATUS )
          TEFF = TEFF + DVAL
        ELSE
          MEREFF = .FALSE.
        END IF

*    Observation length
        CALL ADI_THERE( MERID(I), 'ObsLength', OK, STATUS )
        IF ( OK ) THEN
          CALL ADI_CGET0D( MERID(I), 'ObsLength', DVAL, STATUS )
          OBL = OBL + DVAL
        ELSE
          MEROBL = .FALSE.
        END IF

*    Base MJD
        CALL ADI_THERE( MERID(I), 'MJDObs', OK, STATUS )
        IF ( OK ) THEN
          CALL ADI_CGET0D( MERID(I), 'MJDObs', DVAL, STATUS )
          IF ( DVAL .LT. MJD ) THEN
            MJD = DVAL
            IMJD = I
          END IF
        ELSE
          MERMJD = .FALSE.
        END IF

*    Base TAI
        CALL ADI_THERE( MERID(I), 'TAIObs', OK, STATUS )
        IF ( OK ) THEN
          CALL ADI_CGET0D( MERID(I), 'TAIObs', DVAL, STATUS )
          IF ( DVAL .LT. TAI ) THEN
            TAI = DVAL
            ITAI = I
          END IF
        ELSE
          MERTAI = .FALSE.
        END IF

*    Live times
        CALL ADI_THERE( MERID(I), 'LiveOn', OK, STATUS )
        IF ( OK ) THEN
          CALL ADI_CSIZE( MERID(I), 'LiveOn', IVAL, STATUS )
          NLSLOT = NLSLOT + IVAL
        ELSE
          MERLIV = .FALSE.
        END IF
        CALL ADI_THERE( MERID(I), 'LiveDur', OK, STATUS )
        IF ( .NOT. OK ) THEN
          MERDUR = .FALSE.
        END IF
 
      END DO

*  Create new output structure
      CALL ADI_NEW0( 'TimingInfo', OUTID, STATUS )

*  Are absolute times defined?
      IF ( MERMJD .OR. MERTAI ) THEN

*    Consistency check
        IF ( MERMJD .AND. MERTAI ) THEN
          IF ( IMJD .NE. ITAI ) THEN
            CALL MSG_PRNT( 'WARNING : Inconsistent timing data in '/
     :                        /'inputs, ignoring atomic time data' )
            MERTAI = .FALSE.            
          END IF
        ELSE
          ITAI = IMJD
        END IF

*    Write timing origin
        IF ( MERMJD ) THEN
          CALL ADI_CPUT0D( OUTID, 'MJDObs', MJD, STATUS )
        END IF
        IF ( MERTAI ) THEN
          CALL ADI_CPUT0D( OUTID, 'TAIObs', TAI, STATUS )
        ELSE IF ( MERMJD ) THEN
          CALL TCI_MJD2TAI( MJD, TAI )
          CALL ADI_CPUT0D( OUTID, 'TAIObs', TAI, STATUS )
        END IF

*    Calculate timing offsets
        TBASE = ITAI
        BASE_TAI = TAI
        DO I = 1, NMER

*      Get TAI of observation
          IF ( MERMJD ) THEN
            CALL ADI_CGET0D( MERID(I), 'MJDObs', DVAL, STATUS )
            CALL TCI_MJD2TAI( DVAL, TAI )
          ELSE 
            CALL ADI_CGET0D( MERID(I), 'TAIObs', TAI, STATUS )
          END IF

*      Store offset
          TOFFS(I) = (TAI - BASE_TAI)*86400D0

        END DO

      ELSE 

*    Warning 
        IF ( NMER .GT. 1 ) THEN
          CALL MSG_PRNT( 'No absolute time frame available - assuming '/
     :                   /'inputs have same timing origin' )
        END IF

*    Default offsets
        DO I = 1, NMER
          TOFFS(I) = 0D0
        END DO

      END IF

*  Write exposure times
      IF ( MEROBL ) THEN
        CALL ADI_CPUT0D( OUTID, 'ObsLength', OBL, STATUS )
      END IF
      IF ( MEREXP ) THEN
        CALL ADI_CPUT0D( OUTID, 'Exposure', TEXP, STATUS )
      END IF
      IF ( MEREFF ) THEN
        CALL ADI_CPUT0D( OUTID, 'EffExposure', TEFF, STATUS )
      END IF

*  Merge live times
      IF ( MERLIV ) THEN

*    Get workspace for maximum possible number of output slots
        CALL DYN_MAPD( 1, NLSLOT, ONPTR, STATUS )
        CALL DYN_MAPD( 1, NLSLOT, OFPTR, STATUS )
        IF ( MERDUR ) THEN
          CALL DYN_MAPD( 1, NLSLOT, ODPTR, STATUS )
        END IF

*    Loop over inputs
        START = 0
        BADLIVE = .FALSE.
        DO I = 1, NMER

*      Length of this dataset's live time lists
          CALL ADI_CSIZE( MERID(I), 'LiveOn', ILEN, STATUS )

*      Map input live time data
          CALL ADI_CMAPD( MERID(I), 'LiveOn', 'READ', IONPTR, STATUS )
          CALL ADI_CMAPD( MERID(I), 'LiveOff', 'READ', IOFPTR, STATUS )

*        Convert to absolute TAI if ON/OFF and DURATION units are consistent
          TOFFSET = BASE_TAI + TOFFS(I)
          IF ( MERDUR ) THEN

            CALL ADI_CMAPD( MERID(I), 'LiveDur', 'READ', IDPTR, STATUS )

            IF ( I .EQ. 1 ) THEN
              IF ( TCI_MERGE_BADLIVE( %VAL(IONPTR), %VAL(IOFPTR),
     :                                      %VAL(IDPTR) ) ) THEN
                BADLIVE = .TRUE.
                CALL MSG_PRNT( 'Live time ON and OFF units are'/
     :            /' inconsistent with DURATION values in dataset 1' )
                TOFFSET = 0.0D0
              END IF
            END IF
          END IF

          CALL TCI_MERGE_COPYD( START, ILEN, .FALSE., %VAL(IONPTR),
     :                            TOFFSET, %VAL(ONPTR), STATUS )
          CALL TCI_MERGE_COPYD( START, ILEN, .FALSE., %VAL(IOFPTR),
     :                            TOFFSET, %VAL(OFPTR), STATUS )

          IF ( MERDUR ) THEN
            CALL TCI_MERGE_COPYD( START, ILEN, .FALSE., %VAL(IDPTR), 
     :                                     0.0, %VAL(ODPTR), STATUS )
          END IF
          START = START + ILEN

*      Release input data
          CALL ADI_CUNMAP( MERID(I),  'LiveOn', IONPTR, STATUS )
          CALL ADI_CUNMAP( MERID(I),  'LiveOn', IOFPTR, STATUS )
          IF ( MERDUR ) THEN
            CALL ADI_CUNMAP( MERID(I),  'LiveDur', IDPTR, STATUS )
          END IF

        END DO

*    Sort live time components into increasing order of the ON times
*    and subtract off the BASE_TAI of the output dataset
        IF ( BADLIVE ) THEN
          CALL TCI_MERGE_SORT( NLSLOT, MERDUR, %VAL(ONPTR), %VAL(OFPTR),
     :                           %VAL(ODPTR), 0.0D0, ONLSLOT, STATUS )
        ELSE
          CALL TCI_MERGE_SORT( NLSLOT, MERDUR, %VAL(ONPTR), %VAL(OFPTR),
     :                 %VAL(ODPTR), BASE_TAI + TOFFS(TBASE), ONLSLOT, 
     :                 STATUS )
        END IF

*    Write output live times
        CALL ADI_CPUT1D( OUTID, 'LiveOn', ONLSLOT, %VAL(ONPTR), STATUS )
        CALL ADI_CPUT1D( OUTID, 'LiveOff', ONLSLOT, %VAL(OFPTR), 
     :                   STATUS )
        IF ( MERDUR ) THEN
          CALL ADI_CPUT1D( OUTID, 'LiveDur', ONLSLOT, %VAL(ODPTR),
     :                     STATUS )
        END IF

*    Release workspace
        CALL DYN_UNMAP( ONPTR, STATUS )
        CALL DYN_UNMAP( OFPTR, STATUS )
        IF ( MERDUR ) THEN
          CALL DYN_UNMAP( ODPTR, STATUS )
        END IF

      END IF
      
*  Report any errors
      IF ( STATUS .NE. SAI__OK ) CALL AST_REXIT( 'TCI_MERGE', STATUS )

      END



*+  TCI_MERGE_COPYD - Copy from one real vector to another with START & OFFSET.
      SUBROUTINE TCI_MERGE_COPYD( START, LENGTH, USEOFFSET, IN, 
     :                                    OFFSET, OUT, STATUS )
*    Description :
*
*     IN is copied to OUT starting at START, with OFFSET added to the values
*
*    History :
*
*     12 Oct 88 : Original
*
*    Type definitions :
*
      IMPLICIT NONE
*
*    Global constants :
*
      INCLUDE 'SAE_PAR'
*
*    Import :
*
      INTEGER                START                    ! Index value for start of copy
      INTEGER                LENGTH                   ! Length of IN array

      LOGICAL                USEOFFSET

      DOUBLE PRECISION       IN(LENGTH)               ! Array to be copied
      DOUBLE PRECISION       OFFSET                   ! Value to add to IN
*
*    Import-Export :
*
      DOUBLE PRECISION       OUT(*)                   ! Array to be written
*
*    Status :
*
      INTEGER                STATUS
*
*    Local variables :
*
      INTEGER                I                        ! Loop counter
*-

*    Check status
      IF ( STATUS .NE. SAI__OK ) RETURN

      IF ( USEOFFSET ) THEN
        DO I = 1, LENGTH
          OUT(START+I) = IN(I) + OFFSET
        END DO
      ELSE
        DO I = 1, LENGTH
          OUT(START+I) = IN(I)
        END DO
      END IF

      END



*+  TCI_MERGE_SORT - Sorts LIVE_TIME components using ON values
      SUBROUTINE TCI_MERGE_SORT( N, DUROK, ON, OFF, DUR, BASE_TAI,
     :                                             NDIFF, STATUS )
*
*    Method  :
*
*     After the sort, identical live time slots are removed.
*
*    Author :
*
*     David J. Allan (BHVAD::DJA)
*
*    History :
*
*     19 Feb 91 : Original
*
*    Type declarations :
*
      IMPLICIT NONE
*
      INTEGER	       STATUS		      ! Run-time error code
*
*    Global constants :
*
      INCLUDE 'SAE_PAR'
*
*    Import :
*
      INTEGER          N                      ! Number of data points
      LOGICAL          DUROK                  ! Duration OK?
      DOUBLE PRECISION BASE_TAI               ! New base time
*
*    Import/Export :
*
      DOUBLE PRECISION ON(N), OFF(N), DUR(N)  ! The data to sort
*
*    Export :
*
      INTEGER          NDIFF                  ! Number of different live time
*
*    Local variables :
*
      DOUBLE PRECISION SWAPON,SWAPOFF,SWAPDUR ! Temporary data values

      INTEGER          I,J,L,IR               !

      LOGICAL          SAME                   ! Slots the same?
*-

*    Check status
      IF ( STATUS .NE. SAI__OK ) RETURN

*    Initialise
      NDIFF = 1
      L = N/2+1
      IR = N
      IF ( N .EQ. 1 ) GOTO 50

*    Sort into ascending time order
 10   CONTINUE
         IF ( L .GT. 1 ) THEN
            L = L - 1
            SWAPON = ON(L)
            SWAPOFF = OFF(L)
            IF ( DUROK ) SWAPDUR = DUR(L)

         ELSE
            SWAPON = ON(IR)
            SWAPOFF = OFF(IR)
            ON(IR) = ON(1)
            OFF(IR) = OFF(1)
            IF ( DUROK ) THEN
               SWAPDUR = DUR(IR)
               DUR(IR) = DUR(1)
            END IF
            IR = IR - 1
            IF ( IR .EQ. 1 ) THEN
               ON(1) = SWAPON
               OFF(1) = SWAPOFF
               IF ( DUROK ) DUR(1) = SWAPDUR
               GOTO 30
            END IF
         END IF
         I = L
         J = L + L
 20      IF ( J .LE. IR ) THEN
            IF ( J .LT. IR ) THEN
               IF ( ON(J) .LT. ON(J+1) ) J = J + 1
            END IF
            IF ( SWAPON .LT. ON(J)) THEN
               ON(I) = ON(J)
               OFF(I) = OFF(J)
               IF ( DUROK ) DUR(I) = DUR(J)
               I = J
               J = J + J
            ELSE
               J = IR + 1
            END IF
            GOTO 20
         END IF
         ON(I) = SWAPON
         OFF(I) = SWAPOFF
         IF ( DUROK ) DUR(I) = SWAPDUR
      GOTO 10

*    Remove identical slots
 30   NDIFF = 1
      DO I = 2, N

*       Compare with last slot
         IF ( DUROK ) THEN
            SAME = ( ON(I) .EQ. ON(NDIFF) ) .AND.
     :             ( OFF(I) .EQ. OFF(NDIFF) ) .AND.
     :             ( DUR(I) .EQ. DUR(NDIFF) )
         ELSE
            SAME = ( ON(I) .EQ. ON(NDIFF) ) .AND.
     :                        ( OFF(I) .EQ. OFF(NDIFF) )
         END IF

*       If different store the slot
         IF ( .NOT. SAME ) THEN
            NDIFF = NDIFF + 1
            IF ( I .NE. NDIFF ) THEN
               ON(NDIFF) = ON(I)
               OFF(NDIFF) = OFF(I)
               IF ( DUROK ) DUR(NDIFF) = DUR(I)
            END IF
         END IF

      END DO

*    Adjust times to offsets from BASE_TAI
 50   DO I = 1, NDIFF
        ON(I) = ON(I) - BASE_TAI
        OFF(I) = OFF(I) - BASE_TAI
      END DO

      END



*+  TCI_MERGE_BADLIVE - Are live time units up the creek
      LOGICALFUNCTION TCI_MERGE_BADLIVE( ON, OFF, DUR )
*    Description :
*
*     If the duration as derived by OFF-ON is within 1% of DUR corrected
*     to days, then the ON and OFF times are in days and therefore suspect.
*
*    Authors :
*
*     David J. Allan (BHVAD::DJA)
*
*    History :
*
*     21 Feb 91 : Original
*
*    Type definitions :
*
      IMPLICIT NONE
*
*    Import :
*
      DOUBLE PRECISION         ON,OFF,DUR
*-

      TCI_MERGE_BADLIVE = ( ABS((OFF-ON)*86400.0D0/DUR-1.0) .LT. 0.01 )

      END