#!perl

our $DATE = '2016-03-09'; # DATE
our $VERSION = '0.04'; # VERSION

use 5.010001;
# IFUNBUILT
use strict;
use warnings;
# END IFUNBUILT

my %Opts = (
    #input_format => undef,
    output_format => "text",
);
my $Input; # envres
my $Input_Form; # hash/aos/aoaos/aohos
my $Input_Obj; # TableData::Object::* object
my $SubCmd;
my $Output; # envres

sub _get_table_spec_from_envres {
    my $envres = shift;
    my $ff = $envres->[3]{'table.fields'};
    return undef unless $ff;
    my $spec = {fields=>{}};
    my $i = 0;
    for (@$ff) {
        $spec->{fields}{$_} = {pos=>$i};
        $i++;
    }
    $spec;
}

sub parse_cmdline {
    require Getopt::Panjang;

    my $opt_help;
    my %common_opts = (
        'output-format|f=s' => sub {
            my %a = @_;
            $Opts{output_format} = $a{value};
        },
        'json' => sub {
            $Opts{output_format} = 'json';
        },
        'version|v' => sub {
            say "td version ", ($main::VERSION // '?');
            exit 0;
        },
        'help|h|?' => sub { $opt_help++ },
    );

    my $res = Getopt::Panjang::get_options(spec => \%common_opts);
    my $remaining_argv = $res->[3]{'func.remaining_argv'};

    $SubCmd = shift @{ $remaining_argv } // '';
    @ARGV = @$remaining_argv;

    $opt_help++ unless $SubCmd;
    if ($opt_help) {
        if ($SubCmd eq 'sum') {
            print <<'_';
Usage:
  td sum < INPUT
_
        } elsif ($SubCmd eq 'avg') {
            print <<'_';
Usage:
  td avg < INPUT
_
        } elsif ($SubCmd eq 'rowcount') {
            print <<'_';
Usage:
  td rowcount < INPUT
_
        } elsif ($SubCmd eq 'colcount') {
            print <<'_';
Usage:
  td colcount < INPUT
_
        } elsif ($SubCmd eq 'select') {
            print <<'_';
Usage:
  td select <COL,COL2...> [--sort <COL,COL2,...>] < INPUT
_
        } elsif ($SubCmd eq 'sort') {
            print <<'_';
Usage:
  td sort <COL,COL2...> < INPUT
_
        } elsif ($SubCmd ne '') {
            die "Unknown subcommand '$SubCmd'\n";
        } else {
            print <<'_';
Usage:
  td [COMMON_OPTS] <SUBCOMMAND> [SUBCMD_OPTS] < INPUT
  td --version (or -v)
  td --help (or -h, -?)
Common options:
  --output-format=s, -f

Consult manpage/documentation for more details.
_
        }
        exit 0;
    } elsif ($res->[0] != 200 && (
        $res->[3]{'func.val_invalid_opts'} ||
            $res->[3]{'func.val_missing_opts'} ||
            $res->[3]{'func.ambiguous_opts'} ||
            # XXX later when subcommands have options too, allow unknown options
            # and re-get_options() with subcommand's spec
            $res->[3]{'func.unknown_opts'}
        )) {
        die "$res->[1]\n";
    }
}

sub _decode_json {
    require Cpanel::JSON::XS;

    state $json = Cpanel::JSON::XS->new->allow_nonref;
    $json->decode(shift);
}

sub get_input {
    require Data::Check::Structure;

    {
        local $/;
        $Input = _decode_json(~~<STDIN>);
    }

    # give envelope if not enveloped
    unless (ref($Input) eq 'ARRAY' &&
                @$Input >= 2 && @$Input <= 4 &&
                $Input->[0] =~ /\A[2-5]\d\d\z/ &&
                !ref($Input->[1])
            ) {
        $Input = [200, "Envelope added by tabledata", $Input];
    }

    # detect table form
    if (ref($Input->[2]) eq 'HASH') {
        $Input_Form = 'hash';
        require TableData::Object::hash;
        $Input_Obj = TableData::Object::hash->new($Input->[2]);
    } elsif (Data::Check::Structure::is_aos($Input->[2])) {
        $Input_Form = 'aos';
        require TableData::Object::aos;
        $Input_Obj = TableData::Object::aos->new($Input->[2]);
    } elsif (Data::Check::Structure::is_aoaos($Input->[2])) {
        $Input_Form = 'aoaos';
        my $spec = _get_table_spec_from_envres($Input);
        require TableData::Object::aoaos;
        $Input_Obj = TableData::Object::aoaos->new($Input->[2], $spec);
    } elsif (Data::Check::Structure::is_aohos($Input->[2])) {
        $Input_Form = 'aohos';
        my $spec = _get_table_spec_from_envres($Input);
        require TableData::Object::aohos;
        $Input_Obj = TableData::Object::aohos->new($Input->[2], $spec);
    } else {
        # simply pass it through
        $Input_Form = '';
    }
}

sub process {
    if (!$Input_Form) {
        $Output = $Input;
    } elsif ($SubCmd eq 'rowcount') {
        $Output = [200, "OK", $Input_Obj->row_count];
    } elsif ($SubCmd eq 'colcount') {
        $Output = [200, "OK", $Input_Obj->col_count];
    } elsif ($SubCmd =~ /\A(sort|select)\z/) {
        my $res;
        if ($SubCmd eq 'sort') {
            if ($Input_Form eq 'aohos') {
                $res = $Input_Obj->select_as_aohos(undef, undef, \@ARGV);
            } else {
                $res = $Input_Obj->select_as_aoaos(undef, undef, \@ARGV);
            }
        } elsif ($SubCmd eq 'select') {
            if ($Input_Form eq 'aohos') {
                $res = $Input_Obj->select_as_aohos(\@ARGV);
            } else {
                $res = $Input_Obj->select_as_aoaos(\@ARGV);
            }
        }

        my $ff = $res->{spec}{fields};
        my $tff = [];
        for (keys %$ff) {
            $tff->[$ff->{$_}{pos}] = $_;
        }

        $Output = [200, "OK", $res->{data},
                   {'table.fields'=>$tff}];
    } else {
        $Output = [400, "Unknown subcommand '$SubCmd'"];
    }
}

sub display_output {
    require Perinci::Result::Format::Lite;
    my $fres = Perinci::Result::Format::Lite::format(
        $Output, $Opts{output_format});

    # ux: prefix error message with program name
    if ($Output->[0] =~ /\A[45]/ && defined($Output->[1])) {
        $fres = "td: $fres";
    }

    print $fres;
}

# MAIN

parse_cmdline();
get_input();
process();
display_output();

1;
# ABSTRACT: Manipulate table data
# PODNAME: td

__END__

=pod

=encoding UTF-8

=head1 NAME

td - Manipulate table data

=head1 VERSION

This document describes version 0.04 of td (from Perl distribution App-td), released on 2016-03-09.

=head1 SYNOPSIS

Usage:

 % command-that-outputs-table-data-in-json | td <subcommand> [options ...]

Examples:

 # count number of rows (equivalent to "wc -l" Unix command)
 % osnames -l --json | td rowcount

 # count number of columns
 % osnames -l --json | td colcount

 # select some columns
 % osnames -l --json | td select value description

 # sort by column(s)
 % osnames -l --json | td sort value tags
 % osnames -l --json | td sort -- -value

 # NOT YET IMPLEMENTED
 # add a sum rows, which totals all numeric columns
 % list-files -l --json | td sum

 # NOT YET IMPLEMENTED
 # add an average rows, which averages all numeric columns
 % list-files -l --json | td avg

=head1 DESCRIPTION

B<EARLY RELEASE. MORE SUBCOMMANDS WILL BE ADDED.>

=head1 OPTIONS

=head1 HOMEPAGE

Please visit the project's homepage at L<https://metacpan.org/release/App-td>.

=head1 SOURCE

Source repository is at L<https://github.com/perlancar/perl-App-td>.

=head1 BUGS

Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=App-td>

When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.

=head1 SEE ALSO

L<TableDef>

L<TableData::Object>

=head1 AUTHOR

perlancar <perlancar@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2016 by perlancar@cpan.org.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
