#!/usr/bin/env perl

package Amazon::CLI::EC2;

use strict;
use warnings;

use Amazon::API::EC2;
use Carp::Always;
use Carp;
use Data::Dumper;
use English qw( -no_match_vars );
use Getopt::Long qw(:config no_ignore_case);
use JSON;
use List::Util qw(pairs);
use Term::ANSIColor;
use Text::ASCIITable;

use Readonly;

Readonly our $TRUE  => 1;
Readonly our $FALSE => 0;

Readonly our $EMPTY     => q{};
Readonly our $OCTOTHORP => q{#};

Readonly::Hash our %COLORS => (
  running    => 'green',
  terminated => 'red',
  stopped    => 'magenta',
  pending    => 'yellow',
);

our $VERSION = '0.01';

caller or __PACKAGE__->main();

########################################################################
{
  my $service;

  sub _api_method {
    my ( $method, @args ) = @_;

    $service //= Amazon::API::EC2->new(log_level => 'info');

    my $result = eval { return $service->$method(@args); };

    if ( $EVAL_ERROR && ref($EVAL_ERROR) =~ /API::Error/xsm ) {
      print Dumper( [ $EVAL_ERROR->get_response, $EVAL_ERROR->get_error ] );
    }
    elsif ($EVAL_ERROR) {
      croak $EVAL_ERROR;
    }

    return $result;
  }
}
########################################################################

########################################################################
sub describe_instances {
########################################################################
  my (@args) = @_;

  return _api_method( 'DescribeInstances', @args );
}

########################################################################
sub get_instances {
########################################################################
  my ($result) = @_;

  return map { @{ $_->{Instances} } } @{ $result->{Reservations} };
}

########################################################################
sub resolve_instance_name {
########################################################################
  my (@instances) = @_;

  foreach my $item (@instances) {

    my $tags = $item->{Tags};

    if ( $tags && @{$tags} ) {
      foreach my $p ( pairs map { @{$_}{qw(Key Value)} } @{$tags} ) {

        next if $p->[0] ne 'Name';
        $item->{Name} = $p->[1];
        last;
      }
    }
  }

  return @instances;
}

########################################################################
sub sort_instances {
########################################################################
  my (@instances) = @_;

  for (@instances) {
    $_->{Name} //= q{};
  }

  return sort { $a->{State}->{Name} cmp $b->{State}->{Name} or $a->{Name} cmp $b->{Name} } @instances;
}

########################################################################
sub create_instances_table {
########################################################################
  my (@instances) = @_;

  my $t = Text::ASCIITable->new(
    { headingText => 'EC2 Instances',
      allowANSI   => $TRUE
    }
  );

  my @columns = qw(InstanceId Name ImageId InstanceType PrivateIpAddress LaunchTime);

  $t->setCols( $OCTOTHORP, @columns, 'State' );

  my $row = 0;

  foreach my $item (@instances) {
    my $state = $item->{State}->{Name};

    my $ansi_color = colored( $state, $COLORS{$state} // 'red' );
    $t->addRow( ++$row, @{$item}{@columns}, $ansi_color );
  }

  return $t;
}

########################################################################
sub get_filters {
########################################################################
  my ($options) = @_;

  return
    if !$options->{filters};

  my @filter_list;

  foreach my $filter ( @{ $options->{filters} } ) {
    if ( $filter =~ /^Name=([^,]+),Values=(.*)?/xsm ) {
      my ( $name, $values ) = ( $1, $2 );

      my $value_list = [ split /,/xsm, $values ];
      push @filter_list, { Name => $name, Values => $value_list };
    }
    else {
      croak 'not a valid filter';
    }
  }

  return { Filters => \@filter_list };
}

########################################################################
sub usage {
########################################################################
  print {*STDOUT} <<"END_OF_HELP";
usage: $PROGRAM_NAME Options

Options      Description
-------      -----------
--help|h     help
--filters|f  filter list

Filters is a list of filter names and values.

Examples:

 --filters Name=instance-type,Values=t2.micro

 --filters Name=instance-id,Values=i-0628639cfba021f19

 --filters Name=instance-type,Values=t2.micro Name=instance-state-name,Values=running

END_OF_HELP
  return 0;
}

########################################################################
sub main {
########################################################################
  my @option_specs = ( 'help|h', 'filters:s{1,}' );

  my %options = ( filters => [] );

  GetOptions( \%options, @option_specs )
    or croak "$EVAL_ERROR";

  if ( $options{help} ) {
    return usage();
  }

  my $filters = get_filters( \%options );

  my $result = describe_instances($filters);

  my @instances = get_instances($result);

  resolve_instance_name(@instances);

  @instances = sort_instances(@instances);

  print {*STDOUT} create_instances_table(@instances);

  return 0;
}

1;
