!-- gdFilt.f90
!-- 22 Sept 92, Richard Maine: Version 1.0.

module gd_filter

  !-- Filter module for gd_read.
  !-- The main function of this module is managing the lists of
  !-- available and used signals relevant to the filters.
  !-- 21 Oct 91, Richard Maine

  use precision
  use sysdep_io
  use gd_common
  use link_filter

  implicit none
  private

  !-- filter_signal_type has data about a filtered signal.
  type filter_signal_type
    integer :: input_number, output_number
         !-- Get_data signal numbers for the input and output signals.
    logical :: used
  end type

  !-- used_filter_signal_type has data about a used filter signal.
  type used_filter_signal_type
    integer :: input_map, output_map
         !-- maps to the compressed file frame buffers
    real(r_kind) :: input_data, output_data
  end type

  !-- filter_type has data about a filter.
  type filter_type
    logical :: opened  !-- Has the filter connection been opened?
    logical :: filter_used  !-- Is this filter used?
    type(filter_handle_type) :: filter_handle  !-- handle from open_gd_filter.
    type(gd_source_type), pointer :: source
         !-- Generic information about the data source.
    character :: filter_linker*128
    type(filter_signal_type), pointer :: signal(:)
    type(used_filter_signal_type), pointer :: used_signal(:)
    type(filter_type), pointer :: next_filter, prev_filter
         !-- Doubly linked list of filters for a file.
  end type

  type, public :: filter_list_type
    private
    integer :: input_source_number
         !-- Source number of the associated input file.
    type(filter_type), pointer :: first_filter, last_filter
  end type

  !-- Public procedures.
  public init_gd_filter_list, add_gd_filter, close_gd_filters
  public activate_gd_filters, do_gd_filters

  !-- Forward reference for private procedures.
  private release_gd_filter

contains

  subroutine init_gd_filter_list (filter_list, input_source_number)

    !-- Initialize an empty filter_list for gd_read.
    !-- 19 Oct 91, Richard Maine.

    !-------------------- interface.
    type(filter_list_type), intent(out) :: filter_list
    integer, intent(in) :: input_source_number
         !-- Source number of the associated input file.

    !-------------------- executable code.

    nullify(filter_list%first_filter, filter_list%last_filter)
    filter_list%input_source_number = input_source_number
    return
  end subroutine init_gd_filter_list

  subroutine add_gd_filter (filter_list, filter_name, filter_linker, &
       filter_dt, filter_parameters, signal_names, input_signal_numbers, &
       output_offset, interpolate, source_list, signal_list, error)

    !-- Add a filter to the list for a file.
    !-- 21 Oct 91, Richard Maine.

    !-------------------- interface.
    type(filter_list_type), intent(inout) :: filter_list
    character*(*), intent(in) :: filter_name
    character*(*), intent(in), optional :: filter_linker
    real(r_kind), intent(in) :: filter_dt
    real(r_kind), intent(in) :: filter_parameters(:)
         !-- Interpretation of these depends on the filter routines.
         !-- The most common parameter will probably be a break frequency.
    character*(*), intent(in) :: signal_names(:)
         !-- Names of the filter output signals.
    integer, intent(in) :: input_signal_numbers(:)
         !-- Get_data signal numbers for the filter inputs.
         !-- Should not be size 0.
    integer, intent(in) :: output_offset
         !-- Offset in the file signal vector for outputs from this filter.
    logical, intent(in) :: interpolate
         !-- True if signals should default to sync by interpolation.
         !-- False for hold-last-value.
    type(gd_source_list_type) :: source_list
         !-- List of source records for gd_read.
    type(gd_signal_list_type) :: signal_list
         !-- List of signal records for gd_read.
    logical, intent(out) :: error

    !-------------------- local.
    integer :: i, iostat, n_signals
    character :: where*8, description*80
    type(filter_type), pointer :: filter
    type(gd_signal_type), pointer :: gd_signal

    !-------------------- executable code.

    !-- Allocate a new filter record.
    where = 'allocate'
    nullify(filter)
    allocate(filter, stat=iostat)
    if (iostat /= 0) goto 8000
    filter%opened = .false.
    filter%filter_used = .false.

    !-- Allocate and define the signal record.
    n_signals = size(signal_names)
    nullify(filter%signal, filter%used_signal)
    allocate(filter%signal(n_signals), stat=iostat)
    if (iostat /= 0) goto 8000
    filter%signal%input_number = input_signal_numbers

    !-- Open the filter.
    where = 'open'
    filter%filter_linker = ''
    if (present(filter_linker)) filter%filter_linker = filter_linker
    call open_gd_filter(filter%filter_handle, filter_name, &
         filter%filter_linker, filter_dt, filter_parameters, &
         n_signals, description, error)
    if (error) goto 8000
    filter%opened = .true.

    !-- Add source to the get_data list.
    call add_gd_source(source_list, filter_source_code, filter_name, &
         description, filter_dt, filter%source, error)
    if (error) goto 8000

    !-- Add the new signals to the get_data list.
    !-- Tag them with the file source number and signal number,
    !-- because that seems to be what we always need.
    !-- Code in sync_gd and filter_gd currently depends on this.
    where = 'avail'
    signal_loop: do i = 1 , n_signals
      call add_gd_signal(signal_list, signal_names(i), filter%source, &
           gd_signal, 'filter', error)
      if (error) goto 8000
      gd_signal%source_number = filter_list%input_source_number
      gd_signal%source_signal_number = output_offset + i
      gd_signal%interpolate = interpolate
      filter%signal(i)%output_number = gd_signal%signal_number
    end do signal_loop

    !-- Append filter to doubly linked list of filters for this file.
    nullify(filter%next_filter)
    filter%prev_filter => filter_list%last_filter
    if (.not.associated(filter_list%first_filter)) &
         filter_list%first_filter => filter
    if (associated(filter_list%last_filter)) &
         filter_list%last_filter%next_filter => filter
    filter_list%last_filter => filter

    !-- Normal exit.
    error = .false.
    return

    !-- Error exit.
    8000 continue
    call write_error_msg('Add_gd_filter failed at ' // where)
    call release_gd_filter(filter)
    error = .true.
    return
  end subroutine add_gd_filter

  subroutine close_gd_filters (filter_list)

    !-- Close all filters for a file.
    !-- 19 Oct 91, Richard Maine.

    !-------------------- interface.
    type(filter_list_type), intent(inout) :: filter_list

    !-------------------- local.
    type(filter_type), pointer :: filter

    !-------------------- executable code.

    do while (associated(filter_list%last_filter))
      filter => filter_list%last_filter%prev_filter
      call release_gd_filter(filter_list%last_filter)
      filter_list%last_filter => filter
    end do
    nullify(filter_list%first_filter)
    return
  end subroutine close_gd_filters

  subroutine release_gd_filter (filter)

    !-- Close a filter and deallocate space.
    !-- 9 Jan 91, Richard Maine.

    !-------------------- interface.
    type(filter_type), pointer :: filter  !-- intent(inout)

    !-------------------- executable code.

    if (associated(filter)) then
      if (associated(filter%used_signal)) deallocate(filter%used_signal)
      if (associated(filter%signal)) deallocate(filter%signal)
      if (filter%opened) call close_gd_filter(filter%filter_handle)
      deallocate(filter)
    end if
    return
  end subroutine release_gd_filter

  subroutine activate_gd_filters (filter_list, signal, error)

    !-- Activate the used filtered signals for a file.
    !-- Flag their needed inputs as used in turn.
    !-- 22 Sept 92, Richard Maine.

    !-------------------- interface.
    type(filter_list_type), intent(inout) :: filter_list
    type(gd_signal_type), intent(inout) :: signal(0:)
         !-- Array of get_data signal information.
         !-- Use the entry values to determine which filters to activate.
         !-- Set used any needed inputs of activated filters on return.
    logical, intent(out) :: error

    !-------------------- local.
    type(filter_type), pointer :: filter
    integer :: iostat, i, j, n_used
    integer :: buffer_map(0:ubound(signal,1))
    integer, pointer :: output_number(:), input_number(:)

    !-------------------- executable code.

    error = .true.

    !-- Activate the input signals needed for used filter output signals.
    !-- Note we must do this in reverse order to catch dependencies.
    filter => filter_list%last_filter
    do while (associated(filter))
      signal(0)%used = .false.
      output_number => filter%signal%output_number
      input_number => filter%signal%input_number
      do i = 1 , filter%source%n_source_signals
        if (signal(output_number(i))%used) &
             signal(input_number(i))%used = .true.
      end do
      filter => filter%prev_filter
    end do

    !-- Make map from get_data signal numbers to the compressed frame buffer.
    !-- Cannot be done until after all used signals in the file are flagged.
    signal(0)%used = .false.
    buffer_map = 0
    j = 0
    do i = 1 , ubound(signal,1)
      if (signal(i)%used .and. &
           (signal(i)%source_number==filter_list%input_source_number)) then
        j = j + 1
        buffer_map(i) = j
      end if
    end do

    !-- Make the used signal maps for each filter in the list.
    filter => filter_list%first_filter
    do while (associated(filter))
      output_number => filter%signal%output_number
      input_number => filter%signal%input_number
      filter%signal%used = signal(output_number)%used
      n_used = count(filter%signal%used)
      filter%filter_used = n_used > 0
      if (associated(filter%used_signal)) deallocate(filter%used_signal)
      if (filter%filter_used) then
        allocate(filter%used_signal(n_used), stat=iostat)
        if (iostat /= 0) return
        j = 0
        do i = 1 , filter%source%n_source_signals
          if (signal(output_number(i))%used) then
            j = j + 1
            filter%used_signal(j)%output_map = buffer_map(output_number(i))
            filter%used_signal(j)%input_map = buffer_map(input_number(i))
          end if
        end do
      end if
      filter => filter%next_filter
    end do
    error = .false.
    return
  end subroutine activate_gd_filters

  subroutine do_gd_filters(filter_list, reset, time, data, error)

    !-- Apply the activated filters for a file.
    !-- 21 Mar 91, Richard Maine.

    !-------------------- interface.
    type(filter_list_type), intent(in) :: filter_list
    logical, intent(in) :: reset
         !-- Should filters be reset?
         !-- True on first frame of a time interval.
    real(r_kind), intent(in) :: time
         !-- Frame time.
    real(r_kind), intent(inout) :: data(:)
         !-- Compressed data frame.
         !-- Includes only used signals for the file and associated filters.
    logical, intent(out) :: error

    !-------------------- local.
    type(filter_type), pointer :: filter
    type(used_filter_signal_type), pointer :: used_signal(:)

    !-------------------- executable code.

    filter => filter_list%first_filter
    do while (associated(filter))
      if (filter%filter_used) then
        used_signal => filter%used_signal
        used_signal%input_data = data(used_signal%input_map)
        call do_gd_filter(filter%filter_handle, reset, time, &
             used_signal%input_data, used_signal%output_data, error)
        if (error) return
        data(used_signal%output_map) = used_signal%output_data
      end if
      filter => filter%next_filter
    end do
    error = .false.
    return
  end subroutine do_gd_filters

end module gd_filter