package Asterisk::AstMan;
$|=1;
use IO::Socket::INET;
use Data::Dumper;
use Digest::MD5;


=head1 NAME

  Asterisk::AstMan "I wanna talk to a manager!"

=head1 SYNOPSIS

  use Asterisk::Astman;

my $astman = new Asterisk::Manager;


my $man  = init Asterisk::AstMan ({
                                   -user => "astuser",
                                   -secret => "1234",
                                   -host => "localhost"
                                  });


=head1 DESCRIPTION

This module trys to communicate with the manager interface.

=head1 METHODS

                                                                               .

=head2 action(<$action>,[$id])

Call specified Action and provide optional ID 
returns the next item

$hashref = $man->action("Queues","1234")


=head2 command(<$command>)

Call Specified Command

$ouput $man->command("Show Channels");


=head2 reg_event(<$id>,<$coderef>)

Register code to execute when an event has been received.

$newext = sub {
  my $ref = shift;
  print "Got a Bite!\n";
  print Dumper $ref;
};


$man->reg_event("Newexten",$newext);


=head2 reg_regex_event(<$regex>,<$coderef>)

Register code to execute when an event name matches a regular Expression.




$man->reg_regex_event("New.*",$new);


=head2 reg_actionid(<$id>,<$coderef>)

Register to execute certian code when a certian action ID is detected

$todo = sub {
  my $ref = shift;
  print "Got a Bite!\n";
  print Dumper $ref;
};

$hashref = $man->action("Queues","1234")
$man->reg_actionid("1234",$todo);



=head2 listen([$timeout])

listen for a certian amount of time (undef=forever) 
for above events and process them.


=cut




sub init($;$) {
  my $proto = shift;
  my $args = shift;
  my $class = ref($proto) || $proto;
  my $self = { _events => undef , 
	       _connected => undef
	     };

  $self->{_host} = $args->{-host} || "localhost";
  $self->{_port} = $args->{-port} || 5038;
  $self->{_user} = $args->{-user} || undef;
  $self->{_secret} = $args->{-secret} || undef; 
  my $me = bless $self,$class;
  $me->connect;
  $me;
}

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

sub error($$) {
  my($self,$error) = @_;
  die $error;
}

sub chomp($$) {
  my ($self,$str) = @_;
  $str =~ s/[\r\n]//g;
  $str;
}

sub input($;$) {
  my ($self,$m) = @_;
  my $i;
  my @r;
  my $s = $self->{_sock};

  if($m) {
    $i = <$s>;
    return $self->chomp($i);
  }

  while($i =  <$s>) {
    return @r if($i eq "\r\n");
    $i =~ s/[\r\n]+$//g;
    push @r,$i;
  }
  
  @r;
}

sub output($$) {
  my ($self,$data) = @_;
  my $s = $self->{_sock};
  print $s $data;
}

sub format($$;$) {
  my ($self,$data) = @_;
  my $ret;

  if(ref $data eq "HASH" ) {
    foreach my $k (sort keys %$data) {
      $ret .= "$k: $data->{$k}\r\n";
    }
    $ret .= "\r\n";
  }
  else {
    $data =~ s/: [\n\r]+/: 0\r\n/g;

    
    my %h = $data =~ /^([^:]+)\s*:\s*([^\r\n]*)/mg;
    $ret = \%h;
  }

  $ret;

}


sub send($$;$$) {
  my ($self,$param,$reply,$id) = @_;
  # 0 hash 1 array 2 scalar
  $reply ||= 0;


  $self->output($self->format($param));
  my $r;
  my $in;
  my $done = 0;

  do {

    @in = $self->input;

    if($id) {
      foreach(@in) {

	$done++ if(/ActionID: $id/);
      }
    }
  } while($id and ! $done);


  if($reply == 0) {
    $r = $self->format(join "\n",@in);
  }
  elsif($reply == 1) {
    $r = [@in];
  }
  elsif($reply == 2) {
    $r = join "\n",@in;
  }

  $r;
}






sub connect($) {
  my $self = shift;

  $self->{_sock} = new IO::Socket::INET( Proto => 'tcp',
					 PeerAddr => $self->{_host},
					 PeerPort => $self->{_port}
				       ) or return $self->error("Connection refused $self->{_host} port $self->{_port}");
  
  $self->{_sock}->autoflush(1);
  my $title = undef;
  ($title,$self->{_version}) = $self->input(1) =~ /^([^\/]+)\/(.*)/;

  if ($title !~ /Asterisk Call Manager/) {
    return $self->error("Com Error");
  }


  my $reply = $self->send({ Action => 'Challenge',
			    AuthType => 'MD5'});





  my $auth;

  if($reply->{Response} eq 'Success') {
    my $md5 = new Digest::MD5;
    $md5->add($reply->{Challenge});
    $md5->add($self->{_secret});

    $auth = $self->send({Action => 'Login',
			 AuthType => 'MD5',
			 Username => $self->{_user},
			 Key => $md5->hexdigest
			});
  } 
  else {
    $auth = $self->send({ Action => 'Login',
			  Username => $user,
			  Secret => $secret
			});

  }

  

  unless ($auth->{Response} eq 'Success' and $auth->{Message} eq 'Authentication accepted' ) {
    return $self->error("Connection Failed");
  }



}



sub action($$;$) {
  my ($self,$action,$id) = @_;
  $id ||= $$ unless($action eq "Logoff");
  $id = undef if($id == -1);
  my $h = { Action => $action };
  $h->{ActionID} = $id if($id);
  $self->send($h,2,$id);
}


sub command($$) {
  my($self,$command) = @_;
  my $reply = $self->send({'Action' => 'Command',
			   'Command' => $command},2
			 );
  my ($ret) = $reply =~ /Response: Follows(.*?)--END COMMAND--/s;
  $ret;
}

sub disconnect($) {
  my ($self) = @_;
  $self->action("Logoff");
}




sub reg_event($$$) {
  my ($self,$event,$code) = @_;
  $self->{_events}->{$event} = $code;
}


sub reg_regex_event($$$) {
  my ($self,$event,$code) = @_;
  push @{$self->{_rx_events}},[$event,$code];
}



sub reg_actionid($$$) {
  my ($self,$actionid,$code) = @_;
  $self->{_actionids}->{$actionid} = $code;
}

sub listen($;$) {
 my ($self,$timeout) = @_;
 my $done = 0;
 $SIG{ALRM} = sub {$done=1};

 if($timeout) {
   alarm($timeout);
 }


 while(! $done and my $r = $self->format(join "\n",$self->input)) {
   #print Dumper $r;
   #print "$r->{Event}\n";
   my $func = $self->{_events}->{$r->{Event}} || $self->{_events}->{ALL} || $self->{_actionids}->{$r->{ActionID}} || undef;
   if(! $func ) {
     foreach my $k (@{$self->{_rx_events}}) {
       $func = $k->[1] if($r->{Event} =~ /$k->[0]/);
     }
   }

   &$func($r) if(ref $func);
 }
 
}



