package Interface6051;
use strict;
use constant uncouplerDelay => 15;
1;

sub new {
    my $class = shift;
    my $self = {
        triggers => {},
        lastState => '',
        activeUncouplerId => 0,
        activeUncouplerSubid => 0,
        activeUncouplerTime => 0,
    };
    bless $self, $class;
    return $self;
}

sub transmit { }

sub receive { }

sub DESTROY {
    my $self = shift;
    $self->transmit(32);
}


# COMMON ROUTINES

sub stop {
    my $self = shift;
    select(undef, undef, undef, 0.15);
    $self->transmit(97);
    select(undef, undef, undef, 0.5);
    $self->{activeUncouplerId} = 0;
    $self->{activeUncouplerSubid} = 0;
    $self->{activeUncouplerTime} = 0;
}

sub start {
    my $self = shift;
    select(undef, undef, undef, 0.15);
    $self->transmit(96);
    select(undef, undef, undef, 0.15);
}

sub engine { # engine id, speed 0..14, function-p
    my $self = shift;
    warn "engine id ($_[0]) out of range\n" if $_[0] < 1 or $_[0] > 80;
    warn "speed ($_[1]) out of range\n" if $_[1] < 0 or $_[1] > 14;
    $self->transmit($_[1] + ($_[2] ? 16 : 0), $_[0]);
}

sub invert { # engine id, function-p
    my $self = shift;
    warn "engine id ($_[0]) out of range" if $_[0] < 1 or $_[0] > 80;
    # for some reason, you need to send an engine stop command first
    $self->engine($_[0], 0, $_[1]);
    $self->transmit(15 + ($_[1] ? 16 : 0), $_[0]);
    $self->engine($_[0], 0, $_[1]);
}

sub functions { # engine id, f1, f2, f3, f4
    my $self = shift;
    warn "engine id ($_[0]) out of range\n" if $_[0] < 1 or $_[0] > 80;
    $self->transmit(64 + ($_[1] ? 1 : 0) + ($_[2] ? 2 : 0) + ($_[3] ? 4 : 0) + ($_[4] ? 8 : 0), $_[0]);
}

sub switch { # switch id, direction (1 is open/branch/outer/red, 0 is closed/straight/inner/green)
    my $self = shift;
    warn "switch id ($_[0]) out of range\n" if $_[0] < 1 or $_[0] > 256;
    select(undef, undef, undef, 0.1);
    $self->transmit($_[1] ? 34 : 33, $_[0] == 256 ? 0 : $_[0]);
    select(undef, undef, undef, 0.2);
    $self->transmit(32);
    if ($self->{activeUncouplerTime}) {
        my $id = $self->{activeUncouplerId};
        my $subid = $self->{activeUncouplerSubid};
        $self->transmit($subid ? 34 : 33, $id == 256 ? 0 : $id);
    }
}

sub uncoupler { # uncoupler id, subid (1 is red, 0 is green)
    my $self = shift;
    warn "uncoupler id ($_[0]) out of range\n" if $_[0] < 1 or $_[0] > 256;
    $self->transmit($_[1] ? 34 : 33, $_[0] == 256 ? 0 : $_[0]);
    $self->{activeUncouplerId} = $_[0];
    $self->{activeUncouplerSubid} = $_[1];
    $self->{activeUncouplerTime} = time;
}

sub uncouplerOff {
    my $self = shift;
    $self->transmit(32);
    $self->{activeUncouplerId} = 0;
    $self->{activeUncouplerSubid} = 0;
    $self->{activeUncouplerTime} = 0;
}

sub resetSolenoids {
    my $self = shift;
    if ($self->{activeUncouplerTime} and
        $self->{activeUncouplerTime} + uncouplerDelay < time) {
        $self->uncouplerOff();
        return 1;
    }
    return 0;
}

sub getSensor { # sensor id
    my $self = shift;
    warn "sensor id ($_[0]) out of range\n" if $_[0] < 1 or $_[0] > 31;
    $self->transmit(192 + $_[0]);
    return $self->receive();
}

sub getSensors { # number of sensors to read
    my $self = shift;
    warn "sensor count ($_[0]) out of range\n" if $_[0] < 1 or $_[0] > 31;
    $self->transmit(128 + $_[0]);
    return $self->receive();
}

sub resetSensors {
    my $self = shift;
    $self->getSensors(31);
    $self->{lastState} = '';
}

sub sensorsTriggered { # sensor IDs to check
    my $self = shift;
    my $targets = {};
    foreach (@_) {
        $targets->{$_} = 1;
    }
    my $offset = 0;
    my $state = $self->getSensors(31);
    foreach my $byte (split(//, $state)) {
        foreach my $bit (0..7) {
            if ((ord($byte) & (1 << $bit)) > 0) {
                my $id = $offset + $bit;
                if (defined $targets->{$id}) {
                    return $id;
                }
            }
        }
        $offset += 8;
    }
    return 0;
}

sub processSensors {
    my $self = shift;
    my $offset = 0;
    my $state = $self->getSensors(31);
    foreach my $byte (split(//, $state & ~$self->{lastState})) {
        foreach my $bit (0..7) {
            if ((ord($byte) & (1 << $bit)) > 0) {
                my $id = $offset + $bit;
                if (defined $self->{triggers}->{$id}) {
                    &{$self->{triggers}->{$id}}();
                } elsif (defined $self->{triggers}->{default}) {
                    &{$self->{triggers}->{default}}($id);
                } else {
                    warn "sensor $id isn't registered\n";
                }
            }
        }
        $offset += 8;
    }
    $self->{lastState} = $state;
}

sub register {
    my $self = shift;
    my($id, $function) = @_;
    $self->{triggers}->{$id} = $function;
}

sub unregister {
    my $self = shift;
    my($id) = @_;
    $self->{triggers}->{$id} = undef;
}
