#!perl -w

use strict;
use warnings;
use Net::Fastly;
use File::Basename;
use Data::Dumper;
use POSIX qw(strftime);
use YAML;

BEGIN {
    eval {
      require Term::ShellUI;
      Term::ShellUI->import();
      require Term::ANSIColor;
      Term::ANSIColor->import(qw(:constants));
  
    };

    die "You must install Term::ShellUI and Term::ANSIColorin order to run the Fastly shell\n" if $@;
}

=head1 NAME

fastly - a command line shell for interacting with the Fastly infrastructure

=head1 USAGE

    fastly [option[s]] 

=head1 CONFIGURATION

You can either have a config file in either ~/.fastly or /etc/fastly with

    user     = <login>
    password = <password>
    
B<NOTE:> For the time being you must use username and password, not API key. This may change in the future.

Alternatively you can pass in any of those options on the command line

    fastly --user=<login> --password=<password>
    
=head1 PROXYING

There are three ways to proxy:

The first method is to put a proxy option in your .fastly file (or pass it in on)

    proxy = http://localhost:8080
    
The second is to pass it in on the command line

    fastly --user <login> --password <password> --proxy http://localhost:8080

Lastly, the third method is to set your C<https_proxy> environment variable. So, in Bash

    % export https_proxy=http://localhost:8080

or in CSH or TCSH

    % setenv https_proxy=http://localhost:8080    

=head1 DESCRIPTION
    

=cut

my %opts   = Net::Fastly::get_options($ENV{HOME}."/.fastly", "/etc/fastly");
my $fastly = Net::Fastly->new(%opts);

my $customer = $fastly->current_customer;



my $term = Term::ShellUI->new(app => "fastly", keep_quotes => 0,  history_file => $ENV{HOME}."/.fastly_history", prompt => "fastly> ");
          
#$term->{debug_complete}=5;
 
print 'Using '.$term->{term}->ReadLine."\n";
# precache the list of services

my $basecommands = { 
    "help" => {
        desc => "Print helpful information",
        args => sub { shift->help_args(undef, @_); },
        method => sub { shift->help_call(undef, @_); }
    },
    "quit" => { 
        desc => "Quit", 
        maxargs => 0,
        method => sub { shift->exit_requested(1) },
        exclude_from_history => 1,
    },
    "who" => {
        desc => "Print who is logged in",
        proc => \&who,
    }, 
    "show" => {
        desc => "Display objects",
        cmds => {
            service => {
                desc => "Show a service",
                doc => "Lists services",
                minargs => 1,
                maxargs => 3,
                args => [\&list_service_complete, sub { ['version'] }, \&list_version],
                proc => \&show_service,
            },
            services => {
                desc => "List services",
                proc => \&show_services,
            },
            versions => {
                desc    => "Show all the versions for a service",
                cmds    => {
                    service => {
                        desc    => "The service name",
                        minargs => 1,
                        maxargs => 1,
                        args    => [\&list_service_complete ],
                        proc    => \&show_versions,
                    }
                }
            },
            stats => {
                desc => "Show stats for a service",
                cmds => {
                    service => {
                        desc    => "The service name",
                        minargs => 1,
                        args => [\&list_service_complete, sub { ['aggregate'] }, sub { ['by']}, sub { [qw(minute hour day)]}, sub { ['split'] }, sub { ['by']} , sub { ['datacenter'] }, undef],
                        proc => \&show_stats_service,
                    },
                    # all => {
                    #                    args => [sub { ['aggregate'] }, sub { ['by']}, sub { [qw(minute hour day)]}, sub { ['split'] }, sub { ['by']} , sub { ['datacenter'] }, undef],
                    #                    proc => \&show_stats_all,
                    #                    }
                }        
            },
            diff => {
                desc => "Difference between two versions",
                minargs => 5,
                maxargs => 5,
                proc => \&diff,
                args => [\&list_service_complete, sub { ['version'] }, \&list_version, sub { ['to'] }, \&list_version, undef],
            },
        }
    },
    "create" => {
        desc => "Create a new object",
        cmds => {
            service => {
                desc    => "The service name",
                minargs => 1,
                args => [\&list_service_complete, sub { ['version'] }, \&list_version, \&service_models, \&name_map_complete, \&model_args],
                proc => \&create,
            },
            
        },
    },
    "add" => {
        desc => "Add a backend to a director or a director to an origin",
        cmds => {
           service => {
                desc    => "The service name",
                minargs => 1,
                args    => [\&list_service_complete, sub { ['version'] }, \&list_version, sub { ['backend', 'director'] }, \&list_target,  sub { ['to'] }, \&list_target_type, \&list_target],
                proc    => \&add,
            },
        }
    },
    "delete" => {
        desc => "Delete an object",
        cmds => {
            service => {
                desc    => "The service name",
                minargs => 1,
                args => [\&list_service_complete, sub { ['version'] }, \&list_version, \&service_models, \&list_objects, undef],
                proc => \&delete,
            }
        }
    },
    "set" => {
        desc => "Update an object",
        cmds => {
            service => {
                desc    => "The service name",
                minargs => 5,
                args => [\&list_service_complete, sub { ['version'] }, \&list_version, \&service_models, \&list_objects, \&model_args],
                proc => \&set,
            },
            customer => {
                desc => "Assume a customer",
                args     => [\&list_customers_complete], 
                proc     => \&set_customer,
            }
        }
    },
    "upload" => {
        desc => "Upload a custom VCL file",
        cmds => {
            service => {
                desc    => "The service name",
                minargs => 5,
                args => [\&list_service_complete, sub { ['version'] }, \&list_version, sub { ['vcl']}, \&list_objects, sub { ['from'] }, sub { ['file'] },
                        sub { shift->complete_files(@_) }, undef],
                proc => \&upload,
            }
        }
    },
    "download" => {
        desc => "Download a vcl file",
        cmds => {
            service => {
                desc    => "The service name",
                minargs => 5,
                args => [\&list_service_complete, sub { ['version'] }, \&list_version, sub { ['vcl'] }, \&list_objects, sub { ['to'] }, sub { ['file'] }, undef],
                proc => \&download,
            }
        }
    },
    "validate" => {
        desc => "Check to see that the currently uploaded VCL is valid",
        cmds => {
            service => {
                desc    => "The service name",
                minargs => 3,
                maxargs => 3,
                args => [\&list_service_complete, sub { ['version'] }, \&list_version],
                proc => \&validate,
            }
        }
    },
    "dump" => {
        desc => "Show the generated VCL for a given service",
        cmds => {
            service => {
                desc    => "The service name",
                minargs => 3,
                maxargs => 3,
                args => [\&list_service_complete, sub { ['version'] }, \&list_version],
                proc => \&dump,
            }
        }
    },
    "activate" => {
        desc => "Activate a version for use",
        cmds => {
            service => {
                desc    => "The service name",
                minargs => 3,
                maxargs => 3,
                args => [\&list_service_complete, sub { ['version'] }, \&list_version],
                proc => \&activate,
            }
        }
    },
    "deactivate" => {
        desc => "Deactive a service (note, this makes it not serve)",
        cmds => {
            service => {
                desc    => "The service name",
                minargs => 1,
                maxargs => 3,
                args => [\&list_service_complete, sub { ['version'] }, \&list_version],
                proc => \&deactivate,
            }
        }
    },
    "purge" => {
        desc => "Remove objects from the cache",
        cmds => {
            service => {
                desc    => "The service name",
                minargs => 1,
                maxargs => 1,
                args => [\&list_service_complete],
                proc => \&purge_service,
            }, 
            url => {
                desc    => "A url",
                minargs => 1,
                maxargs => 1,
                proc    => \&purge_url,
            },
            key => {
                desc    => "The service name",
                minargs => 3,
                maxargs => 3,
                args => [undef, sub { ['service' ] }, \&list_service_complete],
                proc => \&purge_key,
            }
        }
    },
    "clone" => {
        desc => "Clone a configuration so that the new version can be modified",
        cmds => {
            service => {
                desc    => "The service name",
                minargs => 3,
                maxargs => 3,
                args => [\&list_service_complete, sub { ['version'] }, \&list_version],
                proc => \&clone,
            }
        }
    }
};


sub who {
    if ($fastly->client->fully_authed) {
        my $user = $fastly->current_user;
        print "Logged in as ".$user->name." <".$user->login."> of ".$user->customer->name."  [". $user->customer->id ."]\n";
        print "Currently acting as customer ".$customer->name." [".$customer->id."]\n" unless $customer->id eq $user->customer->id;
    } else {
        print "Logged with api key as ".$customer->name."\n";
    } 
}

sub set_customer {
    my $name = shift;
    return (BOLD, RED, "You are not an admin", RESET, "\n") unless $fastly->fully_authed && $fastly->current_user->can_do('admin');
    my %customers = map { $_->name => $_ } $fastly->list_customers;
    my $tmp  = $customers{$name};
    unless ($tmp) {
        print_error("Can't find the customer '$name'", RESET, "\n");
        return;       
    }
    $fastly->set_customer($tmp->id);
    $customer = $tmp;
    print "Set assumed customer to ".$customer->name." [".$customer->id."]\n";
}

sub name_map_complete {
    my $t = shift;
    my $c = shift;
    my $commands = $fastly->commands;
    my $command  = $commands->{"$c->{args}->[3].create"};
    if ($command->{name}) {
        return (RED . "    \nNAME" . RESET);
    } else {
        return ["MAP"];
    }
}

sub show_stats_service {
    my $service_name = shift;
    my ($service)    = $fastly->search_services(name => $service_name);
    show_stats($service, @_);
}

sub show_stats {
    my $service = shift; 
    my $type = "minutely";
    if ($_[2]) {
        $type = "hourly" if ($_[2] eq 'hour');
        $type = "daily" if ($_[2] eq 'day');
    }

    my $stats = eval { $service->stats($type) }; 
    unless ($stats) {
    my $str = "$@";
        print BOLD, RED, "Error: ", RESET, $str,"\n";
        return;
    }

    my $split = 1;
    
    printf(BOLD."%-21s%-5s%-10s%-10s%-6s%-10s%-6s%5s   %-7s %-7s %-7s".RESET."\n", "Time", "DC", "Bytes", "Requests", "Hits", "Misses", "Pass", "%", "200", "304", "5xx");

    unless (keys %$stats) {
        print BOLD, RED, "No stats available", RESET, "\n";
        return;
    }
  
    my @stats;
    my $last;
    my $total = {start_time => time, datacenter => ''} ;
    foreach my $datacenter (keys %$stats) {
        my $stat = $stats->{$datacenter};
        next unless ref($stat);
        $stat->{datacenter} = $datacenter;
        $stat->{start_time} = $stats->{recorded};
        foreach (qw(hits requests pass uncacheable body_size header_size miss status_5xx status_200 status_304)) {
            $total->{$_} ||= 0;
            $total->{$_} += $stat->{$_} || 0;
        }
        if ($split) {
            push @stats, $stat;
        } else {
            unless ($last) {
                $last = $stat;
                next;
            }
            if ($last->{start_time} != $stat->{start_time}) {
                push @stats, $last;
                $last = $stat;
            } else {
                foreach (qw(hits requests pass uncacheable body_size header_size miss status_5xx status_200 status_304)) {
                    $last->{$_} += $stat->{$_};
                }
            }
        }
    }
    push @stats, $last unless $split;
    push @stats, undef, $total;
    foreach my $stats (@stats) {
        unless ($stats && keys %$stats>2) {
            print "----\n";
            next;
        }
        my $ymd = strftime "%Y:%m:%d", gmtime($stats->{start_time});
        my $hms = strftime "%H:%M:%S", gmtime($stats->{start_time});
        
        my $size = $stats->{body_size} + $stats->{header_size};
        if ($size > 1024*1024*1024) {
            $size = sprintf("%.2f GB", $size / 1024 / 1024 / 1024);
        } elsif ($size > 1024*1024) {
            $size = sprintf("%.2f MB", $size / 1024 / 1024);
        } elsif ($size > 1024) {
            $size = sprintf("%.2f KB", $size / 1024);
        } else {
            $size = sprintf("%d B", $size);
        }
        my $hitp = "00.0%";
        if ($stats->{hits}) {
            $hitp = sprintf("%.1f%%", (($stats->{hits}/($stats->{requests}-$stats->{pass}-$stats->{uncacheable}))*100));
        }
        my $dc = "ALL";
        $dc = $stats->{datacenter} if ($split);
        printf("%s%-11s%-10s%s%s%-5s%s%-10s%-10s%-6s%-10s%-6s%-6s  %-7s %-7s %s%-7s%s\n", RED, $ymd, $hms, RESET, BLUE, $dc, RESET, $size, $stats->{requests}, $stats->{hits}, $stats->{miss}, $stats->{pass}, $hitp, $stats->{status_200}, $stats->{status_304}, RED, $stats->{status_5xx}, RESET);
    }
}

sub clone {
    my $service_name   = shift;
    shift;
    my $version_number = shift;
    
    my ($service) = $fastly->search_services(name => $service_name);
    my $version   = $service->version($version_number);
    my $new       = eval { $version->clone };
    if ($@) {
        print_error($@);
    } else {
        print "Version $version_number cloned to ".$new->number."\n";
    }
}

sub dump {
    my $service_name   = shift;
    shift;
    my $version_number = shift;

    my ($service) = $fastly->search_services(name => $service_name);
    my $version   = $service->version($version_number);
    my $vcl       = eval { $version->generated_vcl };
    if ($@) {
        print_error($@);
    } else {
        print present({$vcl->_as_hash});
    }
}

sub validate {
    my $service_name   = shift;
    shift;
    my $version_number = shift;

    my ($service)  = $fastly->search_services(name => $service_name);
    my $version    = $service->version($version_number);
    my $valid      = eval { $version->validate };
    
    if ($@) {
        print_error($@);
    } else {
        print "Config validated\n";
    }
}

sub activate {
    my $service_name   = shift;
    shift;
    my $version_number = shift;

    my ($service)  = $fastly->search_services(name => $service_name);
    my $version    = $service->version($version_number);
    my $response   = eval { $version->activate};
    
    if ($@) {
        print_error($@);
    } else {
        print "Set $service_name version ".$version->number." to active\n";
    }
}

sub deactivate {
    my $service_name   = shift;
    shift;
    my $version_number = shift;

    my ($service)  = $fastly->search_services(name => $service_name);
    my $version    = $service->version($version_number);
    my $response   = eval { $version->deactivate};
    
    if ($@) {
        print_error($@);
    } else {
        print "Service $service_name version ".$version->number." has been deactivated.\n";
    }
}

sub purge_service {
    my $service_name = shift;
    my $version      = shift;
    my ($service)    = $fastly->search_services(name => $service_name);
    my $ok           = eval { $service->purge_all };
    if ($@) {
        print_error($@);
    } else {
        print "Purged\n";
    }
}

sub purge_url {
    my $url = shift;
    my $ok  = eval { $fastly->purge($url) };
    if ($@) {
        print_error($@);
    } else {
        print "Purged\n";
    }
}

sub purge_key {
    my $key          = shift;
    shift;
    my $service_name = shift;
    my ($service)    = $fastly->search_services(name => $service_name);
    my $ok           = eval { $service->purge_by_key($key) };
    if ($@) {
        print_error($@);
    } else {
        print "Purged\n";
    }
}

use File::Temp qw(tempfile);
sub diff {
    my $service_name = shift;
    shift;
    my $from_version = shift;
    shift;
    my $to_version = shift;
    my $from = eval { $fastly->search_services(name => $service_name, version => $from_version) };
    if ($@) { print_error($@); }
    my $to = eval { $fastly->search_services(name => $service_name, version => $to_version) };
    if ($@) { print_error($@); }
    
    if ($to->{type} eq 'error') { print BOLD, RED, "Error: ", RESET, $to->{data}->{error},"\n"; return };


    my ($to_fh, $to_fn) = tempfile();
    my ($from_fh, $from_fn) = tempfile();
    print $to_fh present({$to->_as_hash});
    print $from_fh present({$from->_as_hash});
    my $diff = `diff -u $from_fn $to_fn`;
    print "$diff";
    print "\n\n";

}

sub set {
    my $service_name = shift;
    shift;
    my $version      = shift;
    my $type         = shift;
    
    

    my $service  = eval { $fastly->search_services(name => $service_name ) };
    if ($@ || !$service) {
    my $str = "$@";
        print BOLD, RED, "Error finding service $service_name: ", RESET, $str,"\n";
        return;
    }

    my $name     = shift;
   
    my $method = "get_$type";; 
    my $obj    =  eval { $fastly->$method($service->id, $version, $name) };
    if ($@ || !$obj) {
    my $str = "$@";
        print BOLD, RED, "Error finding $type $name: ", RESET, $str,"\n";
        return;
    }

    
    while (my $prop = shift @_) {
        my $val = shift @_ || last;
        if ($prop eq 'name') {
            print BOLD, RED, "You can't update the name on a $type", RESET,"\n";
            return
        }
        unless ($obj->can($prop)) {
            print BOLD, RED, "$type doesn't have the property '$prop' ", RESET,"\n";
            return
        }
        $obj->$prop($val);
    }
    $method = "update_$type"; 
    my $o = eval { $fastly->$method($obj) };
    if ($@) {
        print_error($@);
    } else {
        print "Update successful\n";
    }
    return;
}
# 
# # sub error_completion {
# #     my $t = shift;
# #     my $r = shift;
# #     if ($r->{type} eq 'error') {
# #         $t->completemsg(sprintf("%s%sError: %s %s\n", BOLD, RED, RESET, $r->{data}->{error}));
# #         return 1;
# #     }
# #     return 0;    
# # }
# 
# # sub error {
# #     my $r = shift;
# #     if ($r->{type} eq 'error') {
# #         print BOLD, RED, "Error: ", RESET, $r->{data}->{error},"\n";
# #         return 1;
# #     }
# #     return 0;
# # }
# 


sub print_error {
    my $error = shift;
    chomp($error);
    print BOLD, RED, "Error: ", RESET, RED, $error . RESET . "\n";
}

sub upload {
    my $service_name = shift;
    shift;
    my $version_num  = shift;
    my $type    = shift;
    my $name    = shift;
    shift;
    shift;
    my $file    = shift || basename($name);

    local($/);
    open(my $fh, "<$file") || die "No file '$file'";
    my $data = <$fh>;
    close($fh);

    eval {
        my $service = $fastly->search_services(name => $service_name);
        my $version = $service->version($version_num);
    #XXX eats error
        my $vcl     = eval { $version->vcl($name) };
        my $r;
        if ($vcl) {
          $vcl->content($data);
          $r = $vcl->save;
        } else {
          $r = $version->upload_vcl($name, $data);
        }
    };
    if ($@) {
        print_error($@);
    } else {
        print "Uploaded $file as $name to service $service_name version $version_num\n";
    }
    return;
}

sub download {
    my $service_name = shift;
    shift;
    my $version_num  = shift;
    my $type    = shift;
    my $name    = shift;
    shift;
    my $tmp     = shift; # so that people can do 'download service <id> version <num> vcl <name> to <file>' as well
    my $file    = shift || $tmp || $name;

    my $dir     = dirname($file);
    $file       = join "/", ($dir, basename($file, ".vcl").".vcl"); # ensure there's a .vcl ending

    

    eval {
        my $service = $fastly->search_services(name => $service_name);
        my $version = $service->version($version_num);
        my $vcl     = eval { $version->vcl($name, include_content => 1) };
        die "Couldn't find the vcl named $name" unless $vcl;
        open(my $fh, ">$file") || die "Couldn't open the file '$file': $!";
        print $fh $vcl->content;
        close($fh);
    };
    if ($@) {
        print_error($@);
    } else {
        print "Downloaded service $service_name version $version_num vcl $name to $file\n";
    }
    return;
}

sub delete {
    my @args    = @_;
    my $name    = shift @args;
    my $service = eval { $fastly->search_services(name => $name) };
    if ($@) {
        print_error($@);
        return;
    }
        
    if (!@args) {
        my $r = eval { $fastly->delete_service($service) };
        if (!$r) {
            print BOLD, RED, "Error: ", RESET,  "Couldn't delete service $name\n";
        } else {
            print "Deleted service ".$service->id."\n";
        }
        return;
    }
   
    if (@args < 2) {
        print RED, "Need version", RESET, "\n";
        return;
    }
    shift @args;
    my $version = shift @args;
    my $type    = shift @args;
    my $tname   = shift @args;
    my $meth    = "delete_$type";
    my $r       = eval { $fastly->$meth({ service_id => $service->id, version => $version, name => $tname }) };
    if ($@) {
        print_error($@);
    } else {
       print "Deleted $type $tname\n";
    }
    return;
}

sub add {
    my @args            = @_;
    my $service_name    = $args[0];
    my $version_num     = $args[2];
    my $dest_type       = $args[6];
    my $dest_name       = $args[7];
    my $src_type        = $args[3];
    my $src_name        = $args[4];
    my $service         = eval { $fastly->search_services(name => $service_name ) };
    my $src_meth        = "get_${src_type}";
    my $src             = $fastly->$src_meth($service->id, $version_num, $src_name);
    my $dest_meth       = "get_${dest_type}";
    my $dest            = $fastly->$dest_meth($service->id, $version_num, $dest_name);
    my $add_meth        = "add_${src_type}";
    
    my $out = eval { $dest->$add_meth($src) };
    if ($@) {
        print_error($@);
    } else {
        warn "Added $src_type ".$src->name." to $dest_type ".$dest->name." for service ".$service->name." v${version_num}\n";
    }
}

sub create {
    my @args = @_;
    my $name = shift @args;
    if (!@args) {
        my $service = eval { $fastly->create_service(name => $name ) };
        if ($@) {
            print_error($@);
        } else {
            print "Created service ".$service->id."\n";
        }
        return;
    }
    
    if (@args < 2) {
        print RED, "Need version", RESET, "\n";
        return;
    }

    my $service = eval { $fastly->search_services(name => $name) };

    #my $service_name = shift @args;
    shift @args;
    my $version = shift @args;
    my $type = shift @args;

    my $commands = $fastly->commands;
    my $cmd = $commands->{"${type}.create"};

    my %args;
    if ($cmd->{name}) {
        push @args, "name", shift @args 
    } else {
        shift @args;
    }

    my $meth = "create_$type"; 
    my $obj  = eval { $fastly->$meth(service_id => $service->id, version => $version, @args) };
    if ($@) {
        print_error($@);
    } else {
        warn present({$obj->_as_hash});
    }
}

sub list_objects {
    no strict 'refs';
    my $t    = shift;
    my $c    = shift; 
    my @args = @{$c->{args}};
    my $service_name = $args[0];
    my $version_num  = $args[2];
    my $type         = $args[3];
    my ($service)    = $fastly->search_services(name => $service_name, version => $version_num);
    my $meth         = "list_$type" . "s";
    my @objects      = $fastly->$meth( service_id => $service->id , version => $version_num);
    return [sort map { $_->name } @objects];
}


sub model_args {
    my $t = shift;
    my $c = shift; 
    my $commands = $fastly->commands;
    my $cmd      = $commands->{"$c->{args}->[3].create"};
    my @args     = @{$c->{args}};

    my %seen;
    my $i = 0;
    foreach my $arg (@args) {
        $i++;
        next if($i < 6);
        $seen{$arg}++ if ($i % 2 == 0);
    }

    my $offset;
    if (@args % 2 == 0 && $args[-1] && $cmd->{$args[-1]}) {
        $offset = -1;
    }
    if (@args % 2 == 1) {
        $offset = -2;
    }    
    if ($offset) {
        if ($cmd->{$args[$offset]}->{type} =~/heavenly::(.*)/) {
            my $service   = $fastly->search_services(name => $args[0], version => $args[2]);
            my ($version) = $service->versions; 
            my $x = [$version->{$1} ];
            return $x if @$x;
        }
        return sprintf("   (%s%s%s)", RED, $cmd->{$args[$offset]}->{desc}, RESET);
    }

    if ($c->{twice}) {
        $t->completemsg("\n\n");
        foreach my $c (sort {$a cmp $b} keys %$cmd) {
            next if ($c eq 'name' || $c eq 'service' || $c eq 'version');
            next if ($seen{$c});
            if (exists $cmd->{$c}->{default}) {
                $t->completemsg(sprintf("%s%-25s%s  %s%s%s  (%s)\n",BOLD,$c,RESET, BLUE,$cmd->{$c}->{desc},RESET, $cmd->{$c}->{default} || ""));
            } else {
                $t->completemsg(sprintf("%s%s%-25s%s  %s%s%s\n",BOLD, RED,$c,RESET, BLUE,$cmd->{$c}->{desc},RESET));
            }
        } 
    }
    my @r;
    foreach my $c (sort {$a cmp $b} keys %$cmd) {
        next if ($c eq 'name' || $c eq 'service' || $c eq 'version');
        next if ($seen{$c});
        push @r, $c;
    }
    push @r, "" if(@r == 1);
    return \@r;
}

our %_mappings;
sub _mappings {
    return %_mappings if keys %_mappings;
    foreach my $class (@Net::Fastly::CLASSES) {
        my $name = lc $class;
        $name    =~ s/^net::fastly:://;
        $_mappings{$name} = $class;
    }
    %_mappings
}

sub service_models {
    my $t = shift;
    my $c = shift; 
    my %mapping = _mappings;
    if ($@) {
        my $string = "$@";  #copy becuase RED can reset it
        $t->completemsg(sprintf("\n%s%s%s\n", RED, $string, RESET));
        return [];
    }
    my @options;
    foreach my $name (keys %_mappings) {
        next if ($name eq 'service');
        next if ($name eq 'version');
        push @options, $name;
    }
    if (defined($c->{str}) && $c->{str} ne "") {
        return [ grep { $_ =~ /^$c->{str}/} sort @options ];
    }
    return [sort @options];
}


sub show_service {
    my $service_name    = shift;
    shift;
    my $version_number  = shift;
    my ($service)       = $fastly->search_services(name => $service_name, version => $version_number);
    print present({$service->_as_hash});
}

sub show_versions {
    my $service_name    = shift;
    my ($service)       = $fastly->search_services(name => $service_name);
    my @versions        = $service->versions;
    foreach my $version (@versions) {
        print $version->number."\t\t".($version->updated_at || $version->created_at || "???")." ".($version->comment || "")."\n";
    }
}

sub list_services {
    $fastly->list_services();
}

sub show_services {
    my @services = list_services;
    foreach my $service (sort { $a->name cmp $b->name } @services) {
        print $service->id."\t\t".$service->name."\n";
    }
}

sub list_service_complete {
    my $t = shift;
    my $c = shift; 
    my @services = list_services;
    if (defined($c->{str}) && $c->{str} ne "") {
        my $match  = lc($c->{str});
        return [ grep { lc($_) =~ /^$match/ } sort map { $_->name } @services ];
    } else {
        return [ sort map { $_->name } @services ]
    }


    #my $substr = 0;
    #if ($c->{str}) {
    #    $substr = index($c->{args}->[0], $c->{str});
    #} else {
    #    $substr = $c->{tokoff};
    #}
}

sub list_version {
    my $t = shift;
    my $c = shift;
    my $service_name    = $c->{args}->[0];
    my ($service)       = $fastly->search_services(name => $service_name);
    return [sort {$a <=> $b} map { $_->number } $service->versions];
}

sub list_customers_complete {
    my $t = shift;
    my $c = shift;
    my @customers = $fastly->list_customers;
    if (defined($c->{str}) && $c->{str} ne "") {
        my $match  = lc($c->{str});
        return [ grep { lc($_) =~ /^$match/ } sort map { $_->name } @customers ];
    } else {
        return [ sort map { $_->name } @customers ]
    }
}

sub list_target {
    my $t = shift;
    my $c = shift;
    my $service_name    = $c->{args}->[0];
    my $version_num     = $c->{args}->[2];
    my $type            = $c->{args}->[6] || $c->{args}->[3];
    my $version         = _get_details($service_name, $version_num); 
    return [] unless $version;
    my $objs = $version->{"${type}s"} || [];
    return [ map { $_->{name} } @$objs ];
}

sub list_target_type {
    my $t = shift;
    my $c = shift;
    my $service_name    = $c->{args}->[0];
    my $version_num     = $c->{args}->[2];
    my $type            = $c->{args}->[3];
    if ($type eq "backend") {
        return ["director"];
    } elsif ($type eq "director") {
        return ["origin"];
    } else {
        return [];
    }
}



sub present {
    my $obj = shift;
    Dump($obj);
}

sub _get_details {
    my $service_name = shift;
    my $version_num  = shift;
    my ($service)       = $fastly->search_services(name => $service_name);
    return undef unless $service;
    my $details         = $service->details(version => $version_num);
    return $details->{version};
}


$term->commands($basecommands);
$term->run(@ARGV);

=head1 COMMANDS

=head2 help

Display a help message with available commands.

=head2 show

Display various thing. The sub commands are

=head3 services

List all the services you have access to with their ids and names.

    fastly> show services

might show

    KXKPV9svJFuPapAMjzxgP       FooCorp
    6g2rQokiwAGSRdGYhCY76v      Example-Service
    Y9puwhPNS5Y1tAjUbxp7Z       Test

=head3 service

Display the information from one particular service including all 
backends, directors, domains and origins.

    fastly> show service <service name>
    
=head3 versions

Show the creation date of all the versions for a service.

    fastly> show versions <service name>

=head3 diff

Display the diff between two different versions

    fastly> show diff <service name> version <version number> to <version number>

=head3 stats

Display the stats for a service. Default last argument is minutely. 

    fastly> show stats <service name> [all|minutely|hourly|daily]

=head2 create

Create a new object.

    fastly> create service <service name>
    fastly> create service <service name> version <version number> backend  <name> [options[s]]
    fastly> create service <service name> version <version number> director <name> [options[s]]
    fastly> create service <service name> version <version number> domain   <name> [options[s]]
    fastly> create service <service name> version <version number> origin   <name> [options[s]]

Options look like

    fastly> create service <service name> version <version number> backend  <name> ipv4 <ip address>

=head2 set

Update an object. A note - you cannot change the names of things.

 fastly> set service <service name> version <version number> backend  <name> [options[s]]
 fastly> set service <service name> version <version number> director <name> [options[s]]
 fastly> set service <service name> version <version number> domain   <name> [options[s]]
 fastly> set service <service name> version <version number> origin   <name> [options[s]]

Agagin, like create, options look like

    fastly> set service <service name> version <version number> director <name> retries <retries>

=head2 delete

Delete an object from a configuration.

    fastly> delete service <service name>
    fastly> delete service <service name> version <version number>
    fastly> delete service <service name> version <version number> backend  <backend name>
    fastly> delete service <service name> version <version number> director <director name>
    fastly> delete service <service name> version <version number> domain   <domain name>
    fastly> delete service <service name> version <version number> origin   <origin name>
    
=head2 clone

Clone a configuration so that the new version can be modified.

    fastly> clone service <service name> version <version number>

=head2 activate

Activate a version for use - this will lock it and prevent any further modification.

    fastly> activate service <service name> version <version number>
    
=head2 purge

Remove objects from the cache.

    fastly> purge service <service name>
    
    fastly> purge <url>

=head2 upload

Upload a custom VCL file

    fastly> upload service <service name> version <version number> from <file> [as <vcl name>]

=head2 validate

Check to see that the currently uploaded VCL is valid.

    fastly> validate service <service name> version <version number>

=head2 dump

Show the generated VCL for a given service.

fastly> dump service <service name> version <version number>

=head2 quit

Exit the Fastly shell.

=head1 COPYRIGHT

Copyright 2011 - Fastly Inc

Mail support at fastly dot com if you have problems.

=head1 DEVELOPERS

http://github.com/fastly/fastly-perl

http://www.fastly.com/documentation

=cut