package RFC::FilterMultiplex;
# $Id: FilterMultiplex.pm,v 1.10 2023/12/05 11:28:13 sqko Exp $
#################################################################
#
#
#       RFC::FilterMultiplex
#
# This device class defines a composite input filter where a 
# control device determines which of a number of input devices
# are used for input (read) operations.
#
# This allows for selecting one of several measured values for
# data logging and/or calculations based on the value of the
# control device.
#
# All device types can be used a control device, however the 
# actual value of the control device is coerced to an integer
# (by rounding to nearest integer) which is then used as index 
# to determine which of the inputs are to be used.
# Thus a value of 0.45 will be evaluated to 0 and 1.5 will be 
# evaluated to 2.
#
# If the control value is outside the range of possible input
# devices the first or last device is used as appropriate.
#
# To obtain an instance of the RFC::FilterMultiplex device call
# one of the constructor:
#
# my $id = RFC::FilterMultiplex->new($name,$rig);
#
# my $id = RFC::OutputMultiplex->new($name,$rig);
#
# The FilterMultiplex device class is for multiplexing inputs
# and the OutputMultiplex class is for multiplexing outputs as
# well as input.
#
################################################################# 

use strict;
use warnings;
use RFC::BaseDevice;
use RFC::Header;
our @ISA = qw(RFC::BaseDevice);
our $AUTOLOAD;


BEGIN {
    use Exporter ();
    our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
    $VERSION = 1.0;
    %EXPORT_TAGS = (
        'standard'=>[],
	);
    Exporter::export_ok_tags('standard');
    Exporter::export_tags('standard');
    @ISA = qw(Exporter);
}

sub new()
{
    my $class = shift;
    my $name = shift;
    my $rig = shift;
    my $self = $class->SUPER::new($name,$rig);
    $self->{'_type'} = 'Input multiplex filter'; 
    $self->{'_html_template'} = 'Filter.tt';
    $self->{'_setup_tags'} = ['class','mode','device_type','inputs','control_device_type','control_device','title','show_plot','read_function'];
    $self->{'_setup_tags_readonly'} = ['mode'];
    $self->{'_reload_tags'} = ['type','class','device','device_type','control_device_type','inputs'];
    $self->{'_desc'} = {
        'class' => 'Device class',
        'mode' => 'Device control mode',
        'inputs' => 'number of inputs',
        'device_type' => 'Device type',
        'control_device_type' => 'Device type for control device',
	'control_device' => 'Device used for selecting input',
        'title' => 'Optional title',
        'read_function' => 'Device function to use for read operations',
    };
    $self->{'_default'} = {
        'device_type' => 'simplechannel',
        'control_device_type' => 'simplechannel',
        'inputs' => 2,
        'read_function' => 'read',
    };
    bless $self,$class;
    $self->{'_values_class'} = [RFC::Device::list_filter()];
    $self->{'_values_device_type'} = ['',$self->rig->list_types_nofilter()];
    $self->{'_values_control_device_type'} = ['',$self->rig->list_types_nofilter()];
    $self->{'_values_inputs'} = [2,3,4,5,6,7,8,9];
    $self->{'_section'} = 'filter_'.$self->name;
    $self->{'_values_read_function'} = ['read'];
    $self->load_conf();
    my @list = ();
    foreach ($self->rig->avaliable_devices($self->{'_control_device_type'}))
    {
        push @list,$_ unless ($_ eq $self->name()); ## Do not include self ##
    }
    $self->{'_values_control_device'} = ['',@list];
    if ($self->{'_control_device_type'} && $self->{'_control_device'})
    {
        ### get device if it already exist ###
        my $dev = $self->rig->get_device($self->{'_control_device_type'},$self->{'_control_device'});
        if ($dev)
        {
            $self->{'_dev'} = $dev;
        }
    }
    my $found_read = 0;
    my @l = ();
    foreach ($self->rig->avaliable_devices($self->{'_device_type'}))
    {
        push @l,$_ unless ($_ eq $self->name()); ## Do not include self ##
    }
    for (my $x = 0; $x < $self->{'_inputs'}; $x++)
    {
        my $tag = 'input_device_'.$x;
        $self->{'_values_'.$tag} = ['',@l];
        push @{$self->{'_setup_tags'}},$tag;
        $self->{'_'.$tag} = $self->get_default($tag);
        my $conf = $self->get_conf($tag);
        $self->{'_'.$tag} = $conf if (defined($conf) && $conf =~ /\w/);
        $self->{'_desc'}->{$tag} = 'Input device '.$x;
        ## Handle factors on inputs ###
        next if ($found_read);
        next unless ($self->{'_dev_'.$x});
        my @arr = $self->{'_dev_'.$x}->read_names();
        $self->{'_values_read_function'} = [@arr];
        my @arr2 = $self->{'_dev_'.$x}->list_set_functions();
        $self->{'_set_fn_names'} = [@arr2];
        $found_read = 1;
    }
    ### Input devices can not be controlled directly, only thorugh notification from inputs.
    $self->{'_readonly'} = 1;
    ### Note, do NOT run init as part of constructor! ###
    $self->rig->register_device($self->{'_device_type'},$self);
    return $self;
}


sub init()
{
    my $self = shift;
    my @list = ();
    foreach ($self->rig->avaliable_devices($self->{'_device_type'}))
    {
        push @list,$_ unless ($_ eq $self->name()); ## Do not include self ##
    }
    ### Remove any duplicates in list  ###
    my %hash = ();
    foreach (@list)
    {
        $hash{$_} = 1;
    }
    @list = ();
    foreach my $key (sort keys %hash)
    {
        push @list,$key;
    }
    my $found_read = 0;
    for (my $x = 0; $x < $self->{'_inputs'}; $x++)
    {
        my $tag = '_input_device_'.$x;
        $self->{'_values'.$tag} = ['',@list];
        next if ($self->{'_dev_'.$x}); 
        my $dev = $self->rig->get_device($self->{'_device_type'},$self->{$tag});
        $dev = $self->rig->get_device('filter',$self->{$tag}) unless ($dev);
        unless ($dev)
        {
            $dev = $self->rig->init_device($self->{'_device_type'},$self->{$tag});
            $dev = $self->rig->init_device('filter',$self->{$tag}) unless ($dev);
            $dev->readstring_ignore(1) if ($dev);
        }
        if ($dev)
        {
            $dev->debug($self->debug);
            $self->{'_dev_'.$x} = $dev; 
            $dev->attatch_observer(\$self);
        }
        next if ($found_read);
        next unless ($self->{'_dev_'.$x});
        my @arr = $self->{'_dev_'.$x}->read_names();
        $self->{'_values_read_function'} = [@arr];
        my @arr2 = $self->{'_dev_'.$x}->list_set_functions();
        $self->{'_set_fn_names'} = [@arr2];
        $found_read = 1;
    }
    unless ($self->{'_dev'})
    {
        my $tag = '_control_device';
        my $dev = $self->rig->get_device($self->{'_control_device_type'},$self->{$tag});
        $dev = $self->rig->get_device('filter',$self->{$tag}) unless ($dev);
        unless ($dev)
        {
            $dev = $self->rig->init_device($self->{'_control_device_type'},$self->{$tag});
            $dev = $self->rig->init_device('filter',$self->{$tag}) unless ($dev);
            $dev->readstring_ignore(1) if ($dev);
        }
        if ($dev)
        {
            $dev->debug($self->debug);
            $self->{'_dev'} = $dev; 
        }
    }
    return $self;
}

sub selected_index
{
    my $self = shift;
    if ($self->{'_dev'})
    {
	my $control_val = $self->{'_dev'}->read(@_);
	$control_val = int($control_val + 0.5);
	$control_val = 0 if ($control_val < 0);
	$control_val = $self->{'_inputs'} - 1 if ($control_val >= $self->{'_inputs'});
	return $control_val;
    }
    return undef;
}

sub selected_device
{
    my $self = shift;
    if ($self->{'_dev'})
    {
	my $control_val = $self->selected_index(@_);
	my $tag = '_dev_'.$control_val;
	return $self->{$tag} if ($self->{$tag});
    }
    return undef;
}


sub Accept
{
    my $self = shift;
    my $visitor = shift;
    return undef if ($self->{'_seen_visitors'}{$visitor});
    for (my $x = 0; $x < $self->{'_inputs'}; $x++)
    {
        my $tag = '_dev_'.$x;
        $self->{$tag}->Accept($visitor) if ($self->{$tag});
    }
    $self->{'_dev'}->Accept($visitor) if ($self->{'_dev'});
    $visitor->VisitComplex($self);
    $self->{'_seen_visitors'}{$visitor} = 1;
    return undef;
}

sub print_config()
{
    my $self = shift;
    $self->init();
    my @arr = ();
    push @arr,'Name: '.$self->name;
    push @arr,'Type: '.$self->type;
    push @arr,$self->{'_dev'}->print_config() if ($self->{'_dev'});
    push @arr,'Inputs: '.$self->{'_inputs'};
    for (my $x = 0; $x < $self->{'_inputs'}; $x++)
    {
        my $tag = '_dev_'.$x;
        push @arr,$self->{$tag}->print_config(@_) if ($self->{$tag});
    }
    return join(', ',@arr);
}


sub local_init()
{
    #### Stub function intended for overloading for any impelmention dependent initialisations ###
    my $self = shift;
    return $self;
}


sub read
{
    my $self = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug) if ($self->debug);
    }
    $self->check_cache($data);
    my $val = -32768;
    my $cached = $data->get($self->type,$self->name);
    if (defined($cached))
    {
        $self->writedebug("Using already measured value for ".$self->name) if ($self->debug > 3); 
        $val = $cached;
    }
    else
    {
        $self->init();
	my $dev = $self->selected_device($data);
	$val = $dev-> read_named($self->{'_read_function'},$data) if ($dev);
	my $control_val = $self->selected_index($data);
        $data->set($self->type,$self->name,$val);
	$self->writedebug("Value of input[$control_val] was: $val") if ($self->debug);
    }
    return $val;
}


sub test()
{
    my $self = shift;
    $self->init();
    my $res = 1;
    for (my $x = 0; $x < $self->{'_inputs'}; $x++)
    {
        my $tag = '_dev_'.$x;
        unless ($self->{$tag})
        {
            warn "Warning: Input device $x not defined for $self!\n";
            next;
        }
        $res = 0 unless ($self->{$tag}->test);
    }
    $res = 0 unless ($self->{'_dev'} && $self->{'_dev'}->test);
    return $res;
}


sub test_string()
{
    my $self = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug) if ($self->debug);
    }
    $self->check_cache($data);
    $self->init(); 
    my $type = $self->type();
    my $val = $self->read($data);
    my @arr = ('Composite device "'.$self->name.'" of type "'.$type.'": Value: '.$val);
    for (my $x = 0; $x < $self->{'_inputs'}; $x++)
    {
        my $tag = '_dev_'.$x;
        if ($self->{$tag})
        {
            my $str = $self->{$tag}->test_string($data);
            chomp $str;
            push @arr,'Input device '.$x.': '.$str;
            if ($self->{$tag}->can('cutoff_set'))
            {
                push @arr,'Cutoff set is: '.$self->{$tag}->cutoff_set();
            }
        }
        else
        {
            push @arr,'Input device '.$x.' is not defined!';
        }
    }
    if ($self->{'_dev'})
    {
        my $str = $self->{'_dev'}->test_string($data);
        chomp $str;
        push @arr,'Control device: '.$str;
	push @arr,'Selected device: '.$self->selected_index($data);
    }
    else
    {
	push @arr,'Control device is not defined!';
    }
    my $string = join("\n",@arr)."\n\n";
    return $string;
}

sub readonly_value($)
{
    my $self = shift;
    my $tag = shift;
    return undef unless ($tag);
    ### allow call to get 'mode' for undefined devices ###
    return 'Na' if ($tag eq 'mode' && !$self->{'_device'});
    $self->init();
    my $dev = $self->selected_device;
    if ($dev)
    {
        return $dev->mode() if ($tag eq 'mode');
        return $dev->readonly_value($tag);
    }
    return undef;
}

sub readonly()
{
    my $self = shift;
    $self->init();
    return $self->SUPER::readonly();
}


sub AUTOLOAD()
{
    ################################################################
    ### AUTOLOAD() , redirect all method calls not defined by    ###
    ### Basedevice to the underlying selected device             ###
    ################################################################
    my $self = shift;
    {
	my $dev = $self->selected_device();
	if ($dev)
	{
	    no strict "refs";
	    $AUTOLOAD =~ /\:\:(\w+)$/;
	    $dev->$1(@_);
	}
    }
}

package RFC::OutputMultiplex;
our @ISA = qw(RFC::FilterMultiplex);


sub new()
{
    my $class = shift;
    my $name = shift;
    my $rig = shift;
    my $self = $class->SUPER::new($name,$rig);
    $self->{'_type'} = 'Output multiplex filter'; 
    $self->{'_html_template'} = 'Filter.tt';
    $self->{'_setup_tags'} = ['class','mode','device_type','outputs','control_device_type','control_device','title','show_plot','read_function','allow_override'];
    $self->{'_setup_tags_readonly'} = ['mode'];
    $self->{'_reload_tags'} = ['type','class','device','device_type','control_device_type','outputs','control_device'];
    $self->{'_desc'} = {
        'class' => 'Device class',
        'mode' => 'Device control mode',
        'outputs' => 'number of outputs',
        'device_type' => 'Device type',
        'control_device_type' => 'Device type for control device',
	'control_device' => 'Device used for selecting input',
        'title' => 'Optional title',
        'allow_override' => 'Allow direct commands to multiplexed devices',
        'read_function' => 'Device function to use for read operations',
    };
    $self->{'_default'} = {
        'device_type' => 'simplechannel',
        'control_device_type' => 'simplechannel',
        'outputs' => 2,
        'read_function' => 'read',
        'allow_override' => 'No',
    };
    bless $self,$class;
    $self->{'_values_class'} = [RFC::Device::list_filter()];
    $self->{'_values_device_type'} = ['',$self->rig->list_types_nofilter()];
    $self->{'_values_control_device_type'} = ['',$self->rig->list_types_nofilter()];
    $self->{'_values_outputs'} = [2,3,4,5,6,7,8,9];
    $self->{'_values_allow_override'} = ['No','Yes'];
    $self->{'_section'} = 'filter_'.$self->name;
    $self->{'_values_read_function'} = ['read'];
    $self->load_conf();
    my @list = ();
    foreach ($self->rig->avaliable_devices($self->{'_control_device_type'}))
    {
        push @list,$_ unless ($_ eq $self->name()); ## Do not include self ##
    }
    $self->{'_values_control_device'} = ['',@list];
    if ($self->{'_control_device_type'} && $self->{'_control_device'})
    {
        ### get device if it already exist ###
        my $dev = $self->rig->get_device($self->{'_control_device_type'},$self->{'_control_device'});
        if ($dev)
        {
            $self->{'_dev'} = $dev;
        }
    }
    my $found_read = 0;
    my @l = ();
    foreach ($self->rig->avaliable_devices($self->{'_device_type'}))
    {
        push @l,$_ unless ($_ eq $self->name()); ## Do not include self ##
    }
    for (my $x = 0; $x < $self->{'_outputs'}; $x++)
    {
        my $tag = 'output_device_'.$x;
        $self->{'_values_'.$tag} = ['',@l];
        push @{$self->{'_setup_tags'}},$tag;
        $self->{'_'.$tag} = $self->get_default($tag);
        my $conf = $self->get_conf($tag);
        $self->{'_'.$tag} = $conf if (defined($conf) && $conf =~ /\w/);
        $self->{'_desc'}->{$tag} = 'Output device '.$x;
        ## Handle factors on inputs ###
        next if ($found_read);
        next unless ($self->{'_dev_'.$x});
        my @arr = $self->{'_dev_'.$x}->read_names();
        $self->{'_values_read_function'} = [@arr];
        my @arr2 = $self->{'_dev_'.$x}->list_set_functions();
        $self->{'_set_fn_names'} = [@arr2];
        $found_read = 1;
    }
    $self->{'_readonly'} = 0;
    ### Note, do NOT run init as part of constructor! ###
    return $self;
}


sub init()
{
    my $self = shift;
    my @list = ();
    foreach ($self->rig->avaliable_devices($self->{'_device_type'}))
    {
        push @list,$_ unless ($_ eq $self->name()); ## Do not include self ##
    }
    ### Remove any duplicates in list  ###
    my %hash = ();
    foreach (@list)
    {
        $hash{$_} = 1;
    }
    @list = ();
    foreach my $key (sort keys %hash)
    {
        push @list,$key;
    }
    my $found_read = 0;
    for (my $x = 0; $x < $self->{'_outputs'}; $x++)
    {
        my $tag = '_output_device_'.$x;
        $self->{'_values'.$tag} = ['',@list];
        next if ($self->{'_dev_'.$x}); 
        my $dev = $self->rig->get_device($self->{'_device_type'},$self->{$tag});
        $dev = $self->rig->get_device('filter',$self->{$tag}) unless ($dev);
        unless ($dev)
        {
            $dev = $self->rig->init_device($self->{'_device_type'},$self->{$tag});
            $dev = $self->rig->init_device('filter',$self->{$tag}) unless ($dev);
            $dev->readstring_ignore(1) if ($dev);
        }
        if ($dev)
        {
            $dev->debug($self->debug);
            $self->{'_dev_'.$x} = $dev; 
            $dev->attatch_observer(\$self);
	    ### Explicitly set readonly on underlying devices ###
	    $dev->set_readonly() unless ($self->{'_allow_override'} =~  /yes/i);
        }
        next if ($found_read);
        next unless ($self->{'_dev_'.$x});
        my @arr = $self->{'_dev_'.$x}->read_names();
        $self->{'_values_read_function'} = [@arr];
        my @arr2 = $self->{'_dev_'.$x}->list_set_functions();
        $self->{'_set_fn_names'} = [@arr2];
        $found_read = 1;
    }
    unless ($self->{'_dev'})
    {
        my $tag = '_control_device';
        my $dev = $self->rig->get_device($self->{'_control_device_type'},$self->{$tag});
        $dev = $self->rig->get_device('filter',$self->{$tag}) unless ($dev);
        unless ($dev)
        {
            $dev = $self->rig->init_device($self->{'_control_device_type'},$self->{$tag});
            $dev = $self->rig->init_device('filter',$self->{$tag}) unless ($dev);
            $dev->readstring_ignore(1) if ($dev);
        }
        if ($dev)
        {
            $dev->debug($self->debug);
            $self->{'_dev'} = $dev; 
        }
    }
    return $self;
}


sub selected_index
{
    my $self = shift;
    if ($self->{'_dev'})
    {
	my $control_val = $self->{'_dev'}->read(@_);
	$control_val = int($control_val + 0.5);
	$control_val = 0 if ($control_val < 0);
	$control_val = $self->{'_outputs'} - 1 if ($control_val >= $self->{'_outputs'});
	return $control_val;
    }
    return undef;
}


sub Accept
{
    my $self = shift;
    my $visitor = shift;
    return undef if ($self->{'_seen_visitors'}{$visitor});
    for (my $x = 0; $x < $self->{'_outputs'}; $x++)
    {
        my $tag = '_dev_'.$x;
        $self->{$tag}->Accept($visitor) if ($self->{$tag});
    }
    $self->{'_dev'}->Accept($visitor) if ($self->{'_dev'});
    $visitor->VisitComplex($self);
    $self->{'_seen_visitors'}{$visitor} = 1;
    return undef;
}

sub print_config()
{
    my $self = shift;
    $self->init();
    my @arr = ();
    push @arr,'Name: '.$self->name;
    push @arr,'Type: '.$self->type;
    push @arr,$self->{'_dev'}->print_config() if ($self->{'_dev'});
    push @arr,'Outputs: '.$self->{'_outputs'};
    for (my $x = 0; $x < $self->{'_outputs'}; $x++)
    {
        my $tag = '_dev_'.$x;
        push @arr,$self->{$tag}->print_config(@_) if ($self->{$tag});
    }
    return join(', ',@arr);
}


sub notify
{
    my $self = shift;
    $self->init();
    my $data = RFC::Cache->new();
    my @response = $self->SUPER::notify(@_);
    my $dev = $self->selected_device();
    if ($dev)
    {
	push @response,$dev->notify(@_);
    }
    return @response;
}

sub observe($$)
{
    my $self = shift;
    my $action = shift;
    my $value = shift;
    my $data = RFC::Cache->new();
    my $dev = $self->selected_device();
    if ($dev)
    {
	return $dev->observe($action,$value,@_);
    }
}




sub local_init()
{
    #### Stub function intended for overloading for any impelmention dependent initialisations ###
    my $self = shift;
    return $self;
}


sub read
{
    my $self = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug) if ($self->debug);
    }
    $self->check_cache($data);
    my $val = -32768;
    my $cached = $data->get($self->type,$self->name);
    if (defined($cached))
    {
        $self->writedebug("Using already measured value for ".$self->name) if ($self->debug > 3); 
        $val = $cached;
    }
    else
    {
	my $dev = $self->selected_device();
	$val = $dev->read_named($self->{'_read_function'},$data) if ($dev);
	my $control_val = $self->selected_index($data);
        $data->set($self->type,$self->name,$val);
	$self->writedebug("Value of Output[$control_val] was: $val") if ($self->debug);
    }
    return $val;
}


sub test()
{
    my $self = shift;
    $self->init();
    my $res = 1;
    for (my $x = 0; $x < $self->{'_outputs'}; $x++)
    {
        my $tag = '_dev_'.$x;
        unless ($self->{$tag})
        {
            warn "Warning: Output device $x not defined for $self!\n";
            next;
        }
        $res = 0 unless ($self->{$tag}->test);
    }
    $res = 0 unless ($self->{'_dev'} && $self->{'_dev'}->test);
    return $res;
}


sub test_string()
{
    my $self = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug) if ($self->debug);
    }
    $self->check_cache($data);
    $self->init(); 
    my $type = $self->type();
    my $val = $self->read($data);
    my @arr = ('Composite device "'.$self->name.'" of type "'.$type.'": Value: '.$val);
    for (my $x = 0; $x < $self->{'_outputs'}; $x++)
    {
        my $tag = '_dev_'.$x;
        if ($self->{$tag})
        {
            my $str = $self->{$tag}->test_string($data);
            chomp $str;
            push @arr,'Output device '.$x.': '.$str;
            if ($self->{$tag}->can('cutoff_set'))
            {
                push @arr,'Cutoff set is: '.$self->{$tag}->cutoff_set();
            }
        }
        else
        {
            push @arr,'Output device '.$x.' is not defined!';
        }
    }
    if ($self->{'_dev'})
    {
        my $str = $self->{'_dev'}->test_string($data);
        chomp $str;
        push @arr,'Control device: '.$str;
	push @arr,'Selected device: '.$self->selected_index($data);
    }
    else
    {
	push @arr,'Control device is not defined!';
    }
    my $string = join("\n",@arr)."\n\n";
    return $string;
}


return 1;
