/*
 * rofi
 *
 * MIT/X11 License
 * Copyright © 2013-2020 Qball Cow <qball@gmpclient.org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */

#include <config.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <glib.h>
#include <glib/gstdio.h>
#include "rofi.h"
#include "history.h"
#include "settings.h"

/**
 * History element
 */
typedef struct __element
{
    /** Index in history */
    long int index;
    /** Entry */
    char     *name;
}_element;

static int __element_sort_func ( const void *ea, const void *eb, void *data __attribute__( ( unused ) ) )
{
    _element *a = *(_element * *) ea;
    _element *b = *(_element * *) eb;
    return b->index - a->index;
}

static void __history_write_element_list ( FILE *fd, _element **list, unsigned int length )
{
    if ( list == NULL || length == 0 ) {
        return;
    }
    // Sort the list before writing out.
    g_qsort_with_data ( list, length, sizeof ( _element* ), __element_sort_func, NULL );

    // Get minimum index.
    int min_value = list[length - 1]->index;

    // Set the max length of the list.
    length = ( length > config.max_history_size ) ? config.max_history_size : length;

    // Write out entries.
    for ( unsigned int iter = 0; iter < length; iter++ ) {
        fprintf ( fd, "%ld %s\n", list[iter]->index - min_value, list[iter]->name );
    }
}

static char ** __history_get_element_list_fields ( FILE *fd, unsigned int *length )
{
    unsigned int real_length = 0;
    char         **retv      = NULL;;
    if  ( length == NULL ) {
        return NULL;
    }
    *length = 0;

    if ( fd == NULL ) {
        return NULL;
    }
    char    *buffer       = NULL;
    size_t  buffer_length = 0;
    ssize_t l             = 0;
    while ( ( l = getline ( &buffer, &buffer_length, fd ) ) > 0 ) {
        // Jump to the first space.
        const char *start = strchr ( buffer, ' ' );
        // not found, skip.
        if ( start == NULL ) {
            continue;
        }
        start++;
        // remove trailing \n
        buffer[l - 1] = '\0';
        if ( real_length < ( *length + 2 ) ) {
            real_length += 15;
            // Resize and check.
            retv = g_realloc ( retv, ( real_length ) * sizeof ( char* ) );
        }
        // Parse the number of times.
        retv[( *length )] = g_strndup ( start, l - 1 - ( start - buffer ) );
        // Force trailing '\0'
        retv[( *length ) + 1] = NULL;

        ( *length )++;
    }
    if ( buffer_length > 0 ) {
        g_free ( buffer );
    }
    return retv;
}

static _element ** __history_get_element_list ( FILE *fd, unsigned int *length )
{
    unsigned int real_length = 0;
    _element     **retv      = NULL;

    if  ( length == NULL ) {
        return NULL;
    }
    *length = 0;

    if ( fd == NULL ) {
        return NULL;
    }
    char    *buffer       = NULL;
    size_t  buffer_length = 0;
    ssize_t l             = 0;
    while ( ( l = getline ( &buffer, &buffer_length, fd ) ) > 0 ) {
        char * start = NULL;
        // Skip empty lines.
        if ( l <= 1 ) {
            continue;
        }

        long int index = strtol ( buffer, &start, 10 );
        if ( start == buffer || *start == '\0' ) {
            continue;
        }
        start++;
        if ( ( l - ( start - buffer ) ) < 2 ) {
            continue;
        }
        if ( real_length < ( *length + 2 ) ) {
            real_length += 15;
            // Resize and check.
            retv = g_realloc ( retv, ( real_length ) * sizeof ( _element* ) );
        }

        retv[( *length )] = g_malloc ( sizeof ( _element ) );

        // remove trailing \n
        buffer[l - 1] = '\0';
        // Parse the number of times.
        retv[( *length )]->index = index;
        retv[( *length )]->name  = g_strndup ( start, l - 1 - ( start - buffer ) );
        // Force trailing '\0'
        retv[( *length ) + 1] = NULL;

        ( *length )++;
    }
    if ( buffer != NULL  ) {
        free ( buffer );
        buffer = NULL;
    }
    return retv;
}

void history_set ( const char *filename, const char *entry )
{
    if ( config.disable_history ) {
        return;
    }

    // Check if program should be ignored
    for ( char *checked_prefix = strtok ( config.ignored_prefixes, ";" ); checked_prefix != NULL; checked_prefix = strtok ( NULL, ";" ) ) {
        // For each ignored prefix

        while ( g_unichar_isspace ( g_utf8_get_char ( checked_prefix ) ) ) {
            checked_prefix = g_utf8_next_char ( checked_prefix ); // Some users will probably want "; " as their separator for aesthetics.
        }

        if ( g_str_has_prefix ( entry, checked_prefix ) ) {
            return;
        }
    }

    int          found  = 0;
    unsigned int curr   = 0;
    unsigned int length = 0;
    _element     **list = NULL;
    // Open file for reading and writing.
    FILE         *fd = g_fopen ( filename, "r" );
    if ( fd != NULL ) {
        // Get list.
        list = __history_get_element_list ( fd, &length );
        // Close file, if fails let user know on stderr.
        if ( fclose ( fd ) != 0 ) {
            g_warning ( "Failed to close history file: %s", g_strerror ( errno ) );
        }
    }
    // Look if the entry exists.
    for ( unsigned int iter = 0; !found && iter < length; iter++ ) {
        if ( strcmp ( list[iter]->name, entry ) == 0 ) {
            curr  = iter;
            found = 1;
        }
    }

    if ( found ) {
        // If exists, increment list index number
        list[curr]->index++;
    }
    else{
        // If not exists, add it.
        // Increase list by one
        list         = g_realloc ( list, ( length + 2 ) * sizeof ( _element* ) );
        list[length] = g_malloc ( sizeof ( _element ) );
        // Copy name
        if ( list[length] != NULL ) {
            list[length]->name = g_strdup ( entry );
            // set # hits
            list[length]->index = 1;

            length++;
            list[length] = NULL;
        }
    }

    fd = fopen ( filename, "w" );
    if ( fd == NULL ) {
        g_warning ( "Failed to open file: %s", g_strerror ( errno ) );
    }
    else {
        // Write list.
        __history_write_element_list ( fd, list, length );
        // Close file, if fails let user know on stderr.
        if ( fclose ( fd ) != 0 ) {
            g_warning ( "Failed to close history file: %s", g_strerror ( errno ) );
        }
    }
    // Free the list.
    for ( unsigned int iter = 0; iter < length; iter++ ) {
        g_free ( list[iter]->name );
        g_free ( list[iter] );
    }
    g_free ( list );
}

void history_remove ( const char *filename, const char *entry )
{
    if ( config.disable_history ) {
        return;
    }
    _element     ** list = NULL;
    int          found   = 0;
    unsigned int curr    = 0;
    unsigned int length  = 0;
    // Open file for reading and writing.
    FILE         *fd = g_fopen ( filename, "r" );
    if ( fd == NULL ) {
        g_warning ( "Failed to open file: %s", g_strerror ( errno ) );
        return;
    }
    // Get list.
    list = __history_get_element_list ( fd, &length );

    // Close file, if fails let user know on stderr.
    if ( fclose ( fd ) != 0 ) {
        g_warning ( "Failed to close history file: %s", g_strerror ( errno ) );
    }
    // Find entry.
    for ( unsigned int iter = 0; !found && iter < length; iter++ ) {
        if ( strcmp ( list[iter]->name, entry ) == 0 ) {
            curr  = iter;
            found = 1;
        }
    }

    // If found, remove it and write out new file.
    if ( found ) {
        // Remove the entry.
        g_free ( list[curr]->name );
        g_free ( list[curr] );
        // Swap last to here (if list is size 1, we just swap empty sets).
        list[curr] = list[length - 1];
        // Empty last.
        list[length - 1] = NULL;
        length--;

        fd = g_fopen ( filename, "w" );
        // Clear list.
        if ( fd != NULL ) {
            // Write list.
            __history_write_element_list ( fd, list, length );
            // Close file, if fails let user know on stderr.
            if ( fclose ( fd ) != 0 ) {
                g_warning ( "Failed to close history file: %s", g_strerror ( errno ) );
            }
        }
        else{
            g_warning ( "Failed to open file: %s", g_strerror ( errno ) );
        }
    }

    // Free the list.
    for ( unsigned int iter = 0; iter < length; iter++ ) {
        g_free ( list[iter]->name );
        g_free ( list[iter] );
    }
    if ( list != NULL ) {
        g_free ( list );
    }
}

char ** history_get_list ( const char *filename, unsigned int *length )
{
    *length = 0;

    if ( config.disable_history ) {
        return NULL;
    }
    char **retv = NULL;
    // Open file.
    FILE *fd = g_fopen ( filename, "r" );
    if ( fd == NULL ) {
        // File that does not exists is not an error, so ignore it.
        // Everything else? panic.
        if ( errno != ENOENT ) {
            g_warning ( "Failed to open file: %s", g_strerror ( errno ) );
        }
        return NULL;
    }
    // Get list.
    retv = __history_get_element_list_fields ( fd, length );

    // Close file, if fails let user know on stderr.
    if ( fclose ( fd ) != 0 ) {
        g_warning ( "Failed to close history file: %s", g_strerror ( errno ) );
    }
    return retv;
}