package ksb::StatusView 0.20;

# Helper used to handle a generic 'progress update' status for the module
# build, update, install, etc. processes.
#
# Currently supports TTY output only but it's not impossible to visualize
# extending this to a GUI or even web server as options.

use ksb;

use ksb::Debug 0.20 qw(colorize);

use IO::Handle;

sub new
{
    my $class = shift;
    my $defaultOpts = {
        cur_progress   => -1,
        progress_total => -1,
        status         => '',

        # Records number of modules built stats
        mod_total      => -1,
        mod_failed     => 0,
        mod_success    => 0,
    };

    # Must bless a hash ref since subclasses expect it.
    return bless $defaultOpts, $class;
}

# Sets the 'base' message to show as part of the update. E.g. "Compiling..."
sub setStatus
{
    my ($self, $newStatus) = @_;
    $self->{status} = colorize($newStatus);
}

# Sets the amount of progress made vs. the total progress possible.
sub setProgress
{
    my ($self, $newProgress) = @_;

    my $oldProgress = $self->{cur_progress};
    $self->{cur_progress} = $newProgress;

    $self->update() if ($oldProgress != $newProgress);
}

# Sets the total amount of progress deemed possible.
sub setProgressTotal
{
    my ($self, $newProgressTotal) = @_;
    $self->{progress_total} = $newProgressTotal;
}

# Gets (or sets, if arg provided) number of modules to be built.
sub numberModulesTotal
{
    my ($self, $newTotal) = @_;
    $self->{mod_total} = $newTotal if $newTotal;
    return $self->{mod_total};
}

# Gets (or sets, if arg provided) number of modules built successfully.
sub numberModulesSucceeded
{
    my ($self, $newTotal) = @_;
    $self->{mod_success} = $newTotal if $newTotal;
    return $self->{mod_success};
}

# Gets (or sets, if arg provided) number of modules not built successfully.
sub numberModulesFailed
{
    my ($self, $newTotal) = @_;
    $self->{mod_failed} = $newTotal if $newTotal;
    return $self->{mod_failed};
}

# Sends out the I/O needed to ensure the latest status is displayed.
# E.g. for TTY it clears the line and redisplays the current stats.
sub update
{
    my $self = shift;
    my $progress_total = $self->{progress_total};
    my $msg;

    my ($mod_total, $mod_success, $mod_failed) =
        @{$self}{qw(mod_total mod_success mod_failed)};

    my $fmt_spec = ($mod_total >= 100) ? "%03d" : "%02d";
    my $status_line = $self->{status};

    if ($mod_total > 1) {
        # Build up message in reverse order
        $msg = "$mod_total modules";
        $msg = colorize("r[b[$mod_failed] failed, ") . $msg if $mod_failed;
        $msg = colorize("g[b[$mod_success] built, ") . $msg if $mod_success;

        $status_line = $self->{status} . " ($msg)";
    }

    if ($progress_total > 0) {
        $msg = sprintf ("%0.1f%%",
            $self->{cur_progress} * 100 / $progress_total,
        ) . $status_line;
    }
    elsif ($self->{cur_progress} < 0) {
        $msg = $status_line;
    }
    else {
        my $spinner = '-\\|/';
        $msg =
            substr($spinner, $self->{cur_progress} % length($spinner), 1) .
            $status_line;
    }

    _clearLineAndUpdate($msg);
}

# For TTY outputs, this clears the line (if we actually had dirtied it) so
# the rest of the program can resume output from where it'd been left off.
sub releaseTTY
{
    my $self = shift;
    my $msg = shift // '';

    _clearLineAndUpdate(colorize($msg));
}

sub _clearLineAndUpdate
{
    my $msg = shift;

    # Give escape sequence to return to column 1 and clear the entire line
    # Then print message and return to column 1 again in case somewhere else
    # uses the tty.
    print "\e[1G\e[K$msg\e[1G";
    STDOUT->flush;
}

1;
