#!/usr/bin/perl

use strict;
use warnings;

use Getopt::Long;
use SQL::Statement;
use LWP::UserAgent;
use HTTP::Request;
use JSON;

my $hostname = 'localhost';
my $port = 5984;

GetOptions("hostname=s" => \$hostname,
           "port=i" => \$port);

unless (scalar(@ARGV)) {
    print "USAGE: $0 [--host=hostname] [--port=port] sql\n";
    exit 1;
}
my $sql = $ARGV[0];

my $options = {
    hostname => $hostname,
    port     => $port,
};

# parse the SQL
my $parser = SQL::Parser->new('AnyData', {RaiseError => 0, PrintError => 0});
my $res = $parser->parse($sql);

unless ($res) {
    print '{ "error": "unable to parse SQL statement" }' . "\n";
    exit(1);
}

unless ($parser->structure->{command} eq 'SELECT') {
    print '{ "error":"only SELECT supported currently" }' . "\n";
    exit(1);
}

if (scalar(@{$parser->structure->{table_names}}) > 1) {
    print '{ "error":"unable to join against multiple databases" }' . "\n";
    exit(1);
}

# build and encode the map statement
my $json = encode_json({map => map_function($parser->structure)});

# make the request
my $user_agent = LWP::UserAgent->new();
$user_agent->timeout(1800);
$user_agent->env_proxy;

my $uri = 'http://' . $hostname . ':' . $port . '/' . $parser->structure->{table_names}->[0] . '/_temp_view';

my $request = HTTP::Request->new(POST => $uri, undef, $json);
$request->header('Content-Type' => 'application/json');

my $response = $user_agent->request($request);

# deal with the response
unless ($response->is_success()) {
    print $response->status_line() . ': ' . $response->content() . "\n";
    exit 1;
}

print $response->content;


# build map function
sub map_function
{
    my ($parsed) = @_;

    my $order   = order_clause($parsed->{sort_spec_list});
    my $columns = column_clause($parsed->{column_names}, $parsed->{col_obj});
    my $where   = where_clause($parsed->{where_clause});

    my $ret = 'function(doc) { ';
    if ($where) {
        $ret .= 'if ' . $where . ' { ';
    }
    $ret .= 'emit(' . $order . ', ' . $columns . ');';

    if ($where) {
        $ret .= ' }';
    }
    
    $ret .=' }';
    
    return $ret;
}

# generate the emit document
sub column_clause
{
    my ($columns, $col_obj) = @_;
    
    my @cols = ( );
    foreach my $col (@$columns) {
        if ($col eq '*') {
            push @cols, 'doc';
        } else {
            push @cols, $col_obj->{$col}->{alias} . ': doc.' . $col;
        }
    }

    return $cols[0]
        if (scalar(@cols) == 1);
        
    return '{ ' . join(", ", @cols) . ' }';
}

# generate the key
sub order_clause
{
    my ($order) = @_;
    
    return 'null'
        unless ($order && scalar(@$order));

    my @cols = ( );
    foreach my $col (@$order) {
        push @cols, keys %$col;
    }
    
    return '{ "' . $cols[0] . '": doc.' . $cols[0] . ' }'
        if (scalar(@cols) == 1);

    return '{ ' . join(", ", map { '"' . $_ . '": doc.' . $_ } @cols) . ' }';
}

# generate the conditional
sub where_clause
{
    my ($clause) = @_;
    
    return
        unless $clause;
        
    # end of tree?
    if ($clause->{type}) {
        return _col($clause);
    }

    return '(' . where_clause($clause->{arg1}) .
           ' ' . _op($clause->{op}) .
           ' ' . where_clause($clause->{arg2}) . ')';
}

sub _op
{
    my ($op) = @_;

    return '=='
        if ($op eq '=');
    return '||'
        if ($op eq 'OR');
    return '&&'
        if ($op eq 'AND');
    return '!='
        if ($op eq '<>');
    return $op;
}

sub _col
{
    my ($col) = @_;
    
    return 'doc.' . $col->{value}
        if ($col->{type} eq 'column');

    return "'" . $col->{value} . "'"
        if ($col->{type} eq 'string');
        
    return $col->{value};
}