/*
 * 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.
 *
 */

/** Log domain used by the theme engine.*/
#define G_LOG_DOMAIN    "Theme"

#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
// GFile stuff.
#include <gio/gio.h>
#include "theme.h"
#include "theme-parser.h"
#include "helper.h"
#include "settings.h"
#include "widgets/textbox.h"
#include "view.h"
#include "rofi.h"
#include "rofi-types.h"

void yyerror ( YYLTYPE *yylloc, const char *, const char * );
static gboolean distance_compare ( RofiDistance d, RofiDistance e )
{
    // TODO UPDATE
    return d.base.type == e.base.type && d.base.distance == e.base.distance && d.style == e.style;
}

static gpointer rofi_g_list_strdup ( gconstpointer data, G_GNUC_UNUSED gpointer user_data )
{
    return g_strdup ( data );
}

ThemeWidget *rofi_theme_find_or_create_name ( ThemeWidget *base, const char *name )
{
    for ( unsigned int i = 0; i < base->num_widgets; i++ ) {
        if ( g_strcmp0 ( base->widgets[i]->name, name ) == 0 ) {
            return base->widgets[i];
        }
    }

    base->widgets                    = g_realloc ( base->widgets, sizeof ( ThemeWidget* ) * ( base->num_widgets + 1 ) );
    base->widgets[base->num_widgets] = g_slice_new0 ( ThemeWidget );
    ThemeWidget *retv = base->widgets[base->num_widgets];
    retv->parent = base;
    retv->name   = g_strdup ( name );
    base->num_widgets++;
    return retv;
}
/**
 *  Properties
 */
Property *rofi_theme_property_create ( PropertyType type )
{
    Property *retv = g_slice_new0 ( Property );
    retv->type = type;
    return retv;
}

static RofiDistanceUnit  *rofi_theme_property_copy_distance_unit ( RofiDistanceUnit *unit )
{
    RofiDistanceUnit *retv = g_slice_new0 ( RofiDistanceUnit );
    *retv = *unit;
    if ( unit->left ) {
        retv->left = rofi_theme_property_copy_distance_unit ( unit->left );
    }
    if ( unit->right ) {
        retv->right = rofi_theme_property_copy_distance_unit ( unit->right );
    }
    return retv;
}
RofiDistance rofi_theme_property_copy_distance  ( RofiDistance const distance )
{
    RofiDistance retv = distance;
    if ( distance.base.left ) {
        retv.base.left = rofi_theme_property_copy_distance_unit ( distance.base.left );
    }
    if ( distance.base.right ) {
        retv.base.right = rofi_theme_property_copy_distance_unit ( distance.base.right );
    }
    return retv;
}

Property* rofi_theme_property_copy ( Property *p )
{
    Property *retv = rofi_theme_property_create ( p->type );
    retv->name = g_strdup ( p->name );

    switch ( p->type )
    {
    case P_STRING:
        retv->value.s = g_strdup ( p->value.s );
        break;
    case P_LIST:
        retv->value.list = g_list_copy_deep ( p->value.list, rofi_g_list_strdup, NULL );
        break;
    case P_LINK:
        retv->value.link.name = g_strdup ( p->value.link.name );
        retv->value.link.ref  = NULL;
        if ( p->value.link.def_value ) {
            retv->value.link.def_value = rofi_theme_property_copy ( p->value.link.def_value );
        }
        break;
    case P_PADDING:
    {
        retv->value                = p->value;
        retv->value.padding.top    = rofi_theme_property_copy_distance ( p->value.padding.top );
        retv->value.padding.left   = rofi_theme_property_copy_distance ( p->value.padding.left );
        retv->value.padding.bottom = rofi_theme_property_copy_distance ( p->value.padding.bottom );
        retv->value.padding.right  = rofi_theme_property_copy_distance ( p->value.padding.right );
        break;
    }
    default:
        retv->value = p->value;
    }
    return retv;
}

static void rofi_theme_distance_unit_property_free ( RofiDistanceUnit *unit )
{
    if ( unit->left ) {
        rofi_theme_distance_unit_property_free ( unit->left );
        unit->left = NULL;
    }
    if ( unit->right ) {
        rofi_theme_distance_unit_property_free ( unit->right );
        unit->right = NULL;
    }
    g_slice_free ( RofiDistanceUnit, unit );
}
static void rofi_theme_distance_property_free ( RofiDistance *distance )
{
    if ( distance->base.left ) {
        rofi_theme_distance_unit_property_free ( distance->base.left );
        distance->base.left = NULL;
    }
    if ( distance->base.right ) {
        rofi_theme_distance_unit_property_free ( distance->base.right );
        distance->base.right = NULL;
    }
}

void rofi_theme_property_free ( Property *p )
{
    if ( p == NULL ) {
        return;
    }
    g_free ( p->name );
    if ( p->type == P_STRING ) {
        g_free ( p->value.s );
    }
    else if ( p->type == P_LINK ) {
        g_free ( p->value.link.name );
        if ( p->value.link.def_value ) {
            rofi_theme_property_free ( p->value.link.def_value );
        }
    }
    if ( p->type == P_PADDING ) {
        rofi_theme_distance_property_free ( &( p->value.padding.top ) );
        rofi_theme_distance_property_free ( &( p->value.padding.right ) );
        rofi_theme_distance_property_free ( &( p->value.padding.bottom ) );
        rofi_theme_distance_property_free ( &( p->value.padding.left ) );
    }
    g_slice_free ( Property, p );
}

/**
 * This function is a hack to insert backward support for older theme with the updated listvie structure.
 */
static void rofi_theme_insert_listview_backwards_fix ( void )
{
    GHashTable  *table = g_hash_table_new_full ( g_str_hash, g_str_equal, NULL, (GDestroyNotify) rofi_theme_property_free );
    ThemeWidget *t     = rofi_theme_find_or_create_name ( rofi_theme, "element" );
    ThemeWidget *tt    = rofi_theme_find_or_create_name ( rofi_theme, "element-text" );
    ThemeWidget *ti    = rofi_theme_find_or_create_name ( rofi_theme, "element-icon" );

    // Inherit text color
    Property *ptc = rofi_theme_property_create ( P_INHERIT );
    ptc->name = g_strdup ( "text-color" );
    g_hash_table_replace ( table, ptc->name, ptc );
    // Transparent background
    Property *ptb = rofi_theme_property_create ( P_COLOR );
    ptb->name              = g_strdup ( "background-color" );
    ptb->value.color.red   = 0.0;
    ptb->value.color.green = 0.0;
    ptb->value.color.blue  = 0.0;
    ptb->value.color.alpha = 0.0;
    g_hash_table_replace ( table, ptb->name, ptb );

    rofi_theme_widget_add_properties ( tt, table );

    RofiDistance dsize = (RofiDistance){ .base = { 1.2, ROFI_PU_CH, ROFI_DISTANCE_MODIFIER_NONE, NULL, NULL }, .style = ROFI_HL_SOLID };
    Property     *pts  = rofi_theme_property_create ( P_PADDING );
    pts->value.padding.top = pts->value.padding.right = pts->value.padding.bottom = pts->value.padding.left = dsize;
    pts->name              = g_strdup ( "size" );
    g_hash_table_replace ( table, pts->name, pts );

    rofi_theme_widget_add_properties ( ti, table );

    /** Add spacing between icon and text. */
    g_hash_table_destroy ( table );
    table = g_hash_table_new_full ( g_str_hash, g_str_equal, NULL, (GDestroyNotify) rofi_theme_property_free );
    Property     *psp = rofi_theme_property_create ( P_PADDING );
    psp->name = g_strdup ( "spacing" );
    RofiDistance d = (RofiDistance){ .base = { 5, ROFI_PU_PX, ROFI_DISTANCE_MODIFIER_NONE, NULL, NULL }, .style = ROFI_HL_SOLID };
    psp->value.padding = (RofiPadding){ d, d, d, d };
    g_hash_table_replace ( table, psp->name, psp );
    rofi_theme_widget_add_properties ( t, table );
    g_hash_table_destroy ( table );
}

void rofi_theme_reset ( void )
{
    rofi_theme_free ( rofi_theme );
    rofi_theme       = g_slice_new0 ( ThemeWidget );
    rofi_theme->name = g_strdup ( "Root" );
    // Hack to fix backwards compatibility.
    rofi_theme_insert_listview_backwards_fix ( );
}

void rofi_theme_free ( ThemeWidget *widget )
{
    if ( widget == NULL ) {
        return;
    }
    if ( widget->properties ) {
        g_hash_table_destroy ( widget->properties );
        widget->properties = NULL;
    }
    if ( widget->media ) {
        g_slice_free ( ThemeMedia, widget->media );
    }
    for ( unsigned int i = 0; i < widget->num_widgets; i++ ) {
        rofi_theme_free ( widget->widgets[i] );
    }
    g_free ( widget->widgets );
    g_free ( widget->name );
    g_slice_free ( ThemeWidget, widget );
}

/**
 * print
 */
inline static void printf_double ( double d )
{
    char buf[G_ASCII_DTOSTR_BUF_SIZE];
    g_ascii_formatd ( buf, G_ASCII_DTOSTR_BUF_SIZE, "%.4lf", d );
    fputs ( buf, stdout );
}

static void rofi_theme_print_distance_unit ( RofiDistanceUnit *unit )
{
    if ( unit->modtype == ROFI_DISTANCE_MODIFIER_GROUP ) {
        fputs ( "( ", stdout );
    }
    if ( unit->left ) {
        rofi_theme_print_distance_unit ( unit->left );
    }

    if ( unit->modtype == ROFI_DISTANCE_MODIFIER_ADD ) {
        fputs ( " + ", stdout );
    }
    else if ( unit->modtype == ROFI_DISTANCE_MODIFIER_SUBTRACT ) {
        fputs ( " - ", stdout );
    }
    else if ( unit->modtype == ROFI_DISTANCE_MODIFIER_DIVIDE ) {
        fputs ( " / ", stdout );
    }
    else if ( unit->modtype == ROFI_DISTANCE_MODIFIER_MULTIPLY ) {
        fputs ( " * ", stdout );
    }
    else if ( unit->modtype == ROFI_DISTANCE_MODIFIER_MODULO ) {
        fputs ( " % ", stdout );
    }
    if ( unit->right ) {
        rofi_theme_print_distance_unit ( unit->right );
    }

    if ( unit->modtype == ROFI_DISTANCE_MODIFIER_NONE ) {
        if ( unit->type == ROFI_PU_PX ) {
            printf ( "%upx ", (unsigned int) unit->distance );
        }
        else if ( unit->type == ROFI_PU_MM ) {
            printf_double ( unit->distance );
            fputs ( "mm ", stdout );
        }
        else if ( unit->type == ROFI_PU_PERCENT ) {
            printf_double ( unit->distance );
            fputs ( "% ", stdout );
        }
        else if ( unit->type == ROFI_PU_CH ) {
            printf_double ( unit->distance );
            fputs ( "ch ", stdout );
        }
        else {
            printf_double ( unit->distance );
            fputs ( "em ", stdout );
        }
    }
    if ( unit->modtype == ROFI_DISTANCE_MODIFIER_GROUP ) {
        fputs ( " )", stdout );
    }
}

static void rofi_theme_print_distance ( RofiDistance d )
{
    if ( d.base.modtype == ROFI_DISTANCE_MODIFIER_GROUP ) {
        fputs ( "calc( ", stdout );
    }
    rofi_theme_print_distance_unit ( &( d.base ) );
    if ( d.base.modtype == ROFI_DISTANCE_MODIFIER_GROUP ) {
        fputs ( ")", stdout );
    }
    if ( d.style == ROFI_HL_DASH ) {
        printf ( "dash " );
    }
}
/** Textual representation of Window Location */
const char * const WindowLocationStr[9] = {
    "center",
    "northwest",
    "north",
    "northeast",
    "east",
    "southeast",
    "south",
    "southwest",
    "west"
};

static void int_rofi_theme_print_property ( Property *p )
{
    switch ( p->type )
    {
    case P_LIST:
        printf ( "[ " );
        for ( GList *iter = p->value.list; iter != NULL; iter = g_list_next ( iter ) ) {
            printf ( "%s", (char *) ( iter->data ) );
            if ( iter->next != NULL ) {
                printf ( "," );
            }
        }
        printf ( " ]" );
        break;
    case P_ORIENTATION:
        printf ( "%s", ( p->value.i == ROFI_ORIENTATION_HORIZONTAL ) ? "horizontal" : "vertical" );
        break;
    case P_HIGHLIGHT:
        if ( p->value.highlight.style & ROFI_HL_BOLD ) {
            printf ( "bold " );
        }
        if ( p->value.highlight.style & ROFI_HL_UNDERLINE ) {
            printf ( "underline " );
        }
        if ( p->value.highlight.style & ROFI_HL_STRIKETHROUGH ) {
            printf ( "strikethrough " );
        }
        if ( p->value.highlight.style & ROFI_HL_ITALIC ) {
            printf ( "italic " );
        }
        if ( p->value.highlight.style & ROFI_HL_COLOR ) {
            printf ( "rgba ( %.0f, %.0f, %.0f, %.0f %% )",
                     ( p->value.highlight.color.red * 255.0 ),
                     ( p->value.highlight.color.green * 255.0 ),
                     ( p->value.highlight.color.blue * 255.0 ),
                     ( p->value.highlight.color.alpha * 100.0 ) );
        }
        break;
    case P_POSITION:
        printf ( "%s", WindowLocationStr[p->value.i] );
        break;
    case P_STRING:
        printf ( "\"%s\"", p->value.s );
        break;
    case P_INTEGER:
        printf ( "%d", p->value.i );
        break;
    case P_DOUBLE:
        printf ( "%.2f", p->value.f );
        break;
    case P_BOOLEAN:
        printf ( "%s", p->value.b ? "true" : "false" );
        break;
    case P_COLOR:
        printf ( "rgba ( %.0f, %.0f, %.0f, %.0f %% )",
                 ( p->value.color.red * 255.0 ),
                 ( p->value.color.green * 255.0 ),
                 ( p->value.color.blue * 255.0 ),
                 ( p->value.color.alpha * 100.0 ) );
        break;
    case P_PADDING:
        if ( distance_compare ( p->value.padding.top, p->value.padding.bottom ) &&
             distance_compare ( p->value.padding.left, p->value.padding.right ) &&
             distance_compare ( p->value.padding.left, p->value.padding.top ) ) {
            rofi_theme_print_distance ( p->value.padding.left );
        }
        else if ( distance_compare ( p->value.padding.top, p->value.padding.bottom ) &&
                  distance_compare ( p->value.padding.left, p->value.padding.right ) ) {
            rofi_theme_print_distance ( p->value.padding.top );
            rofi_theme_print_distance ( p->value.padding.left );
        }
        else if ( !distance_compare ( p->value.padding.top, p->value.padding.bottom ) &&
                  distance_compare ( p->value.padding.left, p->value.padding.right ) ) {
            rofi_theme_print_distance ( p->value.padding.top );
            rofi_theme_print_distance ( p->value.padding.left );
            rofi_theme_print_distance ( p->value.padding.bottom );
        }
        else {
            rofi_theme_print_distance ( p->value.padding.top );
            rofi_theme_print_distance ( p->value.padding.right );
            rofi_theme_print_distance ( p->value.padding.bottom );
            rofi_theme_print_distance ( p->value.padding.left );
        }
        break;
    case P_LINK:
        if ( p->value.link.def_value ) {
            printf ( "var( %s, ", p->value.link.name );
            int_rofi_theme_print_property ( p->value.link.def_value );
            printf ( ")" );
        }
        else {
            printf ( "var(%s)", p->value.link.name );
        }
        break;
    case P_INHERIT:
        printf ( "inherit" );
        break;
    default:
        break;
    }
}

static void rofi_theme_print_property_index ( size_t pnl, int depth, Property *p )
{
    int pl = strlen ( p->name );
    printf ( "%*s%s:%*s ", depth, "", p->name, (int) pnl - pl, "" );
    int_rofi_theme_print_property ( p );
    putchar ( ';' );
    putchar ( '\n' );
}

static void rofi_theme_print_index ( ThemeWidget *widget )
{
    GHashTableIter iter;
    gpointer       key, value;
    if ( widget->properties ) {
        int         index = 0;
        GList       *list = NULL;
        ThemeWidget *w    = widget;
        while ( w ) {
            if ( g_strcmp0 ( w->name, "Root" ) == 0 ) {
                break;
            }
            list = g_list_prepend ( list, w->name );
            w    = w->parent;
        }
        if ( g_list_length ( list ) > 0 ) {
            index = 4;
            for ( GList *iter = g_list_first ( list ); iter != NULL; iter = g_list_next ( iter ) ) {
                char *name = (char *) iter->data;
                fputs ( name, stdout );
                if ( iter->prev == NULL && iter->next ) {
                    putchar ( ' ' );
                }
                else if ( iter->next ) {
                    putchar ( '.' );
                }
            }
            printf ( " {\n" );
        }
        else {
            index = 4;
            printf ( "* {\n" );
        }
        size_t property_name_length = 0;
        g_hash_table_iter_init ( &iter, widget->properties );
        while ( g_hash_table_iter_next ( &iter, &key, &value ) ) {
            Property *p = (Property *) value;
            property_name_length = MAX ( strlen ( p->name ), property_name_length );
        }
        g_hash_table_iter_init ( &iter, widget->properties );
        while ( g_hash_table_iter_next ( &iter, &key, &value ) ) {
            Property *p = (Property *) value;
            rofi_theme_print_property_index ( property_name_length, index, p );
        }
        printf ( "}\n" );
        g_list_free ( list );
    }
    for ( unsigned int i = 0; i < widget->num_widgets; i++ ) {
        rofi_theme_print_index ( widget->widgets[i] );
    }
}
void rofi_theme_print ( ThemeWidget *widget )
{
    if ( widget != NULL ) {
        printf ( "/**\n * rofi -dump-theme output.\n * Rofi version: %s\n **/\n", PACKAGE_VERSION );
        rofi_theme_print_index ( widget );
    }
}

/**
 * Main lex parser.
 */
int yyparse ();

/**
 * Destroy the internal of lex parser.
 */
void yylex_destroy ( void );

/**
 * Global handle input file to flex parser.
 */
extern FILE* yyin;

/**
 * @param yylloc The file location.
 * @param what   What we are parsing, filename or string.
 * @param s      Error message string.
 *
 * Error handler for the lex parser.
 */
void yyerror ( YYLTYPE *yylloc, const char *what, const char* s )
{
    char    *what_esc = what ? g_markup_escape_text ( what, -1 ) : g_strdup ( "" );
    GString *str      = g_string_new ( "" );
    g_string_printf ( str, "<big><b>Error while parsing theme:</b></big> <i>%s</i>\n", what_esc );
    g_free ( what_esc );
    char *esc = g_markup_escape_text ( s, -1 );
    g_string_append_printf ( str, "\tParser error: <span size=\"smaller\" style=\"italic\">%s</span>\n", esc );
    g_free ( esc );
    if ( yylloc->filename != NULL ) {
        g_string_append_printf ( str, "\tLocation:     line %d column %d to line %d column %d.\n" \
                                 "\tFile          '%s'\n", yylloc->first_line, yylloc->first_column, yylloc->last_line, yylloc->last_column, yylloc->filename );
    }
    else {
        g_string_append_printf ( str, "\tLocation:     line %d column %d to line %d column %d\n", yylloc->first_line, yylloc->first_column, yylloc->last_line, yylloc->last_column );
    }
    g_log ( "Parser", G_LOG_LEVEL_DEBUG, "Failed to parse theme:\n%s", str->str );
    rofi_add_error_message ( str );
}

static void rofi_theme_copy_property_int ( G_GNUC_UNUSED gpointer key, gpointer value, gpointer user_data )
{
    GHashTable *table = (GHashTable *) user_data;
    Property   *p     = rofi_theme_property_copy ( (Property *) value );
    g_hash_table_replace ( table, p->name, p );
}
void rofi_theme_widget_add_properties ( ThemeWidget *widget, GHashTable *table )
{
    if ( table == NULL ) {
        return;
    }
    if ( widget->properties == NULL ) {
        widget->properties = g_hash_table_new_full ( g_str_hash, g_str_equal, NULL, (GDestroyNotify) rofi_theme_property_free );
    }
    g_hash_table_foreach ( table, rofi_theme_copy_property_int, widget->properties );
}

/**
 * Public API
 */

static inline ThemeWidget *rofi_theme_find_single ( ThemeWidget *widget, const char *name )
{
    for ( unsigned int j = 0; widget && j < widget->num_widgets; j++ ) {
        if ( g_strcmp0 ( widget->widgets[j]->name, name ) == 0 ) {
            return widget->widgets[j];
        }
    }
    return widget;
}

static ThemeWidget *rofi_theme_find ( ThemeWidget *widget, const char *name, const gboolean exact )
{
    if ( widget == NULL || name == NULL ) {
        return widget;
    }
    char *tname   = g_strdup ( name );
    char *saveptr = NULL;
    int  found    = TRUE;
    for ( const char *iter = strtok_r ( tname, ".", &saveptr ); iter != NULL; iter = strtok_r ( NULL, ".", &saveptr ) ) {
        found = FALSE;
        ThemeWidget *f = rofi_theme_find_single ( widget, iter );
        if ( f != widget ) {
            widget = f;
            found  = TRUE;
        }
        else if ( exact ) {
            break;
        }
    }
    g_free ( tname );
    if ( !exact || found ) {
        return widget;
    }
    else {
        return NULL;
    }
}

static void rofi_theme_resolve_link_property ( Property *p, int depth )
{
    // Set name, remove '@' prefix.
    const char *name = p->value.link.name;// + (*(p->value.link.name)== '@'?1:0;
    g_info ( "Resolving link to %s", p->value.link.name );
    if ( depth > 20 ) {
        g_warning ( "Found more then 20 redirects for property. Stopping." );
        p->value.link.ref = p;
        return;
    }

    if ( rofi_theme->properties && g_hash_table_contains ( rofi_theme->properties, name ) ) {
        Property *pr = g_hash_table_lookup ( rofi_theme->properties, name );
        g_info ( "Resolving link %s found: %s", p->value.link.name, pr->name );
        if ( pr->type == P_LINK ) {
            if ( pr->value.link.ref == NULL ) {
                rofi_theme_resolve_link_property ( pr, depth + 1 );
            }
            if ( pr->value.link.ref != pr ) {
                p->value.link.ref = pr->value.link.ref;
                return;
            }
        }
        else {
            p->value.link.ref = pr;
            return;
        }
    }
    // No found and we have default value.
    if ( p->value.link.def_value ) {
        p->value.link.ref = p->value.link.def_value;
        return;
    }

    // No found, set ref to self.
    p->value.link.ref = p;
}

Property *rofi_theme_find_property ( ThemeWidget *widget, PropertyType type, const char *property, gboolean exact )
{
    while ( widget ) {
        if ( widget->properties && g_hash_table_contains ( widget->properties, property ) ) {
            Property *p = g_hash_table_lookup ( widget->properties, property );
            if ( p->type == P_INHERIT ) {
                return p;
            }
            if ( p->type == P_LINK ) {
                if ( p->value.link.ref == NULL ) {
                    // Resolve link.
                    rofi_theme_resolve_link_property ( p, 0 );
                }
                if ( p->value.link.ref != NULL && p->value.link.ref->type == type ) {
                    return p->value.link.ref;
                }
            }
            if ( p->type == type ) {
                return p;
            }
            // RofiPadding and integer can be converted.
            if ( p->type == P_INTEGER && type == P_PADDING ) {
                return p;
            }
            g_debug ( "Found property: '%s' on '%s', but type %s does not match expected type %s.",
                      property, widget->name,
                      PropertyTypeName[p->type],
                      PropertyTypeName[type]
                      );
        }
        if ( exact ) {
            return NULL;
        }
        // Fall back to defaults.
        widget = widget->parent;
    }
    return NULL;
}
ThemeWidget *rofi_theme_find_widget ( const char *name, const char *state, gboolean exact )
{
    // First find exact match based on name.
    ThemeWidget *widget = rofi_theme_find_single ( rofi_theme, name );
    widget = rofi_theme_find ( widget, state, exact );

    return widget;
}

int rofi_theme_get_position ( const widget *widget, const char *property, int def )
{
    ThemeWidget *wid = rofi_theme_find_widget ( widget->name, widget->state, FALSE );
    Property    *p   = rofi_theme_find_property ( wid, P_POSITION, property, FALSE );
    if ( p ) {
        if ( p->type == P_INHERIT ) {
            if ( widget->parent ) {
                return rofi_theme_get_position ( widget->parent, property, def );
            }
            return def;
        }
        return p->value.i;
    }
    g_debug ( "Theme entry: #%s %s property %s unset.", widget->name, widget->state ? widget->state : "", property );
    return def;
}

int rofi_theme_get_integer ( const widget *widget, const char *property, int def )
{
    ThemeWidget *wid = rofi_theme_find_widget ( widget->name, widget->state, FALSE );
    Property    *p   = rofi_theme_find_property ( wid, P_INTEGER, property, FALSE );
    if ( p ) {
        if ( p->type == P_INHERIT ) {
            if ( widget->parent ) {
                return rofi_theme_get_integer ( widget->parent, property, def );
            }
            return def;
        }
        return p->value.i;
    }
    g_debug ( "Theme entry: #%s %s property %s unset.", widget->name, widget->state ? widget->state : "", property );
    return def;
}
RofiDistance rofi_theme_get_distance ( const widget *widget, const char *property, int def )
{
    ThemeWidget *wid = rofi_theme_find_widget ( widget->name, widget->state, FALSE );
    Property    *p   = rofi_theme_find_property ( wid, P_PADDING, property, FALSE );
    if ( p ) {
        if ( p->type == P_INHERIT ) {
            if ( widget->parent ) {
                return rofi_theme_get_distance ( widget->parent, property, def );
            }
            return (RofiDistance){ .base = { def, ROFI_PU_PX, ROFI_DISTANCE_MODIFIER_NONE, NULL, NULL }, .style = ROFI_HL_SOLID };
        }
        if ( p->type == P_INTEGER ) {
            return (RofiDistance){ .base = { p->value.i, ROFI_PU_PX, ROFI_DISTANCE_MODIFIER_NONE, NULL, NULL }, .style = ROFI_HL_SOLID };
        }
        else {
            return p->value.padding.left;
        }
    }
    g_debug ( "Theme entry: #%s %s property %s unset.", widget->name, widget->state ? widget->state : "", property );
    return (RofiDistance){ .base = { def, ROFI_PU_PX, ROFI_DISTANCE_MODIFIER_NONE, NULL, NULL }, .style = ROFI_HL_SOLID };
}

int rofi_theme_get_boolean ( const widget *widget, const char *property, int def )
{
    ThemeWidget *wid = rofi_theme_find_widget ( widget->name, widget->state, FALSE );
    Property    *p   = rofi_theme_find_property ( wid, P_BOOLEAN, property, FALSE );
    if ( p ) {
        if ( p->type == P_INHERIT ) {
            if ( widget->parent ) {
                return rofi_theme_get_boolean ( widget->parent, property, def );
            }
            return def;
        }
        return p->value.b;
    }
    g_debug ( "Theme entry: #%s %s property %s unset.", widget->name, widget->state ? widget->state : "", property );
    return def;
}
RofiOrientation rofi_theme_get_orientation ( const widget *widget, const char *property, RofiOrientation def )
{
    ThemeWidget *wid = rofi_theme_find_widget ( widget->name, widget->state, FALSE );
    Property    *p   = rofi_theme_find_property ( wid, P_ORIENTATION, property, FALSE );
    if ( p ) {
        if ( p->type == P_INHERIT ) {
            if ( widget->parent ) {
                return rofi_theme_get_orientation ( widget->parent, property, def );
            }
            return def;
        }
        return p->value.b;
    }
    g_debug ( "Theme entry: #%s %s property %s unset.", widget->name, widget->state ? widget->state : "", property );
    return def;
}

const char *rofi_theme_get_string ( const widget *widget, const char *property, const char *def )
{
    ThemeWidget *wid = rofi_theme_find_widget ( widget->name, widget->state, FALSE );
    Property    *p   = rofi_theme_find_property ( wid, P_STRING, property, FALSE );
    if ( p ) {
        if ( p->type == P_INHERIT ) {
            if ( widget->parent ) {
                return rofi_theme_get_string ( widget->parent, property, def );
            }
            return def;
        }
        return p->value.s;
    }
    g_debug ( "Theme entry: #%s %s property %s unset.", widget->name, widget->state ? widget->state : "", property );
    return def;
}
double rofi_theme_get_double ( const widget *widget, const char *property, double def )
{
    ThemeWidget *wid = rofi_theme_find_widget ( widget->name, widget->state, FALSE );
    Property    *p   = rofi_theme_find_property ( wid, P_DOUBLE, property, FALSE );
    if ( p ) {
        if ( p->type == P_INHERIT ) {
            if ( widget->parent ) {
                return rofi_theme_get_double ( widget->parent, property, def );
            }
            return def;
        }
        return p->value.f;
    }
    // Fallback to integer if double is not found.
    p = rofi_theme_find_property ( wid, P_INTEGER, property, FALSE );
    if ( p ) {
        if ( p->type == P_INHERIT ) {
            if ( widget->parent ) {
                return rofi_theme_get_double ( widget->parent, property, def );
            }
            return def;
        }
        return (double) p->value.i;
    }
    g_debug ( "Theme entry: #%s %s property %s unset.", widget->name, widget->state ? widget->state : "", property );
    return def;
}
void rofi_theme_get_color ( const widget *widget, const char *property, cairo_t *d )
{
    ThemeWidget *wid = rofi_theme_find_widget ( widget->name, widget->state, FALSE );
    Property    *p   = rofi_theme_find_property ( wid, P_COLOR, property, FALSE );
    if ( p ) {
        if ( p->type == P_INHERIT ) {
            if ( widget->parent ) {
                rofi_theme_get_color ( widget->parent, property, d );
            }
            return;
        }
        cairo_set_source_rgba ( d,
                                p->value.color.red,
                                p->value.color.green,
                                p->value.color.blue,
                                p->value.color.alpha
                                );
    }
    else {
        g_debug ( "Theme entry: #%s %s property %s unset.", widget->name, widget->state ? widget->state : "", property );
    }
}
RofiPadding rofi_theme_get_padding ( const widget *widget, const char *property, RofiPadding pad )
{
    ThemeWidget *wid = rofi_theme_find_widget ( widget->name, widget->state, FALSE );
    Property    *p   = rofi_theme_find_property ( wid, P_PADDING, property, FALSE );
    if ( p ) {
        if ( p->type == P_INHERIT ) {
            if ( widget->parent ) {
                return rofi_theme_get_padding ( widget->parent, property, pad );
            }
            return pad;
        }
        if ( p->type == P_PADDING ) {
            pad = p->value.padding;
        }
        else {
            RofiDistance d = (RofiDistance){ .base = { p->value.i, ROFI_PU_PX, ROFI_DISTANCE_MODIFIER_NONE, NULL, NULL }, .style = ROFI_HL_SOLID };
            return (RofiPadding){ d, d, d, d };
        }
    }
    g_debug ( "Theme entry: #%s %s property %s unset.", widget->name, widget->state ? widget->state : "", property );
    return pad;
}

GList *rofi_theme_get_list ( const widget *widget, const char * property, const char *defaults )
{
    ThemeWidget *wid2 = rofi_theme_find_widget ( widget->name, widget->state, TRUE );
    Property    *p    = rofi_theme_find_property ( wid2, P_LIST, property, TRUE );
    if ( p ) {
        if ( p->type == P_INHERIT ) {
            if ( widget->parent ) {
                return rofi_theme_get_list ( widget->parent, property, defaults );
            }
        }
        else if ( p->type == P_LIST ) {
            return g_list_copy_deep ( p->value.list, rofi_g_list_strdup, NULL );
        }
    }
    char **r = defaults ? g_strsplit ( defaults, ",", 0 ) : NULL;
    if ( r ) {
        GList *l = NULL;
        for ( int i = 0; r[i] != NULL; i++ ) {
            l = g_list_append ( l, r[i] );
        }
        g_free ( r );
        return l;
    }
    return NULL;
}

RofiHighlightColorStyle rofi_theme_get_highlight ( widget *widget, const char *property, RofiHighlightColorStyle th )
{
    ThemeWidget *wid = rofi_theme_find_widget ( widget->name, widget->state, FALSE );
    Property    *p   = rofi_theme_find_property ( wid, P_HIGHLIGHT, property, FALSE );
    if ( p ) {
        if ( p->type == P_INHERIT ) {
            if ( widget->parent ) {
                return rofi_theme_get_highlight ( widget->parent, property, th );
            }
            return th;
        }
        return p->value.highlight;
    }
    g_debug ( "Theme entry: #%s %s property %s unset.", widget->name, widget->state ? widget->state : "", property );
    return th;
}

static int get_pixels ( RofiDistanceUnit *unit, RofiOrientation ori )
{
    int val = unit->distance;

    if ( unit->type == ROFI_PU_EM ) {
        val = unit->distance * textbox_get_estimated_char_height ();
    }
    else if ( unit->type == ROFI_PU_CH ) {
        val = unit->distance * textbox_get_estimated_ch ();
    }
    else if ( unit->type == ROFI_PU_PERCENT ) {
        if ( ori == ROFI_ORIENTATION_VERTICAL ) {
            int height = 0;
            rofi_view_get_current_monitor ( NULL, &height );
            val = ( unit->distance * height ) / ( 100.0 );
        }
        else {
            int width = 0;
            rofi_view_get_current_monitor ( &width, NULL );
            val = ( unit->distance * width ) / ( 100.0 );
        }
    }
    else if ( unit->type == ROFI_PU_MM ) {
        val = unit->distance * config.dpi / 25.4;
    }
    return val;
}

static int distance_unit_get_pixel ( RofiDistanceUnit *unit, RofiOrientation ori )
{
    switch ( unit->modtype )
    {
    case ROFI_DISTANCE_MODIFIER_GROUP:
        return distance_unit_get_pixel ( unit->left, ori );
        break;
    case ROFI_DISTANCE_MODIFIER_ADD:
        return distance_unit_get_pixel ( unit->left, ori ) + distance_unit_get_pixel ( unit->right, ori );
    case ROFI_DISTANCE_MODIFIER_SUBTRACT:
        return distance_unit_get_pixel ( unit->left, ori ) - distance_unit_get_pixel ( unit->right, ori );
    case ROFI_DISTANCE_MODIFIER_MULTIPLY:
        return distance_unit_get_pixel ( unit->left, ori ) * distance_unit_get_pixel ( unit->right, ori );
    case ROFI_DISTANCE_MODIFIER_DIVIDE:
    {
        int a = distance_unit_get_pixel ( unit->left, ori );
        int b = distance_unit_get_pixel ( unit->right, ori );
        if ( b != 0 ) {
            return a / b;
        }
        return a;
    }
    case ROFI_DISTANCE_MODIFIER_MODULO:
    {
        int a = distance_unit_get_pixel ( unit->left, ori );
        int b = distance_unit_get_pixel ( unit->right, ori );
        if ( b != 0 ) {
            return a % b;
        }
        return 0;
    }
    default:
        break;
    }
    return get_pixels ( unit, ori );
}

int distance_get_pixel ( RofiDistance d, RofiOrientation ori )
{
    return distance_unit_get_pixel ( &( d.base ), ori );
}

void distance_get_linestyle ( RofiDistance d, cairo_t *draw )
{
    if ( d.style == ROFI_HL_DASH ) {
        const double dashes[1] = { 4 };
        cairo_set_dash ( draw, dashes, 1, 0.0 );
    }
    else {
        cairo_set_dash ( draw, NULL, 0, 0.0 );
    }
}

gboolean rofi_theme_is_empty ( void )
{
    if ( rofi_theme == NULL ) {
        return TRUE;
    }
    if ( rofi_theme->properties == NULL && rofi_theme->num_widgets == 0 ) {
        return TRUE;
    }
    if ( rofi_theme->num_widgets == 3 ) {
        // HACK: check for default added elements.
        for ( unsigned int i = 0; i < rofi_theme->num_widgets; i++ ) {
            if ( strncmp ( rofi_theme->widgets[i]->name, "element", 7 ) != 0 ) {
                return FALSE;
            }
        }
        return TRUE;
    }

    return FALSE;
}

#ifdef THEME_CONVERTER

static char * rofi_theme_convert_color ( char *col )
{
    char *r = g_strstrip ( col );
    if ( *r == '#' && strlen ( r ) == 9 ) {
        char a1 = r[1];
        char a2 = r[2];
        r[1] = r[3];
        r[2] = r[4];
        r[3] = r[5];
        r[4] = r[6];
        r[5] = r[7];
        r[6] = r[8];
        r[7] = a1;
        r[8] = a2;
    }

    return r;
}
void rofi_theme_convert_old ( void )
{
    {
        char *str = g_strdup_printf ( "#window { border: %d; padding: %d;}", config.menu_bw, config.padding );
        rofi_theme_parse_string ( str );
        g_free ( str );
    }
    if ( config.color_window ) {
        char               **retv = g_strsplit ( config.color_window, ",", -1 );
        const char * const conf[] = {
            "* { background: %s; }",
            "* { border-color: %s; }",
            "* { separatorcolor: %s; }"
        };
        for ( int i = 0; retv && i < 3 && retv[i]; i++ ) {
            char *str = g_strdup_printf ( conf[i], rofi_theme_convert_color ( retv[i] ) );
            rofi_theme_parse_string ( str );
            g_free ( str );
        }
        g_strfreev ( retv );
    }
    if ( config.color_normal ) {
        char               **retv = g_strsplit ( config.color_normal, ",", -1 );
        const char * const conf[] = {
            "* { normal-background: %s; }",
            "* { foreground: %s; normal-foreground: @foreground; alternate-normal-foreground: @foreground; }",
            "* { alternate-normal-background: %s; }",
            "* { selected-normal-background: %s; }",
            "* { selected-normal-foreground: %s; }"
        };
        for ( int i = 0; retv && retv[i] && i < 5; i++ ) {
            char *str = g_strdup_printf ( conf[i], rofi_theme_convert_color ( retv[i] ) );
            rofi_theme_parse_string ( str );
            g_free ( str );
        }
        g_strfreev ( retv );
    }
    if ( config.color_urgent ) {
        char               **retv = g_strsplit ( config.color_urgent, ",", -1 );
        const char * const conf[] = {
            "* { urgent-background: %s; }",
            "* { urgent-foreground: %s; alternate-urgent-foreground: @urgent-foreground;}",
            "* { alternate-urgent-background: %s; }",
            "* { selected-urgent-background: %s; }",
            "* { selected-urgent-foreground: %s; }"
        };
        for ( int i = 0; retv && retv[i] && i < 5; i++ ) {
            char *str = g_strdup_printf ( conf[i], rofi_theme_convert_color ( retv[i] ) );
            rofi_theme_parse_string ( str );
            g_free ( str );
        }
        g_strfreev ( retv );
    }
    if ( config.color_active ) {
        char               **retv = g_strsplit ( config.color_active, ",", -1 );
        const char * const conf[] = {
            "* { active-background: %s; }",
            "* { active-foreground: %s; alternate-active-foreground: @active-foreground;}",
            "* { alternate-active-background: %s; }",
            "* { selected-active-background: %s; }",
            "* { selected-active-foreground: %s; }"
        };
        for ( int i = 0; retv && retv[i] && i < 5; i++ ) {
            char *str = g_strdup_printf ( conf[i], rofi_theme_convert_color ( retv[i] ) );
            rofi_theme_parse_string ( str );
            g_free ( str );
        }
        g_strfreev ( retv );
    }

    if ( config.separator_style != NULL  ) {
        if ( g_strcmp0 ( config.separator_style, "none" ) == 0 ) {
            const char *const str = "#listview { border: 0px; }";
            rofi_theme_parse_string ( str );
            const char *const str2 = "#mode-switcher { border: 0px; }";
            rofi_theme_parse_string ( str2 );
            const char *const str3 = "#message { border: 0px; }";
            rofi_theme_parse_string ( str3 );
        }
        else if  ( g_strcmp0 ( config.separator_style, "solid" ) == 0 ) {
            const char *const str = "#listview { border: 2px solid 0px 0px 0px; }";
            rofi_theme_parse_string ( str );
            const char *const str2 = "#mode-switcher { border: 2px solid 0px 0px 0px; }";
            rofi_theme_parse_string ( str2 );
            const char *const str3 = "#message { border: 2px solid 0px 0px 0px; }";
            rofi_theme_parse_string ( str3 );
        } /* dash is default */
    }
    /* Line Margin */
    {
        char *str = g_strdup_printf ( "#listview { spacing: %dpx;}", config.line_margin );
        rofi_theme_parse_string ( str );
        g_free ( str );
    }
    /* Line Padding */
    {
        char *str = g_strdup_printf ( "#element, inputbar, message { padding: %dpx;}", config.line_padding );
        rofi_theme_parse_string ( str );
        g_free ( str );
    }
    if ( config.hide_scrollbar ) {
        const char *str = "#listview { scrollbar: false; }";
        rofi_theme_parse_string ( str );
    }
    else {
        const char *str = "#listview { scrollbar: true; }";
        rofi_theme_parse_string ( str );
        char       *str2 = g_strdup_printf ( "#scrollbar { handle-width: %dpx; }", config.scrollbar_width );
        rofi_theme_parse_string ( str2 );
        g_free ( str2 );
    }
    if ( config.fake_transparency ) {
        char *str = g_strdup_printf ( "#window { transparency: \"%s\"; }", config.fake_background );
        rofi_theme_parse_string ( str );
        g_free ( str );
    }
}
#endif // THEME_CONVERTER

char * rofi_theme_parse_prepare_file ( const char *file, const char *parent_file )
{
    char *filename = rofi_expand_path ( file );
    // If no absolute path specified, expand it.
    if ( parent_file != NULL && !g_path_is_absolute ( filename )   ) {
        char *basedir = g_path_get_dirname ( parent_file );
        char *path    = g_build_filename ( basedir, filename, NULL );
        g_free ( filename );
        filename = path;
        g_free ( basedir );
    }
    GFile *gf = g_file_new_for_path ( filename );
    g_free ( filename );
    filename = g_file_get_path ( gf );
    g_object_unref ( gf );

    return filename;
}

void rofi_theme_parse_merge_widgets ( ThemeWidget *parent, ThemeWidget *child )
{
    g_assert ( parent != NULL );
    g_assert ( child != NULL );

    if ( parent == rofi_theme && g_strcmp0 ( child->name, "*" ) == 0 ) {
        rofi_theme_widget_add_properties ( parent, child->properties );
        return;
    }

    ThemeWidget *w = rofi_theme_find_or_create_name ( parent, child->name );
    rofi_theme_widget_add_properties ( w, child->properties );
    for ( unsigned int i = 0; i < child->num_widgets; i++ ) {
        rofi_theme_parse_merge_widgets ( w, child->widgets[i] );
    }
}

void  rofi_theme_parse_process_conditionals ( void )
{
    workarea mon;
    monitor_active ( &mon );
    if ( rofi_theme == NULL ) {
        return;
    }
    for ( unsigned int i = 0; i < rofi_theme->num_widgets; i++ ) {
        ThemeWidget *widget = rofi_theme->widgets[i];
        if ( widget->media != NULL ) {
            switch ( widget->media->type )
            {
            case THEME_MEDIA_TYPE_MIN_WIDTH:
            {
                int w = widget->media->value;
                if ( mon.w >= w ) {
                    for ( unsigned int x = 0; x < widget->num_widgets; x++ ) {
                        rofi_theme_parse_merge_widgets ( rofi_theme, widget->widgets[x] );
                    }
                }
                break;
            }
            case THEME_MEDIA_TYPE_MAX_WIDTH:
            {
                int w = widget->media->value;
                if ( mon.w < w ) {
                    for ( unsigned int x = 0; x < widget->num_widgets; x++ ) {
                        rofi_theme_parse_merge_widgets ( rofi_theme, widget->widgets[x] );
                    }
                }
                break;
            }
            case THEME_MEDIA_TYPE_MIN_HEIGHT:
            {
                int h = widget->media->value;
                if ( mon.h >= h ) {
                    for ( unsigned int x = 0; x < widget->num_widgets; x++ ) {
                        rofi_theme_parse_merge_widgets ( rofi_theme, widget->widgets[x] );
                    }
                }
                break;
            }
            case THEME_MEDIA_TYPE_MAX_HEIGHT:
            {
                int h = widget->media->value;
                if ( mon.h < h ) {
                    for ( unsigned int x = 0; x < widget->num_widgets; x++ ) {
                        rofi_theme_parse_merge_widgets ( rofi_theme, widget->widgets[x] );
                    }
                }
                break;
            }
            case THEME_MEDIA_TYPE_MON_ID:
            {
                if ( mon.monitor_id == widget->media->value ) {
                    for ( unsigned int x = 0; x < widget->num_widgets; x++ ) {
                        rofi_theme_parse_merge_widgets ( rofi_theme, widget->widgets[x] );
                    }
                }
                break;
            }
            case THEME_MEDIA_TYPE_MIN_ASPECT_RATIO:
            {
                double r = widget->media->value;
                if ( ( mon.w / (double) mon.h ) >= r ) {
                    for ( unsigned int x = 0; x < widget->num_widgets; x++ ) {
                        rofi_theme_parse_merge_widgets ( rofi_theme, widget->widgets[x] );
                    }
                }
                break;
            }
            case THEME_MEDIA_TYPE_MAX_ASPECT_RATIO:
            {
                double r = widget->media->value;
                if ( ( mon.w / (double) mon.h ) < r ) {
                    for ( unsigned int x = 0; x < widget->num_widgets; x++ ) {
                        rofi_theme_parse_merge_widgets ( rofi_theme, widget->widgets[x] );
                    }
                }
                break;
            }
            default:
            {
                break;
            }
            }
        }
    }
}

ThemeMediaType rofi_theme_parse_media_type ( const char *type )
{
    if ( g_strcmp0 ( type, "monitor-id" ) == 0 ) {
        return THEME_MEDIA_TYPE_MON_ID;
    }
    else if ( g_strcmp0 ( type, "min-width" ) == 0 ) {
        return THEME_MEDIA_TYPE_MIN_WIDTH;
    }
    else if ( g_strcmp0 ( type, "min-height" ) == 0 ) {
        return THEME_MEDIA_TYPE_MIN_HEIGHT;
    }
    else if ( g_strcmp0 ( type, "max-width" ) == 0 ) {
        return THEME_MEDIA_TYPE_MAX_WIDTH;
    }
    else if ( g_strcmp0 ( type, "max-height" ) == 0 ) {
        return THEME_MEDIA_TYPE_MAX_HEIGHT;
    }
    else if ( g_strcmp0 ( type, "min-aspect-ratio" ) == 0 ) {
        return THEME_MEDIA_TYPE_MIN_ASPECT_RATIO;
    }
    else if ( g_strcmp0 ( type, "max-aspect-ratio" ) == 0 ) {
        return THEME_MEDIA_TYPE_MAX_ASPECT_RATIO;
    }
    return THEME_MEDIA_TYPE_INVALID;
}

gboolean rofi_theme_has_property ( const widget *widget, const char *property )
{
    ThemeWidget *wid = rofi_theme_find_widget ( widget->name, widget->state, FALSE );
    Property    *p   = rofi_theme_find_property ( wid, P_STRING, property, FALSE );
    if ( p ) {
        if ( p->type == P_INHERIT ) {
            if ( widget->parent ) {
                return rofi_theme_has_property ( widget->parent, property );
            }
            return FALSE;
        }
        return TRUE;
    }
    return FALSE;
}
