/*
 *  gui.c: An ncurses GUI for nwipe.
 *
 *  Copyright Darik Horn <dajhorn-dban@vanadac.com>.
 *
 *  Modifications to original dwipe Copyright Andy Beverley <andy@andybev.com>
 *
 *  This program is free software; you can redistribute it and/or modify it under
 *  the terms of the GNU General Public License as published by the Free Software
 *  Foundation, version 2.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT
 *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 *  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along with
 *  this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */

/* RATIONALE:
 *
 *   This entire GUI is a non-portable task-specific thunk.
 *
 *   The alternatives are, however, no better. The CDK is large and clumsy,
 *   and things like ncurses libmenu are not worth the storage overhead.
 *
 */
#include <ncurses.h>
#include <panel.h>
#include <stdint.h>
#include <time.h>

#include "nwipe.h"
#include "context.h"
#include "method.h"
#include "prng.h"
#include "options.h"
#include "gui.h"
#include "pass.h"
#include "logging.h"
#include "version.h"

#define NWIPE_GUI_PANE 8

/* Header window: width, height, x coordinate, y coordinate. */
#define NWIPE_GUI_HEADER_W COLS
#define NWIPE_GUI_HEADER_H 1
#define NWIPE_GUI_HEADER_X 0
#define NWIPE_GUI_HEADER_Y 0

/* Footer window: width, height, x coordinate, y coordinate. */
#define NWIPE_GUI_FOOTER_W COLS
#define NWIPE_GUI_FOOTER_H 1
#define NWIPE_GUI_FOOTER_X 0
#define NWIPE_GUI_FOOTER_Y ( LINES - 1 )

/* Options window: width, height, x coorindate, y coordinate. */
#define NWIPE_GUI_OPTIONS_W 44
#define NWIPE_GUI_OPTIONS_H 7
#define NWIPE_GUI_OPTIONS_Y 1
#define NWIPE_GUI_OPTIONS_X 0

/* Options fields, relative to their window. */
#define NWIPE_GUI_OPTIONS_TAB 10
#define NWIPE_GUI_OPTIONS_ENTROPY_Y 1
#define NWIPE_GUI_OPTIONS_ENTROPY_X 1
#define NWIPE_GUI_OPTIONS_PRNG_Y 2
#define NWIPE_GUI_OPTIONS_PRNG_X 1
#define NWIPE_GUI_OPTIONS_METHOD_Y 3
#define NWIPE_GUI_OPTIONS_METHOD_X 1
#define NWIPE_GUI_OPTIONS_VERIFY_Y 4
#define NWIPE_GUI_OPTIONS_VERIFY_X 1
#define NWIPE_GUI_OPTIONS_ROUNDS_Y 5
#define NWIPE_GUI_OPTIONS_ROUNDS_X 1

/* Stats window: width, height, x coordinate, y coordinate. */
#define NWIPE_GUI_STATS_W ( COLS - 44 )
#define NWIPE_GUI_STATS_H 7
#define NWIPE_GUI_STATS_Y 1
#define NWIPE_GUI_STATS_X 44

/* Stats fields, relative to their window. */
#define NWIPE_GUI_STATS_RUNTIME_Y 1
#define NWIPE_GUI_STATS_RUNTIME_X 1
#define NWIPE_GUI_STATS_ETA_Y 2
#define NWIPE_GUI_STATS_ETA_X 1
#define NWIPE_GUI_STATS_LOAD_Y 3
#define NWIPE_GUI_STATS_LOAD_X 1
#define NWIPE_GUI_STATS_THROUGHPUT_Y 4
#define NWIPE_GUI_STATS_THROUGHPUT_X 1
#define NWIPE_GUI_STATS_ERRORS_Y 5
#define NWIPE_GUI_STATS_ERRORS_X 1
#define NWIPE_GUI_STATS_TAB 16

/* Select window: width, height, x coordinate, y coordinate. */
#define NWIPE_GUI_MAIN_W COLS
#define NWIPE_GUI_MAIN_H ( LINES - NWIPE_GUI_MAIN_Y - 1 )
#define NWIPE_GUI_MAIN_Y 8
#define NWIPE_GUI_MAIN_X 0

#define SKIP_DEV_PREFIX 5

/* Window pointers. */
WINDOW* footer_window;
WINDOW* header_window;
WINDOW* main_window;
WINDOW* options_window;
WINDOW* stats_window;

PANEL* footer_panel;
PANEL* header_panel;
PANEL* main_panel;
PANEL* options_panel;
PANEL* stats_panel;

/* Options window title. */
const char* options_title = " Options ";

/* Statistics window title. */
const char* stats_title = " Statistics ";

/* Footer labels. */
const char* main_window_footer = "S=Start M=Method P=PRNG V=Verify R=Rounds B=Blanking Space=Select Ctrl-C=Quit";
const char* main_window_footer_warning_lower_case_s =
    "  WARNING: To start the wipe press capital S, you pressed lower case s  ";
const char* main_window_footer_warning_no_drive_selected =
    "  No drives selected, use spacebar to select a drive, then press S to start  ";
const char* selection_footer = "J=Down K=Up Space=Select Backspace=Cancel Ctrl-C=Quit";
const char* end_wipe_footer = "B=Blank screen Ctrl-C=Quit";
const char* rounds_footer = "Left=Erase Esc=Cancel Ctrl-C=Quit";
const char* wipes_finished_footer = "Wipe finished - press enter to exit. Logged to STDOUT";

/* The number of lines available in the terminal */
int stdscr_lines;

/* The number of columns available in the terminal */
int stdscr_cols;

/* The size of the terminal lines when previously checked */
int stdscr_lines_previous;

/* The size of the terminal columns when previously checked */
int stdscr_cols_previous;

void nwipe_gui_title( WINDOW* w, const char* s )
{
    /**
     * Prints the string 's' centered on the first line of the window 'w'.
     */

    /* The number of lines in the window. (Not used.) */
    int wy;
    (void) wy; /* flag wy not used to the compiler, to silence warning */

    /* The number of columns in the window. */
    int wx;

    /* Get the window dimensions. */
    getmaxyx( w, wy, wx );

    /*Calculate available total margin */
    int margin = ( wx - strlen( s ) );
    if( margin < 0 )
    {
        margin = 0;
    }

    /* Print the title. */
    mvwprintw( w, 0, margin / 2, "%s", s );

} /* nwipe_gui_title */

void nwipe_gui_init( void )
{
    /**
     * Initializes the ncurses gui.
     */

    /* Initialize the screen. */
    initscr();

    /* Disable TTY line buffering. */
    cbreak();

    /* Disable TTY echo. */
    noecho();

    /* Enable most special keys. */
    keypad( stdscr, TRUE );

    if( has_colors() )
    {
        /* Initialize color capabilities. */
        start_color();

        if( can_change_color() )
        {
            /* Redefine cyan to gray. */
            init_color( COLOR_CYAN, 128, 128, 128 );
        }

        /* Set white on blue as the emphasis color. */
        init_pair( 1, COLOR_WHITE, COLOR_BLUE );

        /* Set gray (or cyan) on blue as the normal color. */
        init_pair( 2, COLOR_CYAN, COLOR_BLUE );

        /* Set red on blue as the hilite color. */
        init_pair( 3, COLOR_RED, COLOR_BLUE );

        /* Set blue on white as the color for the header and footer windows. */
        init_pair( 4, COLOR_BLUE, COLOR_WHITE );

        /* Set white on green for success messages. */
        init_pair( 5, COLOR_WHITE, COLOR_GREEN );

        /* Set white on red for failure messages. */
        init_pair( 6, COLOR_WHITE, COLOR_RED );

        /* Set black on black for when hiding the display. */
        init_pair( 7, COLOR_BLACK, COLOR_BLACK );

        /* Set green on blue for reverse bold messages */
        init_pair( 8, COLOR_GREEN, COLOR_WHITE );

        /* Set green on blue for reverse bold error messages */
        init_pair( 9, COLOR_RED, COLOR_WHITE );

        /* Set black on yellow for warning messages */
        init_pair( 10, COLOR_BLACK, COLOR_YELLOW );

        /* Set the background style. */
        wbkgdset( stdscr, COLOR_PAIR( 1 ) | ' ' );
    }

    /* Clear the screen. */
    wclear( stdscr );

    /* Create the header window. */
    nwipe_gui_create_header_window();

    /* Create the footer window and panel */
    nwipe_gui_create_footer_window( main_window_footer );

    /* Create the options window and panel */
    nwipe_gui_create_options_window();

    /* Create the stats window. */
    nwipe_gui_create_stats_window();

    /* Create a new main window and panel */
    nwipe_gui_create_main_window();

    update_panels();
    doupdate();

    /* Hide the cursor. */
    curs_set( 0 );

} /* nwipe_gui_init */

void nwipe_gui_free( void )
{
    /**
     * Releases the ncurses gui.
     *
     */
    /* Free ncurses resources. */
    if( del_panel( footer_panel ) != OK )
    {
        nwipe_log( NWIPE_LOG_ERROR, "Deleting footer panel failed!." );
    }
    if( del_panel( header_panel ) != OK )
    {
        nwipe_log( NWIPE_LOG_ERROR, "Deleting header panel failed!." );
    }
    if( del_panel( main_panel ) != OK )
    {
        nwipe_log( NWIPE_LOG_ERROR, "Deleting main panel failed!." );
    }
    if( del_panel( options_panel ) != OK )
    {
        nwipe_log( NWIPE_LOG_ERROR, "Deleting options panel failed!." );
    }
    if( del_panel( stats_panel ) != OK )
    {
        nwipe_log( NWIPE_LOG_ERROR, "Deleting stats panel failed!." );
    }
    if( delwin( footer_window ) != OK )
    {
        nwipe_log( NWIPE_LOG_ERROR, "Deleting footer window failed!." );
    }
    if( delwin( header_window ) != OK )
    {
        nwipe_log( NWIPE_LOG_ERROR, "Deleting header window failed!." );
    }
    if( delwin( main_window ) != OK )
    {
        nwipe_log( NWIPE_LOG_ERROR, "Deleting main window failed!." );
    }
    if( delwin( options_window ) != OK )
    {
        nwipe_log( NWIPE_LOG_ERROR, "Deleting options window failed!." );
    }
    if( delwin( stats_window ) != OK )
    {
        nwipe_log( NWIPE_LOG_ERROR, "Deleting stats window failed!." );
    }
    if( endwin() != OK )
    {
        nwipe_log( NWIPE_LOG_ERROR, "Curses endwin() failed !" );
    }

} /* nwipe_gui_free */

void nwipe_gui_create_main_window()
{
    /* Create the main window. */
    main_window = newwin( NWIPE_GUI_MAIN_H, NWIPE_GUI_MAIN_W, NWIPE_GUI_MAIN_Y, NWIPE_GUI_MAIN_X );
    main_panel = new_panel( main_window );

    if( has_colors() )
    {
        /* Set the background style. */
        wbkgdset( main_window, COLOR_PAIR( 1 ) | ' ' );

        /* Apply the color change. */
        wattron( main_window, COLOR_PAIR( 1 ) );
    }

    /* Clear the main window. */
    werase( main_window );

    /* Add a border. */
    box( main_window, 0, 0 );

    /* refresh main window */
    wnoutrefresh( main_window );

} /* nwipe_gui_create_main_window */

void nwipe_gui_create_header_window()
{
    /* Create the header window. */
    header_window = newwin( NWIPE_GUI_HEADER_H, NWIPE_GUI_HEADER_W, NWIPE_GUI_HEADER_Y, NWIPE_GUI_HEADER_X );
    header_panel = new_panel( header_window );

    if( has_colors() )
    {
        /* Set the background style of the header window. */
        wbkgdset( header_window, COLOR_PAIR( 4 ) | ' ' );
    }

    /* Clear the header window. */
    werase( header_window );

    /* Print the product banner. */
    nwipe_gui_title( header_window, banner );

    /* Refresh the header window */
    wnoutrefresh( header_window );

} /* nwipe_gui_create_header_window */

void nwipe_gui_create_footer_window( const char* footer_text )
{
    /* Create the footer window. */
    footer_window = newwin( NWIPE_GUI_FOOTER_H, NWIPE_GUI_FOOTER_W, NWIPE_GUI_FOOTER_Y, NWIPE_GUI_FOOTER_X );
    footer_panel = new_panel( footer_window );

    if( has_colors() )
    {
        /* Set the background style of the footer window. */
        wbkgdset( footer_window, COLOR_PAIR( 4 ) | ' ' );
    }

    /* Erase the footer window. */
    werase( footer_window );

    /* Add help text to the footer */
    nwipe_gui_title( footer_window, footer_text );

    /* Refresh the footer window */
    wnoutrefresh( footer_window );

} /* nwipe_gui_create_footer_window */

void nwipe_gui_amend_footer_window( const char* footer_text )
{
    /* Clear the footer window. */
    werase( footer_window );

    /* Add help text to the footer */
    nwipe_gui_title( footer_window, footer_text );

    /* Refresh the footer window */
    wnoutrefresh( footer_window );

} /* nwipe_gui_amend_footer_window */

void nwipe_gui_create_options_window()
{
    /* Create the options window. */
    options_window = newwin( NWIPE_GUI_OPTIONS_H, NWIPE_GUI_OPTIONS_W, NWIPE_GUI_OPTIONS_Y, NWIPE_GUI_OPTIONS_X );
    options_panel = new_panel( options_window );

    if( has_colors() )
    {
        /* Set the background style of the options window. */
        wbkgdset( options_window, COLOR_PAIR( 1 ) | ' ' );

        /* Apply the color change to the options window. */
        wattron( options_window, COLOR_PAIR( 1 ) );
    }

    /* Clear the options window. */
    werase( options_window );

    /* Add a border. */
    box( options_window, 0, 0 );

} /* nwipe_gui_create_options_window */

void nwipe_gui_create_stats_window()
{
    /* Create the stats window. */
    stats_window = newwin( NWIPE_GUI_STATS_H, NWIPE_GUI_STATS_W, NWIPE_GUI_STATS_Y, NWIPE_GUI_STATS_X );
    stats_panel = new_panel( stats_window );

    if( has_colors() )
    {
        /* Set the background style of the stats window. */
        wbkgdset( stats_window, COLOR_PAIR( 1 ) | ' ' );

        /* Apply the color change to the stats window. */
        wattron( stats_window, COLOR_PAIR( 1 ) );
    }

    /* Clear the new window. */
    werase( stats_window );

    /* Add a border. */
    box( stats_window, 0, 0 );

    /* Add a title. */
    nwipe_gui_title( stats_window, stats_title );

    /* Print field labels. */
    mvwprintw( stats_window, NWIPE_GUI_STATS_RUNTIME_Y, NWIPE_GUI_STATS_RUNTIME_X, "Runtime:       " );
    mvwprintw( stats_window, NWIPE_GUI_STATS_ETA_Y, NWIPE_GUI_STATS_ETA_X, "Remaining:     " );
    mvwprintw( stats_window, NWIPE_GUI_STATS_LOAD_Y, NWIPE_GUI_STATS_LOAD_X, "Load Averages: " );
    mvwprintw( stats_window, NWIPE_GUI_STATS_THROUGHPUT_Y, NWIPE_GUI_STATS_THROUGHPUT_X, "Throughput:    " );
    mvwprintw( stats_window, NWIPE_GUI_STATS_ERRORS_Y, NWIPE_GUI_STATS_ERRORS_X, "Errors:        " );

} /* nwipe_gui_create_stats_window */

void nwipe_gui_create_all_windows_on_terminal_resize( const char* footer_text )
{
    /* Get the terminal size */
    getmaxyx( stdscr, stdscr_lines, stdscr_cols );

    /* If the user has resized the terminal then recreate the windows and panels */
    if( stdscr_cols_previous != stdscr_cols || stdscr_lines_previous != stdscr_lines )
    {
        /* save the revised terminal size so we check whether the user has resized next time */
        stdscr_lines_previous = stdscr_lines;
        stdscr_cols_previous = stdscr_cols;

        /* Clear the screen. */
        wclear( stdscr );

        /* Create a new header window and panel due to terminal size having changed */
        nwipe_gui_create_header_window();

        /* Create a new main window and panel due to terminal size having changed */
        nwipe_gui_create_main_window();

        /* Create a new footer window and panel due to terminal size having changed */
        nwipe_gui_create_footer_window( footer_text );

        /* Create a new options window and panel due to terminal size having changed */
        nwipe_gui_create_options_window();

        /* Create a new stats window and panel due to terminal size having changed */
        nwipe_gui_create_stats_window();

        /* Update the options window. */
        nwipe_gui_options();

        update_panels();
        doupdate();
    }
}

void nwipe_gui_select( int count, nwipe_context_t** c )
{
    /**
     * The primary user interface.  Allows the user to
     * change options and specify the devices to be wiped.
     *
     * @parameter  count       The number of contexts in the array.
     * @parameter  c           An array of device contexts.
     *
     * @modifies   c[].select  Sets the select flag according to user input.
     * @modifies   options     Sets program options according to to user input.
     *
     */

    extern int terminate_signal;

    /* Widget labels. */
    const char* select_title = " Disks and Partitions ";

    /* The number of lines available in the window. */
    int wlines;

    /* The number of columns available in the window. */
    int wcols;

    /* The number of selection elements that we can show in the window. */
    int slots;

    /* The index of the element that is visible in the first slot. */
    int offset = 0;

    /* The selection focus. */
    int focus = 0;

    /* A generic loop variable. */
    int i = 0;

    /* User input buffer. */
    int keystroke;

    /* The current working line. */
    int yy;

    /* Flag, Valid key hit = 1, anything else = 0 */
    int validkeyhit;

    /* counts number of drives and partitions that have been selected */
    int number_of_selected_contexts = 0;

    /* Control A toggle status -1=indefined, 0=all drives delected, 1=all drives selected */
    int select_all_toggle_status = -1;

    /* Get the terminal size */
    getmaxyx( stdscr, stdscr_lines, stdscr_cols );

    /* save the terminal size so we check whether the user has resized */
    stdscr_lines_previous = stdscr_lines;
    stdscr_cols_previous = stdscr_cols;

    do
    {

        nwipe_gui_create_all_windows_on_terminal_resize( main_window_footer );

        /* There is one slot per line. */
        getmaxyx( main_window, wlines, wcols );

        /* Less two lines for the box and two lines for padding. */
        slots = wlines - 4;
        if( slots < 0 )
        {
            slots = 0;
        }

        /* The code here adjusts the offset value, required when the terminal is resized vertically */
        if( slots > count )
        {
            offset = 0;
        }
        else
        {
            if( focus >= count )
            {
                /* The focus is already at the last element. */
                focus = count - 1;
            }
            if( focus < 0 )
            {
                /* The focus is already at the last element. */
                focus = 0;
            }
        }

        if( count >= slots && slots > 0 )
        {
            offset = focus + 1 - slots;
            if( offset < 0 )
            {
                offset = 0;
            }
        }

        /* Clear the main window, necessary when switching selections such as method etc */
        werase( main_window );

        /* Refresh main window */
        wnoutrefresh( main_window );

        /* If the user selected an option the footer text would have changed.
         * Here we set it back to the main key help text */
        nwipe_gui_create_footer_window( main_window_footer );

        /* Refresh the stats window */
        wnoutrefresh( stats_window );

        /* Refresh the options window */
        wnoutrefresh( options_window );

        /* Update the options window. */
        nwipe_gui_options();

        /* Initialize the line offset. */
        yy = 2;

        for( i = 0; i < slots && i < count; i++ )
        {

            /* Move to the next line. */
            mvwprintw( main_window, yy++, 1, " " );

            if( i + offset == focus )
            {
                if( c[focus]->select == NWIPE_SELECT_TRUE || c[focus]->select == NWIPE_SELECT_FALSE )
                {
                    /* Print the 'enabled' cursor. */
                    waddch( main_window, ACS_RARROW );
                }

                else
                {
                    /* Print the 'disabled' cursor. */
                    waddch( main_window, ACS_DIAMOND );
                }
            }

            else
            {
                /* Print whitespace. */
                waddch( main_window, ' ' );
            }

            /* In the event of the offset value some how becoming invalid, this if statement will prevent a segfault
             * and the else part will log the out of bounds values for debugging */
            if( i + offset >= 0 && i + offset < count )
            {

                switch( c[i + offset]->select )
                {
                    case NWIPE_SELECT_TRUE:

                        wprintw( main_window, " [wipe] %i. %s", ( i + offset + 1 ), c[i + offset]->device_label );
                        break;

                    case NWIPE_SELECT_FALSE:
                        /* Print an element that is not selected. */
                        wprintw( main_window, " [    ] %i. %s", ( i + offset + 1 ), c[i + offset]->device_label );
                        break;

                    case NWIPE_SELECT_TRUE_PARENT:

                        /* This element will be wiped when its parent is wiped. */
                        wprintw( main_window, " [****] %i. %s", ( i + offset + 1 ), c[i + offset]->device_label );
                        break;

                    case NWIPE_SELECT_FALSE_CHILD:

                        /* We can't wipe this element because it has a child that is being wiped. */
                        wprintw( main_window, " [----] %i. %s", ( i + offset + 1 ), c[i + offset]->device_label );
                        break;

                    case NWIPE_SELECT_DISABLED:

                        /* We don't know how to wipe this device. (Iomega Zip drives.) */
                        wprintw( main_window, " [????] %s", "Unrecognized Device" );
                        break;

                    default:

                        /* TODO: Handle the sanity error. */
                        break;

                } /* switch select */
            }
            else
            {
                nwipe_log( NWIPE_LOG_DEBUG,
                           "GUI.c,nwipe_gui_select(), scroll, array index out of bounds, i=%u, count=%u, slots=%u, "
                           "focus=%u, offset=%u",
                           i,
                           count,
                           slots,
                           focus,
                           offset );
            }

        } /* for */

        if( offset > 0 )
        {
            mvwprintw( main_window, 1, wcols - 8, " More " );
            waddch( main_window, ACS_UARROW );
        }

        if( count - offset > slots )
        {
            mvwprintw( main_window, wlines - 2, wcols - 8, " More " );
            waddch( main_window, ACS_DARROW );
        }

        /* Draw a border around the menu window. */
        box( main_window, 0, 0 );

        /* Print a title. */
        nwipe_gui_title( main_window, select_title );

        /* Refresh the window. */
        wnoutrefresh( main_window );

        /* Output to physical screen */
        doupdate();

        /* Wait 250ms for input from getch, if nothing getch will then continue,
         * This is necessary so that the while loop can be exited by the
         * terminate_signal e.g.. the user pressing control-c to exit.
         * Do not change this value, a higher value means the keys become
         * sluggish, any slower and more time is spent unnecessarily looping
         * which wastes CPU cycles.
         */

        do
        {
            validkeyhit = 0;
            timeout( 250 );  // block getch() for 250ms.
            keystroke = getch();  // Get user input.
            timeout( -1 );  // Switch back to blocking mode.

            /* We don't necessarily use these but for future reference, here are some cntrl + key values
             * ^W - 23, ^E - 5, ^R - 18, ^T - 20, ^Y - 25, ^U - 21, ^I - 9, ^O - 15, ^P - 16, ^A - 1, ^D - 4
             * ^F - 6, ^G - 7, ^H - 8, ^K - 11, ^L - 12, ^X - 24, ^V - 22, ^B - 2, ^N - 14
             * Use nwipe_log( NWIPE_LOG_DEBUG, "Key Name: %s - %u", keyname(keystroke),keystroke) to
             * figure out what code is returned by what ever key combination */

            switch( keystroke )
            {
                case KEY_DOWN:
                case 'j':
                case 'J':

                    validkeyhit = 1;

                    /* Increment the focus. */
                    focus += 1;

                    if( focus >= count )
                    {
                        /* The focus is already at the last element. */
                        focus = count - 1;
                        break;
                    }

                    if( focus - offset >= slots )
                    {
                        /* The next element is offscreen. Scroll down. */
                        offset += 1;
                        break;
                    }

                    break;

                case KEY_UP:
                case 'k':
                case 'K':

                    validkeyhit = 1;

                    /* Decrement the focus. */
                    focus -= 1;

                    if( focus < 0 )
                    {
                        /* The focus is already at the last element. */
                        focus = 0;
                        break;
                    }

                    if( focus < offset )
                    {
                        /* The next element is offscreen. Scroll up. */
                        offset -= 1;
                        break;
                    }

                    break;

                case KEY_ENTER:
                case 10:
                case ' ':

                    validkeyhit = 1;

                    /* TODO: This block should be made into a function. */

                    if( c[focus]->select == NWIPE_SELECT_TRUE )
                    {
                        /* Reverse the selection of this element. */
                        c[focus]->select = NWIPE_SELECT_FALSE;

                        if( c[focus]->device_part == 0 )
                        {
                            /* Sub-deselect all partitions and slices within this disk. */
                            for( i = 0; i < count; i++ )
                            {
                                if( c[i]->device_type == c[focus]->device_type
                                    && c[i]->device_host == c[focus]->device_host
                                    && c[i]->device_bus == c[focus]->device_bus
                                    && c[i]->device_target == c[focus]->device_target
                                    && c[i]->device_lun == c[focus]->device_lun && c[i]->device_part > 0 )
                                {
                                    c[i]->select = NWIPE_SELECT_FALSE;
                                }

                            } /* for all contexts */

                        } /* if sub-deselect */

                        else
                        {
                            /* The number of selected partitions or slices within this disk. */
                            int j = 0;

                            for( i = 0; i < count; i++ )
                            {
                                if( c[i]->device_type == c[focus]->device_type
                                    && c[i]->device_host == c[focus]->device_host
                                    && c[i]->device_bus == c[focus]->device_bus
                                    && c[i]->device_target == c[focus]->device_target
                                    && c[i]->device_lun == c[focus]->device_lun && c[i]->device_part > 0
                                    && c[i]->select == NWIPE_SELECT_TRUE )
                                {
                                    /* Increment the counter. */
                                    j += 1;
                                }

                            } /* for all contexts */

                            if( j == 0 )
                            {
                                /* Find the parent disk of this partition or slice. */
                                for( i = 0; i < count; i++ )
                                {
                                    if( c[i]->device_type == c[focus]->device_type
                                        && c[i]->device_host == c[focus]->device_host
                                        && c[i]->device_bus == c[focus]->device_bus
                                        && c[i]->device_target == c[focus]->device_target
                                        && c[i]->device_lun == c[focus]->device_lun && c[i]->device_part == 0 )
                                    {
                                        /* Enable the disk element. */
                                        c[i]->select = NWIPE_SELECT_FALSE;
                                    }

                                } /* for all contexts */

                            } /* if */

                        } /* else super-enable */

                        break;

                    } /* if NWIPE_SELECT_TRUE */

                    if( c[focus]->select == NWIPE_SELECT_FALSE )
                    {
                        /* Reverse the selection. */
                        c[focus]->select = NWIPE_SELECT_TRUE;

                        if( c[focus]->device_part == 0 )
                        {
                            /* Sub-select all partitions and slices within this disk. */
                            for( i = 0; i < count; i++ )
                            {
                                if( c[i]->device_type == c[focus]->device_type
                                    && c[i]->device_host == c[focus]->device_host
                                    && c[i]->device_bus == c[focus]->device_bus
                                    && c[i]->device_target == c[focus]->device_target
                                    && c[i]->device_lun == c[focus]->device_lun && c[i]->device_part > 0 )
                                {
                                    c[i]->select = NWIPE_SELECT_TRUE_PARENT;
                                }

                            } /* for */

                        } /* if sub-select */

                        else
                        {
                            /* ASSERT: ( c[focus]->device_part > 0 ) */

                            /* Super-deselect the disk that contains this device. */
                            for( i = 0; i < count; i++ )
                            {
                                if( c[i]->device_type == c[focus]->device_type
                                    && c[i]->device_host == c[focus]->device_host
                                    && c[i]->device_bus == c[focus]->device_bus
                                    && c[i]->device_target == c[focus]->device_target
                                    && c[i]->device_lun == c[focus]->device_lun && c[i]->device_part == 0 )
                                {
                                    c[i]->select = NWIPE_SELECT_FALSE_CHILD;
                                }
                            }

                        } /* else super-deselect */

                        break;

                    } /* if NWIPE_SELECT_FALSE */

                    /* TODO: Explain to the user why they can't change this. */
                    break;

                case 'm':
                case 'M':

                    validkeyhit = 1;

                    /*  Run the method dialog. */
                    nwipe_gui_method();
                    break;

                case 'p':
                case 'P':

                    validkeyhit = 1;

                    /* Run the PRNG dialog. */
                    nwipe_gui_prng();

                    break;

                case 'r':
                case 'R':

                    validkeyhit = 1;

                    /* Run the rounds dialog. */
                    nwipe_gui_rounds();

                    break;

                case 'v':
                case 'V':

                    validkeyhit = 1;

                    /* Run the option dialog. */
                    nwipe_gui_verify();
                    break;

                case 'b':
                case 'B':

                    validkeyhit = 1;

                    /* Run the noblank dialog. */
                    nwipe_gui_noblank();
                    break;

                case 'S':

                    /* User wants to start the wipe */
                    validkeyhit = 1;

                    /* Have any drives have been selected ? */
                    number_of_selected_contexts = 0;
                    for( i = 0; i < count; i++ )
                    {
                        if( c[i]->select == NWIPE_SELECT_TRUE )
                        {
                            number_of_selected_contexts += 1;
                        }
                    }

                    /* if no drives have been selected, print a warning on the footer */
                    if( number_of_selected_contexts == 0 )
                    {
                        wattron( footer_window, COLOR_PAIR( 10 ) );
                        nwipe_gui_amend_footer_window( main_window_footer_warning_no_drive_selected );
                        doupdate();
                        sleep( 3 );
                        wattroff( footer_window, COLOR_PAIR( 10 ) );

                        /* After the delay return footer text back to key help */
                        nwipe_gui_amend_footer_window( main_window_footer );
                        doupdate();

                        /* Remove any repeated S key strokes, without this the gui would hang
                         * for a period of time, i.e sleep above x number of repeated 's' keystrokes
                         * which could run into minutes */
                        do
                        {
                            timeout( 250 );  // block getch() for 250ms.
                            keystroke = getch();  // Get user input.
                            timeout( -1 );  // Switch back to blocking mode.
                        } while( keystroke == 'S' );

                        /* Remove the S from keystroke, which allows us to stay within the selection menu loop */
                        keystroke = 0;
                    }

                    break;

                case 's':

                    /* user has mistakenly hit the lower case 's' instead of capital 'S' */
                    validkeyhit = 1;

                    /* Warn the user about their mistake */
                    wattron( footer_window, COLOR_PAIR( 10 ) );
                    nwipe_gui_amend_footer_window( main_window_footer_warning_lower_case_s );
                    doupdate();
                    sleep( 3 );
                    wattroff( footer_window, COLOR_PAIR( 10 ) );

                    /* After the delay return footer text back to key help */
                    nwipe_gui_amend_footer_window( main_window_footer );
                    doupdate();

                    /* Remove any repeated s key strokes, without this the gui would hang
                     * for a period of time, i.e sleep above x number of repeated 's' keystrokes
                     * which could run into minutes */
                    do
                    {
                        timeout( 250 );  // block getch() for 250ms.
                        keystroke = getch();  // Get user input.
                        timeout( -1 );  // Switch back to blocking mode.
                    } while( keystroke == 's' );

                    break;

                case 1:

                    /* Ctrl A - Toggle select/deselect all drives */
                    validkeyhit = 1;

                    if( select_all_toggle_status == -1 || select_all_toggle_status == 0 )
                    {
                        for( i = 0; i < count; i++ )
                        {
                            c[i]->select = NWIPE_SELECT_TRUE;
                        }
                        select_all_toggle_status = 1;
                    }
                    else
                    {
                        if( select_all_toggle_status == 1 )
                        {
                            for( i = 0; i < count; i++ )
                            {
                                c[i]->select = NWIPE_SELECT_FALSE;
                            }
                            select_all_toggle_status = 0;
                        }
                        else
                        {
                            nwipe_log(
                                NWIPE_LOG_ERROR,
                                "gui.c:nwipe_gui_select(), Invalid value in variable select_all_toggle_status = %d",
                                select_all_toggle_status );
                        }
                    }

                    break;

            } /* keystroke switch */

            /* Check the terminal size, if the user has changed it the while loop checks for
             * this change and exits the valid key hit loop so the windows can be updated */
            getmaxyx( stdscr, stdscr_lines, stdscr_cols );

        } /* key hit loop */
        while( validkeyhit == 0 && terminate_signal != 1 && stdscr_cols_previous == stdscr_cols
               && stdscr_lines_previous == stdscr_lines );

    } while( keystroke != 'S' && terminate_signal != 1 );

    if( keystroke == 'S' )
    {
        /* If user has pressed S to start wipe change status line */
        werase( footer_window );
        nwipe_gui_title( footer_window, end_wipe_footer );
        wnoutrefresh( footer_window );
    }

} /* nwipe_gui_select */

void nwipe_gui_options( void )
{
    /**
     * Updates the options window.
     *
     * @modifies  options_window
     *
     */

    /* Erase the window. */
    werase( options_window );

    mvwprintw(
        options_window, NWIPE_GUI_OPTIONS_ENTROPY_Y, NWIPE_GUI_OPTIONS_ENTROPY_X, "Entropy: Linux Kernel (urandom)" );

    mvwprintw(
        options_window, NWIPE_GUI_OPTIONS_PRNG_Y, NWIPE_GUI_OPTIONS_PRNG_X, "PRNG:    %s", nwipe_options.prng->label );

    mvwprintw( options_window,
               NWIPE_GUI_OPTIONS_METHOD_Y,
               NWIPE_GUI_OPTIONS_METHOD_X,
               "Method:  %s",
               nwipe_method_label( nwipe_options.method ) );

    mvwprintw( options_window, NWIPE_GUI_OPTIONS_VERIFY_Y, NWIPE_GUI_OPTIONS_VERIFY_X, "Verify:  " );

    switch( nwipe_options.verify )
    {
        case NWIPE_VERIFY_NONE:
            wprintw( options_window, "Off" );
            break;

        case NWIPE_VERIFY_LAST:
            wprintw( options_window, "Last Pass" );
            break;

        case NWIPE_VERIFY_ALL:
            wprintw( options_window, "All Passes" );
            break;

        default:
            wprintw( options_window, "Unknown %i", nwipe_options.verify );

    } /* switch verify */

    mvwprintw( options_window, NWIPE_GUI_OPTIONS_ROUNDS_Y, NWIPE_GUI_OPTIONS_ROUNDS_X, "Rounds:  " );
    if( nwipe_options.noblank )
    {
        wprintw( options_window, "%i (no final blanking pass)", nwipe_options.rounds );
    }
    else
    {
        wprintw( options_window, "%i (plus blanking pass)", nwipe_options.rounds );
    }

    /* Add a border. */
    box( options_window, 0, 0 );

    /* Add a title. */
    nwipe_gui_title( options_window, options_title );

    /* Refresh the window. */
    // wrefresh( options_window );
    wnoutrefresh( options_window );

} /* nwipe_gui_options */

void nwipe_gui_rounds( void )
{
    /**
     * Allows the user to change the rounds option.
     *
     * @modifies  nwipe_options.rounds
     * @modifies  main_window
     *
     */

    /* Set the initial focus. */
    int focus = nwipe_options.rounds;

    /* The first tabstop. */
    const int tab1 = 2;

    /* The current working row. */
    int yy;

    /* Input buffer. */
    int keystroke;

    extern int terminate_signal;

    /* Update the footer window. */
    werase( footer_window );
    nwipe_gui_title( footer_window, rounds_footer );
    wrefresh( footer_window );

    do
    {
        /* Erase the main window. */
        werase( main_window );

        nwipe_gui_create_all_windows_on_terminal_resize( selection_footer );

        /* Add a border. */
        box( main_window, 0, 0 );

        /* Add a title. */
        nwipe_gui_title( main_window, " Rounds " );

        /* Initialize the working row. */
        yy = 4;

        mvwprintw( main_window, yy++, tab1, "This is the number of times to run the wipe method on each device." );
        mvwprintw( main_window, yy++, tab1, "" );

        if( focus > 0 )
        {
            /* Print the syslinux configuration hint. */
            mvwprintw( main_window, yy++, tab1, "syslinux.cfg:  nuke=\"nwipe --rounds %i\"", focus );

            /* Print this line last so that the cursor is in the right place. */
            mvwprintw( main_window, 2, tab1, "> %i", focus );
        }
        else
        {
            mvwprintw( main_window, yy++, tab1, "The number of rounds must be a non-negative integer." );

            /* Print this line last so that the cursor is in the right place. */
            mvwprintw( main_window, 2, tab1, "> " );
        }

        /* Reveal the cursor. */
        curs_set( 1 );

        /* Refresh the window. */
        wrefresh( main_window );

        /* Wait 250ms for input from getch, if nothing getch will then continue,
         * This is necessary so that the while loop can be exited by the
         * terminate_signal e.g.. the user pressing control-c to exit.
         * Do not change this value, a higher value means the keys become
         * sluggish, any slower and more time is spent unnecessarily looping
         * which wastes CPU cycles.
         */
        timeout( 250 );  // block getch() for 250ms.
        keystroke = getch();  // Get a keystroke.
        timeout( -1 );  // Switch back to blocking mode.

        switch( keystroke )
        {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':

                if( focus < 100000000 )
                {
                    /* Left shift, base ten. */
                    focus *= 10;

                    /* This assumes ASCII input, where the zero character is 0x30. */
                    focus += keystroke - 48;
                }

                break;

            /* Escape key. */
            case 27:
                return;

            case KEY_BACKSPACE:
            case KEY_LEFT:
            case 127:

                /* Right shift, base ten. */
                focus /= 10;

                break;

        } /* switch keystroke */

        /* Hide the cursor. */
        curs_set( 0 );

    } while( keystroke != 10 && terminate_signal != 1 );

    if( focus > 0 )
    {
        /* Set the number of rounds. */
        nwipe_options.rounds = focus;
    }

} /* nwipe_guid_rounds */

void nwipe_gui_prng( void )
{
    /**
     * Allows the user to change the PRNG.
     *
     * @modifies  nwipe_options.prng
     * @modifies  main_window
     *
     */

    extern nwipe_prng_t nwipe_twister;
    extern nwipe_prng_t nwipe_isaac;
    extern int terminate_signal;

    /* The number of implemented PRNGs. */
    const int count = 2;

    /* The first tabstop. */
    const int tab1 = 2;

    /* The second tabstop. */
    const int tab2 = 30;

    /* Set the initial focus. */
    int focus = 0;

    /* The current working row. */
    int yy;

    /* Input buffer. */
    int keystroke;

    /* Update the footer window. */
    werase( footer_window );
    nwipe_gui_title( footer_window, selection_footer );
    wrefresh( footer_window );

    if( nwipe_options.prng == &nwipe_twister )
    {
        focus = 0;
    }
    if( nwipe_options.prng == &nwipe_isaac )
    {
        focus = 1;
    }

    do
    {
        /* Clear the main window. */
        werase( main_window );

        nwipe_gui_create_all_windows_on_terminal_resize( selection_footer );

        /* Initialize the working row. */
        yy = 2;

        /* Print the options. */
        mvwprintw( main_window, yy++, tab1, "" );
        mvwprintw( main_window, yy++, tab1, "" );
        mvwprintw( main_window, yy++, tab1, "  %s", nwipe_twister.label );
        mvwprintw( main_window, yy++, tab1, "  %s", nwipe_isaac.label );
        mvwprintw( main_window, yy++, tab1, "" );

        /* Print the cursor. */
        mvwaddch( main_window, 4 + focus, tab1, ACS_RARROW );

        switch( focus )
        {
            case 0:

                mvwprintw( main_window, 2, tab2, "syslinux.cfg:  nuke=\"nwipe --prng twister\"" );

                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "The Mersenne Twister, by Makoto Matsumoto and Takuji Nishimura, is a        " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "generalized feedback shift register PRNG that is uniform and                " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "equidistributed in 623-dimensions with a proven period of 2^19937-1.        " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "                                                                            " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "This implementation passes the Marsaglia Diehard test suite.                " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "                                                                            " );
                break;

            case 1:

                mvwprintw( main_window, 2, tab2, "syslinux.cfg:  nuke=\"nwipe --prng isaac\"" );

                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "ISAAC, by Bob Jenkins, is a PRNG derived from RC4 with a minimum period of  " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "2^40 and an expected period of 2^8295.  It is difficult to recover the      " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "initial PRNG state by cryptanalysis of the ISAAC stream.                    " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "                                                                            " );
                break;

        } /* switch */

        /* Add a border. */
        box( main_window, 0, 0 );

        /* Add a title. */
        nwipe_gui_title( main_window, " Pseudo Random Number Generator " );

        /* Refresh the window. */
        wrefresh( main_window );

        /* Wait 250ms for input from getch, if nothing getch will then continue,
         * This is necessary so that the while loop can be exited by the
         * terminate_signal e.g.. the user pressing control-c to exit.
         * Do not change this value, a higher value means the keys become
         * sluggish, any slower and more time is spent unnecessarily looping
         * which wastes CPU cycles.
         */
        timeout( 250 );  // block getch() for 250ms.
        keystroke = getch();  // Get a keystroke.
        timeout( -1 );  // Switch back to blocking mode.

        switch( keystroke )
        {
            case KEY_DOWN:
            case 'j':
            case 'J':

                if( focus < count - 1 )
                {
                    focus += 1;
                }
                break;

            case KEY_UP:
            case 'k':
            case 'K':

                if( focus > 0 )
                {
                    focus -= 1;
                }
                break;

            case KEY_ENTER:
            case ' ':
            case 10:

                if( focus == 0 )
                {
                    nwipe_options.prng = &nwipe_twister;
                }
                if( focus == 1 )
                {
                    nwipe_options.prng = &nwipe_isaac;
                }
                return;

            case KEY_BACKSPACE:
            case KEY_BREAK:

                return;

        } /* switch */

    } while( terminate_signal != 1 );

} /* nwipe_gui_prng */

void nwipe_gui_verify( void )
{
    /**
     * Allows the user to change the verification option.
     *
     * @modifies  nwipe_options.verify
     * @modifies  main_window
     *
     */

    extern int terminate_signal;

    /* The number of definitions in the nwipe_verify_t enumeration. */
    const int count = 3;

    /* The first tabstop. */
    const int tab1 = 2;

    /* The second tabstop. */
    const int tab2 = 30;

    /* Set the initial focus. */
    int focus = nwipe_options.verify;

    /* The current working row. */
    int yy;

    /* Input buffer. */
    int keystroke;

    /* Update the footer window. */
    werase( footer_window );
    nwipe_gui_title( footer_window, selection_footer );
    wrefresh( footer_window );

    do
    {
        nwipe_gui_create_all_windows_on_terminal_resize( selection_footer );

        /* Clear the main window. */
        werase( main_window );

        /* Initialize the working row. */
        yy = 2;

        /* Print the options. */
        mvwprintw( main_window, yy++, tab1, "  Verification Off  " );
        mvwprintw( main_window, yy++, tab1, "  Verify Last Pass  " );
        mvwprintw( main_window, yy++, tab1, "  Verify All Passes " );
        mvwprintw( main_window, yy++, tab1, "                    " );

        /* Print the cursor. */
        mvwaddch( main_window, 2 + focus, tab1, ACS_RARROW );

        switch( focus )
        {
            case 0:

                mvwprintw( main_window, 2, tab2, "syslinux.cfg:  nuke=\"nwipe --verify off\"" );

                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "Do not verify passes. The wipe will be a write-only operation.              " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "                                                                            " );
                break;

            case 1:

                mvwprintw( main_window, 2, tab2, "syslinux.cfg:  nuke=\"nwipe --verify last\"" );

                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "Check whether the device is actually empty after the last pass fills the    " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "device with zeros.                                                          " );
                break;

            case 2:

                mvwprintw( main_window, 2, tab2, "syslinux.cfg:  nuke=\"nwipe --verify all\"" );

                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "After every pass, read back the pattern and check whether it is correct.    " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "                                                                            " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "This program writes the entire length of the device before it reads back    " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "for verification, even for random pattern passes, to better ensure that     " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "hardware caches are actually flushed.                                       " );
                break;

        } /* switch */

        /* Add a border. */
        box( main_window, 0, 0 );

        /* Add a title. */
        nwipe_gui_title( main_window, " Verification Mode " );

        /* Refresh the window. */
        wrefresh( main_window );

        /* Wait 250ms for input from getch, if nothing getch will then continue,
         * This is necessary so that the while loop can be exited by the
         * terminate_signal e.g.. the user pressing control-c to exit.
         * Do not change this value, a higher value means the keys become
         * sluggish, any slower and more time is spent unnecessarily looping
         * which wastes CPU cycles.
         */
        timeout( 250 );  // block getch() for 250ms.
        keystroke = getch();  // Get a keystroke.
        timeout( -1 );  // Switch back to blocking mode.

        switch( keystroke )
        {
            case KEY_DOWN:
            case 'j':
            case 'J':

                if( focus < count - 1 )
                {
                    focus += 1;
                }
                break;

            case KEY_UP:
            case 'k':
            case 'K':

                if( focus > 0 )
                {
                    focus -= 1;
                }
                break;

            case KEY_ENTER:
            case ' ':
            case 10:

                if( focus >= 0 && focus < count )
                {
                    nwipe_options.verify = focus;
                }
                if( nwipe_options.verify != NWIPE_VERIFY_NONE )
                {
                    nwipe_options.noblank = 0;
                }
                return;

            case KEY_BACKSPACE:
            case KEY_BREAK:

                return;

        } /* switch */

    } while( terminate_signal != 1 );

} /* nwipe_gui_verify */

void nwipe_gui_noblank( void )
{
    /**
     * Allows the user to change the verification option.
     *
     * @modifies  nwipe_options.noblank
     * @modifies  main_window
     *
     */

    extern int terminate_signal;

    /* The number of options available. */
    const int count = 2;

    /* The first tabstop. */
    const int tab1 = 2;

    /* The second tabstop. */
    const int tab2 = 40;

    /* Set the initial focus. */
    int focus = nwipe_options.noblank;

    /* The current working row. */
    int yy;

    /* Input buffer. */
    int keystroke;

    /* Update the footer window. */
    werase( footer_window );
    nwipe_gui_title( footer_window, selection_footer );
    wrefresh( footer_window );

    do
    {
        nwipe_gui_create_all_windows_on_terminal_resize( selection_footer );

        /* Clear the main window. */
        werase( main_window );

        /* Initialize the working row. */
        yy = 2;

        /* Print the options. */
        mvwprintw( main_window, yy++, tab1, "  Perform a final blanking pass       " );
        mvwprintw( main_window, yy++, tab1, "  Do not perform final blanking pass  " );
        mvwprintw( main_window, yy++, tab1, "                                      " );

        /* Print the cursor. */
        mvwaddch( main_window, 2 + focus, tab1, ACS_RARROW );

        switch( focus )
        {
            case 0:

                mvwprintw( main_window, 2, tab2, "syslinux.cfg:  nuke=\"nwipe\"" );

                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "Perform a final blanking pass after the wipe, leaving disk with only zeros. " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "Note that the RCMP TSSIT OPS-II method never blanks the device regardless   " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "of this setting.                                                            " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "                                                                            " );
                break;

            case 1:

                mvwprintw( main_window, 2, tab2, "syslinux.cfg:  nuke=\"nwipe --noblank\"" );

                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "Do not perform a final blanking pass. Leave data as per final wiping pass.  " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "Note that the RCMP TSSIT OPS-II method never blanks the device regardless   " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "of this setting.                                                            " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "                                                                            " );
                break;

        } /* switch */

        /* Add a border. */
        box( main_window, 0, 0 );

        /* Add a title. */
        nwipe_gui_title( main_window, " Final Blanking Pass " );

        /* Refresh the window. */
        wrefresh( main_window );

        /* Wait 250ms for input from getch, if nothing getch will then continue,
         * This is necessary so that the while loop can be exited by the
         * terminate_signal e.g.. the user pressing control-c to exit.
         * Do not change this value, a higher value means the keys become
         * sluggish, any slower and more time is spent unnecessarily looping
         * which wastes CPU cycles.
         */
        timeout( 250 );  // block getch() for 250ms.
        keystroke = getch();  // Get a keystroke.
        timeout( -1 );  // Switch back to blocking mode.

        switch( keystroke )
        {
            case KEY_DOWN:
            case 'j':
            case 'J':

                if( focus < count - 1 )
                {
                    focus += 1;
                }
                break;

            case KEY_UP:
            case 'k':
            case 'K':

                if( focus > 0 )
                {
                    focus -= 1;
                }
                break;

            case KEY_ENTER:
            case ' ':
            case 10:

                if( focus >= 0 && focus < count )
                {
                    nwipe_options.noblank = focus;
                }
                if( nwipe_options.noblank )
                {
                    nwipe_options.verify = NWIPE_VERIFY_NONE;
                }
                return;

            case KEY_BACKSPACE:
            case KEY_BREAK:

                return;

        } /* switch */

    }

    while( terminate_signal != 1 );
} /* nwipe_gui_noblank */

void nwipe_gui_method( void )
{
    /**
     * Allows the user to change the wipe method.
     *
     * @modifies  nwipe_options.method
     * @modifies  main_window
     *
     */

    extern int terminate_signal;

    /* The number of implemented methods. */
    const int count = 8;

    /* The first tabstop. */
    const int tab1 = 2;

    /* The second tabstop. */
    const int tab2 = 30;

    /* The currently selected method. */
    int focus = 0;

    /* The current working row. */
    int yy;

    /* Input buffer. */
    int keystroke;

    /* Update the footer window. */
    werase( footer_window );
    nwipe_gui_title( footer_window, selection_footer );
    wrefresh( footer_window );

    if( nwipe_options.method == &nwipe_zero )
    {
        focus = 0;
    }
    if( nwipe_options.method == &nwipe_ops2 )
    {
        focus = 1;
    }
    if( nwipe_options.method == &nwipe_dodshort )
    {
        focus = 2;
    }
    if( nwipe_options.method == &nwipe_dod522022m )
    {
        focus = 3;
    }
    if( nwipe_options.method == &nwipe_gutmann )
    {
        focus = 4;
    }
    if( nwipe_options.method == &nwipe_random )
    {
        focus = 5;
    }
    if( nwipe_options.method == &nwipe_verify )
    {
        focus = 6;
    }
    if( nwipe_options.method == &nwipe_is5enh )
    {
        focus = 7;
    }

    do
    {
        /* Clear the main window. */
        werase( main_window );

        nwipe_gui_create_all_windows_on_terminal_resize( selection_footer );

        /* Initialize the working row. */
        yy = 2;

        /* Print the options. */
        mvwprintw( main_window, yy++, tab1, "  %s", nwipe_method_label( &nwipe_zero ) );
        mvwprintw( main_window, yy++, tab1, "  %s", nwipe_method_label( &nwipe_ops2 ) );
        mvwprintw( main_window, yy++, tab1, "  %s", nwipe_method_label( &nwipe_dodshort ) );
        mvwprintw( main_window, yy++, tab1, "  %s", nwipe_method_label( &nwipe_dod522022m ) );
        mvwprintw( main_window, yy++, tab1, "  %s", nwipe_method_label( &nwipe_gutmann ) );
        mvwprintw( main_window, yy++, tab1, "  %s", nwipe_method_label( &nwipe_random ) );
        mvwprintw( main_window, yy++, tab1, "  %s", nwipe_method_label( &nwipe_verify ) );
        mvwprintw( main_window, yy++, tab1, "  %s", nwipe_method_label( &nwipe_is5enh ) );
        mvwprintw( main_window, yy++, tab1, "                                             " );

        /* Print the cursor. */
        mvwaddch( main_window, 2 + focus, tab1, ACS_RARROW );

        switch( focus )
        {
            case 0:

                mvwprintw( main_window, 2, tab2, "syslinux.cfg: nuke=\"nwipe --method zero\"" );
                mvwprintw( main_window, 3, tab2, "Security Level: Low (1 pass)" );

                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "This method fills the device with zeros. Note that the rounds option does    " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "not apply to this method. This method always runs one round.                 " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "                                                                             " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "Use this method to blank disks before internal redeployment, or before       " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "reinstalling Microsoft Windows to remove the data areas that the format      " );
                mvwprintw(
                    main_window, yy++, tab1, "utility preserves.                                                    " );
                break;

            case 1:

                mvwprintw( main_window, 2, tab2, "syslinux.cfg: nuke=\"nwipe --method ops2\"" );
                mvwprintw( main_window, 3, tab2, "Security Level: Medium (8 passes)" );

                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "The Royal Canadian Mounted Police Technical Security Standard for            " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "Information Technology, Appendix OPS-II: Media Sanitization.                 " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "                                                                             " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "This implementation, with regards to paragraph 2 section A of the standard,  " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "uses a pattern that is one random byte and that is changed each round.       " );
                break;

            case 2:

                mvwprintw( main_window, 2, tab2, "syslinux.cfg: nuke=\"nwipe --method dodshort\"" );
                mvwprintw( main_window, 3, tab2, "Security Level: Medium (3 passes)" );

                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "The American Department of Defense 5220.22-M short wipe.                     " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "This method is composed of passes 1, 2 & 7 from the standard wipe.           " );
                break;

            case 3:

                mvwprintw( main_window, 2, tab2, "syslinux.cfg: nuke=\"nwipe --method dod522022m\"" );
                mvwprintw( main_window, 3, tab2, "Security Level: Medium (7 passes)" );

                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "The American Department of Defense 5220.22-M standard wipe.                  " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "This implementation uses the same algorithm as the Heidi Eraser product.     " );
                break;

            case 4:

                mvwprintw( main_window, 2, tab2, "syslinux.cfg: nuke=\"nwipe --method gutmann\"" );
                mvwprintw( main_window, 3, tab2, "Security Level: High (35 passes)" );

                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "This is the method described by Peter Gutmann in the paper entitled          " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "\"Secure Deletion of Data from Magnetic and Solid-State Memory\".            " );
                break;

            case 5:

                mvwprintw( main_window, 2, tab2, "syslinux.cfg: nuke=\"nwipe --method random\"" );
                mvwprintw( main_window, 3, tab2, "Security Level: Depends on Rounds" );

                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "This method fills the device with a stream from the PRNG. It is probably the " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "best method to use on modern hard disk drives because encoding schemes vary. " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "                                                                             " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "This method has a medium security level with 4 rounds, and a high security   " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "level with 8 rounds.                                                         " );
                break;

            case 6:

                mvwprintw( main_window, 2, tab2, "syslinux.cfg: nuke=\"nwipe --method verify\"" );
                mvwprintw( main_window, 3, tab2, "Security Level: None" );

                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "This method only reads the device and checks that it is all zero.            " );

                break;

            case 7:

                mvwprintw( main_window, 2, tab2, "syslinux.cfg: nuke=\"nwipe --method is5enh\"" );
                mvwprintw( main_window, 3, tab2, "Security Level: Medium (3 passes)" );

                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "HMG IA/IS 5 (Infosec Standard 5): Secure Sanitisation of Protectively Marked " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "Information or Sensitive Information                                         " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "                                                                             " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "This method fills the device with 0s, then with 1s, then with a PRNG stream, " );
                mvwprintw( main_window,
                           yy++,
                           tab1,
                           "then reads the device to verify the PRNG stream was successfully written.    " );
                break;

        } /* switch */

        /* Add a border. */
        box( main_window, 0, 0 );

        /* Add a title. */
        nwipe_gui_title( main_window, " Wipe Method " );

        /* Refresh the window. */
        wrefresh( main_window );

        /* Wait 250ms for input from getch, if nothing getch will then continue,
         * This is necessary so that the while loop can be exited by the
         * terminate_signal e.g.. the user pressing control-c to exit.
         * Do not change this value, a higher value means the keys become
         * sluggish, any slower and more time is spent unnecessarily looping
         * which wastes CPU cycles.
         */
        timeout( 250 ); /* block getch() for 250ms */
        keystroke = getch(); /* Get a keystroke. */
        timeout( -1 ); /* Switch back to blocking mode */

        switch( keystroke )
        {
            case KEY_DOWN:
            case 'j':
            case 'J':

                if( focus < count - 1 )
                {
                    focus += 1;
                }
                break;

            case KEY_UP:
            case 'k':
            case 'K':

                if( focus > 0 )
                {
                    focus -= 1;
                }
                break;

            case KEY_BACKSPACE:
            case KEY_BREAK:

                return;

        } /* switch */

    } while( keystroke != KEY_ENTER && keystroke != ' ' && keystroke != 10 && terminate_signal != 1 );

    switch( focus )
    {
        case 0:
            nwipe_options.method = &nwipe_zero;
            break;

        case 1:
            nwipe_options.method = &nwipe_ops2;
            break;

        case 2:
            nwipe_options.method = &nwipe_dodshort;
            break;

        case 3:
            nwipe_options.method = &nwipe_dod522022m;
            break;

        case 4:
            nwipe_options.method = &nwipe_gutmann;
            break;

        case 5:
            nwipe_options.method = &nwipe_random;
            break;

        case 6:
            nwipe_options.method = &nwipe_verify;
            break;

        case 7:
            nwipe_options.method = &nwipe_is5enh;
            break;
    }

} /* nwipe_gui_method */

void nwipe_gui_load( void )
{
    /**
     * Prints the system load average to the statistics window.
     *
     * @modifies stat_window      Prints the system load average to the statistics window.
     *
     */

    /* A file handle for the stat file. */
    FILE* nwipe_fp;

    /* The one, five, and fifteen minute load averages. */
    float load_01;
    float load_05;
    float load_15;

    /* Open the loadavg file. */
    nwipe_fp = fopen( NWIPE_KNOB_LOADAVG, "r" );

    /* Print the label. */
    mvwprintw( stats_window, NWIPE_GUI_STATS_LOAD_Y, NWIPE_GUI_STATS_LOAD_X, "Load Averages:" );

    if( nwipe_fp )
    {
        /* The load averages are the first three numbers in the file. */
        if( 3 == fscanf( nwipe_fp, "%f %f %f", &load_01, &load_05, &load_15 ) )
        {
            /* Print the load average. */
            mvwprintw( stats_window,
                       NWIPE_GUI_STATS_LOAD_Y,
                       NWIPE_GUI_STATS_TAB,
                       "%04.2f %04.2f %04.2f",
                       load_01,
                       load_05,
                       load_15 );
        }
        else
        {
            /* Print an error. */
            mvwprintw( stats_window, NWIPE_GUI_STATS_LOAD_Y, NWIPE_GUI_STATS_TAB, "(fscanf error %i)", errno );
        }

        /* Close the loadavg file. */
        fclose( nwipe_fp );
    }
    else
    {
        mvwprintw( stats_window, NWIPE_GUI_STATS_LOAD_Y, NWIPE_GUI_STATS_TAB, "(fopen error %i)", errno );
    }

} /* nwipe_gui_load */

void* nwipe_gui_status( void* ptr )
{
    /**
     * Shows runtime statistics and overall progress.
     *
     * @parameter count           The number of contexts in the array.
     * @parameter c               An array of device contexts.
     *
     * @modifies  main_window     Prints information into the main window.
     * @modifies  c[].throughput  Updates the i/o throughput value.
     *
     */

    extern int terminate_signal;

    nwipe_thread_data_ptr_t* nwipe_thread_data_ptr;
    nwipe_thread_data_ptr = (nwipe_thread_data_ptr_t*) ptr;

    nwipe_context_t** c;
    nwipe_misc_thread_data_t* nwipe_misc_thread_data;
    int count;

    c = nwipe_thread_data_ptr->c;
    nwipe_misc_thread_data = nwipe_thread_data_ptr->nwipe_misc_thread_data;
    count = nwipe_misc_thread_data->nwipe_selected;

    char nomenclature_result_str[NOMENCLATURE_RESULT_STR_SIZE]; /* temporary usage */

    /* Spinner character */
    char spinner_string[2];

    /* We count time from when this function is first called. */
    static time_t nwipe_time_start = 0;

    /* Whether the screen has been blanked by the user. */
    static int nwipe_gui_blank = 0;

    /* The current time. */
    time_t nwipe_time_now;

    /* The time when all wipes ended */
    time_t nwipe_time_stopped;

    /* The index of the element that is visible in the first slot. */
    static int offset;

    /* The number of elements that we can show in the window. */
    int slots;

    /* Window dimensions. */
    int wlines;
    int wcols;

    /* Generic loop variable. */
    int i;

    /* The current working line in the main window. */
    int yy;

    /* User input buffer. */
    int keystroke;

    /* controls main while loop */
    int loop_control;

    /* The combined througput of all processes. */
    nwipe_misc_thread_data->throughput = 0;

    /* The estimated runtime of the slowest device. */
    nwipe_misc_thread_data->maxeta = 0;

    /* The combined number of errors of all processes. */
    nwipe_misc_thread_data->errors = 0;

    /* Time values. */
    int nwipe_hh;
    int nwipe_mm;
    int nwipe_ss;

    struct timespec tim, tim2;
    tim.tv_sec = 0;
    tim.tv_nsec = 100000000L; /* sleep for 0.1 seconds */

    /* Throughput variables */
    u64 nwipe_throughput;

    /* The number of active wipe processes. */
    /* Set to 1 initially to start loop.    */
    int nwipe_active = 1;

    if( nwipe_time_start == 0 )
    {
        /* This is the first time that we have been called. */
        nwipe_time_start = time( NULL ) - 1;
    }

    nwipe_gui_title( footer_window, end_wipe_footer );

    loop_control = 1;

    while( loop_control )
    {
        /* IMPORTANT ! Halfdelay(1) causes getch() to pause for 0.1 secs. This is important for two reasons.
         * 1. Pauses the getch for 0.1 secs so that the screen is only updated max 10 times/sec. Without
         *    this delay the loop would run hundreds of times per sec maxing out the core.
         * 2. By keeping the delay below 0.2 seconds, i.e 0.1, it makes the keypress and resizing
         *    nice and responsive.
         */
        halfdelay( 1 );  // Important, don't change this unless you know what you are doing ! Related to getch().

        keystroke = getch();  // Get user input.

        /* Get the current time. */
        if( nwipe_active && terminate_signal != 1 )
        {
            nwipe_time_now = time( NULL );
            nwipe_time_stopped = nwipe_time_now;
        }
        else
        {
            nwipe_time_now = nwipe_time_stopped;
        }

        /* Erase the main window. */
        werase( main_window );

        /* Erase the stats window. */
        werase( stats_window );

        /* Erase the footer window */
        werase( footer_window );

        /* Only repaint the windows on terminal resize if the user hasn't blanked the screen */
        if( nwipe_gui_blank == 0 )
        {
            if( nwipe_active != 0 )
            {
                /* if resizing the terminal during a wipe a specific footer is required */
                nwipe_gui_create_all_windows_on_terminal_resize( end_wipe_footer );
            }
            else
            {
                /* and if the wipes have finished a different footer is required */
                nwipe_gui_create_all_windows_on_terminal_resize( wipes_finished_footer );
            }
        }

        /* Initialize our working offset to the third line. */
        yy = 2;

        /* Get the window dimensions. */
        getmaxyx( main_window, wlines, wcols );

        /* Less four lines for the box and padding. */
        slots = wlines - 4;

        /* Each element prints three lines. */
        slots /= 3;

        if( nwipe_active == 0 || terminate_signal == 1 )
        {
            nwipe_gui_title( footer_window, wipes_finished_footer );

            // Refresh the footer_window ;
            wnoutrefresh( footer_window );
        }

        if( terminate_signal == 1 )
        {
            loop_control = 0;
        }

        if( keystroke > 0x0a && keystroke < 0x7e && nwipe_gui_blank == 1 )
        {
            /* Show screen */
            nwipe_gui_blank = 0;

            /* Set background */
            wbkgdset( stdscr, COLOR_PAIR( 1 ) );
            wclear( stdscr );

            /* Unhide panels */
            show_panel( header_panel );
            show_panel( footer_panel );
            show_panel( stats_panel );
            show_panel( options_panel );
            show_panel( main_panel );

            /* reprint the footer */
            nwipe_gui_title( footer_window, end_wipe_footer );

            // Refresh the footer_window ;
            wnoutrefresh( footer_window );

            /* Update panels */
            update_panels();
            doupdate();
        }
        else if( keystroke > 0 )
        {

            switch( keystroke )
            {

                case 'b':
                case 'B':

                    /* Blank screen. */
                    nwipe_gui_blank = 1;
                    hide_panel( header_panel );
                    hide_panel( footer_panel );
                    hide_panel( stats_panel );
                    hide_panel( options_panel );
                    hide_panel( main_panel );

                    /* Set the background style. */
                    wbkgdset( stdscr, COLOR_PAIR( 7 ) );
                    wclear( stdscr );

                    break;

                case KEY_DOWN:
                case 'j':
                case 'J':

                    /* Scroll down. */
                    offset += 1;

                    if( count < slots )
                    {
                        offset = 0;
                    }

                    else if( offset + slots > count )
                    {
                        offset = count - slots;
                    }

                    break;

                case KEY_UP:
                case 'k':
                case 'K':

                    /* Scroll up. */
                    offset -= 1;

                    if( offset < 0 )
                    {
                        offset = 0;
                    }

                    break;

                case ' ':
                case 0x0a:

                    /* Check whether we have finished all wipes, if yes exit while loop if user pressed spacebar or
                     * return. */
                    if( !nwipe_active || terminate_signal == 1 )
                    {
                        loop_control = 0;
                    }

                    break;

                default:

                    /* Do nothing. */
                    break;
            }

        } /* keystroke */

        /* if wipe has completed and user has specified auto poweroff or nowait then we can skip waiting for the user to
         * press return */
        if( !nwipe_active )
        {
            if( nwipe_options.autopoweroff || nwipe_options.nowait )
            {
                loop_control = 0;
            }
        }

        /* Update screen if not blanked. */
        if( nwipe_gui_blank == 0 )
        {

            if( terminate_signal != 1 )
            {
                nwipe_active = compute_stats( ptr );  // Returns number of active wipe threads
            }

            /* Print information for the user. */
            for( i = offset; i < offset + slots && i < count; i++ )
            {
                /* Print the device label. */
                mvwprintw( main_window, yy++, 2, "%s", c[i]->device_label );

                /* Check whether the child process is still running the wipe. */
                if( c[i]->wipe_status == 1 )
                {
                    /* Print percentage and pass information. */
                    mvwprintw( main_window,
                               yy++,
                               4,
                               "[%5.2f%%, round %i of %i, pass %i of %i] ",
                               c[i]->round_percent,
                               c[i]->round_working,
                               c[i]->round_count,
                               c[i]->pass_working,
                               c[i]->pass_count );

                } /* child running */
                else
                {
                    if( c[i]->result == 0 )
                    {
                        mvwprintw( main_window, yy++, 4, "[%05.2f%% complete, SUCCESS! ", c[i]->round_percent );
                    }
                    else if( c[i]->signal )
                    {
                        wattron( main_window, COLOR_PAIR( 9 ) );
                        mvwprintw( main_window, yy++, 4, "(>>> FAILURE! <<<, signal %i) ", c[i]->signal );
                        wattroff( main_window, COLOR_PAIR( 9 ) );
                    }
                    else
                    {
                        wattron( main_window, COLOR_PAIR( 9 ) );
                        mvwprintw( main_window, yy++, 4, "(>>>FAILURE!<<<, code %i) ", c[i]->result );
                        wattroff( main_window, COLOR_PAIR( 9 ) );
                    }

                } /* child returned */

                if( c[i]->verify_errors )
                {
                    wprintw( main_window, "[verify errors: %llu] ", c[i]->verify_errors );
                }
                if( c[i]->pass_errors )
                {
                    wprintw( main_window, "[pass errors: %llu] ", c[i]->pass_errors );
                }
                if( c[i]->wipe_status == 1 )
                {
                    switch( c[i]->pass_type )
                    {
                        /* Each text field in square brackets should be the same number of characters
                         * to retain output in columns */
                        case NWIPE_PASS_FINAL_BLANK:
                            if( !c[i]->sync_status )
                            {
                                wprintw( main_window, "[ blanking] " );
                            }
                            break;

                        case NWIPE_PASS_FINAL_OPS2:
                            if( !c[i]->sync_status )
                            {
                                wprintw( main_window, "[OPS2final] " );
                            }
                            break;

                        case NWIPE_PASS_WRITE:
                            if( !c[i]->sync_status )
                            {
                                wprintw( main_window, "[ writing ] " );
                            }
                            break;

                        case NWIPE_PASS_VERIFY:
                            if( !c[i]->sync_status )
                            {
                                wprintw( main_window, "[verifying] " );
                            }
                            break;

                        case NWIPE_PASS_NONE:
                            break;
                    }

                    if( c[i]->sync_status )
                    {
                        wprintw( main_window, "[ syncing ] " );
                    }
                }

                /* Determine throughput nomenclature for this drive and output drives throughput to GUI */
                Determine_C_B_nomenclature( c[i]->throughput, nomenclature_result_str, NOMENCLATURE_RESULT_STR_SIZE );

                wprintw( main_window, "[%s/s] ", nomenclature_result_str );

                /* Insert whitespace. */
                yy += 1;

                /* Increment the next spinner character for this context if the thread is active */
                if( c[i]->wipe_status == 1 )
                {
                    spinner( c, i );
                    spinner_string[0] = c[i]->spinner_character[0];
                }
                else
                {
                    /* if the wipe thread is no longer active, replace the spinner with a space */
                    spinner_string[0] = ' ';
                }
                spinner_string[1] = 0;
                wprintw( main_window, " %s ", spinner_string );
            }

            if( offset > 0 )
            {
                mvwprintw( main_window, 1, wcols - 8, " More " );
                waddch( main_window, ACS_UARROW );
            }

            if( count - offset > slots )
            {
                mvwprintw( main_window, wlines - 2, wcols - 8, " More " );
                waddch( main_window, ACS_DARROW );
            }

            /* Box the main window. */
            box( main_window, 0, 0 );

            /* Refresh the main window. */
            wnoutrefresh( main_window );

            /* Update the load average field, but only if we are still wiping */
            if( nwipe_active && terminate_signal != 1 )
            {
                nwipe_gui_load();
            }

            nwipe_throughput = nwipe_misc_thread_data->throughput;

            /* Determine the nomenclature for the combined throughput */
            Determine_C_B_nomenclature( nwipe_throughput, nomenclature_result_str, NOMENCLATURE_RESULT_STR_SIZE );

            /* Print the combined throughput. */
            mvwprintw( stats_window, NWIPE_GUI_STATS_THROUGHPUT_Y, NWIPE_GUI_STATS_THROUGHPUT_X, "Throughput:" );

            mvwprintw(
                stats_window, NWIPE_GUI_STATS_THROUGHPUT_Y, NWIPE_GUI_STATS_TAB, "%s/s", nomenclature_result_str );

            /* Change the current time into a delta. */
            nwipe_time_now -= nwipe_time_start;

            /* Put the delta into HH:mm:ss form. */
            nwipe_hh = nwipe_time_now / 3600;
            nwipe_time_now %= 3600;
            nwipe_mm = nwipe_time_now / 60;
            nwipe_time_now %= 60;
            nwipe_ss = nwipe_time_now;

            /* Print the runtime. */
            mvwprintw( stats_window, NWIPE_GUI_STATS_RUNTIME_Y, 1, "Runtime:" );
            mvwprintw( stats_window,
                       NWIPE_GUI_STATS_RUNTIME_Y,
                       NWIPE_GUI_STATS_TAB,
                       "%02i:%02i:%02i",
                       nwipe_hh,
                       nwipe_mm,
                       nwipe_ss );

            mvwprintw( stats_window, NWIPE_GUI_STATS_ETA_Y, 1, "Remaining:" );

            time_t nwipe_maxeta = nwipe_misc_thread_data->maxeta;
            if( nwipe_maxeta > 0 )
            {
                /* Do it again for the estimated runtime remaining. */
                nwipe_hh = nwipe_maxeta / 3600;
                nwipe_maxeta %= 3600;
                nwipe_mm = nwipe_maxeta / 60;
                nwipe_maxeta %= 60;
                nwipe_ss = nwipe_maxeta;

                /* Print the estimated runtime remaining. */
                mvwprintw( stats_window,
                           NWIPE_GUI_STATS_ETA_Y,
                           NWIPE_GUI_STATS_TAB,
                           "%02i:%02i:%02i",
                           nwipe_hh,
                           nwipe_mm,
                           nwipe_ss );
            }

            /* Print the error count. */
            mvwprintw( stats_window, NWIPE_GUI_STATS_ERRORS_Y, NWIPE_GUI_STATS_ERRORS_X, "Errors:" );
            mvwprintw(
                stats_window, NWIPE_GUI_STATS_ERRORS_Y, NWIPE_GUI_STATS_TAB, "  %llu", nwipe_misc_thread_data->errors );

            /* Add a border. */
            box( stats_window, 0, 0 );

            /* Add a title. */
            mvwprintw( stats_window, 0, ( NWIPE_GUI_STATS_W - strlen( stats_title ) ) / 2, "%s", stats_title );

            /* Refresh internal representation of stats window */
            wnoutrefresh( stats_window );

            /* Output all windows to screen */
            doupdate();

        }  // end blank screen if

    } /* End of while loop */

    if( nwipe_options.logfile[0] == '\0' )
    {
        nwipe_gui_title( footer_window, wipes_finished_footer );
    }
    else
    {
        char finish_message[NWIPE_GUI_FOOTER_W];
        snprintf( finish_message,
                  sizeof( finish_message ),
                  "Wipe finished - press enter to exit. Logged to %s",
                  nwipe_options.logfile );
        nwipe_gui_title( footer_window, finish_message );
    }
    terminate_signal = 1;

    return NULL;
} /* nwipe_gui_status */

int compute_stats( void* ptr )
{
    nwipe_thread_data_ptr_t* nwipe_thread_data_ptr;
    nwipe_thread_data_ptr = (nwipe_thread_data_ptr_t*) ptr;

    nwipe_context_t** c;
    nwipe_misc_thread_data_t* nwipe_misc_thread_data;

    c = nwipe_thread_data_ptr->c;
    nwipe_misc_thread_data = nwipe_thread_data_ptr->nwipe_misc_thread_data;
    int count = nwipe_misc_thread_data->nwipe_selected;

    int nwipe_active = 0;
    int i;

    time_t nwipe_time_now = time( NULL );

    nwipe_misc_thread_data->throughput = 0;
    nwipe_misc_thread_data->maxeta = 0;
    nwipe_misc_thread_data->errors = 0;

    /* Enumerate all contexts to compute statistics. */
    for( i = 0; i < count; i++ )
    {
        /* Check whether the child process is still running the wipe. */
        if( c[i]->wipe_status == 1 )
        {
            /* Increment the child counter. */
            nwipe_active += 1;

            /* Even if the wipe has finished ALWAYS run the stats one last time so the final SUCCESS percentage value is
             * correct. Maintain a rolling average of throughput. */
            nwipe_update_speedring( &c[i]->speedring, c[i]->round_done, nwipe_time_now );

            if( c[i]->speedring.timestotal > 0 && c[i]->wipe_status == 1 )
            {
                /* Update the current average throughput in bytes-per-second. */
                c[i]->throughput = c[i]->speedring.bytestotal / c[i]->speedring.timestotal;

                /* Update the estimated remaining runtime. */
                /* Check that throughput is not zero (sometimes caused during a sync) */
                if( c[i]->throughput == 0 )
                {
                    c[i]->throughput = 1;
                }

                c[i]->eta = ( c[i]->round_size - c[i]->round_done ) / c[i]->throughput;

                if( c[i]->eta > nwipe_misc_thread_data->maxeta )
                {
                    nwipe_misc_thread_data->maxeta = c[i]->eta;
                }
            }

            /* Calculate the average throughput */
            c[i]->throughput = (double) c[i]->round_done / (double) difftime( nwipe_time_now, c[i]->start_time );
        }

        /* Update the percentage value. */
        c[i]->round_percent = (double) c[i]->round_done / (double) c[i]->round_size * 100;

        if( c[i]->wipe_status == 1 )
        {
            /* Accumulate combined throughput. */
            nwipe_misc_thread_data->throughput += c[i]->throughput;
        }

        /* Accumulate the error count. */
        nwipe_misc_thread_data->errors += c[i]->pass_errors;
        nwipe_misc_thread_data->errors += c[i]->verify_errors;

    } /* for statistics */

    return nwipe_active;
}

void nwipe_update_speedring( nwipe_speedring_t* speedring, u64 speedring_bytes, time_t speedring_now )
{

    if( speedring->timeslast == 0 )
    {
        /* Ignore the first sample and initialize. */
        speedring->timeslast = speedring_now;
        return;
    }

    if( speedring_now - speedring->timeslast < NWIPE_KNOB_SPEEDRING_GRANULARITY )
    {
        /* Avoid jitter caused by frequent updates. */
        return;
    }

    /* Subtract the oldest speed sample from the accumulator. */
    speedring->bytestotal -= speedring->bytes[speedring->position];
    speedring->timestotal -= speedring->times[speedring->position];

    /* Put the lastest bytes-per-second sample into the ring buffer. */
    speedring->bytes[speedring->position] = speedring_bytes - speedring->byteslast;
    speedring->times[speedring->position] = speedring_now - speedring->timeslast;

    /* Add the newest speed sample to the accumulator. */
    speedring->bytestotal += speedring->bytes[speedring->position];
    speedring->timestotal += speedring->times[speedring->position];

    /* Remember the last sample. */
    speedring->byteslast = speedring_bytes;
    speedring->timeslast = speedring_now;

    if( ++speedring->position >= NWIPE_KNOB_SPEEDRING_SIZE )
    {
        speedring->position = 0;
    }
}

int spinner( nwipe_context_t** ptr, int device_idx )
{
    nwipe_context_t** c;

    c = ptr;

    /* The spinner characters |/-\|/-\ */
    char sc[9] = "|/-\\|/-\\/";

    /* check sanity of index */
    if( c[device_idx]->spinner_idx < 0 || c[device_idx]->spinner_idx > 7 )
    {
        return 1;
    }

    c[device_idx]->spinner_character[0] = sc[c[device_idx]->spinner_idx];

    c[device_idx]->spinner_idx++;

    if( c[device_idx]->spinner_idx > 7 )
    {
        c[device_idx]->spinner_idx = 0;
    }

    return 0;
}
