/*
 * ****************************************************************************
 *  customers.c: Functions related to customer processing for the PDF erasure *
 *  certificate.                                                              *
 * ****************************************************************************
 *
 *  Copyright PartialVolume <https://github.com/PartialVolume>.
 *
 *  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.
 *
 */

#include <stdio.h>
#include "nwipe.h"
#include "context.h"
#include "gui.h"
#include "logging.h"
#include "conf.h"
#include "customers.h"
#include <sys/stat.h>

void customer_processes( int mode )
{
    /* This function reads the customers.csv file, counts the number of lines,
     * converts line feeds to NULL and constructs a array of pointers that point
     * to the variable length strings.
     *
     * Depending on the value of mode the pointer array is passed to either
     * the select_customers() or delete_customer() functions.
     */

    int idx;
    int idx2;
    FILE* fptr;

    struct stat st;
    intmax_t size = 0;
    int lines;
    int list_idx;
    int current_list_size;

    size_t result_size;

    extern char nwipe_customers_file[];

    /* Determine size of customers.csv file */
    stat( nwipe_customers_file, &st );
    size = st.st_size;
    current_list_size = 0;

    nwipe_customers_buffer_t raw_buffer = (nwipe_customers_buffer_t) calloc( 1, size + 1 );

    /* Allocate storage for the contents of customers.csv */
    nwipe_customers_buffer_t buffer = (nwipe_customers_buffer_t) calloc( 1, size + 1 );

    /* Allocate storage for the processed version of customers.csv,
     * i.e we convert the csv format to strings without the quotes
     * and semi colon delimiters
     */
    nwipe_customers_pointers_t list = (nwipe_customers_pointers_t) calloc( 1, sizeof( char* ) );
    current_list_size += sizeof( char* );

    /* Open customers.csv */
    if( ( fptr = fopen( nwipe_customers_file, "rb" ) ) == NULL )
    {
        nwipe_log( NWIPE_LOG_ERROR, "Unable to open %s", nwipe_customers_file );
        free( buffer );
        free( *list );
        return;
    }

    /* Read the customers.csv file and populate the list array with the data */
    result_size = fread( raw_buffer, size, 1, fptr );

    fclose( fptr );

    /* Validate csv contents. With the exception of line feeds,
     * remove non printable characters and move to a secondary buffer.
     */
    idx = 0;
    idx2 = 0;
    while( idx < size )
    {
        if( ( raw_buffer[idx] > 0x1F && raw_buffer[idx] < 0x7F ) || raw_buffer[idx] == 0x0A )
        {
            /* copy printable characters and line feeds */
            buffer[idx2++] = raw_buffer[idx];
        }
        idx++;
    }

    /* Construct a array of pointers that point to each line of the csv file
     */
    idx = 0;
    lines = 0;
    list_idx = 0;

    while( idx < size )
    {
        if( buffer[idx] == 0x0A )
        {
            buffer[idx] = 0;

            /* increment the line counter, but don't count
             * the first line as that is the csv header line.
             */
            if( idx != 0 )
            {
                lines++;

                /* Change the line feed to a NULL, string terminator */
                buffer[idx] = 0;

                /* Save the pointer to the first data line of the csv. */
                list[list_idx++] = &buffer[idx + 1];

                current_list_size += sizeof( char* );

                /* Expand allocated memory by the size of one pointer */
                if( ( list = realloc( list, current_list_size ) ) == NULL )
                {
                    nwipe_log( NWIPE_LOG_ERROR, "Unable to realloc customer list array, out of memory?" );
                    break;
                }
                current_list_size += sizeof( char* );
            }
        }
        else
        {
            /* Replace colons with commas */
            if( buffer[idx] == ';' )
            {
                buffer[idx] = ',';
            }
        }
        idx++;
    }

    /* Sync lines variable to actual number of lines */
    if( lines > 0 )
        lines--;

    if( idx == size )
    {
        /* makesure the very last entry is NULL terminated */
        buffer[idx] = 0;
    }

    /* Select the requested mode, customer or delete customer.
     */
    switch( mode )
    {
        case SELECT_CUSTOMER:
            select_customers( lines, list );
            break;

        case DELETE_CUSTOMER:
            delete_customer( lines, list );
            break;
    }

    free( raw_buffer );
    free( buffer );
}

void select_customers( int count, char** customer_list_array )
{
    int selected_entry = 0;
    char window_title[] = " Select Customer For PDF Report ";

    /* Display the customer selection window */
    nwipe_gui_list( count, window_title, customer_list_array, &selected_entry );

    /* Save the selected customer details to nwipe's config file /etc/nwipe/nwipe.conf
     * If selected entry equals 0, then the customer did not select an entry so skip save.
     */
    if( selected_entry != 0 )
    {
        save_selected_customer( &customer_list_array[selected_entry - 1] );
    }
}

void delete_customer( int count, char** customer_list_array )
{
    char window_title[] = " Delete Customer ";
    int selected_entry = 0;

    nwipe_gui_list( count, window_title, customer_list_array, &selected_entry );

    if( selected_entry != 0 )
    {
        delete_customer_csv_entry( &selected_entry );
    }
}

void write_customer_csv_entry( char* customer_name,
                               char* customer_address,
                               char* customer_contact_name,
                               char* customer_contact_phone )
{
    /**
     * Write the attached strings in csv format to the first
     * line after the header (line 2 of file)
     */

    FILE* fptr = 0;
    FILE* fptr2 = 0;

    size_t result_size;

    /* General index variables */
    int idx1, idx2, idx3;

    /* Length of the new customer line */
    int csv_line_length;

    /* Size of the new buffer that holds old contents plus new entry */
    int new_customers_buffer_size;

    struct stat st;

    extern char nwipe_customers_file[];
    extern char nwipe_customers_file_backup[];
    extern char nwipe_customers_file_backup_tmp[];

    intmax_t existing_file_size = 0;

    /* pointer to the new customer entry in csv format. */
    char* csv_buffer = 0;

    /* pointer to the buffer containing the existing customer file */
    char* customers_buffer = 0;

    /* pointer to the buffer containing the existing customer file plus the new entry */
    char* new_customers_buffer = 0;

    size_t new_customers_buffer_length;

    /* Determine length of all four strings and malloc sufficient storage + 12 = 8 quotes + three colons + null */
    csv_line_length = strlen( customer_name ) + strlen( customer_address ) + strlen( customer_contact_name )
        + strlen( customer_contact_phone ) + 12;
    if( !( csv_buffer = calloc( 1, csv_line_length == 0 ) ) )
    {
        nwipe_log( NWIPE_LOG_ERROR, "func:nwipe_gui_add_customer:csv_buffer, calloc returned NULL " );
    }
    else
    {
        /* Determine current size of the csv file containing the customers */
        stat( nwipe_customers_file, &st );
        existing_file_size = st.st_size;

        /* calloc sufficient storage to hold the existing customers file */
        if( !( customers_buffer = calloc( 1, existing_file_size + 1 ) ) )
        {
            nwipe_log( NWIPE_LOG_ERROR, "func:nwipe_gui_add_customer:customers_buffer, calloc returned NULL " );
        }
        else
        {
            /* create a third buffer which is the combined size of the previous two, i.e existing file size, plus the
             * new customer entry + 1 (NULL) */
            new_customers_buffer_size = existing_file_size + csv_line_length + 1;

            if( !( new_customers_buffer = calloc( 1, new_customers_buffer_size ) ) )
            {
                nwipe_log( NWIPE_LOG_ERROR, "func:nwipe_gui_add_customer:customers_buffer, calloc returned NULL " );
            }
            else
            {
                /* Read the whole of customers.csv file into customers_buffer */
                if( ( fptr = fopen( nwipe_customers_file, "rb" ) ) == NULL )
                {
                    nwipe_log( NWIPE_LOG_ERROR, "Unable to open %s", nwipe_customers_file );
                }
                else
                {
                    /* Read the customers.csv file and populate the list array with the data */
                    if( ( result_size = fread( customers_buffer, existing_file_size, 1, fptr ) ) != 1 )
                    {
                        nwipe_log(
                            NWIPE_LOG_ERROR,
                            "func:nwipe_gui_add_customer:Error reading customers file, # bytes read not as expected "
                            "%i bytes",
                            result_size );
                    }
                    else
                    {
                        /* --------------------------------------------------------------------
                         * Read the first line which is the csv header from the existing customer
                         * buffer & write to the new buffer.
                         */

                        idx1 = 0;  // Index for the current csv buffer
                        idx2 = 0;  // Index for the new csv buffer
                        idx3 = 0;  // Index for new customer fields

                        while( idx1 < existing_file_size && idx2 < new_customers_buffer_size )
                        {
                            if( customers_buffer[idx1] != LINEFEED )
                            {
                                new_customers_buffer[idx2++] = customers_buffer[idx1++];
                            }
                            else
                            {
                                new_customers_buffer[idx2++] = LINEFEED;
                                break;
                            }
                        }

                        /* --------------------------------------------------------------------------
                         * Copy the new customer name entry so it is immediately after the csv header
                         */

                        /* Start with first entries opening quote */
                        new_customers_buffer[idx2++] = '"';

                        /* Copy the customer_name string */
                        while( idx3 < FIELD_LENGTH && idx2 < new_customers_buffer_size && customer_name[idx3] != 0 )
                        {
                            new_customers_buffer[idx2++] = customer_name[idx3++];
                        }

                        /* Close customer name field with a quote */
                        new_customers_buffer[idx2++] = '"';

                        /* Insert field delimiters, we use a semi-colon, not a comma ';' */
                        new_customers_buffer[idx2++] = ';';

                        /* -----------------------------------------------------------------------------
                         * Copy the new customer address entry so it is immediately after the csv header
                         */

                        idx3 = 0;

                        /* Start with first entries opening quote */
                        new_customers_buffer[idx2++] = '\"';

                        /* Copy the customer_name string */
                        while( idx3 < FIELD_LENGTH && idx2 < new_customers_buffer_size && customer_address[idx3] != 0 )
                        {
                            new_customers_buffer[idx2++] = customer_address[idx3++];
                        }

                        /* Close customer name field with a quote */
                        new_customers_buffer[idx2++] = '\"';

                        /* Insert field delimiters, we use a semi-colon, not a comma ';' */
                        new_customers_buffer[idx2++] = ';';

                        /* -----------------------------------------------------------------------------
                         * Copy the new customer contact name entry so it is immediately after the csv header
                         */

                        idx3 = 0;

                        /* Start with first entries opening quote */
                        new_customers_buffer[idx2++] = '\"';

                        /* Copy the customer_name string */
                        while( idx3 < FIELD_LENGTH && idx2 < new_customers_buffer_size
                               && customer_contact_name[idx3] != 0 )
                        {
                            new_customers_buffer[idx2++] = customer_contact_name[idx3++];
                        }

                        /* Close customer name field with a quote */
                        new_customers_buffer[idx2++] = '\"';

                        /* Insert field delimiters, we use a semi-colon, not a comma ';' */
                        new_customers_buffer[idx2++] = ';';

                        /* -----------------------------------------------------------------------------
                         * Copy the new customer contact phone entry so it is immediately after the csv header
                         */

                        idx3 = 0;

                        /* Start with first entries opening quote */
                        new_customers_buffer[idx2++] = '\"';

                        /* Copy the customer_name string */
                        while( idx3 < FIELD_LENGTH && idx2 < new_customers_buffer_size
                               && customer_contact_phone[idx3] != 0 )
                        {
                            new_customers_buffer[idx2++] = customer_contact_phone[idx3++];
                        }

                        /* Close customer name field with a quote */
                        new_customers_buffer[idx2++] = '\"';

                        /* Insert a line feed to finish the new entry */
                        new_customers_buffer[idx2++] = LINEFEED;

                        /* skip any LINEFEEDS in the existing customer entry as we just inserted one */
                        while( customers_buffer[idx1] != 0 && customers_buffer[idx1] == LINEFEED )
                        {
                            idx1++;
                        }

                        /* -------------------------------------------------------------------------------
                         * Now copy the existing customer entries, if any, immediately after the new entry
                         */

                        while( idx1 < existing_file_size && idx2 < new_customers_buffer_size )
                        {
                            /* Removes any nulls when copying and pasting, which would break this process? */
                            if( customers_buffer[idx1] == 0 )
                            {
                                while( idx1 < existing_file_size && customers_buffer[idx1] == 0 )
                                {
                                    idx1++;
                                }
                            }
                            new_customers_buffer[idx2++] = customers_buffer[idx1++];
                        }

                        /* Rename the customers.csv file to customers.csv.backup */
                        if( rename( nwipe_customers_file, nwipe_customers_file_backup_tmp ) != 0 )
                        {
                            nwipe_log( NWIPE_LOG_ERROR,
                                       "Unable to rename %s to %s",
                                       nwipe_customers_file,
                                       nwipe_customers_file_backup_tmp );
                        }
                        else
                        {
                            /* Create/open the customers.csv file */
                            if( ( fptr2 = fopen( nwipe_customers_file, "wb" ) ) == NULL )
                            {
                                nwipe_log( NWIPE_LOG_ERROR, "Unable to open %s", nwipe_customers_file );
                            }
                            else
                            {
                                /* write the new customers.csv file */
                                new_customers_buffer_length = strlen( new_customers_buffer );

                                if( ( result_size = fwrite(
                                          new_customers_buffer, sizeof( char ), new_customers_buffer_length, fptr2 ) )
                                    != new_customers_buffer_length )
                                {
                                    nwipe_log(
                                        NWIPE_LOG_ERROR,
                                        "func:write_customer_csv_entry:fwrite: Error result_size = %i not as expected",
                                        result_size );
                                }
                                else
                                {
                                    /* Remove the customer.csv.backup file if it exists */
                                    if( remove( nwipe_customers_file_backup ) != 0 )
                                    {
                                        nwipe_log(
                                            NWIPE_LOG_ERROR, "Unable to remove %s", nwipe_customers_file_backup_tmp );
                                    }
                                    else
                                    {
                                        /* Rename the customers.csv.backup.tmp file to customers.csv.backup */
                                        if( rename( nwipe_customers_file_backup_tmp, nwipe_customers_file_backup )
                                            != 0 )
                                        {
                                            nwipe_log( NWIPE_LOG_ERROR,
                                                       "Unable to rename %s to %s",
                                                       nwipe_customers_file,
                                                       nwipe_customers_file_backup_tmp );
                                        }
                                        nwipe_log( NWIPE_LOG_INFO,
                                                   "Succesfully write new customer entry to %s",
                                                   nwipe_customers_file );
                                    }
                                }
                                fclose( fptr2 );
                            }
                        }
                        fclose( fptr );
                    }
                }
                free( new_customers_buffer );
            }
            free( customers_buffer );
        }
        free( csv_buffer );
    }
}

void delete_customer_csv_entry( int* selected_entry )
{
    /**
     * Deletes a line from the csv file. The line to be deleted is determined
     * by the value of selected_entry
     */
    FILE* fptr = 0;
    FILE* fptr2 = 0;

    size_t result_size;

    struct stat st;

    extern char nwipe_customers_file[];
    extern char nwipe_customers_file_backup[];
    extern char nwipe_customers_file_backup_tmp[];

    intmax_t existing_file_size = 0;

    int linecount;

    /* General index variables */
    int idx1, idx2, idx3;

    /* pointer to the buffer containing the existing customer file */
    char* customers_buffer = 0;

    /* pointer to the buffer containing the existing customer minus the deleted entry */
    char* new_customers_buffer = 0;

    int status_flag = 0;

    size_t new_customers_buffer_length;

    /* Determine current size of the csv file containing the customers */
    stat( nwipe_customers_file, &st );
    existing_file_size = st.st_size;

    /* calloc sufficient storage to hold the existing customers file */
    if( !( customers_buffer = calloc( 1, existing_file_size + 1 ) ) )
    {
        nwipe_log( NWIPE_LOG_ERROR,
                   "func:nwipe_gui_delete_customer_csv_entry:customers_buffer, calloc returned NULL " );
    }
    else
    {
        /* create a second buffer which is identical in size to the first, it will store the customer
         * csv file minus the one selected entry
         */

        if( !( new_customers_buffer = calloc( 1, existing_file_size + 1 ) ) )
        {
            nwipe_log( NWIPE_LOG_ERROR,
                       "func:nwipe_gui_delete_customer_csv_entry:customers_buffer, calloc returned NULL " );
        }
        else
        {
            /* Read the whole of customers.csv file into customers_buffer */
            if( ( fptr = fopen( nwipe_customers_file, "rb" ) ) == NULL )
            {
                nwipe_log( NWIPE_LOG_ERROR,
                           "func:nwipe_gui_delete_customer_csv_entry:Unable to open %s",
                           nwipe_customers_file );
            }
            else
            {
                /* Read the customers.csv file and populate the list array with the data */
                if( ( result_size = fread( customers_buffer, existing_file_size, 1, fptr ) ) != 1 )
                {
                    nwipe_log( NWIPE_LOG_ERROR,
                               "func:nwipe_gui_delete_customer_csv_entry:Error reading customers file, # elements read "
                               "not as expected "
                               "%i elements",
                               result_size );
                }
                else
                {
                    /* --------------------------------------------------------------------
                     * Read the first line which is the csv header from the existing customer
                     * buffer & write to the new buffer.
                     */

                    idx1 = 0;  // Index for the current csv buffer
                    idx2 = 0;  // Index for the new csv buffer

                    linecount = 1;  // count the lines in the csv file starting at line 1

                    while( idx1 < existing_file_size && idx2 < existing_file_size )
                    {
                        if( customers_buffer[idx1] != LINEFEED )
                        {
                            new_customers_buffer[idx2++] = customers_buffer[idx1++];
                        }
                        else
                        {
                            new_customers_buffer[idx2++] = customers_buffer[idx1++];
                            break;
                        }
                    }

                    /* -------------------------------------------------------------------------------
                     * Now copy the existing customer entries, counting the lines as we go and when we
                     * get to the the line selected for deletion we skip over it and then carry on
                     * copying.
                     */

                    while( idx1 < existing_file_size && idx2 < existing_file_size )
                    {
                        /* Don't copy nulls */
                        if( customers_buffer[idx1] == 0 )
                        {
                            idx1++;
                            continue;
                        }

                        /* Is this the line to delete? */
                        if( linecount == *selected_entry )
                        {
                            /* skip all the characters in this line */
                            while( idx1 < existing_file_size && customers_buffer[idx1] != LINEFEED )
                            {
                                idx1++;
                            }

                            /* skip the trailing linefeed if it exists, may not exist if last line */
                            if( customers_buffer[idx1] == LINEFEED )
                            {
                                idx1++;
                            }
                            linecount++;
                            nwipe_log( NWIPE_LOG_INFO, "Deleted customer entry from cache" );
                            status_flag = 1;
                        }
                        else
                        {
                            /* Is the character a LINEFEED? */
                            if( customers_buffer[idx1] == LINEFEED )
                            {
                                linecount++;
                            }

                            /* Copy a character */
                            new_customers_buffer[idx2++] = customers_buffer[idx1++];
                        }
                    }

                    /* Rename the customers.csv file to customers.csv.backup */
                    if( rename( nwipe_customers_file, nwipe_customers_file_backup_tmp ) != 0 )
                    {
                        nwipe_log( NWIPE_LOG_ERROR,
                                   "func:delete_customer_csv_entry:Unable to rename %s to %s",
                                   nwipe_customers_file,
                                   nwipe_customers_file_backup_tmp );
                    }
                    else
                    {
                        /* Create/open the customers.csv file */
                        if( ( fptr2 = fopen( nwipe_customers_file, "wb" ) ) == NULL )
                        {
                            nwipe_log( NWIPE_LOG_ERROR,
                                       "func:delete_customer_csv_entry:Unable to open %s",
                                       nwipe_customers_file );
                        }
                        else
                        {
                            /* write the new customers.csv file */
                            new_customers_buffer_length = strlen( new_customers_buffer );

                            if( ( result_size = fwrite(
                                      new_customers_buffer, sizeof( char ), new_customers_buffer_length, fptr2 ) )
                                != new_customers_buffer_length )
                            {
                                nwipe_log(
                                    NWIPE_LOG_ERROR,
                                    "func:delete_customer_csv_entry:fwrite: Error result_size = %i not as expected",
                                    result_size );
                            }
                            else
                            {
                                /* Remove the customer.csv.backup file if it exists */
                                if( remove( nwipe_customers_file_backup ) != 0 )
                                {
                                    nwipe_log( NWIPE_LOG_ERROR,
                                               "func:delete_customer_csv_entry:Unable to remove %s",
                                               nwipe_customers_file_backup_tmp );
                                }
                                else
                                {
                                    /* Rename the customers.csv.backup.tmp file to customers.csv.backup */
                                    if( rename( nwipe_customers_file_backup_tmp, nwipe_customers_file_backup ) != 0 )
                                    {
                                        nwipe_log( NWIPE_LOG_ERROR,
                                                   "func:delete_customer_csv_entry:Unable to rename %s to %s",
                                                   nwipe_customers_file,
                                                   nwipe_customers_file_backup_tmp );
                                    }
                                    if( status_flag == 1 )
                                    {
                                        nwipe_log(
                                            NWIPE_LOG_INFO, "Deleted customer entry in %s", nwipe_customers_file );
                                    }
                                    else
                                    {
                                        nwipe_log( NWIPE_LOG_INFO,
                                                   "Failed to delete customer entry in %s",
                                                   nwipe_customers_file );
                                    }
                                }
                            }
                            fclose( fptr2 );
                        }
                    }
                }
                fclose( fptr );
            }
            free( new_customers_buffer );
        }
        free( customers_buffer );
    }
}
