#########################################################################################
# Package       HiPi::Interrupt::Handler
# Description:  Main Thread Interrupt Handler
# Created       Wed Apr 24 16:22:56 2013
# SVN Id        $Id: Handler.pm 22 2016-03-04 21:38:30Z Mark Dootson $
# Copyright:    Copyright (c) 2013 Mark Dootson
# Licence:      This work is free software; you can redistribute it and/or modify it 
#               under the terms of the GNU General Public License as published by the 
#               Free Software Foundation; either version 3 of the License, or any later 
#               version.
#########################################################################################

package HiPi::Interrupt::Handler;

#########################################################################################

use 5.14.0;
use strict;
use warnings;
use parent qw( HiPi::Class );
use Carp;
use HiPi::Interrupt::Message;
use Time::HiRes;

our $VERSION = '0.33';

__PACKAGE__->create_accessors( qw(
    docontinue
    timeout
    pinref
    cbackstart
    cbackadd
    cbackremove
    cbackinterrupt
    cbackerror
    cbackcontinue
    cbackstop
    
));

sub new {
    my ($class, %params) = @_;
    $params{timeout} ||= 100;
    $params{pinref} = {};
    
    $params{cbackstart}     = \&_null_cback;
    $params{cbackadd}       = \&_default_cback;
    $params{cbackremove}    = \&_default_cback;
    $params{cbackinterrupt} = \&_default_cback;
    $params{cbackstop}      = \&_null_cback;
    $params{cbackcontinue}  = \&_null_cback;
    $params{cbackerror}     = \&_null_cback;
    
    my $self = $class->SUPER::new(%params);
    return $self;
}

sub register_callback {
    my($self, $cbname, $codref) = @_;
    if( ref($codref) ne 'CODE' ) {
        croak('code reference parameter is not a code ref');
    }
    
    my $checkval = lc($cbname);
    
    if ( $checkval eq 'start') {
        $self->cbackstart($codref);
    } elsif( $checkval eq 'add') {
        $self->cbackadd($codref);
    } elsif( $checkval eq 'remove') {
        $self->cbackremove($codref);
    } elsif( $checkval eq 'interrupt') {
        $self->cbackinterrupt($codref);
    } elsif( $checkval eq 'stop') {
        $self->cbackstop($codref);
    }  elsif( $checkval eq 'continue') {
        $self->cbackcontinue($codref);
    } elsif( $checkval eq 'error') {
        $self->cbackerror($codref);
    } else {
        croak(qq(unknown callback name $cbname));
    }
}

sub stop {
    my $self = shift;
    HiPi::Interrupt->close_interrupts;
    $self->docontinue(0);
}

sub sleep_timeout {
    my($self, $millisecs) = @_;
    Time::HiRes::usleep( int($millisecs * 1000) );
}

sub get_timestamp {
    my ($secs, $msecs) = Time::HiRes::gettimeofday();
    my $timestamp = ($secs * 1000) + int($msecs / 1000);
    return( $timestamp );
}

sub poll {
    my $self = shift;
    $self->on_action_start;
    $self->docontinue(1);
    while($self->docontinue) {
        my $actions = 0;
        while (defined(my $msg = HiPi::Interrupt->tqueue->dequeue_nb)) {
            $actions ++;
            my $mobj = HiPi::Interrupt::Message->new($msg);
            
            my $checkval = $mobj->action || 'undefined';
            
            if( $checkval eq 'add') {
                $self->on_action_add($mobj);
            } elsif( $checkval eq 'remove') {
                $self->on_action_remove($mobj);
            } elsif( $checkval eq 'interrupt') {
                $self->on_action_interrupt($mobj);
            } elsif( $checkval eq 'stop') {
                # we ignore stop messages that
                # should currently only come
                # when application is closing
                $actions --;
            } else {
                $self->on_action_error($mobj);
            }
        }
        $self->sleep_timeout($self->timeout) if !$actions;
        $self->on_action_continue($actions);
    }
    $self->on_action_stop;
}

sub add_pin {
    my($self, $pin, $pinclass) = @_;
    
    $pinclass ||= 'undefined';
    
    if(ref($pin)) {
        if($pin->isa('HiPi::Device::GPIO::Pin')) {
            $self->add_gpio_pin($pin->pinid);
        } elsif($pin->isa('HiPi::BCM2835::Pin')) {
            $self->add_bcmd_pin($pin->pinid);
        } elsif($pin->isa('HiPi::Wiring::Pin')) {
            $self->add_wire_pin($pin->pinid);
        } else {
            croak( 'Unknown pin class ' . ref($pin) );
        }
        return;
    } elsif( $pinclass eq 'undefined' && defined($self->pinref->{$pin}) ) {
        $pinclass = $self->pinref->{$pin}
    }
    
    my $checkval = lc($pinclass);
    if ( $checkval eq 'gpio') {
        $self->add_gpio_pin($pin);
    } elsif($checkval eq 'bcmd') {
        $self->add_bcmd_pin($pin);
    } elsif($checkval eq 'wire') {
        $self->add_wire_pin($pin);
    } else {
        croak(qq(unknown pin class $pinclass));
    }
}

sub remove_pin {
    my($self, $pin, $pinclass) = @_;
    
    $pinclass ||= 'undefined';
    
    if(ref($pin)) {
        if($pin->isa('HiPi::Device::GPIO::Pin')) {
            $self->remove_gpio_pin($pin->pinid);
        } elsif($pin->isa('HiPi::BCM2835::Pin')) {
            $self->remove_bcmd_pin($pin->pinid);
        } elsif($pin->isa('HiPi::Wiring::Pin')) {
            $self->remove_wire_pin($pin->pinid);
        } else {
            croak( 'Unknown pin class ' . ref($pin) );
        }
        return;
    } elsif( $pinclass eq 'undefined' && defined($self->pinref->{$pin}) ) {
        $pinclass = $self->pinref->{$pin}
    }
    
    my $checkval = lc($pinclass);
    if ( $checkval eq 'gpio') {
        $self->remove_gpio_pin($pin);
    } elsif($checkval eq 'bcmd') {
        $self->remove_bcmd_pin($pin);
    } elsif($checkval eq 'wire') {
        $self->remove_wire_pin($pin);
    } else {
        croak(qq(unknown pin class $pinclass));
    }
}

sub set_polltimeout {
    my($self, $value) = @_;
    my $msg = { action => 'polltimeout', timeout => $value };
    HiPi::Interrupt->broadcast_message(%$msg);
}

sub set_valuetimeout {
    my($self, $value) = @_;
    my $msg = { action => 'valuetimeout', timeout => $value };
    HiPi::Interrupt->broadcast_message(%$msg);
}

#------------------------------------------
# Events Handling
#------------------------------------------

sub _default_cback {
    my($self, $msg) = @_;
    if($msg->error && $self->cbackerror ) {
        $self->cbackerror->($self, $msg);
    }
}

sub _null_cback { my @params = @_; }

sub on_action_start {
    my($self) = @_;
    $self->cbackstart->($self);
}

sub on_action_add {
    my($self, $msg) = @_;
    $self->cbackadd->($self, $msg);
}

sub on_action_remove {
    my($self, $msg) = @_;
    $self->cbackremove->($self, $msg);
}

sub on_action_interrupt {
    my($self, $msg) = @_;
    $self->cbackinterrupt->($self, $msg);
}

sub on_action_error {
    my($self, $msg) = @_;
    $self->cbackerror->($self, $msg);
}

sub on_action_continue {
    my ($self, $actions) = @_;
    $self->cbackcontinue->($self, $actions);
}

sub on_action_stop {
    my($self, $msg) = @_;
    $self->cbackstop->($self, $msg);
}


#------------------------------------------
# GPIO Handling
#------------------------------------------

sub add_gpio_pin {
    my($self, $pin) = @_;
    $self->pinref->{$pin} = 'gpio';
    HiPi::Interrupt->add_gpio_pin($pin);
}

sub remove_gpio_pin {
    my($self, $pin) = @_;
    $self->pinref->{$pin} = undef;
    HiPi::Interrupt->remove_gpio_pin($pin);
}

sub set_gpio_polltimeout {
    my($self, $value) = @_;
    HiPi::Interrupt->set_gpio_polltimeout($value);
}

sub set_gpio_valuetimeout {
    my($self, $value) = @_;
    HiPi::Interrupt->set_gpio_valuetimeout($value);
}

#------------------------------------------
# BCM2835 Handling
#------------------------------------------

sub add_bcmd_pin {
    my($self, $pin) = @_;
    $self->pinref->{$pin} = 'bcmd';
    HiPi::Interrupt->add_bcmd_pin($pin);
}

sub remove_bcmd_pin {
    my($self, $pin) = @_;
    $self->pinref->{$pin} = undef;
    HiPi::Interrupt->remove_bcmd_pin($pin);
}

sub set_bcmd_polltimeout {
    my($self, $value) = @_;
    HiPi::Interrupt->set_bcmd_polltimeout($value);
}

sub set_bcmd_valuetimeout {
    my($self, $value) = @_;
    HiPi::Interrupt->set_bcmd_valuetimeout($value);
}

#------------------------------------------
# Wiring Handling
#------------------------------------------

sub add_wire_pin {
    my($self, $pin) = @_;
    $self->pinref->{$pin} = 'wire';
    HiPi::Interrupt->add_wire_pin($pin);
}

sub remove_wire_pin {
    my($self, $pin) = @_;
    $self->pinref->{$pin} = undef;
    HiPi::Interrupt->remove_wire_pin($pin);
}

sub set_wire_polltimeout {
    my($self, $value) = @_;
    HiPi::Interrupt->set_wire_polltimeout($value);
}

sub set_wire_valuetimeout {
    my($self, $value) = @_;
    HiPi::Interrupt->set_wire_valuetimeout($value);
}


1;
