#!/usr/bin/perl

use File::Util;
use File::Spec;
use File::Basename;
use Term::ReadKey;
use warnings;
use strict;

my $epname = undef;
my $eptype = undef;
my $obtype = undef;


unless (dirname(File::Spec->rel2abs(File::Spec->updir())) =~ /^.*\/endpoints$/){
    print "Sorry. This program must be run from your endpoints directory only.\n";
    exit(1);
}


GETEPTYPE:
print "Will this be an Outbound or Inbound Message Endpoint?(OB/IB): ";
$eptype = ReadLine(0);
chomp($eptype);
unless ($eptype =~ /OB|IB/) {
    print "There is no default. Please type OB or IB to continue!\n";
    goto GETEPTYPE;
}

if($eptype eq 'OB'){
  GETOBTYPE:
    print "Will the Outbound Message Endpoint be Single-Phased or Double-Phased?(SP/DP): ";
    $obtype = ReadLine(0);
    chomp($obtype);
    unless ($obtype =~ /SP|DP/) {
        print "There is no default. Please type SP or DP to continue!\n";
        goto GETOBTYPE;
    }
}

GETEPNAME:
print "Specifiy a name for your new Message Endpoint \n";
print q|Tip: Use "OB_[Name]" for Outbound or "IB_[Name]" for Inbound: |;
$epname = ReadLine(0);
chomp($epname);
unless ($epname) {
    print "There is no default. Please enter a name for your endpoint!\n";
    goto GETEPNAME;
}

my $f = File::Util->new();

# create the EP directory
$f->make_dir($epname,0755);

# the conf file
my $epconf =
q{
# Start with AES?
start = 1

# The popper frequency constant
popper_freq = 20

# Retry freq when soc fails
soc_retry = 10

# Inter Kernel Communication Parameters (must match AES config)
ikc_addr = 127.0.0.1
ikc_port = 12345

# SOAP Service Information PLEASE ADJUST
soap_proxy = 'http://ws.yourdomain.com/webservices.php'
soap_service = 'http://ws.yourdomain.com/soapservices.wsdl'

# SOAP Service Credentials (undef if not specified)
socuser = specify
socpass = specify

# STOMP Service Credentials (undef if not specified)
stcuser = specify
stcpass = specify

# MQ Address and port (defaults to value below)
mq_address = '127.0.0.1'
mq_port = 61614


};

# create the conf file
$f->write_file(
    file => "./$epname/endpoint.conf",
    bitmask => 0644,
    mode => 'append',
    content => $epconf,
);

my $epexe =
qq{#!/usr/bin/perl

# This is needed because this process is started
# relative to the main aes executable
BEGIN{
    unshift \@INC, 'endpoints/$epname';
}

use $epname;

my \$endpoint = $epname->new();
\$endpoint->run();
};

# create the exe file
$f->write_file(
    file => "./$epname/endpoint",
    bitmask => 0755,
    mode => 'append',
    content => $epexe,
);

my $ib =
q`
package `.$epname.q`;

use base POE::Component::Server::AsyncEndpoint::ChannelAdapter;

use POE;
use POE::Component::Server::AsyncEndpoint::ChannelAdapter::Stomp;
use POE::Component::Server::AsyncEndpoint::ChannelAdapter::SOAP;
use POE::Component::Server::AsyncEndpoint::ChannelAdapter::Config;
use JSON;
use Carp qw(croak);

sub new {

  my $class = shift;
  my $Alias = '`.$epname.q`';

  # init the channel adapter
  my $self = $class->SUPER::new(
    Alias => $Alias,
    OStates => [qw(
                    put`.$epname.q`
                    put`.$epname.q`_response
                    soap_response
                )],

  );

  # init the STOMP client
  my $stc = POE::Component::Server::AsyncEndpoint::ChannelAdapter::Stomp->spawn(
    Alias  => $Alias.'_STC',
    mq_address  => $self->{config}->mq_address,
    mq_port  => $self->{config}->mq_port,
    queue  => '/queue/QUEUE_NAME_HERE', # adjust to your needs
    direction  => 'IB',
    user  => $self->{config}->stcuser,
    pass  => $self->{config}->stcpass,
    rx_callback => {
      to_session => $Alias,
      to_handler => 'put`.$epname.q`',
    },
  );

  # init the SOAP client
  my $soc = POE::Component::Server::AsyncEndpoint::ChannelAdapter::SOAP->spawn(
    proxy => $self->{config}->soap_proxy,
    service => $self->{config}->soap_service,
    to_session => $Alias,
    to_handler => 'soap_response',
    retry_reconnect => 1,
  );

  # save the clients
  $self->{STC} = $stc;
  $self->{SOC} = $soc;

  return $self;

}

# run the endpoint
sub run {
  my $self = shift;
  $self->SUPER::run();
}


sub put`.$epname.q` {
  my ($self, $kernel, $session, $frame) = @_[ OBJECT, KERNEL, SESSION, ARG0 ];
  my $soc_call = 'put`.$epname.q`';
  unless($self->{SOC}->{soc_stat}){
    $kernel->delay_set($soc_call, $self->{config}->soc_retry, $frame);
  }
  $self->{msgid} = $frame->headers->{'message-id'};
  $self->{soc_call} = $soc_call;
  my $call = $soc->$soc_call(
    $self->{config}->socuser,
    $self->{config}->socpass,
    $frame->body
  );
}

# SOAP responses (including errors)
sub soap_response {
  my ( $self, $kernel, $session, $soc_ret ) = @_[ OBJECT, KERNEL, SESSION, ARG0 ];
  my $call  = undef;
  my $data  = undef;
  my $error = undef;

  if(ref $soc_ret eq 'ARRAY'){
    $call = $soc_ret->[0];
    $data = $soc_ret->[1];
    unless($call eq $self->{soc_call}){
      $error = 'ERROR: SOAP Response does not match current Call';
    }
    if($call =~ m/^-ERROR-$/){
      $error = $data;
      $call = $self->{soc_call};
    }
    $self->{soc_call} = undef;
  }
  else{
    $error = 'ERROR: SOAP Response is Invalid';
  }

  unless($error){
    $kernel->yield($call.'_response', $data);
  }
  else {
    my $logmsg = "ERROR: SOAP Call: $call Failed\n";
    $kernel->post($session, 'logit', 'alert', $logmsg);
    $kernel->delay_set($call, $self->{config}->soc_retry);
  }
}


sub put`.$epname.q`_response {
  my ( $self, $kernel, $session, $data ) = @_[ OBJECT, KERNEL, SESSION, ARG0 ];
  my $msgid = $self->{message_id};
  if ($data eq 'OK') {
    $self->{message_id} = undef;
    $kernel->call($self->{STC}->config('Alias'), 'ACK_message', $msgid);
    my $logmsg = "OK put`.$epname.q` para Message ID $msgid\n";
    $kernel->yield('logit', 'alert', $logmsg);
  }
  else {
    my $logmsg = "SOAP Call put`.$epname.q`FIFO was executed,, ".
      "but did not return OK: ($data)\n";
    $kernel->yield('logit', 'alert', $logmsg);
    $kernel->delay_set('put`.$epname.q`', $self->{config}->soc_retry, $frame);
  }
}


1;

`;


my $obsp =
q`
package `.$epname.q`;

use base POE::Component::Server::AsyncEndpoint::ChannelAdapter;

use POE;
use POE::Component::Server::AsyncEndpoint::ChannelAdapter::Stomp;
use POE::Component::Server::AsyncEndpoint::ChannelAdapter::SOAP;
use POE::Component::Server::AsyncEndpoint::ChannelAdapter::Config;
use JSON;
use Carp qw(croak);

sub new {

  my $class = shift;
  my $Alias = `.$epname.q`;

  # init the endpoint
  my $self = $class->SUPER::new({
    Alias => $Alias,
    OStates => [qw(
                    pop`.$epname.q`FIFO
                    pop`.$epname.q`FIFO_response
                    popped`.$epname.q`FIFO
                    popped`.$epname.q`FIFO_response
                    soap_response
                    stomp_receipt
                )],
  });

  # init the STOMP client
  my $stc = POE::Component::Server::AsyncEndpoint::ChannelAdapter::Stomp->spawn(
    Alias         => $Alias . '_STC',
    mq_address    => $self->{config}->mq_address,
    mq_port       => $self->{config}->mq_port,
    queue         => '/queue/QUEUE_NAME_HERE', # adjust to your needs
    direction     => 'OB',
    user          => $self->{config}->socuser,
    pass          => $self->{config}->socpass,
    rcpt_callback => {
      to_session => $Alias,
      to_handler => 'stomp_receipt',
    },

  );

  # init the SOAP client
  my $soc = POE::Component::Server::AsyncEndpoint::ChannelAdapter::SOAP->spawn(
    proxy   => $self->{config}->soap_proxy,
    service => $self->{config}->soap_service,
    to_session => $Alias,
    to_handler => 'soap_response',
    retry_reconnect => 1,
  );

  # save the clients
  $self->{STC} = $stc;
  $self->{SOC} = $soc;

  POE::Kernel->post($Alias, 'pop`.$epname.q`FIFO' );
  return $self;

}

# run the endpoint
sub run {
  my $self = shift;
  $self->SUPER::run();
}

# pop the fifo
sub pop`.$epname.q`FIFO {
  my ( $self, $kernel, $session ) = @_[ OBJECT, KERNEL, SESSION ];
  # reset popper timer
  $kernel->delay_set('pop`.$epname.q`FIFO', $self->{config}->popper_freq);
  # not ready to pop...
  return unless ($self->{ikc_stat} && $self->{STC}->{stc_stat} && $self->{SOC}->{soc_stat});
  # no new pop until we flush the current one
  return if defined($self->{fifo_id});

  my $soc_call = 'pop`.$epname.q`FIFO';
  $self->{soc_call} = $soc_call;
  $self->{SOC}->$soc_call(
    $self->{config}->socuser,
    $self->{config}->socpass
  );
}

# marks fifo as popped
sub popped`.$epname.q`FIFO {
  my ( $self, $kernel, $session ) = @_[ OBJECT, KERNEL, SESSION ];
  my $soc_call = 'popped`.$epname.q`FIFO';
  unless($self->{SOC}->{soc_stat}){
    $kernel->delay_set($soc_call, $self->{config}->soc_retry);
  }
  $self->{soc_call} = $soc_call;
  $self->{SOC}->$soc_call(
    $self->{config}->socuser,
    $self->{config}->socpass,
    $self->{fifo_id},
  );
}


# STOMP receipt from MQ
sub stomp_receipt {
  my ($self, $kernel, $frame) = @_[OBJECT, KERNEL, ARG0];
  my $receipt_id = $frame->headers->{receipt};
  my $fifo_id    = $self->{fifo_id};

  if ($receipt_id != $fifo_id) {
    my $logmsg = "FATAL ERROR: Receipt id does not match expected\n";
    $kernel->yield('logit', 'emergency', $logmsg);
    croak $logmsg;
  }

  $kernel->yield('popped`.$epname.q`FIFO');

}

# SOAP responses (including errors)
sub soap_response {
  my ( $self, $kernel, $session, $soc_ret ) = @_[ OBJECT, KERNEL, SESSION, ARG0 ];
  my $call  = undef;
  my $data  = undef;
  my $error = undef;

  if(ref $soc_ret eq 'ARRAY'){
    $call = $soc_ret->[0];
    $data = $soc_ret->[1];
    unless($call eq $self->{soc_call}){
      $error = 'ERROR: SOAP Response does not match current Call';
    }
    if($call =~ m/^-ERROR-$/){
      $error = $data;
      $call = $self->{soc_call};
    }
    $self->{soc_call} = undef;
  }
  else{
    $error = 'ERROR: SOAP Response is Invalid';
  }

  unless($error){
    $kernel->yield($call.'_response', $data);
  }
  else {
    my $logmsg = "ERROR: SOAP Call: $call Failed\n";
    $kernel->post($session, 'logit', 'alert', $logmsg);
    $kernel->delay_set($call, $self->{config}->soc_retry);
  }
}

sub pop`.$epname.q`FIFO_response {
  my ( $self, $kernel, $session, $data ) = @_[ OBJECT, KERNEL, SESSION, ARG0 ];
  my $stc    = $self->{STC};
  my $fifo_id = $self->{fifo_id};
  my $nframe = $stc->stomp->send({
    destination => $stc->config('Queue'),
    data        => $data,
    receipt     => $fifo_id,
  });

  my $logmsg = "OK SOAP pop`.$epname.q`FIFO and sent to the MQ";
  $kernel->post($session, 'logit', 'alert', $logmsg);
  $kernel->post($stc->config('Alias'), 'send_data', $nframe );
}

sub popped`.$epname.q`FIFO_response {
  my ( $self, $kernel, $session, $data ) = @_[ OBJECT, KERNEL, SESSION, ARG0 ];
  if($data eq 'OK'){
    $self->{fifo_id} = undef;
    $self->{params}  = undef;
  }
  else{
    my $logmsg = "SOAP Call popped`.$epname.q`FIFO was executed, ".
        "but did not return OK: ($data)\n";
    $kernel->post($session, 'logit', 'alert', $logmsg);
    $kernel->delay_set('popped`.$epname.q`FIFO_response', $self->{config}->soc_retry);
  }
}


1;

`;

my $obdp =
q`
package `.$epname.q`;

use base POE::Component::Server::AsyncEndpoint::ChannelAdapter;

use POE;
use POE::Component::Server::AsyncEndpoint::ChannelAdapter::Stomp;
use POE::Component::Server::AsyncEndpoint::ChannelAdapter::SOAP;
use POE::Component::Server::AsyncEndpoint::ChannelAdapter::Config;
use JSON;
use Carp qw(croak);

sub new {

  my $class = shift;
  my $Alias = '`.$epname.q`';

  # init the endpoint
  my $self = $class->SUPER::new(
    Alias => $Alias,
    OStates => [qw(
                   pop`.$epname.q`FIFO
                   pop`.$epname.q`FIFO_response
                   get`.$epname.q`
                   get`.$epname.q`_response
                   popped`.$epname.q`FIFO
                   popped`.$epname.q`FIFO_response
                   soap_response
                   stomp_receipt
               )],
  );

  # init the STOMP client
  my $stc = POE::Component::Server::AsyncEndpoint::ChannelAdapter::Stomp->spawn(
    Alias         => $Alias . '_STC',
    mq_address    => $self->{config}->mq_address,
    mq_port       => $self->{config}->mq_port,
    queue         => '/queue/QUEUE_NAME_HERE', # adjust to your needs
    direction     => 'OB',
    user          => $self->{config}->stcuser,
    pass          => $self->{config}->stcpass,
    rcpt_callback => {
      to_session => $Alias,
      to_handler => 'stomp_receipt',
    },
  );

  # init the SOAP client
  my $soc = POE::Component::Server::AsyncEndpoint::ChannelAdapter::SOAP->spawn(
    proxy   => $self->{config}->soap_proxy,
    service => $self->{config}->soap_service,
    to_session => $Alias,
    to_handler => 'soap_response',
    retry_reconnect => 1,
  );

  # save the clients
  $self->{STC} = $stc;
  $self->{SOC} = $soc;

  # init the fifo popper
  POE::Kernel->post($Alias, 'pop`.$epname.q`FIFO');
  return $self;

}

# run the endpoint
sub run {
  my $self = shift;
  $self->SUPER::run();
}

# pop the fifo
sub pop`.$epname.q`FIFO {
  my ( $self, $kernel, $session ) = @_[ OBJECT, KERNEL, SESSION ];
  # reset popper timer
  $kernel->delay_set('pop`.$epname.q`FIFO', $self->{config}->popper_freq);
  # not ready to pop...
  return unless ($self->{ikc_stat} && $self->{STC}->{stc_stat} && $self->{SOC}->{soc_stat});
  # no new pop until we flush the current one
  return if defined($self->{fifo_id});

  my $soc_call = 'pop`.$epname.q`FIFO';
  $self->{soc_call} = $soc_call;
  $self->{SOC}->$soc_call(
    $self->{config}->socuser,
    $self->{config}->socpass,
  );
}

# main OB WebService
sub get`.$epname.q` {
  my ( $self, $kernel, $session ) = @_[ OBJECT, KERNEL, SESSION ];
  my $soc_call = 'get`.$epname.q`';
  unless($self->{SOC}->{soc_stat}){
    $kernel->delay_set($soc_call, $self->{config}->soc_retry);
  }
  $self->{soc_call} = $soc_call;
  $self->{SOC}->$soc_call(
    $self->{config}->socuser,
    $self->{config}->socpass,
    # Adjust to your needs (see soap_response and pop`.$epname.q`FIFO_response)
    $self->{params}->{document_id},
  );

}

# marks fifo as popped
sub popped`.$epname.q`FIFO {
  my ( $self, $kernel, $session ) = @_[ OBJECT, KERNEL, SESSION ];
  my $soc_call = 'popped`.$epname.q`FIFO';
  unless($self->{SOC}->{soc_stat}){
    $kernel->delay_set($soc_call, $self->{config}->soc_retry);
  }
  $self->{soc_call} = $soc_call;
  $self->{SOC}->$soc_call(
    $self->{config}->socuser,
    $self->{config}->socpass,
    $self->{fifo_id},
  );
}

# STOMP receipt from MQ
sub stomp_receipt {
  my ( $self, $kernel, $session, $frame ) = @_[ OBJECT, KERNEL, SESSION, ARG0 ];
  my $receipt_id = $frame->headers->{receipt};
  my $fifo_id    = $self->{fifo_id};

  if ($receipt_id != $fifo_id) {
    my $logmsg = "FATAL ERROR: Receipt id does not match expected\n";
    $kernel->yield('logit', 'emergency', $logmsg);
    croak $logmsg;
  }

  $kernel->yield('popped`.$epname.q`FIFO');
}

# SOAP responses (including errors)
sub soap_response {
  my ( $self, $kernel, $session, $soc_ret ) = @_[ OBJECT, KERNEL, SESSION, ARG0 ];
  my $call  = undef;
  my $data  = undef;
  my $error = undef;

  if(ref $soc_ret eq 'ARRAY'){
    $call = $soc_ret->[0];
    $data = $soc_ret->[1];
    unless($call eq $self->{soc_call}){
      $error = 'ERROR: SOAP Response does not match current Call';
    }
    if($call =~ m/^-ERROR-$/){
      $error = $data;
      $call = $self->{soc_call};
    }
    $self->{soc_call} = undef;
  }
  else{
    $error = 'ERROR: SOAP Response is Invalid';
  }

  unless($error){
    $kernel->yield($call.'_response', $data);
  }
  else {
    my $logmsg = "ERROR: SOAP Call: $call Failed\n";
    $kernel->post($session, 'logit', 'alert', $logmsg);
    $kernel->delay_set($call, $self->{config}->soc_retry);
  }
}

sub pop`.$epname.q`FIFO_response {
  my ( $self, $kernel, $session, $data ) = @_[ OBJECT, KERNEL, SESSION, ARG0 ];
  my $retval = from_json( $data );
  if ( my $fifo_id = $retval->{fifo_id} ) {
    $self->{fifo_id} = $fifo_id;
    $self->{params}  = from_json( $retval->{params} );
    $kernel->post( $session, 'get`.$epname.q`' ) if defined $self->{fifo_id};
  } else {
    $self->{fifo_id} = undef;
    $self->{params}  = undef;
  }
}

# main web service response
sub get`.$epname.q`_response {
  my ( $self, $kernel, $session, $data ) = @_[ OBJECT, KERNEL, SESSION, ARG0 ];
  my $stc    = $self->{STC};
  my $fifo_id = $self->{fifo_id};
  my $nframe = $stc->stomp->send({
    destination => $stc->config('Queue'),
    data        => $data,
    receipt     => $fifo_id,
  });

  my $logmsg = "OK SOAP get`.$epname.q` and sent to the MQ";
  $kernel->post($session, 'logit', 'alert', $logmsg);
  $kernel->post($stc->config('Alias'), 'send_data', $nframe );
}

sub popped`.$epname.q`FIFO_response {
  my ( $self, $kernel, $session, $data ) = @_[ OBJECT, KERNEL, SESSION, ARG0 ];
  # Example only... adjust to your needs
  if ( $data eq 'OK' ) {
    $self->{fifo_id} = undef;
    $self->{params}  = undef;
  }
  else {
    my $logmsg = "SOAP Call popped`.$epname.q` executed fine, ".
      "but did not restun OK: ($data)\n";
    $kernel->post($session, 'logit', 'alert', $logmsg);
    $kernel->delay_set('popped`.$epname.q`FIFO', $self->{config}->soc_retry);
  }
}

1;

`;


my $content = undef;

if($eptype eq 'IB'){
    $content = $ib;
}
elsif($eptype eq 'OB'){
    if($obtype eq 'SP'){
        $content = $obsp;
    } else {
        $content = $obdp;
    }
}


# create the module file
$f->write_file(
    file => "./$epname/$epname.pm",
    bitmask => 0644,
    mode => 'append',
    content => $content,
);

