#!/usr/bin/env perl

use strict;
use warnings;

use v5.14;

my $home = $ENV{HOME};

my %items = (
    apache    => '/usr/local/apache',
    profile   => '/var/cpanel/easy/apache/profile',
    templates => '/var/cpanel/templates',
    conf      => '/var/cpanel/conf/apache',
);

my %dispatch = (
    save    => \&do_save,
    restore => \&do_restore,
    list    => \&do_list,
    ls      => \&do_list,
    dir     => \&do_list,
    delete  => \&do_delete,
    del     => \&do_delete,
    rm      => \&do_delete,
);

sub apconf_dir {
    return "$home/.apconf";
}

sub date_string {
    my ( $sec, $min, $hr, $mday, $mon, $yr ) = localtime;
    return sprintf(
        "%04d%02d%02d_%02d%02d%02d",
        $yr + 1900, $mon + 1, $mday, $hr, $min, $sec
    );
}

# Save one element of a configuration
sub save_item {
    my ( $conf_dir, $name, $path ) = @_;

    if ( system 'cp', '-a', $path, "$conf_dir/$name" ) {
        die "Failed to save $path to $conf_dir/$name: $!";
    }
    else {
        say "Saved $path to $conf_dir/$name";
    }
}

# Save a configuration
sub do_save {
    my ($conf) = @_;
    my $apconf_dir = apconf_dir();
    $conf //= date_string();
    if ( $conf =~ /^\.+$/ or $conf =~ /\// ) {
        say "Configuration name $conf is prohibited";
        exit -1;
    }
    mkdir $apconf_dir unless -d $apconf_dir;
    my $conf_dir = "$apconf_dir/$conf";
    if ( -d $conf_dir ) {
        say "Cannot save! $conf_dir already exists";
        exit -1;
    }

    mkdir $conf_dir or die "Failed to create $conf_dir: $!";

    for my $k ( keys %items ) {
        save_item( $conf_dir, $k, $items{$k} );
    }
}

# Restore one element of a configuration
sub restore_item {
    my ( $conf_dir, $name, $path ) = @_;

    if ( -d "$conf_dir/$name" ) {
        my $item_save;
        if ( -d $path ) {
            $item_save = join( '-', $path, time );
            system 'mv', $path, $item_save
              and die "Failed to save existing $path: $!";
        }
        if ( system 'cp', '-a', "$conf_dir/$name", $path ) {
            warn "Failed to restore $conf_dir/$name to $path: $!";
            if ($item_save) {
                system 'mv', $item_save, $path
                  and die "Failed to restore existing $path: $!";
            }
            exit -1;
        }
        else {
            say "Restored $conf_dir/$name to $path ...";
            if ($item_save) {
                system 'rm', '-rf', $item_save
                  and say "Failed to clean up old $path, saved in $item_save";
            }
        }
    }
}

# Restore a previously saved configuration
sub do_restore {
    my ($conf) = @_;
    unless( defined $conf ) {
        say "Missing required configuration name";
        exit -1;
    }
    if ( $conf =~ /^\.+$/ or $conf =~ /\// ) {
        say "Configuration name $conf is prohibited";
        exit -1;
    }
    my $apconf_dir = apconf_dir();
    my $conf_dir = "$apconf_dir/$conf";
    if ( !-d $conf_dir ) {
        say "Cannot restore! $conf_dir does not exist";
        exit -1;
    }

    for my $k ( keys %items ) {
        restore_item( $conf_dir, $k, $items{$k} );
    }
}

# List the configurations available to restore
sub do_list {
    my $apconf_dir = apconf_dir();
    if ( !-d $apconf_dir ) {
        say "Cannot list configurations: $apconf_dir does not exist";
        exit -1;
    }
    opendir( my $dh, $apconf_dir ) or die "Could not open $apconf_dir: $!";
    my @dirs = sort
      grep { -d "$apconf_dir/$_" }
      grep { $_ !~ /^\.+$/ } readdir($dh);
    if (@dirs) {
        say "    $_" foreach @dirs;
    }
    else {
        say "No saved configurations found";
    }
}

# Delete a saved configuration
sub do_delete {
    if ( !@_ ) {
        say "Nothing to delete!";
        exit -1;
    }
    my $apconf_dir = apconf_dir();
    if ( !-d $apconf_dir ) {
        say "Cannot list configurations: $apconf_dir does not exist";
        exit -1;
    }

    # Filter the list before deleting anything; all must pass or nothing happens
    my %confs;
    for my $conf (@_) {
        if ( $conf =~ /^\.+$/ or $conf =~ /\// ) {
            say "Configuration name $conf is prohibited";
            exit -1;
        }
        if ( $conf =~ /^\d\d+/ ) {
            opendir( my $dh, $apconf_dir ) or die "Cannot read $apconf_dir: $!";
            for my $dir ( readdir $dh ) {
                next if $dir =~ /^\.\.?$/;
                next unless $dir =~ /^${conf}/;
                my $conf_dir = "$apconf_dir/$dir";
                next unless -d $conf_dir;
                $confs{$conf_dir} = 1;
            }
        }
        else {
            my $conf_dir = "$apconf_dir/$conf";
            if ( !-d "$conf_dir" ) {
                say "$conf_dir does not exist or is not a directory";
                exit -1;
            }
            $confs{$conf_dir} = 1;
        }
    }

    for my $conf ( sort( keys %confs ) ) {
        say "Deleting $conf ...";
        system 'rm', '-rf', $conf
          and die "Failed to delete: $!";
    }
}

sub run {
    my ($command) = shift;

    if( defined $command ) {
        my $cmd = lc $command;

        if ( exists $dispatch{$cmd} ) {
            goto $dispatch{$cmd};
        }

        say "Unknown command: $command";
    }

    say "Known commands:\n\t" . join( ' ', sort( keys %dispatch ) );
}

if ( !caller ) {
    run(@ARGV);
}

