Преглед на файлове

Code separated in serveral modules.

Iñigo Valentin преди 4 години
родител
ревизия
5b2d237f0b

+ 0 - 2
.gitignore

@@ -1,7 +1,5 @@
 data.sqlite
 *.sqlite
-./RuneOptimizer
 src/util
 src/RuneOptimizer/RuneOptimizer
-RuneOptimizer
 src/RuneOptimizer/util/

+ 9 - 1561
src/RuneOptimizer/RuneOptimizer.c

@@ -21,6 +21,15 @@
 #include <sqlite3.h>
 #include <math.h>
 #include "RuneOptimizer.h"
+#include "error/error.h"
+#include "help/help.h"
+#include "help/help.c"
+#include "optimize/optimize.h"
+#include "optimize/optimize.c"
+#include "team/team.h"
+#include "team/team.c"
+#include "update/update.h"
+#include "update/update.c"
 
 /**
  * Starts the program.
@@ -94,1564 +103,3 @@ int open_database(){
         return SUCCESS;
     }
 }
-
-void show_help(){
-    printf("\nRune Optimizer v0.1\n");
-    printf("\n  Usage:\n");
-    printf("  RuneOptimizer [command] [options]\n");
-    printf("\n\n  Command: helps\n");
-    printf("\n    Display this help text and exists. It has no options.\n");
-    printf("\n\n  Command: update\n");
-    printf("\n    Updates the information and builds a database. Currently unimplemented.\n");
-    printf("\n\n  Command: team\n");
-    printf("\n    Manages teams.\n");
-    printf("\n    Usage\n");
-    printf("    RuneOptimizer team [action] [options]\n");
-    printf("\n      Actions: \n\n");
-    printf("        list [team] [options]        List list of all or the selected team.\n");
-    printf("\n        [team] is optional and can can be a team ID or a team name (case sensitive)\n");
-    printf("\n        Options: \n\n");
-    printf("          -u | --units        Include the units in the details.\n");
-    printf("\n\n  Command: optimize\n");
-    printf("\n    Calculates an optimization for a unit.\n");
-    printf("\n    Usage\n");
-    printf("    RuneOptimizer optimize [unit] [options]\n");
-    printf("\n      [unit] can be a unit ID or a unit name (case sensitive)\n");
-    printf("\n      Options: \n\n");
-    printf("        -h | --min_hp <NUM>         Minumum HP to consider in the optimization.\n");
-    printf("                                    It defaults to the unit's current value.\n");
-    printf("        -a | --min_atk <NUM>        Minumum ATK to consider in the optimization.\n");
-    printf("                                    It defaults to the unit's current value.\n");
-    printf("        -d | --min_def <NUM>        Minumum DEF to consider in the optimization.\n");
-    printf("                                    It defaults to the unit's current value.\n");
-    printf("        -s | --min_spd <NUM>        Minumum SPD to consider in the optimization.\n");
-    printf("                                    It defaults to the unit's current value.\n");
-    printf("        -c | --min_crr <NUM>        Minumum CRIT RATE to consider in the optimization.\n");
-    printf("                                    It defaults to the unit's current value.\n");
-    printf("        -d | --min_crd <NUM>        Minumum CRIT DAMAGE to consider in the optimization.\n");
-    printf("                                    It defaults to the unit's current value.\n");
-    printf("        -r | --min_res <NUM>        Minumum RES to consider in the optimization.\n");
-    printf("                                    It defaults to the unit's current value.\n");
-    printf("        -f | --min_acc <NUM>        Minumum ACC to consider in the optimization.\n");
-    printf("                                    It defaults to the unit's current value.\n");
-    printf("        -p | --min_ehp <NUM>        Minumum effective HP to consider in the optimization.\n");
-    printf("                                    It defaults to 0.\n");
-    printf("        -m | --min_dmg <NUM>        Minumum computed damage to consider in the optimization.\n");
-    printf("                                    It defaults to 0.\n");
-    printf("        -l | --level <LV>           Level to consider the runes during the optimization.\n");
-    printf("                                    It only affects the rune main stats. Valid values are\n");
-    printf("                                    'current', '12' and '15'. Default is 'current'\n");
-    printf("        -t | --stats <ST1>,<ST2>... Stats than can be selected as mains for slots 2, 4 and 6.\n");
-    printf("                                    Only the selected stats will be included, so this option\n");
-    printf("                                    is mandatory. Accepted values are 'hp', 'atk', 'def',\n");
-    printf("                                    'hpflat', 'atkflat', 'defflat', 'spd', 'crr', 'crd',\n");
-    printf("                                    'res' and 'acc'. Values must be comma-separated, and up\n");
-    printf("                                    to 12 can be included.\n");
-    printf("        -e | --sets <S1>,<S2>...    Rune sets that than can be considered during the optimization.\n");
-    printf("                                    Only the selected sets will be included, so this option is\n");
-    printf("                                    mandatory. Accepted values  the rune net names, lowercase.\n");
-    printf("                                    Values must be comma-separated, and up to 3 can be included.\n");
-    printf("        -g | --gui                  Formats the output to be consumed by the GUI.\n");
-    return;
-}
-
-int optimize(int argc, char *argv[]){
-
-    // Get unit identifier
-    if (argc < 3){
-        fprintf(stderr, "No unit specified for optimization\n");
-        return ERROR_INPUT_NO_UNIT;
-    }
-
-    // Initialize values for the data to be read form arguments.
-    char requested_level = 0;
-    char gui = 0;
-    char requested_level_column[9] = "current_";
-    int sets[3] = {0, 0, 0};
-    int requested_stats[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-    struct Stats min_stats;
-    // Unsigned, so I cant use -1 as placeholder to check if its modified from
-    // arguments. I'm using just 1 (I don't think anybody will input values of
-    // 1), and later, if they are still 1, they are changed to 0.
-    min_stats.hp = 1;
-    min_stats.atk = 1;
-    min_stats.def = 1;
-    min_stats.spd = 1;
-    min_stats.crr = 1;
-    min_stats.crd = 1;
-    min_stats.res = 1;
-    min_stats.acc = 1;
-    min_stats.ehp = 1;
-    min_stats.dmg = 1;
-
-
-    // Loop command line arguments
-    for (int i = 3; i < argc; i ++){
-
-        // Rune level arguments
-        if (strcmp("--level", argv[i]) == 0 || strcmp("-l", argv[i]) == 0){
-            if (i < argc - 1){
-                if (strcmp("current", argv[i + 1]) == 0){
-                    requested_level = 0;
-                    strcpy(requested_level_column, "current_");
-                }
-                else if (strcmp("12", argv[i + 1]) == 0){
-                    requested_level = 12;
-                    strcpy(requested_level_column, "lv12_");
-                }
-                else if (strcmp("15", argv[i + 1]) == 0){
-                    requested_level = 15;
-                    strcpy(requested_level_column, "lv15_");
-                }
-                else{
-                    fprintf(
-                      stderr,
-                      "Invalid option for argument %s: %s\n"
-                      "Valid options are 'current', '12' or '15'\n",
-                      argv[i], argv[i + 1]
-                    );
-                    return ERROR_INPUT_INVALID_LEVEL;
-                }
-                // Advance one position in argument reading
-                i ++;
-            }
-            else{
-                fprintf(
-                  stderr,
-                  "Argument %s requires a value.\n"
-                  "Valid options are 'current', '12' or '15'\n", argv[i]
-                );
-                return ERROR_INPUT_NO_LEVEL;
-            }
-        }
-
-        // Rune sets argument
-        else if (strcmp("--sets", argv[i]) == 0 || strcmp("-e", argv[i]) == 0){
-            if (i < argc - 1){
-                // Separate string by commas
-                int j = 0;
-                // Returns first token
-                char *token = strtok(argv[i + 1], ",");
-
-                // Keep printing tokens while one of the
-                // delimiters present in the list, or until its complete
-                while (token != NULL && j < 3){
-                    if (strcmp(token, "energy") == 0){
-                        sets[j] = ENERGY;
-                    }
-                    else if (strcmp(token, "guard") == 0){
-                        sets[j] = GUARD;
-                    }
-                    else if (strcmp(token, "swift") == 0){
-                        sets[j] = SWIFT;
-                    }
-                    else if (strcmp(token, "blade") == 0){
-                        sets[j] = BLADE;
-                    }
-                    else if (strcmp(token, "rage") == 0){
-                        sets[j] = RAGE;
-                    }
-                    else if (strcmp(token, "focus") == 0){
-                        sets[j] = FOCUS;
-                    }
-                    else if (strcmp(token, "endure") == 0){
-                        sets[j] = ENDURE;
-                    }
-                    else if (strcmp(token, "fatal") == 0){
-                        sets[j] = FATAL;
-                    }
-                    else if (strcmp(token, "despair") == 0){
-                        sets[j] = DESPAIR;
-                    }
-                    else if (strcmp(token, "vampire") == 0){
-                        sets[j] = VAMPIRE;
-                    }
-                    else if (strcmp(token, "violent") == 0){
-                        sets[j] = VIOLENT;
-                    }
-                    else if (strcmp(token, "nemesis") == 0){
-                        sets[j] = NEMESIS;
-                    }
-                    else if (strcmp(token, "will") == 0){
-                        sets[j] = WILL;
-                    }
-                    else if (strcmp(token, "shield") == 0){
-                        sets[j] = SHIELD;
-                    }
-                    else if (strcmp(token, "revenge") == 0){
-                        sets[j] = REVENGE;
-                    }
-                    else if (strcmp(token, "destroy") == 0){
-                        sets[j] = DESTROY;
-                    }
-                    else if (strcmp(token, "fight") == 0){
-                        sets[j] = FIGHT;
-                    }
-                    else if (strcmp(token, "determination") == 0){
-                        sets[j] = DETERMINATION;
-                    }
-                    else if (strcmp(token, "enhance") == 0){
-                        sets[j] = ENHANCE;
-                    }
-                    else if (strcmp(token, "accuracy") == 0){
-                        sets[j] = ACCURACY;
-                    }
-                    else if (strcmp(token, "tolerance") == 0){
-                        sets[j] = TOLERANCE;
-                    }
-                    else{
-                        fprintf(stderr, "Unknown rune set %s.\n", token);
-                        return ERROR_INPUT_INVALID_SET;
-                    }
-                    token = strtok(NULL, ",");
-                    j ++;
-                }
-                // Advance one position in argument reading
-                i ++;
-            }
-            else{
-                fprintf(
-                  stderr,
-                  "Argument %s requires a list of values separated by commas.\n",
-                  argv[i]
-                );
-                return ERROR_INPUT_NO_SET;
-            }
-        }
-
-        // Accepted stats arguments
-        else if (strcmp("--stats", argv[i]) == 0 || strcmp("-t", argv[i]) == 0){
-            if (i < argc - 1){
-                // Separate string by commas
-                int j = 0;
-                // Returns first token
-                char *token = strtok(argv[i + 1], ",");
-
-                // Keep printing tokens while one of the
-                // delimiters present in the list, or until its complete
-                while (token != NULL && j < 3){
-                    if (strcmp(token, "hp") == 0){
-                        requested_stats[j] = HP_PERCENT;
-                    }
-                    else if (strcmp(token, "atk") == 0){
-                        requested_stats[j] = ATK_PERCENT;
-                    }
-                    else if (strcmp(token, "def") == 0){
-                        requested_stats[j] = DEF_PERCENT;
-                    }
-                    else if (strcmp(token, "hpflat") == 0){
-                        requested_stats[j] = HP_FLAT;
-                    }
-                    else if (strcmp(token, "atkflat") == 0){
-                        requested_stats[j] = ATK_FLAT;
-                    }
-                    else if (strcmp(token, "defflat") == 0){
-                        requested_stats[j] = DEF_FLAT;
-                    }
-                    else if (strcmp(token, "spd") == 0){
-                        requested_stats[j] = SPD;
-                    }
-                    else if (strcmp(token, "crr") == 0){
-                        requested_stats[j] = CRR;
-                    }
-                    else if (strcmp(token, "crd") == 0){
-                        requested_stats[j] = CRD;
-                    }
-                    else if (strcmp(token, "res") == 0){
-                        requested_stats[j] = RES;
-                    }
-                    else if (strcmp(token, "acc") == 0){
-                        requested_stats[j] = ACC;
-                    }
-                    else{
-                        fprintf(stderr, "Unknown rune stat %s.\n", token);
-                        return ERROR_INPUT_INVALID_STAT;
-                    }
-                    token = strtok(NULL, ",");
-                    j ++;
-                }
-                // Advance one position in argument reading
-                i ++;
-            }
-            else{
-                fprintf(stderr, "Argument %s requires a list of values separated by commas.\n", argv[i]);
-                return ERROR_INPUT_NO_STAT;
-            }
-        }
-
-        // Minimum stats arguments.
-        else if (strcmp("--min-hp", argv[i]) == 0 || strcmp("-h", argv[i]) == 0){
-            if (i < argc - 1){
-                unsigned short stat_tmp = atoi(argv[i + 1]);
-                if (stat_tmp == 1){
-                    stat_tmp = 0;
-                }
-                min_stats.hp = stat_tmp;
-                // Advance one position in argument reading
-                i ++;
-            }
-            else{
-                fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
-                return ERROR_INPUT_NO_HP;
-            }
-        }
-        else if (strcmp("--min-atk", argv[i]) == 0 || strcmp("-a", argv[i]) == 0){
-            if (i < argc - 1){
-                unsigned short stat_tmp = atoi(argv[i + 1]);
-                if (stat_tmp == 1){
-                    stat_tmp = 0;
-                }
-                min_stats.atk = stat_tmp;
-                // Advance one position in argument reading
-                i ++;
-            }
-            else{
-                fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
-                return ERROR_INPUT_NO_ATK;
-            }
-        }
-        else if (strcmp("--min-def", argv[i]) == 0 || strcmp("-d", argv[i]) == 0){
-            if (i < argc - 1){
-                unsigned short stat_tmp = atoi(argv[i + 1]);
-                if (stat_tmp == 1){
-                    stat_tmp = 0;
-                }
-                min_stats.def = stat_tmp;
-                // Advance one position in argument reading
-                i ++;
-            }
-            else{
-                fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
-                return ERROR_INPUT_NO_DEF;
-            }
-        }
-        else if (strcmp("--min-spd", argv[i]) == 0 || strcmp("-s", argv[i]) == 0){
-            if (i < argc - 1){
-                unsigned short stat_tmp = atoi(argv[i + 1]);
-                if (stat_tmp == 1){
-                    stat_tmp = 0;
-                }
-                min_stats.spd = stat_tmp;
-                // Advance one position in argument reading
-                i ++;
-            }
-            else{
-                fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
-                return ERROR_INPUT_NO_SPD;
-            }
-        }
-        else if (strcmp("--min-crr", argv[i]) == 0 || strcmp("-c", argv[i]) == 0){
-            if (i < argc - 1){
-                unsigned short stat_tmp = atoi(argv[i + 1]);
-                if (stat_tmp == 1){
-                    stat_tmp = 0;
-                }
-                else if (stat_tmp > 100){
-                    stat_tmp = 100;
-                }
-                min_stats.crr = stat_tmp;
-                // Advance one position in argument reading
-                i ++;
-            }
-            else{
-                fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
-                return ERROR_INPUT_NO_CRR;
-            }
-        }
-        else if (strcmp("--min-crd", argv[i]) == 0 || strcmp("-d", argv[i]) == 0){
-            if (i < argc - 1){
-                unsigned short stat_tmp = atoi(argv[i + 1]);
-                if (stat_tmp == 1){
-                    stat_tmp = 0;
-                }
-                min_stats.crd = stat_tmp;
-                // Advance one position in argument reading
-                i ++;
-            }
-            else{
-                fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
-                return ERROR_INPUT_NO_CRD;
-            }
-        }
-        else if (strcmp("--min-res", argv[i]) == 0 || strcmp("-r", argv[i]) == 0){
-            if (i < argc - 1){
-                unsigned short stat_tmp = atoi(argv[i + 1]);
-                if (stat_tmp == 1){
-                    stat_tmp = 0;
-                }
-                else if (stat_tmp > 100){
-                    stat_tmp = 100;
-                }
-                min_stats.atk = stat_tmp;
-                // Advance one position in argument reading
-                i ++;
-            }
-            else{
-                fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
-                return ERROR_INPUT_NO_RES;
-            }
-        }
-        else if (strcmp("--min-acc", argv[i]) == 0 || strcmp("-f", argv[i]) == 0){
-            if (i < argc - 1){
-                unsigned short stat_tmp = atoi(argv[i + 1]);
-                if (stat_tmp == 1){
-                    stat_tmp = 0;
-                }
-                else if (stat_tmp > 85){
-                    stat_tmp = 85;
-                }
-                min_stats.acc = stat_tmp;
-                // Advance one position in argument reading
-                i ++;
-            }
-            else{
-                fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
-                return ERROR_INPUT_NO_ACC;
-            }
-        }
-        else if (strcmp("--min-ehp", argv[i]) == 0 || strcmp("-p", argv[i]) == 0){
-            if (i < argc - 1){
-                unsigned int stat_tmp = atoi(argv[i + 1]);
-                if (stat_tmp == 1){
-                    stat_tmp = 0;
-                }
-                min_stats.ehp = stat_tmp;
-                // Advance one position in argument reading
-                i ++;
-            }
-            else{
-                fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
-                return ERROR_INPUT_NO_EHP;
-            }
-        }
-        else if (strcmp("--min-dmg", argv[i]) == 0 || strcmp("-m", argv[i]) == 0){
-            if (i < argc - 1){
-                unsigned short stat_tmp = atoi(argv[i + 1]);
-                if (stat_tmp == 1){
-                    stat_tmp = 0;
-                }
-                min_stats.dmg = stat_tmp;
-                // Advance one position in argument reading
-                i ++;
-            }
-            else{
-                fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
-                return ERROR_INPUT_NO_DMG;
-            }
-        }
-
-        // GUI invoked?
-        else if (strcmp("--gui", argv[i]) == 0 || strcmp("-g", argv[i]) == 0){
-            gui = 1;
-        }
-
-    }
-
-    // Now we can open the database.
-    if (SUCCESS != open_database()){
-        return ERROR_DB_CANT_OPEN;
-    }
-
-    // Read the unit from the databse
-    sqlite3_stmt *res;
-    char *sql = "SELECT "
-      "id, name, base_hp, base_atk, base_def, base_spd, base_crr, base_crd, "
-      "base_res, base_acc, current_hp, current_atk, current_def, current_spd, "
-      "current_crr, current_crd, current_res, current_acc "
-      "FROM units WHERE id = ? OR name = ?";
-    db_status = sqlite3_prepare_v2(db, sql, -1, &res, 0);
-    if (db_status != SQLITE_OK) {
-        fprintf(
-          stderr,
-          "Failed to execute statement to select unit: %s\n",
-          sqlite3_errmsg(db)
-        );
-        return ERROR_DB_UNIT;
-    }
-    sqlite3_bind_text(res, 1, argv[2], strlen(argv[2]), NULL);
-    sqlite3_bind_text(res, 2, argv[2], strlen(argv[2]), NULL);
-
-    // Fetch just one line
-    int step = sqlite3_step(res);
-
-    // Create a unit structure with the red data.
-    struct Unit unit;
-    if (step != SQLITE_ROW) {
-        fprintf(stderr, "Unit not found: %s\n", sqlite3_errmsg(db));
-        return ERROR_DB_UNIT_NOF_FOUND;
-    }
-    strcpy(unit.id, sqlite3_column_text(res, 0));
-    strcpy(unit.name, sqlite3_column_text(res, 1));
-    unit.base_hp = sqlite3_column_int(res, 2);
-    unit.base_atk = sqlite3_column_int(res, 3);
-    unit.base_def = sqlite3_column_int(res, 4);
-    unit.base_spd = sqlite3_column_int(res, 5);
-    unit.base_crr = sqlite3_column_int(res, 6);
-    unit.base_crd = sqlite3_column_int(res, 7);
-    unit.base_res = sqlite3_column_int(res, 8);
-    unit.base_acc = sqlite3_column_int(res, 9);
-    unit.base_ehp = calculate_ehp(unit.base_hp, unit.base_def);
-    unit.base_dmg = calculate_dmg(unit.base_atk, unit.base_crr, unit.base_crd);
-    unit.current_hp = sqlite3_column_int(res, 10);
-    unit.current_atk = sqlite3_column_int(res, 11);
-    unit.current_def = sqlite3_column_int(res, 12);
-    unit.current_spd = sqlite3_column_int(res, 13);
-    unit.current_crr = sqlite3_column_int(res, 14);
-    unit.current_crd = sqlite3_column_int(res, 15);
-    unit.current_res = sqlite3_column_int(res, 16);
-    unit.current_acc = sqlite3_column_int(res, 17);
-    unit.current_ehp = calculate_ehp(unit.current_hp, unit.current_def);
-    unit.current_dmg = calculate_dmg(unit.current_atk, unit.current_crr, unit.current_crd);
-
-    // Min stats that have the value 1 get overriden by the current stats.
-    if (min_stats.hp == 1){
-        min_stats.hp = unit.current_hp;
-    }
-    if (min_stats.atk == 1){
-        min_stats.atk = unit.current_atk;
-    }
-    if (min_stats.def == 1){
-        min_stats.def = unit.current_def;
-    }
-    if (min_stats.spd == 1){
-        min_stats.spd = unit.current_spd;
-    }
-    if (min_stats.crr == 1){
-        min_stats.crr = unit.current_crr;
-    }
-    if (min_stats.crd == 1){
-        min_stats.crd = unit.current_crd;
-    }
-    if (min_stats.res == 1){
-        min_stats.res = unit.current_res;
-    }
-    if (min_stats.acc == 1){
-        min_stats.acc = unit.current_acc;
-    }
-    if (min_stats.ehp == 1){
-        min_stats.ehp = 0;
-    }
-    if (min_stats.dmg == 1){
-        min_stats.dmg = 0;
-    }
-
-    // Calculate required rune count
-    struct Rune_Set_Count requested_set_count;
-    requested_set_count.energy = 0;
-    requested_set_count.guard = 0;
-    requested_set_count.swift = 0;
-    requested_set_count.blade = 0;
-    requested_set_count.rage = 0;
-    requested_set_count.focus = 0;
-    requested_set_count.endure = 0;
-    requested_set_count.fatal = 0;
-    requested_set_count.despair = 0;
-    requested_set_count.vampire = 0;
-    requested_set_count.violent = 0;
-    requested_set_count.nemesis = 0;
-    requested_set_count.will = 0;
-    requested_set_count.shield = 0;
-    requested_set_count.revenge = 0;
-    requested_set_count.destroy = 0;
-    requested_set_count.fight = 0;
-    requested_set_count.determination = 0;
-    requested_set_count.enhance = 0;
-    requested_set_count.accuracy = 0;
-    requested_set_count.tolerance = 0;
-    char total_requested_runes = 0;
-    for (int i = 0; i < 3; i ++){
-        switch (sets[i]){
-            case ENERGY:
-                requested_set_count.energy += 2;
-                total_requested_runes += 2;
-                break;
-            case GUARD:
-                requested_set_count.guard += 2;
-                total_requested_runes += 2;
-                break;
-            case SWIFT:
-                requested_set_count.swift += 4;
-                total_requested_runes += 4;
-                break;
-            case BLADE:
-                requested_set_count.blade += 2;
-                total_requested_runes += 2;
-                break;
-            case RAGE:
-                requested_set_count.rage += 4;
-                total_requested_runes += 4;
-                break;
-            case FOCUS:
-                requested_set_count.focus += 2;
-                total_requested_runes += 2;
-                break;
-            case ENDURE:
-                requested_set_count.endure += 2;
-                total_requested_runes += 2;
-                break;
-            case FATAL:
-                requested_set_count.fatal += 4;
-                total_requested_runes += 4;
-                break;
-            case DESPAIR:
-                requested_set_count.despair += 2;
-                total_requested_runes += 2;
-                break;
-            case VAMPIRE:
-                requested_set_count.vampire += 4;
-                total_requested_runes += 4;
-                break;
-            case VIOLENT:
-                requested_set_count.violent += 4;
-                total_requested_runes += 4;
-                break;
-            case NEMESIS:
-                requested_set_count.nemesis += 2;
-                total_requested_runes += 2;
-                break;
-            case WILL:
-                requested_set_count.will += 2;
-                total_requested_runes += 2;
-                break;
-            case SHIELD:
-                requested_set_count.shield += 2;
-                total_requested_runes += 2;
-                break;
-            case REVENGE:
-                requested_set_count.revenge += 2;
-                total_requested_runes += 2;
-                break;
-            case DESTROY:
-                requested_set_count.destroy += 2;
-                total_requested_runes += 2;
-                break;
-            case FIGHT:
-                requested_set_count.fight += 2;
-                total_requested_runes += 2;
-                break;
-            case DETERMINATION:
-                requested_set_count.determination += 2;
-                total_requested_runes += 2;
-                break;
-            case ENHANCE:
-                requested_set_count.enhance += 2;
-                total_requested_runes += 2;
-                break;
-            case ACCURACY:
-                requested_set_count.accuracy += 2;
-                total_requested_runes += 2;
-                break;
-            case TOLERANCE:
-                requested_set_count.tolerance += 2;
-                total_requested_runes += 2;
-                break;
-        }
-    }
-    if (gui != 1){
-        printf("\n");
-    }
-    if (total_requested_runes != 6){
-        fprintf(stderr, "Invalid rune set combination.\n");
-        return ERROR_INPUT_INCOMPLETE_SETS;
-    }
-
-    // Create queries for each slot
-    char query_odd[2500] = "SELECT id, unit, type, ";
-    strcat(query_odd, requested_level_column);
-    strcat(query_odd, "hp_flat, ");
-    strcat(query_odd, requested_level_column);
-    strcat(query_odd, "atk_flat, ");
-    strcat(query_odd, requested_level_column);
-    strcat(query_odd, "def_flat, ");
-    strcat(query_odd, requested_level_column);
-    strcat(query_odd, "hp_percent, ");
-    strcat(query_odd, requested_level_column);
-    strcat(query_odd, "atk_percent, ");
-    strcat(query_odd, requested_level_column);
-    strcat(query_odd, "def_percent, ");
-    strcat(query_odd, requested_level_column);
-    strcat(query_odd, "spd, ");
-    strcat(query_odd, requested_level_column);
-    strcat(query_odd, "crr, ");
-    strcat(query_odd, requested_level_column);
-    strcat(query_odd, "crd, ");
-    strcat(query_odd, requested_level_column);
-    strcat(query_odd, "res, ");
-    strcat(query_odd, requested_level_column);
-    strcat(query_odd, "acc FROM runes WHERE slot = ? AND type IN (");
-    char cur_set[2];
-    char stat_set[2];
-    if (sets[0] != 0){
-        sprintf(cur_set, "%d", sets[0]);
-        strcat(query_odd, cur_set);
-        strcat(query_odd, ", ");
-    }
-    if (sets[1] != 0){
-        sprintf(cur_set, "%d", sets[1]);
-        strcat(query_odd, cur_set);
-        strcat(query_odd, ", ");
-    }
-    if (sets[2] != 0){
-        sprintf(cur_set, "%d", sets[2]);
-        strcat(query_odd, cur_set);
-        strcat(query_odd, ", ");
-    }
-    strcat(query_odd, "-1) ");
-    // TODO: Add team stuff
-
-    char query_even[1500] = "SELECT id, unit, type, ";
-    strcat(query_even, requested_level_column);
-    strcat(query_even, "hp_flat, ");
-    strcat(query_even, requested_level_column);
-    strcat(query_even, "atk_flat, ");
-    strcat(query_even, requested_level_column);
-    strcat(query_even, "def_flat, ");
-    strcat(query_even, requested_level_column);
-    strcat(query_even, "hp_percent, ");
-    strcat(query_even, requested_level_column);
-    strcat(query_even, "atk_percent, ");
-    strcat(query_even, requested_level_column);
-    strcat(query_even, "def_percent, ");
-    strcat(query_even, requested_level_column);
-    strcat(query_even, "spd, ");
-    strcat(query_even, requested_level_column);
-    strcat(query_even, "crr, ");
-    strcat(query_even, requested_level_column);
-    strcat(query_even, "crd, ");
-    strcat(query_even, requested_level_column);
-    strcat(query_even, "res, ");
-    strcat(query_even, requested_level_column);
-    strcat(query_even, "acc FROM runes WHERE slot = ? AND type IN (");
-    if (sets[0] != 0){
-        sprintf(cur_set, "%d", sets[0]);
-        strcat(query_even, cur_set);
-        strcat(query_even, ", ");
-    }
-    if (sets[1] != 0){
-        sprintf(cur_set, "%d", sets[1]);
-        strcat(query_even, cur_set);
-        strcat(query_even, ", ");
-    }
-    if (sets[2] != 0){
-        sprintf(cur_set, "%d", sets[2]);
-        strcat(query_even, cur_set);
-        strcat(query_even, ", ");
-    }
-    strcat(query_even, "-1) AND main_stat IN (");
-    for (int i = 0; i < 12; i ++){
-        if (requested_stats[i] != 0){
-            sprintf(stat_set, "%d", requested_stats[i]);
-            strcat(query_even, stat_set);
-            strcat(query_even, ", ");
-        }
-        else{
-            break;
-        }
-    }
-    strcat(query_even, "-1) ");
-    // TODO: Add team stuff
-
-    // Get data for all the runes from the database.
-    // Im using a 7 position array, and the index 0 is ignored. This is
-    // is because I REALLY NEED to use 1-indexes to match rune slots.
-    sqlite3_stmt *stmt_runes[7];
-    // Im assumming 600 runes per slot is a safe estimate. I hope it doesn't
-    // come back to bite me.
-    struct Rune runes[7][600];
-    db_status = sqlite3_prepare_v2(db, query_odd, -1, &stmt_runes[1], 0);
-    if (db_status != SQLITE_OK) {
-        fprintf(stderr, "Error getting runes for slot 1: %s\n", sqlite3_errmsg(db));
-        return ERROR_DB_RUNES_SLOT;
-    }
-    sqlite3_bind_int(stmt_runes[1], 1, 1);
-    db_status = sqlite3_prepare_v2(db, query_odd, -1, &stmt_runes[3], 0);
-    if (db_status != SQLITE_OK) {
-        fprintf(stderr, "Error getting runes for slot 3: %s\n", sqlite3_errmsg(db));
-        return ERROR_DB_RUNES_SLOT;
-    }
-    sqlite3_bind_int(stmt_runes[3], 1, 3);
-    db_status = sqlite3_prepare_v2(db, query_odd, -1, &stmt_runes[5], 0);
-    if (db_status != SQLITE_OK) {
-        fprintf(stderr, "Error getting runes for slot 5: %s\n", sqlite3_errmsg(db));
-        return ERROR_DB_RUNES_SLOT;
-    }
-    sqlite3_bind_int(stmt_runes[5], 1, 5);
-    db_status = sqlite3_prepare_v2(db, query_odd, -1, &stmt_runes[2], 0);
-    if (db_status != SQLITE_OK) {
-        fprintf(stderr, "Error getting runes for slot 2: %s\n", sqlite3_errmsg(db));
-        return ERROR_DB_RUNES_SLOT;
-    }
-    sqlite3_bind_int(stmt_runes[2], 1, 2);
-    db_status = sqlite3_prepare_v2(db, query_odd, -1, &stmt_runes[4], 0);
-    if (db_status != SQLITE_OK) {
-        fprintf(stderr, "Error getting runes for slot 4: %s\n", sqlite3_errmsg(db));
-        return ERROR_DB_RUNES_SLOT;
-    }
-    sqlite3_bind_int(stmt_runes[4], 1, 4);
-    db_status = sqlite3_prepare_v2(db, query_odd, -1, &stmt_runes[6], 0);
-    if (db_status != SQLITE_OK) {
-        fprintf(stderr, "Error getting runes for slot 6: %s\n", sqlite3_errmsg(db));
-        return ERROR_DB_RUNES_SLOT;
-    }
-    sqlite3_bind_int(stmt_runes[6], 1, 6);
-
-    // Get and populate all the runes
-    int rune_count[7];
-    for (int i = 1; i < 7; i ++){
-        int j = 0;
-        while (1 == 1){
-            int status = sqlite3_step(stmt_runes[i]);
-            if (status == SQLITE_ROW){
-                strcpy(runes[i][j].id, sqlite3_column_text(stmt_runes[i], 0));
-                strcpy(runes[i][j].unit, sqlite3_column_text(stmt_runes[i], 1));
-                runes[i][j].set = sqlite3_column_int(stmt_runes[i], 2);
-                runes[i][j].hp_flat = sqlite3_column_int(stmt_runes[i], 3);
-                runes[i][j].atk_flat = sqlite3_column_int(stmt_runes[i], 4);
-                runes[i][j].def_flat = sqlite3_column_int(stmt_runes[i], 5);
-                runes[i][j].hp_percent = sqlite3_column_int(stmt_runes[i], 6);
-                runes[i][j].atk_percent = sqlite3_column_int(stmt_runes[i], 7);
-                runes[i][j].def_percent = sqlite3_column_int(stmt_runes[i], 8);
-                runes[i][j].spd = sqlite3_column_int(stmt_runes[i], 9);
-                runes[i][j].crr = sqlite3_column_int(stmt_runes[i], 10);
-                runes[i][j].crd = sqlite3_column_int(stmt_runes[i], 11);
-                runes[i][j].res = sqlite3_column_int(stmt_runes[i], 12);
-                runes[i][j].acc = sqlite3_column_int(stmt_runes[i], 13);
-                j ++;
-            }
-            else{
-                break;
-            }
-        }
-        rune_count[i] = j;
-    }
-
-    // If any slot doesn't have matching runes, we can stop now.
-    for (int i = 1; i < 7; i ++){
-        if (rune_count[i] == 0){
-            if (gui != 1){
-                printf("\n  - No availbale runes for slot %d\n", i);
-            }
-            else{
-                fprintf(stderr, "\n  - No available runes for slot %d\n", i);
-            }
-            return SUCCESS;
-        }
-    }
-    if (gui != 1){
-        printf("\n");
-    }
-    unsigned long max_combinations =
-      rune_count[1] *
-      rune_count[2] *
-      rune_count[3] *
-      rune_count[4] *
-      rune_count[5] *
-      rune_count[6];
-
-    // Print a nice summary before starting the long optimization.
-    // This is an output example:
-    //
-    // -----------------------------------    Requested rune sets:
-    // | Lushen                          |       RAGE  BLADE
-    // | ID: 7223811472                  |
-    // -----------------------------------    Accepted stats:
-    // | STAT | BASE   | CURR.  | MIN.   |       ATK  CRR  CRD
-    // -----------------------------------
-    // | HP:  |  9225  | 12262  | 10000  |    Considering runes at level 15
-    // | ATK: |   900  |  2482  |  2482  |
-    // | DEF: |   461  |   703  |   703  |    Runes considered by slot:
-    // | SPD: |   103  |   159  |   159  |      1: 27    2: 39    3: 25
-    // | CRR: |    15% |    79% |    79% |      4: 35    5: 24    6: 32
-    // | CRD: |    50% |   188% |   188% |
-    // | RES: |    15% |    35% |    35% |    Total combinations: 707616000
-    // | ACC: |     0% |    11% |    10% |
-    // -----------------------------------
-    //
-    // Im breaking the 80-characters-line rule here, but only 'cause I'd like
-    // to remain sane.
-    if (gui != 1){
-        printf(" -----------------------------------------    Requested rune sets:\n");
-        printf(" | %*s |      ", -37, unit.name);
-        for (int i = 0; i < 3; i ++){
-            if (sets[i] != 0){
-                printf(" %s ", set_names[sets[i]]);
-            }
-            else{
-                break;
-            }
-        }
-        printf("\n");
-        printf(" | ID: %*s |\n", -33, unit.id);
-        printf(" -----------------------------------------    Accepted stats:\n");
-        printf(" | STAT | BASE     | CURR.    | MIN.     |      ");
-        for (int i = 0; i < 12; i ++){
-            if (requested_stats[i] != 0){
-                printf(" %s ", stat_names[requested_stats[i]]);
-            }
-            else{
-                break;
-            }
-        }
-        printf("\n");
-        printf(" -----------------------------------------\n");
-        printf(" | HP:  | %*d  | %*d  | %*d  |    ", 7, unit.base_hp, 7, unit.current_hp, 7, min_stats.hp);
-        if (requested_level == 0){
-            printf("Using runes at their current level\n");
-        }
-        else{
-            printf("Considering runes at level %d\n", requested_level);
-        }
-        printf(" | ATK: | %*d  | %*d  | %*d  |\n", 7, unit.base_atk, 7, unit.current_atk, 7, min_stats.atk);
-        printf(" | DEF: | %*d  | %*d  | %*d  |    Runes considered by slot:\n", 7, unit.base_def, 7, unit.current_def, 7, min_stats.def);
-        printf(" | SPD: | %*d  | %*d  | %*d  |      1:%*d    2:%*d    3:%*d\n", 7, unit.base_spd, 7, unit.current_spd, 7, min_stats.spd, 3, rune_count[1], 3, rune_count[2], 3, rune_count[3]);
-        printf(" | CRR: | %*d\% | %*d\% | %*d\% |      4:%*d    5:%*d    6:%*d\n", 7, unit.base_crr, 7, unit.current_crr, 7, min_stats.crr, 3, rune_count[4], 3, rune_count[5], 3, rune_count[6]);
-        printf(" | CRD: | %*d\% | %*d\% | %*d\% |\n", 7, unit.base_crd, 7, unit.current_crd, 7, min_stats.crd);
-        printf(" | RES: | %*d\% | %*d\% | %*d\% |    Total combinations: %d\n", 7, unit.base_res, 7, unit.current_res, 7, min_stats.res, max_combinations);
-        printf(" | ACC: | %*d\% | %*d\% | %*d\% |\n", 7, unit.base_acc, 7, unit.current_acc, 7, min_stats.acc);
-        printf(" | EHP: | %*d  | %*d  | %*d  |\n", 7, unit.base_ehp, 7, unit.current_ehp, 7, min_stats.ehp);
-        printf(" | DMG: | %*d  | %*d  | %*d  |\n", 7, unit.base_dmg, 7, unit.current_dmg, 7, min_stats.dmg);
-        printf(" -----------------------------------------\n");
-    }
-
-    // Now its time to loop all 6 'reels' of runes and try to match combos
-    if (gui != 1){
-        printf("\n\n__Optimization progress___________________________\n", max_combinations);
-    }
-    // Initialize arrys and some counters
-    int index[7] = {0, 0, 0, 0, 0, 0};
-    unsigned long tested_combinations = 0;
-    unsigned long valid_sets = 0;
-    unsigned long result_count = 0;
-    Result results[1000];
-    while(
-        index[1] < rune_count[1] &&
-        index[2] < rune_count[2] &&
-        index[3] < rune_count[3] &&
-        index[4] < rune_count[4] &&
-        index[5] < rune_count[5] &&
-        index[6] < rune_count[6]
-    ){
-        // Progress bar, 50 characters to 100%
-        if (
-          gui != 1 &&
-          (tested_combinations + 1) %
-          (unsigned long)(max_combinations / 50)
-          == 0
-        ){
-            printf("#");
-            // Line buffered! need to flush after every char.
-            fflush(stdout);
-        }
-
-        // Calculate rune sets at current indexes.
-        struct Rune_Set_Count set_count;
-        set_count.energy = 0;
-        set_count.guard = 0;
-        set_count.swift = 0;
-        set_count.blade = 0;
-        set_count.rage = 0;
-        set_count.focus = 0;
-        set_count.endure = 0;
-        set_count.fatal = 0;
-        set_count.despair = 0;
-        set_count.vampire = 0;
-        set_count.violent = 0;
-        set_count.nemesis = 0;
-        set_count.will = 0;
-        set_count.shield = 0;
-        set_count.revenge = 0;
-        set_count.destroy = 0;
-        set_count.fight = 0;
-        set_count.determination = 0;
-        set_count.enhance = 0;
-        set_count.accuracy = 0;
-        set_count.tolerance = 0;
-        for (int i = 1; i < 7; i ++){
-            switch (runes[i][index[i]].set){
-                case ENERGY:
-                    set_count.energy ++;
-                    break;
-                case GUARD:
-                    set_count.guard ++;
-                    break;
-                case SWIFT:
-                    set_count.swift ++;
-                    break;
-                case BLADE:
-                    set_count.blade ++;
-                    break;
-                case RAGE:
-                    set_count.rage ++;
-                    break;
-                case FOCUS:
-                    set_count.focus ++;
-                    break;
-                case ENDURE:
-                    set_count.endure ++;
-                    break;
-                case FATAL:
-                    set_count.fatal ++;
-                    break;
-                case DESPAIR:
-                    set_count.despair ++;
-                    break;
-                case VAMPIRE:
-                    set_count.vampire ++;
-                    break;
-                case VIOLENT:
-                    set_count.violent ++;
-                    break;
-                case NEMESIS:
-                    set_count.nemesis ++;
-                    break;
-                case WILL:
-                    set_count.will ++;
-                    break;
-                case SHIELD:
-                    set_count.shield ++;
-                    break;
-                case REVENGE:
-                    set_count.revenge ++;
-                    break;
-                case DESTROY:
-                    set_count.destroy ++;
-                    break;
-                case FIGHT:
-                    set_count.fight ++;
-                    break;
-                case DETERMINATION:
-                    set_count.determination ++;
-                    break;
-                case ENHANCE:
-                    set_count.enhance ++;
-                    break;
-                case ACCURACY:
-                    set_count.accuracy ++;
-                    break;
-                case TOLERANCE:
-                    set_count.tolerance ++;
-                    break;
-            }
-        }
-        // Compare with requested sets
-        if (
-            set_count.energy >= requested_set_count.energy &&
-            set_count.guard >= requested_set_count.guard &&
-            set_count.swift >= requested_set_count.swift &&
-            set_count.blade >= requested_set_count.blade &&
-            set_count.rage >= requested_set_count.rage &&
-            set_count.focus >= requested_set_count.focus &&
-            set_count.endure >= requested_set_count.endure &&
-            set_count.fatal >= requested_set_count.fatal &&
-            set_count.despair >= requested_set_count.despair &&
-            set_count.vampire >= requested_set_count.vampire &&
-            set_count.violent >= requested_set_count.violent &&
-            set_count.nemesis >= requested_set_count.nemesis &&
-            set_count.will >= requested_set_count.will &&
-            set_count.shield >= requested_set_count.shield &&
-            set_count.revenge >= requested_set_count.revenge &&
-            set_count.destroy >= requested_set_count.destroy &&
-            set_count.fight >= requested_set_count.fight &&
-            set_count.determination >= requested_set_count.determination &&
-            set_count.enhance >= requested_set_count.enhance &&
-            set_count.accuracy >= requested_set_count.accuracy &&
-            set_count.tolerance >= requested_set_count.tolerance
-        ){
-            // The current runes form a valid set.
-            valid_sets ++;
-
-            // Calculate new stats
-            struct Stats stats;
-            stats.hp = unit.base_hp;
-            stats.atk = unit.base_atk;
-            stats.def = unit.base_def;
-            stats.spd = unit.base_spd;
-            stats.crr = unit.base_crr;
-            stats.crd = unit.base_crd;
-            stats.res = unit.base_res;
-            stats.acc = unit.base_acc;
-            for (int i = 1; i < 7; i ++){
-                stats.hp += runes[i][index[i]].hp_flat;
-                stats.atk += runes[i][index[i]].atk_flat;
-                stats.def += runes[i][index[i]].def_flat;
-                stats.hp += unit.base_hp * runes[i][index[i]].hp_percent / 100;
-                stats.atk +=
-                  unit.base_hp * runes[i][index[i]].atk_percent / 100;
-                stats.def +=
-                  unit.base_hp * runes[i][index[i]].def_percent / 100;
-                stats.spd += runes[i][index[i]].spd;
-                stats.crr += runes[i][index[i]].crr;
-                stats.crd += runes[i][index[i]].crd;
-                stats.res += runes[i][index[i]].res;
-                stats.acc += runes[i][index[i]].acc;
-            }
-
-            // Calculated stats
-            stats.ehp = calculate_ehp(stats.hp, stats.def);
-            stats.dmg = calculate_dmg(stats.atk, stats.crr, stats.crd);
-
-            // Compare with minimum requeriments
-            if (
-                stats.hp >= min_stats.hp &&
-                stats.atk >= min_stats.atk &&
-                stats.def >= min_stats.def &&
-                stats.spd >= min_stats.spd &&
-                stats.crr >= min_stats.crr &&
-                stats.crd >= min_stats.crd &&
-                stats.res >= min_stats.res &&
-                stats.acc >= min_stats.acc &&
-                stats.ehp >= min_stats.ehp &&
-                stats.dmg >= min_stats.dmg
-            ){
-                // This is a valid sets and all stats are above the minimum.
-                // Create a result with rune indexes, stats, and rating.
-                for (int i = 1; i < 7; i ++){
-                    strcpy(
-                      results[result_count].rune_ids[i - 1],
-                      runes[i][index[i]].id
-                    );
-                }
-                results[result_count].stats.hp = stats.hp;
-                results[result_count].stats.atk = stats.atk;
-                results[result_count].stats.def = stats.def;
-                results[result_count].stats.spd = stats.spd;
-                results[result_count].stats.crr = stats.crr;
-                results[result_count].stats.crd = stats.crd;
-                results[result_count].stats.res = stats.res;
-                results[result_count].stats.acc = stats.acc;
-                results[result_count].stats.ehp = stats.ehp;
-                results[result_count].stats.dmg = stats.dmg;
-
-                // One rating point per stat increase over current stats.
-                // Save for HP, with takes 15 for a raing point
-                results[result_count].rating = 0;
-                results[result_count].rating += ((stats.hp - unit.current_hp) / 15);
-                results[result_count].rating += (stats.atk - unit.current_atk);
-                results[result_count].rating += (stats.def - unit.current_def);
-                results[result_count].rating += (stats.spd - unit.current_spd);
-                results[result_count].rating += (stats.crr - unit.current_crr);
-                results[result_count].rating += (stats.crd - unit.current_crd);
-                results[result_count].rating += (stats.res - unit.current_res);
-                results[result_count].rating += (stats.acc - unit.current_acc);
-                result_count ++;
-            }
-        }
-
-        // Loop control. Rotate the reels 'right to left'
-        tested_combinations ++;
-        index[6] ++;
-        if (index[6] == rune_count[6]){
-            index[6] = 0;
-            index[5] ++;
-        }
-        if (index[5] == rune_count[5]){
-            index[5] = 0;
-            index[4] ++;
-        }
-        if (index[4] == rune_count[4]){
-            index[4] = 0;
-            index[3] ++;
-        }
-        if (index[3] == rune_count[3]){
-            index[3] = 0;
-            index[2] ++;
-        }
-        if (index[2] == rune_count[2]){
-            index[2] = 0;
-            index[1] ++;
-        }
-
-        //DEBUG: force exit with some results TODO
-        //if (result_count > 11){
-        //    break;
-        //}
-    }
-    if (gui != 1){
-        printf("\n");
-    }
-
-    if (result_count > 0){
-        // Yay! Some combinations matched te criteria.
-        if (gui != 1){
-            printf("\n\n%d results found\n", result_count);
-        }
-        // Sort results
-        sort_results(results, result_count);
-
-        // Preview the best option
-        if (gui != 1){
-            printf("\nPresenting best option:\n\n");
-            printf(" ------------------------------\n");
-            printf(" | %*s |\n", -26, unit.name);
-            printf(" | ID: %*s |\n", -22, unit.id);
-            printf(" ------------------------------\n");
-            printf(" | STAT | CURR.    | NEW      |\n");
-            printf(" ------------------------------\n");
-            printf(" | HP:  | %*d  | %*d  |\n", 7, unit.current_hp, 7, results[0].stats.hp);
-            printf(" | ATK: | %*d  | %*d  |\n", 7, unit.current_atk, 7, results[0].stats.atk);
-            printf(" | DEF: | %*d  | %*d  |\n", 7, unit.current_def, 7, results[0].stats.def);
-            printf(" | SPD: | %*d  | %*d  |\n", 7, unit.current_spd, 7, results[0].stats.spd);
-            printf(" | CRR: | %*d\% | %*d\% |\n", 7, unit.current_crr, 7, results[0].stats.crr);
-            printf(" | CRD: | %*d\% | %*d\% |\n", 7, unit.current_crd, 7, results[0].stats.crd);
-            printf(" | RES: | %*d\% | %*d\% |\n", 7, unit.current_res, 7, results[0].stats.res);
-            printf(" | ACC: | %*d\% | %*d\% |\n", 7, unit.current_acc, 7, results[0].stats.acc);
-            printf(" | EHP: | %*d  | %*d  |\n", 7, unit.current_ehp, 7, results[0].stats.ehp);
-            printf(" | DMG: | %*d  | %*d  |\n", 7, unit.current_dmg, 7, results[0].stats.dmg);
-            printf(" ------------------------------\n");
-
-            // This bit may be hard to follow.
-            // I'm populating  8 lines of text with data, to display the runes in
-            // a nice table format.
-            //
-            // This is an output exaple:
-            //
-            // ------------------------------  ------------------------------  ------------------------------
-            // |6|RAGE         | 23285581330|  |1|BLADE        | 22677809846|  |2|BLADE        | 21432847749|
-            // ------------------------------  ------------------------------  ------------------------------
-            // | Storage       |        +12 |  | Storage       |        +12 |  | Perna         |        +15 |
-            // ------------------------------  ------------------------------  ------------------------------
-            // | ACC           48           |  | ATK_FLAT     118           |  | SPD           42           |
-            // | RES            6           |  |                            |  |                            |
-            // | ATK_FLAT      19           |  | RES           14           |  | CRD           16           |
-            // | CRR           12           |  | ACC            8           |  | CRR           10           |
-            // | HP            14           |  | HP_FLAT      580           |  | ATK            7 + 3       |
-            // | CRD           14           |  | CRR           10           |  | DEF            7 + 3       |
-            // ------------------------------  ------------------------------  ------------------------------
-            //
-            // ------------------------------  ------------------------------  ------------------------------
-            // |5|RAGE         | 26260912967|  |4|RAGE         | 27947761086|  |3|RAGE         | 27654723287|
-            // ------------------------------  ------------------------------  ------------------------------
-            // | Lushen        |        +15 |  | Lushen        |        +15 |  | Covenant      |        +12 |
-            // ------------------------------  ------------------------------  ------------------------------
-            // | HP_FLAT     2448           |  | CRD           80           |  | DEF_FLAT     118           |
-            // |                            |  |                            |  | HP_FLAT      167           |
-            // | CRR           16           |  | CRR            6           |  | RES            8           |
-            // | SPD            6 + 2       |  | RES           11           |  | DEF           11           |
-            // | CRD           11           |  | SPD           18           |  | CRD           18           |
-            // | ATK            6 + 5       |  | ATK            8           |  | CRR           12           |
-            // ------------------------------  ------------------------------  ------------------------------
-            //
-            // Again, Im breaking the 80-characters-line rule.
-
-            char present[8][130];
-            strcpy(present[0], "");
-            strcpy(present[1], "");
-            strcpy(present[2], "");
-            strcpy(present[4], "");
-            strcpy(present[5], "");
-            strcpy(present[6], "");
-            strcpy(present[7], "");
-
-            // Retrieve the rune stats from the database
-            printf("\n");
-            for (int i = 6; i != 0;){
-                char *rune_id = results[0].rune_ids[i - 1];
-                sqlite3_stmt *rune_res;
-                char *sql =
-                  "SELECT runes.id, runes.slot, runes.type, units.id, units.name, runes.level "
-                  "FROM runes LEFT JOIN units ON runes.unit = units.id "
-                  "WHERE runes.id = ?";
-                db_status = sqlite3_prepare_v2(db, sql, -1, &rune_res, 0);
-
-                if (db_status == SQLITE_OK) {
-                    sqlite3_bind_text(rune_res, 1, results[0].rune_ids[i - 1], strlen(results[0].rune_ids[i - 1]), NULL);
-                }
-                else {
-                    fprintf(stderr, "Failed to execute statement to select rune: %s\n", sqlite3_errmsg(db));
-                    return ERROR_DB_RUNES_RESULT;
-                }
-
-                int step = sqlite3_step(rune_res);
-                if (step == SQLITE_ROW) {
-                    char tmp[64];
-                    strcpy(tmp, "");
-                    strcat(present[0], "|");
-                    sprintf(tmp, "%*d", 1, sqlite3_column_int(rune_res, 1));
-                    strcat(present[0], tmp);
-                    strcat(present[0], "|");
-                    sprintf(tmp, "%*s|", -13, set_names[sqlite3_column_int(rune_res, 2)]);
-                    strcat(present[0], tmp);
-                    sprintf(tmp, "%*s|  ", 12, sqlite3_column_text(rune_res, 0));
-                    strcat(present[0], tmp);
-
-                    strcat(present[1], "|");
-                    if (sqlite3_column_type(rune_res, 3) == SQLITE_NULL){
-                        sprintf(tmp, " %*s", -14, "Storage");
-                        strcat(present[1], tmp);
-                        strcat(present[1], "|");
-                    }
-                    else{
-                        sprintf(tmp, " %*s", -14, sqlite3_column_text(rune_res, 4));
-                        strcat(present[1], tmp);
-                        strcat(present[1], "|");
-                    }
-
-                    // Rune level
-                    sprintf(tmp, "        +%*s |  ", 2, sqlite3_column_text(rune_res, 5));
-                    strcat(present[1], tmp);
-
-                    sqlite3_stmt *stats_res;
-                    char *sql_stats =
-                      "SELECT rune, slot, stat, value, enchant, grind "
-                      "FROM rune_stats "
-                      "WHERE rune = ?";
-                    db_status = sqlite3_prepare_v2(db, sql_stats, -1, &stats_res, 0);
-
-                    if (db_status == SQLITE_OK) {
-                        sqlite3_bind_text(stats_res, 1, sqlite3_column_text(rune_res, 0), strlen(sqlite3_column_text(rune_res, 0)), NULL);
-                    }
-                    else {
-                        fprintf(stderr, "Failed to execute statement to select rune stats: %s\n", sqlite3_errmsg(db));
-                        return ERROR_DB_STATS_RESULT;
-                    }
-
-                    int curr_slot = -1;
-                    while (1 == 1){
-                        int status = sqlite3_step(stats_res);
-                        //printf("STATUS:  %d   ", status);
-                        if (status == SQLITE_ROW){
-                            // Print empty lines for no-stats
-                            while (sqlite3_column_int(stats_res, 1) != curr_slot){
-                                sprintf(tmp, "|                            |  ");
-                                strcat(present[curr_slot + 3], tmp);
-                                curr_slot ++;
-                            }
-                            if (sqlite3_column_int(stats_res, 1) == curr_slot){
-                                strcat(present[curr_slot + 3], "| ");
-                                sprintf(tmp, "%*s", -9, stat_names[sqlite3_column_int(stats_res, 2)]);
-                                strcat(present[curr_slot + 3], tmp);
-                                sprintf(tmp, " %*d", 6, sqlite3_column_int(stats_res, 3));
-                                strcat(present[curr_slot + 3], tmp);
-
-                                // Display grinds
-                                if (sqlite3_column_int(stats_res, 5) > 0){
-                                    sprintf(tmp, " + %*d", -8, sqlite3_column_int(stats_res, 5));
-                                }
-                                else{
-                                    sprintf(tmp, "           ");
-                                }
-                                strcat(present[curr_slot + 3], tmp);
-                                strcat(present[curr_slot + 3], "|  ");
-                            }
-
-                            curr_slot ++;
-                        }
-                        else{
-                            break;
-                        }
-                    }
-                    sqlite3_finalize(stats_res);
-                }
-                else{
-                    fprintf(stderr, "BAD ROW: %s\n", sqlite3_errmsg(db));
-                }
-                sqlite3_finalize(rune_res);
-
-                // Loop control
-                // It's weird, but I wanna present the runes in the same format the
-                // game does:
-                //
-                // 6 1 2
-                // 5 4 3
-                // After slots 2 and 3, the generated strings are written.
-                if (i == 6){
-                    //printf("-6->1-");
-                    i = 1;
-                }
-                else if (i == 1){
-                    //printf("-1->2-");
-                    i = 2;
-                }
-                else if (i == 2){
-                    // Print and reinitialize the strings
-                    printf("------------------------------  ------------------------------  ------------------------------\n");
-                    printf(present[0]);
-                    printf("\n------------------------------  ------------------------------  ------------------------------\n");
-                    printf(present[1]);
-                    printf("\n------------------------------  ------------------------------  ------------------------------\n");
-                    printf(present[2]);
-                    printf("\n");
-                    printf(present[3]);
-                    printf("\n");
-                    printf(present[4]);
-                    printf("\n");
-                    printf(present[5]);
-                    printf("\n");
-                    printf(present[6]);
-                    printf("\n");
-                    printf(present[7]);
-                    printf("\n------------------------------  ------------------------------  ------------------------------\n");
-                    strcpy(present[0], "");
-                    strcpy(present[1], "");
-                    strcpy(present[2], "");
-                    strcpy(present[3], "");
-                    strcpy(present[4], "");
-                    strcpy(present[5], "");
-                    strcpy(present[6], "");
-                    strcpy(present[7], "");
-                    //printf("-2->5-");
-                    i = 5;
-                }
-                else if (i == 5){
-                    //printf("-5->4-");
-                    i = 4;
-                }
-                else if (i == 4){
-                    //printf("-4->3-");
-                    i = 3;
-                }
-                else if (i == 3){
-                    // Print and exit loop
-                    printf("\n------------------------------  ------------------------------  ------------------------------\n");
-                    printf(present[0]);
-                    printf("\n------------------------------  ------------------------------  ------------------------------\n");
-                    printf(present[1]);
-                    printf("\n------------------------------  ------------------------------  ------------------------------\n");
-                    printf(present[2]);
-                    printf("\n");
-                    printf(present[3]);
-                    printf("\n");
-                    printf(present[4]);
-                    printf("\n");
-                    printf(present[5]);
-                    printf("\n");
-                    printf(present[6]);
-                    printf("\n");
-                    printf(present[7]);
-                    printf("\n------------------------------  ------------------------------  ------------------------------\n");
-                    //printf("-3->0-");
-                    i = 0;
-                }
-            }
-        }
-        else{
-            //Print for GUI
-            char tmp[1000000];
-            strcpy(tmp, "");
-            char json[1000000] = "{\"results\":[";
-            for (int i = 0; i < result_count; i++){
-                //printf("RES LOOP %d\n", i);
-                sprintf(tmp, "{\"rating\":%d,", results[i].rating);
-                strcat(json, tmp);
-                sprintf(tmp, "\"hp\":%d,", results[i].stats.hp);
-                strcat(json, tmp);
-                sprintf(tmp, "\"atk\":%d,", results[i].stats.atk);
-                strcat(json, tmp);
-                sprintf(tmp, "\"dfc\":%d,", results[i].stats.def);
-                strcat(json, tmp);
-                sprintf(tmp, "\"spd\":%d,", results[i].stats.spd);
-                strcat(json, tmp);
-                sprintf(tmp, "\"crr\":%d,", results[i].stats.crr);
-                strcat(json, tmp);
-
-                sprintf(tmp, "\"crd\":%d,", results[i].stats.crd);
-                strcat(json, tmp);
-                sprintf(tmp, "\"res\":%d,", results[i].stats.res);
-                strcat(json, tmp);
-                sprintf(tmp, "\"acc\":%d,", results[i].stats.acc);
-                strcat(json, tmp);
-                sprintf(tmp, "\"ehp\":%d,", results[i].stats.ehp);
-                strcat(json, tmp);
-                sprintf(tmp, "\"dmg\":%d,", results[i].stats.dmg);
-                strcat(json, tmp);
-                strcat(json, "\"runes\":[");
-                //printf("    A %s\n", json);
-                for (int j = 0; j < 5; j++){
-                    //printf("    RUNE LOOP %d\n", j);
-                    sprintf(tmp, "\"%s\",", results[i].rune_ids[j]);
-                    strcat(json, tmp);
-                }
-                //printf("    B %s\n", json);
-                sprintf(tmp, "\"%s\"", results[i].rune_ids[5]);
-                strcat(json, tmp);
-                strcat(json, "]},");
-            }
-            // Remove last comma
-            json[strlen(json) - 1] = '\0';
-
-            // End and print
-            strcat(json, "]}\0");
-            printf("%s", json);
-        }
-    }
-    else{
-        if (gui != 1){
-            printf("No results found\n");
-        }
-        else{
-            fprintf(stderr, "No results found\n");
-        }
-    }
-
-    sqlite3_close(db);
-    return SUCCESS;
-}
-
-void sort_results(Result results[1000], int total){
-    // Bubble sort, by descending rating.
-    int i, j;
-    Result temp;
-
-    for (i = 0; i < total - 1; i++)
-    {
-        for (j = 0; j < (total - 1-i); j++)
-        {
-            if (results[j].rating < results[j + 1].rating)
-            {
-                temp = results[j];
-                results[j] = results[j + 1];
-                results[j + 1] = temp;
-            }
-        }
-    }
-}
-
-unsigned int calculate_ehp(unsigned int hp, unsigned short def){
-    unsigned int ehp = 0;
-    ehp = ceil((((((float) def) * 3.5f) + 1140.0f) * ((float) hp)) / 1000.0f);
-    return ehp;
-}
-
-unsigned short calculate_dmg(unsigned short atk, unsigned short crr, unsigned short crd){
-    unsigned short dmg = 0;
-    float crr_capped = (float) crr;
-    if (crr_capped > 100.0f){
-        crr_capped = 100.0f;
-    }
-    dmg = ceil((((float) atk) * (100.0f - crr_capped) / 100.0f) + ((((float) atk) + (((float) atk) * ((float) crd) / 100.0f)) * crr_capped / 100.0f));
-    return dmg;
-}
-
-int list_teams(int argc, char *argv[]){
-    printf("A");
-    char id[8] = "\0";
-    char query[300] = "SELECT id, name, priority FROM teams ";
-    char tmp[64];
-    if (argc > 3){
-        strcpy(id, argv[3]);
-        strcpy(tmp, "");
-        sprintf(tmp, "WHERE id = '%s' OR name = '%s' ", id, id);
-        strcat(query, tmp);
-    }
-    printf("LISTING TEAMS %s\n%s\n", id, query);
-    if (SUCCESS != open_database()){
-        return ERROR_DB_CANT_OPEN;
-    }
-    sqlite3_stmt *stmt_teams;
-    db_status = sqlite3_prepare_v2(db, query, -1, &stmt_teams, 0);
-    if (db_status != SQLITE_OK) {
-        fprintf(stderr, "Error getting runes for slot 1: %s\n", sqlite3_errmsg(db));
-        return ERROR_DB_RUNES_SLOT;
-    }
-
-    while (1 == 1){
-        int status = sqlite3_step(stmt_teams);
-        if (status == SQLITE_ROW){
-            printf("OK %s\n", sqlite3_column_text(stmt_teams, 2));
-
-        }
-        else{
-            break;
-        }
-    }
-}

+ 1 - 217
src/RuneOptimizer/RuneOptimizer.h

@@ -15,160 +15,6 @@
  * RuneOptimizer. If not, see <https://www.gnu.org/licenses/>.
  */
 
-// Rune set definitions
-#define ENERGY 1
-#define GUARD 2
-#define SWIFT 3
-#define BLADE 4
-#define RAGE 5
-#define FOCUS 6
-#define ENDURE 7
-#define FATAL 8
-#define DESPAIR 10
-#define VAMPIRE 11
-#define VIOLENT 13
-#define NEMESIS 14
-#define WILL 15
-#define SHIELD 16
-#define REVENGE 17
-#define DESTROY 18
-#define FIGHT 19
-#define DETERMINATION 20
-#define ENHANCE 21
-#define ACCURACY 22
-#define TOLERANCE 23
-
-// Rune stat definitions
-#define HP_FLAT 1
-#define HP_PERCENT 2
-#define ATK_FLAT 3
-#define ATK_PERCENT 4
-#define DEF_FLAT 5
-#define DEF_PERCENT 6
-#define SPD 8
-#define CRR 9
-#define CRD 10
-#define RES 11
-#define ACC 12
-
-struct Unit {
-    unsigned char id[12];
-    unsigned char name[50];
-    unsigned int base_hp;
-    unsigned short base_atk;
-    unsigned short base_def;
-    unsigned short base_spd;
-    unsigned short base_crr;
-    unsigned short base_crd;
-    unsigned short base_acc;
-    unsigned short base_res;
-    unsigned int base_ehp;
-    unsigned short base_dmg;
-    unsigned int current_hp;
-    unsigned short current_atk;
-    unsigned short current_def;
-    unsigned short current_spd;
-    unsigned short current_crr;
-    unsigned short current_crd;
-    unsigned short current_acc;
-    unsigned short current_res;
-    unsigned int current_ehp;
-    unsigned short current_dmg;
-};
-
-struct Rune {
-    unsigned char id[12];
-    unsigned char slot;
-    unsigned char set;
-    unsigned char unit[12];
-    unsigned char hp_percent;
-    unsigned char atk_percent;
-    unsigned char def_percent;
-    unsigned short hp_flat;
-    unsigned char atk_flat;
-    unsigned char def_flat;
-    unsigned char spd;
-    unsigned char crr;
-    unsigned char crd;
-    unsigned char acc;
-    unsigned char res;
-};
-
-struct Stats {
-    unsigned int hp;
-    unsigned short atk;
-    unsigned short def;
-    unsigned short spd;
-    unsigned short crr;
-    unsigned short crd;
-    unsigned short acc;
-    unsigned short res;
-    unsigned int ehp;
-    unsigned short dmg;
-};
-
-struct Rune_Set_Count {
-    unsigned short energy;
-    unsigned short guard;
-    unsigned short swift;
-    unsigned short blade;
-    unsigned short rage;
-    unsigned short focus;
-    unsigned short endure;
-    unsigned short fatal;
-    unsigned short despair;
-    unsigned short vampire;
-    unsigned short violent;
-    unsigned short nemesis;
-    unsigned short will;
-    unsigned short shield;
-    unsigned short revenge;
-    unsigned short destroy;
-    unsigned short fight;
-    unsigned short determination;
-    unsigned short enhance;
-    unsigned short accuracy;
-    unsigned short tolerance;
-};
-typedef struct Result {
-    unsigned char rune_ids[6][12];
-    signed int rating;
-    struct Stats stats;
-} Result;
-
-char stat_names[][13] = {
-  "NULL", "HP_FLAT", "HP",  "ATK_FLAT", "ATK", "DEF_FLAT",
-  "DEF",  "NULL",    "SPD", "CRR",      "CRD", "RES",      "ACC"
-};
-char set_names[][24] = {
-  "NULL",    "ENERGY",        "GUARD",   "SWIFT",    "BLADE",   "RAGE",
-  "FOCUS",   "ENDURE",        "FATAL",   "NULL",     "DESPAIR", "VAMPIRE",
-  "VIOLENT", "NEMESIS",       "WILL",    "SHIELD",   "REVENGE", "DESTROY",
-  "FIGHT",   "DETERMINATION", "ENHANCE", "ACCURACY", "TOLERANCE"
-};
-/**
- * Displays the help text.
- */
-void show_help();
-
-/**
- * Displays teams.
- *
- * @param argc Argument count.
- * @param argv Argument list.
- * @return 0 on success, other on error.
- */
-int list_teams(int argc, char *argv[]);
-
-/**
- * Starts the optimization process.
- *
- * @param argc Argument count.
- * @param argv Argument list.
- * @return 0 on success, other on error.
- */
-int optimize(int argc, char *argv[]);
-
 /**
  * Connects to the database.
  *
@@ -176,36 +22,7 @@ int optimize(int argc, char *argv[]);
  *
  * @return 0 on success, other on error.
  */
-int open_databse();
-
-/**
- * Sorts the result array.
- *
- * Sorts them by rating.
- *
- * @param results List of results to sort.
- * @param total Number of results.
- */
-void sort_results(Result results[1000], int total);
-
-/**
- * Calculates efficient HP.
- *
- * @param hp HP stat.
- * @param def DEF stat.
- * @return Calculated EHP.
- */
-unsigned int calculate_ehp(unsigned int hp, unsigned short def);
-
-/**
- * Calculates damage.
- *
- * @param atk ATK stat.
- * @param crr CRR stat.
- * @param crd CRD stat.
- * @return Calculated DMG.
- */
-unsigned short calculate_dmg(unsigned short atk, unsigned short crr, unsigned short crd);
+int open_database();
 
 /**
  * @var Stores the database error statuses.
@@ -216,36 +33,3 @@ int db_status;
  * @var Database connection.
  */
 sqlite3 *db;
-
-// Error code definitions
-#define SUCCESS 0
-#define UNIMPLEMENTED 1
-#define ERROR_INPUT_NO_COMMAND 100
-#define ERROR_INPUT_INVALID_COMMAND 101
-#define ERROR_INPUT_NO_UNIT 102
-#define ERROR_INPUT_INVALID_LEVEL 103
-#define ERROR_INPUT_NO_LEVEL 104
-#define ERROR_INPUT_INVALID_SET 105
-#define ERROR_INPUT_NO_SET 106
-#define ERROR_INPUT_INVALID_STAT 107
-#define ERROR_INPUT_NO_STAT 108
-#define ERROR_INPUT_NO_HP 109
-#define ERROR_INPUT_NO_ATK 110
-#define ERROR_INPUT_NO_DEF 111
-#define ERROR_INPUT_NO_SPD 112
-#define ERROR_INPUT_NO_CRR 113
-#define ERROR_INPUT_NO_CRD 114
-#define ERROR_INPUT_NO_RES 115
-#define ERROR_INPUT_NO_ACC 116
-#define ERROR_INPUT_NO_EHP 117
-#define ERROR_INPUT_NO_DMG 118
-#define ERROR_INPUT_INCOMPLETE_SETS 119
-#define ERROR_INPUT_TEAM_NO_ACTION 120
-#define ERROR_INPUT_TEAM_INVALID_ACTION 121
-
-#define ERROR_DB_CANT_OPEN 200
-#define ERROR_DB_UNIT 201
-#define ERROR_DB_UNIT_NOF_FOUND 202
-#define ERROR_DB_RUNES_SLOT 203
-#define ERROR_DB_RUNES_RESULT 204
-#define ERROR_DB_STATS_RESULT 205

+ 47 - 0
src/RuneOptimizer/error/error.h

@@ -0,0 +1,47 @@
+/*
+ * This file is part of RuneOptimizer.
+ *
+ * RuneOptimizer 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, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * RuneOptimizer 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
+ * RuneOptimizer. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#define SUCCESS 0
+#define UNIMPLEMENTED 1
+#define ERROR_INPUT_NO_COMMAND 100
+#define ERROR_INPUT_INVALID_COMMAND 101
+#define ERROR_INPUT_NO_UNIT 102
+#define ERROR_INPUT_INVALID_LEVEL 103
+#define ERROR_INPUT_NO_LEVEL 104
+#define ERROR_INPUT_INVALID_SET 105
+#define ERROR_INPUT_NO_SET 106
+#define ERROR_INPUT_INVALID_STAT 107
+#define ERROR_INPUT_NO_STAT 108
+#define ERROR_INPUT_NO_HP 109
+#define ERROR_INPUT_NO_ATK 110
+#define ERROR_INPUT_NO_DEF 111
+#define ERROR_INPUT_NO_SPD 112
+#define ERROR_INPUT_NO_CRR 113
+#define ERROR_INPUT_NO_CRD 114
+#define ERROR_INPUT_NO_RES 115
+#define ERROR_INPUT_NO_ACC 116
+#define ERROR_INPUT_NO_EHP 117
+#define ERROR_INPUT_NO_DMG 118
+#define ERROR_INPUT_INCOMPLETE_SETS 119
+#define ERROR_INPUT_TEAM_NO_ACTION 120
+#define ERROR_INPUT_TEAM_INVALID_ACTION 121
+#define ERROR_DB_CANT_OPEN 200
+#define ERROR_DB_UNIT 201
+#define ERROR_DB_UNIT_NOF_FOUND 202
+#define ERROR_DB_RUNES_SLOT 203
+#define ERROR_DB_RUNES_RESULT 204
+#define ERROR_DB_STATS_RESULT 205

+ 59 - 0
src/RuneOptimizer/help/help.c

@@ -0,0 +1,59 @@
+void show_help(){
+    printf("\nRune Optimizer v0.1\n");
+    printf("\n  Usage:\n");
+    printf("  RuneOptimizer [command] [options]\n");
+    printf("\n\n  Command: helps\n");
+    printf("\n    Display this help text and exists. It has no options.\n");
+    printf("\n\n  Command: update\n");
+    printf("\n    Updates the information and builds a database. Currently unimplemented.\n");
+    printf("\n\n  Command: team\n");
+    printf("\n    Manages teams.\n");
+    printf("\n    Usage\n");
+    printf("    RuneOptimizer team [action] [options]\n");
+    printf("\n      Actions: \n\n");
+    printf("        list [team] [options]        List list of all or the selected team.\n");
+    printf("\n        [team] is optional and can can be a team ID or a team name (case sensitive)\n");
+    printf("\n        Options: \n\n");
+    printf("          -u | --units        Include the units in the details.\n");
+    printf("\n\n  Command: optimize\n");
+    printf("\n    Calculates an optimization for a unit.\n");
+    printf("\n    Usage\n");
+    printf("    RuneOptimizer optimize [unit] [options]\n");
+    printf("\n      [unit] can be a unit ID or a unit name (case sensitive)\n");
+    printf("\n      Options: \n\n");
+    printf("        -h | --min_hp <NUM>         Minumum HP to consider in the optimization.\n");
+    printf("                                    It defaults to the unit's current value.\n");
+    printf("        -a | --min_atk <NUM>        Minumum ATK to consider in the optimization.\n");
+    printf("                                    It defaults to the unit's current value.\n");
+    printf("        -d | --min_def <NUM>        Minumum DEF to consider in the optimization.\n");
+    printf("                                    It defaults to the unit's current value.\n");
+    printf("        -s | --min_spd <NUM>        Minumum SPD to consider in the optimization.\n");
+    printf("                                    It defaults to the unit's current value.\n");
+    printf("        -c | --min_crr <NUM>        Minumum CRIT RATE to consider in the optimization.\n");
+    printf("                                    It defaults to the unit's current value.\n");
+    printf("        -d | --min_crd <NUM>        Minumum CRIT DAMAGE to consider in the optimization.\n");
+    printf("                                    It defaults to the unit's current value.\n");
+    printf("        -r | --min_res <NUM>        Minumum RES to consider in the optimization.\n");
+    printf("                                    It defaults to the unit's current value.\n");
+    printf("        -f | --min_acc <NUM>        Minumum ACC to consider in the optimization.\n");
+    printf("                                    It defaults to the unit's current value.\n");
+    printf("        -p | --min_ehp <NUM>        Minumum effective HP to consider in the optimization.\n");
+    printf("                                    It defaults to 0.\n");
+    printf("        -m | --min_dmg <NUM>        Minumum computed damage to consider in the optimization.\n");
+    printf("                                    It defaults to 0.\n");
+    printf("        -l | --level <LV>           Level to consider the runes during the optimization.\n");
+    printf("                                    It only affects the rune main stats. Valid values are\n");
+    printf("                                    'current', '12' and '15'. Default is 'current'\n");
+    printf("        -t | --stats <ST1>,<ST2>... Stats than can be selected as mains for slots 2, 4 and 6.\n");
+    printf("                                    Only the selected stats will be included, so this option\n");
+    printf("                                    is mandatory. Accepted values are 'hp', 'atk', 'def',\n");
+    printf("                                    'hpflat', 'atkflat', 'defflat', 'spd', 'crr', 'crd',\n");
+    printf("                                    'res' and 'acc'. Values must be comma-separated, and up\n");
+    printf("                                    to 12 can be included.\n");
+    printf("        -e | --sets <S1>,<S2>...    Rune sets that than can be considered during the optimization.\n");
+    printf("                                    Only the selected sets will be included, so this option is\n");
+    printf("                                    mandatory. Accepted values  the rune net names, lowercase.\n");
+    printf("                                    Values must be comma-separated, and up to 3 can be included.\n");
+    printf("        -g | --gui                  Formats the output to be consumed by the GUI.\n");
+    return;
+}

+ 21 - 0
src/RuneOptimizer/help/help.h

@@ -0,0 +1,21 @@
+/*
+ * This file is part of RuneOptimizer.
+ *
+ * RuneOptimizer 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, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * RuneOptimizer 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
+ * RuneOptimizer. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Displays the help text.
+ */
+void show_help();

+ 1483 - 0
src/RuneOptimizer/optimize/optimize.c

@@ -0,0 +1,1483 @@
+/*
+ * This file is part of RuneOptimizer.
+ *
+ * RuneOptimizer 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, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * RuneOptimizer 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
+ * RuneOptimizer. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+int optimize(int argc, char *argv[]){
+
+    // Get unit identifier
+    if (argc < 3){
+        fprintf(stderr, "No unit specified for optimization\n");
+        return ERROR_INPUT_NO_UNIT;
+    }
+
+    // Initialize values for the data to be read form arguments.
+    char requested_level = 0;
+    char gui = 0;
+    char requested_level_column[9] = "current_";
+    int sets[3] = {0, 0, 0};
+    int requested_stats[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    struct Stats min_stats;
+    // Unsigned, so I cant use -1 as placeholder to check if its modified from
+    // arguments. I'm using just 1 (I don't think anybody will input values of
+    // 1), and later, if they are still 1, they are changed to 0.
+    min_stats.hp = 1;
+    min_stats.atk = 1;
+    min_stats.def = 1;
+    min_stats.spd = 1;
+    min_stats.crr = 1;
+    min_stats.crd = 1;
+    min_stats.res = 1;
+    min_stats.acc = 1;
+    min_stats.ehp = 1;
+    min_stats.dmg = 1;
+
+
+    // Loop command line arguments
+    for (int i = 3; i < argc; i ++){
+
+        // Rune level arguments
+        if (strcmp("--level", argv[i]) == 0 || strcmp("-l", argv[i]) == 0){
+            if (i < argc - 1){
+                if (strcmp("current", argv[i + 1]) == 0){
+                    requested_level = 0;
+                    strcpy(requested_level_column, "current_");
+                }
+                else if (strcmp("12", argv[i + 1]) == 0){
+                    requested_level = 12;
+                    strcpy(requested_level_column, "lv12_");
+                }
+                else if (strcmp("15", argv[i + 1]) == 0){
+                    requested_level = 15;
+                    strcpy(requested_level_column, "lv15_");
+                }
+                else{
+                    fprintf(
+                      stderr,
+                      "Invalid option for argument %s: %s\n"
+                      "Valid options are 'current', '12' or '15'\n",
+                      argv[i], argv[i + 1]
+                    );
+                    return ERROR_INPUT_INVALID_LEVEL;
+                }
+                // Advance one position in argument reading
+                i ++;
+            }
+            else{
+                fprintf(
+                  stderr,
+                  "Argument %s requires a value.\n"
+                  "Valid options are 'current', '12' or '15'\n", argv[i]
+                );
+                return ERROR_INPUT_NO_LEVEL;
+            }
+        }
+
+        // Rune sets argument
+        else if (strcmp("--sets", argv[i]) == 0 || strcmp("-e", argv[i]) == 0){
+            if (i < argc - 1){
+                // Separate string by commas
+                int j = 0;
+                // Returns first token
+                char *token = strtok(argv[i + 1], ",");
+
+                // Keep printing tokens while one of the
+                // delimiters present in the list, or until its complete
+                while (token != NULL && j < 3){
+                    if (strcmp(token, "energy") == 0){
+                        sets[j] = ENERGY;
+                    }
+                    else if (strcmp(token, "guard") == 0){
+                        sets[j] = GUARD;
+                    }
+                    else if (strcmp(token, "swift") == 0){
+                        sets[j] = SWIFT;
+                    }
+                    else if (strcmp(token, "blade") == 0){
+                        sets[j] = BLADE;
+                    }
+                    else if (strcmp(token, "rage") == 0){
+                        sets[j] = RAGE;
+                    }
+                    else if (strcmp(token, "focus") == 0){
+                        sets[j] = FOCUS;
+                    }
+                    else if (strcmp(token, "endure") == 0){
+                        sets[j] = ENDURE;
+                    }
+                    else if (strcmp(token, "fatal") == 0){
+                        sets[j] = FATAL;
+                    }
+                    else if (strcmp(token, "despair") == 0){
+                        sets[j] = DESPAIR;
+                    }
+                    else if (strcmp(token, "vampire") == 0){
+                        sets[j] = VAMPIRE;
+                    }
+                    else if (strcmp(token, "violent") == 0){
+                        sets[j] = VIOLENT;
+                    }
+                    else if (strcmp(token, "nemesis") == 0){
+                        sets[j] = NEMESIS;
+                    }
+                    else if (strcmp(token, "will") == 0){
+                        sets[j] = WILL;
+                    }
+                    else if (strcmp(token, "shield") == 0){
+                        sets[j] = SHIELD;
+                    }
+                    else if (strcmp(token, "revenge") == 0){
+                        sets[j] = REVENGE;
+                    }
+                    else if (strcmp(token, "destroy") == 0){
+                        sets[j] = DESTROY;
+                    }
+                    else if (strcmp(token, "fight") == 0){
+                        sets[j] = FIGHT;
+                    }
+                    else if (strcmp(token, "determination") == 0){
+                        sets[j] = DETERMINATION;
+                    }
+                    else if (strcmp(token, "enhance") == 0){
+                        sets[j] = ENHANCE;
+                    }
+                    else if (strcmp(token, "accuracy") == 0){
+                        sets[j] = ACCURACY;
+                    }
+                    else if (strcmp(token, "tolerance") == 0){
+                        sets[j] = TOLERANCE;
+                    }
+                    else{
+                        fprintf(stderr, "Unknown rune set %s.\n", token);
+                        return ERROR_INPUT_INVALID_SET;
+                    }
+                    token = strtok(NULL, ",");
+                    j ++;
+                }
+                // Advance one position in argument reading
+                i ++;
+            }
+            else{
+                fprintf(
+                  stderr,
+                  "Argument %s requires a list of values separated by commas.\n",
+                  argv[i]
+                );
+                return ERROR_INPUT_NO_SET;
+            }
+        }
+
+        // Accepted stats arguments
+        else if (strcmp("--stats", argv[i]) == 0 || strcmp("-t", argv[i]) == 0){
+            if (i < argc - 1){
+                // Separate string by commas
+                int j = 0;
+                // Returns first token
+                char *token = strtok(argv[i + 1], ",");
+
+                // Keep printing tokens while one of the
+                // delimiters present in the list, or until its complete
+                while (token != NULL && j < 3){
+                    if (strcmp(token, "hp") == 0){
+                        requested_stats[j] = HP_PERCENT;
+                    }
+                    else if (strcmp(token, "atk") == 0){
+                        requested_stats[j] = ATK_PERCENT;
+                    }
+                    else if (strcmp(token, "def") == 0){
+                        requested_stats[j] = DEF_PERCENT;
+                    }
+                    else if (strcmp(token, "hpflat") == 0){
+                        requested_stats[j] = HP_FLAT;
+                    }
+                    else if (strcmp(token, "atkflat") == 0){
+                        requested_stats[j] = ATK_FLAT;
+                    }
+                    else if (strcmp(token, "defflat") == 0){
+                        requested_stats[j] = DEF_FLAT;
+                    }
+                    else if (strcmp(token, "spd") == 0){
+                        requested_stats[j] = SPD;
+                    }
+                    else if (strcmp(token, "crr") == 0){
+                        requested_stats[j] = CRR;
+                    }
+                    else if (strcmp(token, "crd") == 0){
+                        requested_stats[j] = CRD;
+                    }
+                    else if (strcmp(token, "res") == 0){
+                        requested_stats[j] = RES;
+                    }
+                    else if (strcmp(token, "acc") == 0){
+                        requested_stats[j] = ACC;
+                    }
+                    else{
+                        fprintf(stderr, "Unknown rune stat %s.\n", token);
+                        return ERROR_INPUT_INVALID_STAT;
+                    }
+                    token = strtok(NULL, ",");
+                    j ++;
+                }
+                // Advance one position in argument reading
+                i ++;
+            }
+            else{
+                fprintf(stderr, "Argument %s requires a list of values separated by commas.\n", argv[i]);
+                return ERROR_INPUT_NO_STAT;
+            }
+        }
+
+        // Minimum stats arguments.
+        else if (strcmp("--min-hp", argv[i]) == 0 || strcmp("-h", argv[i]) == 0){
+            if (i < argc - 1){
+                unsigned short stat_tmp = atoi(argv[i + 1]);
+                if (stat_tmp == 1){
+                    stat_tmp = 0;
+                }
+                min_stats.hp = stat_tmp;
+                // Advance one position in argument reading
+                i ++;
+            }
+            else{
+                fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
+                return ERROR_INPUT_NO_HP;
+            }
+        }
+        else if (strcmp("--min-atk", argv[i]) == 0 || strcmp("-a", argv[i]) == 0){
+            if (i < argc - 1){
+                unsigned short stat_tmp = atoi(argv[i + 1]);
+                if (stat_tmp == 1){
+                    stat_tmp = 0;
+                }
+                min_stats.atk = stat_tmp;
+                // Advance one position in argument reading
+                i ++;
+            }
+            else{
+                fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
+                return ERROR_INPUT_NO_ATK;
+            }
+        }
+        else if (strcmp("--min-def", argv[i]) == 0 || strcmp("-d", argv[i]) == 0){
+            if (i < argc - 1){
+                unsigned short stat_tmp = atoi(argv[i + 1]);
+                if (stat_tmp == 1){
+                    stat_tmp = 0;
+                }
+                min_stats.def = stat_tmp;
+                // Advance one position in argument reading
+                i ++;
+            }
+            else{
+                fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
+                return ERROR_INPUT_NO_DEF;
+            }
+        }
+        else if (strcmp("--min-spd", argv[i]) == 0 || strcmp("-s", argv[i]) == 0){
+            if (i < argc - 1){
+                unsigned short stat_tmp = atoi(argv[i + 1]);
+                if (stat_tmp == 1){
+                    stat_tmp = 0;
+                }
+                min_stats.spd = stat_tmp;
+                // Advance one position in argument reading
+                i ++;
+            }
+            else{
+                fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
+                return ERROR_INPUT_NO_SPD;
+            }
+        }
+        else if (strcmp("--min-crr", argv[i]) == 0 || strcmp("-c", argv[i]) == 0){
+            if (i < argc - 1){
+                unsigned short stat_tmp = atoi(argv[i + 1]);
+                if (stat_tmp == 1){
+                    stat_tmp = 0;
+                }
+                else if (stat_tmp > 100){
+                    stat_tmp = 100;
+                }
+                min_stats.crr = stat_tmp;
+                // Advance one position in argument reading
+                i ++;
+            }
+            else{
+                fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
+                return ERROR_INPUT_NO_CRR;
+            }
+        }
+        else if (strcmp("--min-crd", argv[i]) == 0 || strcmp("-d", argv[i]) == 0){
+            if (i < argc - 1){
+                unsigned short stat_tmp = atoi(argv[i + 1]);
+                if (stat_tmp == 1){
+                    stat_tmp = 0;
+                }
+                min_stats.crd = stat_tmp;
+                // Advance one position in argument reading
+                i ++;
+            }
+            else{
+                fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
+                return ERROR_INPUT_NO_CRD;
+            }
+        }
+        else if (strcmp("--min-res", argv[i]) == 0 || strcmp("-r", argv[i]) == 0){
+            if (i < argc - 1){
+                unsigned short stat_tmp = atoi(argv[i + 1]);
+                if (stat_tmp == 1){
+                    stat_tmp = 0;
+                }
+                else if (stat_tmp > 100){
+                    stat_tmp = 100;
+                }
+                min_stats.atk = stat_tmp;
+                // Advance one position in argument reading
+                i ++;
+            }
+            else{
+                fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
+                return ERROR_INPUT_NO_RES;
+            }
+        }
+        else if (strcmp("--min-acc", argv[i]) == 0 || strcmp("-f", argv[i]) == 0){
+            if (i < argc - 1){
+                unsigned short stat_tmp = atoi(argv[i + 1]);
+                if (stat_tmp == 1){
+                    stat_tmp = 0;
+                }
+                else if (stat_tmp > 85){
+                    stat_tmp = 85;
+                }
+                min_stats.acc = stat_tmp;
+                // Advance one position in argument reading
+                i ++;
+            }
+            else{
+                fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
+                return ERROR_INPUT_NO_ACC;
+            }
+        }
+        else if (strcmp("--min-ehp", argv[i]) == 0 || strcmp("-p", argv[i]) == 0){
+            if (i < argc - 1){
+                unsigned int stat_tmp = atoi(argv[i + 1]);
+                if (stat_tmp == 1){
+                    stat_tmp = 0;
+                }
+                min_stats.ehp = stat_tmp;
+                // Advance one position in argument reading
+                i ++;
+            }
+            else{
+                fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
+                return ERROR_INPUT_NO_EHP;
+            }
+        }
+        else if (strcmp("--min-dmg", argv[i]) == 0 || strcmp("-m", argv[i]) == 0){
+            if (i < argc - 1){
+                unsigned short stat_tmp = atoi(argv[i + 1]);
+                if (stat_tmp == 1){
+                    stat_tmp = 0;
+                }
+                min_stats.dmg = stat_tmp;
+                // Advance one position in argument reading
+                i ++;
+            }
+            else{
+                fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
+                return ERROR_INPUT_NO_DMG;
+            }
+        }
+
+        // GUI invoked?
+        else if (strcmp("--gui", argv[i]) == 0 || strcmp("-g", argv[i]) == 0){
+            gui = 1;
+        }
+
+    }
+
+    // Now we can open the database.
+    if (SUCCESS != open_database()){
+        return ERROR_DB_CANT_OPEN;
+    }
+
+    // Read the unit from the databse
+    sqlite3_stmt *res;
+    char *sql = "SELECT "
+      "id, name, base_hp, base_atk, base_def, base_spd, base_crr, base_crd, "
+      "base_res, base_acc, current_hp, current_atk, current_def, current_spd, "
+      "current_crr, current_crd, current_res, current_acc "
+      "FROM units WHERE id = ? OR name = ?";
+    db_status = sqlite3_prepare_v2(db, sql, -1, &res, 0);
+    if (db_status != SQLITE_OK) {
+        fprintf(
+          stderr,
+          "Failed to execute statement to select unit: %s\n",
+          sqlite3_errmsg(db)
+        );
+        return ERROR_DB_UNIT;
+    }
+    sqlite3_bind_text(res, 1, argv[2], strlen(argv[2]), NULL);
+    sqlite3_bind_text(res, 2, argv[2], strlen(argv[2]), NULL);
+
+    // Fetch just one line
+    int step = sqlite3_step(res);
+
+    // Create a unit structure with the red data.
+    struct Unit unit;
+    if (step != SQLITE_ROW) {
+        fprintf(stderr, "Unit not found: %s\n", sqlite3_errmsg(db));
+        return ERROR_DB_UNIT_NOF_FOUND;
+    }
+    strcpy(unit.id, sqlite3_column_text(res, 0));
+    strcpy(unit.name, sqlite3_column_text(res, 1));
+    unit.base_hp = sqlite3_column_int(res, 2);
+    unit.base_atk = sqlite3_column_int(res, 3);
+    unit.base_def = sqlite3_column_int(res, 4);
+    unit.base_spd = sqlite3_column_int(res, 5);
+    unit.base_crr = sqlite3_column_int(res, 6);
+    unit.base_crd = sqlite3_column_int(res, 7);
+    unit.base_res = sqlite3_column_int(res, 8);
+    unit.base_acc = sqlite3_column_int(res, 9);
+    unit.base_ehp = calculate_ehp(unit.base_hp, unit.base_def);
+    unit.base_dmg = calculate_dmg(unit.base_atk, unit.base_crr, unit.base_crd);
+    unit.current_hp = sqlite3_column_int(res, 10);
+    unit.current_atk = sqlite3_column_int(res, 11);
+    unit.current_def = sqlite3_column_int(res, 12);
+    unit.current_spd = sqlite3_column_int(res, 13);
+    unit.current_crr = sqlite3_column_int(res, 14);
+    unit.current_crd = sqlite3_column_int(res, 15);
+    unit.current_res = sqlite3_column_int(res, 16);
+    unit.current_acc = sqlite3_column_int(res, 17);
+    unit.current_ehp = calculate_ehp(unit.current_hp, unit.current_def);
+    unit.current_dmg = calculate_dmg(unit.current_atk, unit.current_crr, unit.current_crd);
+
+    // Min stats that have the value 1 get overriden by the current stats.
+    if (min_stats.hp == 1){
+        min_stats.hp = unit.current_hp;
+    }
+    if (min_stats.atk == 1){
+        min_stats.atk = unit.current_atk;
+    }
+    if (min_stats.def == 1){
+        min_stats.def = unit.current_def;
+    }
+    if (min_stats.spd == 1){
+        min_stats.spd = unit.current_spd;
+    }
+    if (min_stats.crr == 1){
+        min_stats.crr = unit.current_crr;
+    }
+    if (min_stats.crd == 1){
+        min_stats.crd = unit.current_crd;
+    }
+    if (min_stats.res == 1){
+        min_stats.res = unit.current_res;
+    }
+    if (min_stats.acc == 1){
+        min_stats.acc = unit.current_acc;
+    }
+    if (min_stats.ehp == 1){
+        min_stats.ehp = 0;
+    }
+    if (min_stats.dmg == 1){
+        min_stats.dmg = 0;
+    }
+
+    // Calculate required rune count
+    struct Rune_Set_Count requested_set_count;
+    requested_set_count.energy = 0;
+    requested_set_count.guard = 0;
+    requested_set_count.swift = 0;
+    requested_set_count.blade = 0;
+    requested_set_count.rage = 0;
+    requested_set_count.focus = 0;
+    requested_set_count.endure = 0;
+    requested_set_count.fatal = 0;
+    requested_set_count.despair = 0;
+    requested_set_count.vampire = 0;
+    requested_set_count.violent = 0;
+    requested_set_count.nemesis = 0;
+    requested_set_count.will = 0;
+    requested_set_count.shield = 0;
+    requested_set_count.revenge = 0;
+    requested_set_count.destroy = 0;
+    requested_set_count.fight = 0;
+    requested_set_count.determination = 0;
+    requested_set_count.enhance = 0;
+    requested_set_count.accuracy = 0;
+    requested_set_count.tolerance = 0;
+    char total_requested_runes = 0;
+    for (int i = 0; i < 3; i ++){
+        switch (sets[i]){
+            case ENERGY:
+                requested_set_count.energy += 2;
+                total_requested_runes += 2;
+                break;
+            case GUARD:
+                requested_set_count.guard += 2;
+                total_requested_runes += 2;
+                break;
+            case SWIFT:
+                requested_set_count.swift += 4;
+                total_requested_runes += 4;
+                break;
+            case BLADE:
+                requested_set_count.blade += 2;
+                total_requested_runes += 2;
+                break;
+            case RAGE:
+                requested_set_count.rage += 4;
+                total_requested_runes += 4;
+                break;
+            case FOCUS:
+                requested_set_count.focus += 2;
+                total_requested_runes += 2;
+                break;
+            case ENDURE:
+                requested_set_count.endure += 2;
+                total_requested_runes += 2;
+                break;
+            case FATAL:
+                requested_set_count.fatal += 4;
+                total_requested_runes += 4;
+                break;
+            case DESPAIR:
+                requested_set_count.despair += 2;
+                total_requested_runes += 2;
+                break;
+            case VAMPIRE:
+                requested_set_count.vampire += 4;
+                total_requested_runes += 4;
+                break;
+            case VIOLENT:
+                requested_set_count.violent += 4;
+                total_requested_runes += 4;
+                break;
+            case NEMESIS:
+                requested_set_count.nemesis += 2;
+                total_requested_runes += 2;
+                break;
+            case WILL:
+                requested_set_count.will += 2;
+                total_requested_runes += 2;
+                break;
+            case SHIELD:
+                requested_set_count.shield += 2;
+                total_requested_runes += 2;
+                break;
+            case REVENGE:
+                requested_set_count.revenge += 2;
+                total_requested_runes += 2;
+                break;
+            case DESTROY:
+                requested_set_count.destroy += 2;
+                total_requested_runes += 2;
+                break;
+            case FIGHT:
+                requested_set_count.fight += 2;
+                total_requested_runes += 2;
+                break;
+            case DETERMINATION:
+                requested_set_count.determination += 2;
+                total_requested_runes += 2;
+                break;
+            case ENHANCE:
+                requested_set_count.enhance += 2;
+                total_requested_runes += 2;
+                break;
+            case ACCURACY:
+                requested_set_count.accuracy += 2;
+                total_requested_runes += 2;
+                break;
+            case TOLERANCE:
+                requested_set_count.tolerance += 2;
+                total_requested_runes += 2;
+                break;
+        }
+    }
+    if (gui != 1){
+        printf("\n");
+    }
+    if (total_requested_runes != 6){
+        fprintf(stderr, "Invalid rune set combination.\n");
+        return ERROR_INPUT_INCOMPLETE_SETS;
+    }
+
+    // Create queries for each slot
+    char query_odd[2500] = "SELECT id, unit, type, ";
+    strcat(query_odd, requested_level_column);
+    strcat(query_odd, "hp_flat, ");
+    strcat(query_odd, requested_level_column);
+    strcat(query_odd, "atk_flat, ");
+    strcat(query_odd, requested_level_column);
+    strcat(query_odd, "def_flat, ");
+    strcat(query_odd, requested_level_column);
+    strcat(query_odd, "hp_percent, ");
+    strcat(query_odd, requested_level_column);
+    strcat(query_odd, "atk_percent, ");
+    strcat(query_odd, requested_level_column);
+    strcat(query_odd, "def_percent, ");
+    strcat(query_odd, requested_level_column);
+    strcat(query_odd, "spd, ");
+    strcat(query_odd, requested_level_column);
+    strcat(query_odd, "crr, ");
+    strcat(query_odd, requested_level_column);
+    strcat(query_odd, "crd, ");
+    strcat(query_odd, requested_level_column);
+    strcat(query_odd, "res, ");
+    strcat(query_odd, requested_level_column);
+    strcat(query_odd, "acc FROM runes WHERE slot = ? AND type IN (");
+    char cur_set[2];
+    char stat_set[2];
+    if (sets[0] != 0){
+        sprintf(cur_set, "%d", sets[0]);
+        strcat(query_odd, cur_set);
+        strcat(query_odd, ", ");
+    }
+    if (sets[1] != 0){
+        sprintf(cur_set, "%d", sets[1]);
+        strcat(query_odd, cur_set);
+        strcat(query_odd, ", ");
+    }
+    if (sets[2] != 0){
+        sprintf(cur_set, "%d", sets[2]);
+        strcat(query_odd, cur_set);
+        strcat(query_odd, ", ");
+    }
+    strcat(query_odd, "-1) ");
+    // TODO: Add team stuff
+
+    char query_even[1500] = "SELECT id, unit, type, ";
+    strcat(query_even, requested_level_column);
+    strcat(query_even, "hp_flat, ");
+    strcat(query_even, requested_level_column);
+    strcat(query_even, "atk_flat, ");
+    strcat(query_even, requested_level_column);
+    strcat(query_even, "def_flat, ");
+    strcat(query_even, requested_level_column);
+    strcat(query_even, "hp_percent, ");
+    strcat(query_even, requested_level_column);
+    strcat(query_even, "atk_percent, ");
+    strcat(query_even, requested_level_column);
+    strcat(query_even, "def_percent, ");
+    strcat(query_even, requested_level_column);
+    strcat(query_even, "spd, ");
+    strcat(query_even, requested_level_column);
+    strcat(query_even, "crr, ");
+    strcat(query_even, requested_level_column);
+    strcat(query_even, "crd, ");
+    strcat(query_even, requested_level_column);
+    strcat(query_even, "res, ");
+    strcat(query_even, requested_level_column);
+    strcat(query_even, "acc FROM runes WHERE slot = ? AND type IN (");
+    if (sets[0] != 0){
+        sprintf(cur_set, "%d", sets[0]);
+        strcat(query_even, cur_set);
+        strcat(query_even, ", ");
+    }
+    if (sets[1] != 0){
+        sprintf(cur_set, "%d", sets[1]);
+        strcat(query_even, cur_set);
+        strcat(query_even, ", ");
+    }
+    if (sets[2] != 0){
+        sprintf(cur_set, "%d", sets[2]);
+        strcat(query_even, cur_set);
+        strcat(query_even, ", ");
+    }
+    strcat(query_even, "-1) AND main_stat IN (");
+    for (int i = 0; i < 12; i ++){
+        if (requested_stats[i] != 0){
+            sprintf(stat_set, "%d", requested_stats[i]);
+            strcat(query_even, stat_set);
+            strcat(query_even, ", ");
+        }
+        else{
+            break;
+        }
+    }
+    strcat(query_even, "-1) ");
+    // TODO: Add team stuff
+
+    // Get data for all the runes from the database.
+    // Im using a 7 position array, and the index 0 is ignored. This is
+    // is because I REALLY NEED to use 1-indexes to match rune slots.
+    sqlite3_stmt *stmt_runes[7];
+    // Im assumming 600 runes per slot is a safe estimate. I hope it doesn't
+    // come back to bite me.
+    struct Rune runes[7][600];
+    db_status = sqlite3_prepare_v2(db, query_odd, -1, &stmt_runes[1], 0);
+    if (db_status != SQLITE_OK) {
+        fprintf(stderr, "Error getting runes for slot 1: %s\n", sqlite3_errmsg(db));
+        return ERROR_DB_RUNES_SLOT;
+    }
+    sqlite3_bind_int(stmt_runes[1], 1, 1);
+    db_status = sqlite3_prepare_v2(db, query_odd, -1, &stmt_runes[3], 0);
+    if (db_status != SQLITE_OK) {
+        fprintf(stderr, "Error getting runes for slot 3: %s\n", sqlite3_errmsg(db));
+        return ERROR_DB_RUNES_SLOT;
+    }
+    sqlite3_bind_int(stmt_runes[3], 1, 3);
+    db_status = sqlite3_prepare_v2(db, query_odd, -1, &stmt_runes[5], 0);
+    if (db_status != SQLITE_OK) {
+        fprintf(stderr, "Error getting runes for slot 5: %s\n", sqlite3_errmsg(db));
+        return ERROR_DB_RUNES_SLOT;
+    }
+    sqlite3_bind_int(stmt_runes[5], 1, 5);
+    db_status = sqlite3_prepare_v2(db, query_odd, -1, &stmt_runes[2], 0);
+    if (db_status != SQLITE_OK) {
+        fprintf(stderr, "Error getting runes for slot 2: %s\n", sqlite3_errmsg(db));
+        return ERROR_DB_RUNES_SLOT;
+    }
+    sqlite3_bind_int(stmt_runes[2], 1, 2);
+    db_status = sqlite3_prepare_v2(db, query_odd, -1, &stmt_runes[4], 0);
+    if (db_status != SQLITE_OK) {
+        fprintf(stderr, "Error getting runes for slot 4: %s\n", sqlite3_errmsg(db));
+        return ERROR_DB_RUNES_SLOT;
+    }
+    sqlite3_bind_int(stmt_runes[4], 1, 4);
+    db_status = sqlite3_prepare_v2(db, query_odd, -1, &stmt_runes[6], 0);
+    if (db_status != SQLITE_OK) {
+        fprintf(stderr, "Error getting runes for slot 6: %s\n", sqlite3_errmsg(db));
+        return ERROR_DB_RUNES_SLOT;
+    }
+    sqlite3_bind_int(stmt_runes[6], 1, 6);
+
+    // Get and populate all the runes
+    int rune_count[7];
+    for (int i = 1; i < 7; i ++){
+        int j = 0;
+        while (1 == 1){
+            int status = sqlite3_step(stmt_runes[i]);
+            if (status == SQLITE_ROW){
+                strcpy(runes[i][j].id, sqlite3_column_text(stmt_runes[i], 0));
+                strcpy(runes[i][j].unit, sqlite3_column_text(stmt_runes[i], 1));
+                runes[i][j].set = sqlite3_column_int(stmt_runes[i], 2);
+                runes[i][j].hp_flat = sqlite3_column_int(stmt_runes[i], 3);
+                runes[i][j].atk_flat = sqlite3_column_int(stmt_runes[i], 4);
+                runes[i][j].def_flat = sqlite3_column_int(stmt_runes[i], 5);
+                runes[i][j].hp_percent = sqlite3_column_int(stmt_runes[i], 6);
+                runes[i][j].atk_percent = sqlite3_column_int(stmt_runes[i], 7);
+                runes[i][j].def_percent = sqlite3_column_int(stmt_runes[i], 8);
+                runes[i][j].spd = sqlite3_column_int(stmt_runes[i], 9);
+                runes[i][j].crr = sqlite3_column_int(stmt_runes[i], 10);
+                runes[i][j].crd = sqlite3_column_int(stmt_runes[i], 11);
+                runes[i][j].res = sqlite3_column_int(stmt_runes[i], 12);
+                runes[i][j].acc = sqlite3_column_int(stmt_runes[i], 13);
+                j ++;
+            }
+            else{
+                break;
+            }
+        }
+        rune_count[i] = j;
+    }
+
+    // If any slot doesn't have matching runes, we can stop now.
+    for (int i = 1; i < 7; i ++){
+        if (rune_count[i] == 0){
+            if (gui != 1){
+                printf("\n  - No availbale runes for slot %d\n", i);
+            }
+            else{
+                fprintf(stderr, "\n  - No available runes for slot %d\n", i);
+            }
+            return SUCCESS;
+        }
+    }
+    if (gui != 1){
+        printf("\n");
+    }
+    unsigned long max_combinations =
+      rune_count[1] *
+      rune_count[2] *
+      rune_count[3] *
+      rune_count[4] *
+      rune_count[5] *
+      rune_count[6];
+
+    // Print a nice summary before starting the long optimization.
+    // This is an output example:
+    //
+    // -----------------------------------    Requested rune sets:
+    // | Lushen                          |       RAGE  BLADE
+    // | ID: 7223811472                  |
+    // -----------------------------------    Accepted stats:
+    // | STAT | BASE   | CURR.  | MIN.   |       ATK  CRR  CRD
+    // -----------------------------------
+    // | HP:  |  9225  | 12262  | 10000  |    Considering runes at level 15
+    // | ATK: |   900  |  2482  |  2482  |
+    // | DEF: |   461  |   703  |   703  |    Runes considered by slot:
+    // | SPD: |   103  |   159  |   159  |      1: 27    2: 39    3: 25
+    // | CRR: |    15% |    79% |    79% |      4: 35    5: 24    6: 32
+    // | CRD: |    50% |   188% |   188% |
+    // | RES: |    15% |    35% |    35% |    Total combinations: 707616000
+    // | ACC: |     0% |    11% |    10% |
+    // -----------------------------------
+    //
+    // Im breaking the 80-characters-line rule here, but only 'cause I'd like
+    // to remain sane.
+    if (gui != 1){
+        printf(" -----------------------------------------    Requested rune sets:\n");
+        printf(" | %*s |      ", -37, unit.name);
+        for (int i = 0; i < 3; i ++){
+            if (sets[i] != 0){
+                printf(" %s ", set_names[sets[i]]);
+            }
+            else{
+                break;
+            }
+        }
+        printf("\n");
+        printf(" | ID: %*s |\n", -33, unit.id);
+        printf(" -----------------------------------------    Accepted stats:\n");
+        printf(" | STAT | BASE     | CURR.    | MIN.     |      ");
+        for (int i = 0; i < 12; i ++){
+            if (requested_stats[i] != 0){
+                printf(" %s ", stat_names[requested_stats[i]]);
+            }
+            else{
+                break;
+            }
+        }
+        printf("\n");
+        printf(" -----------------------------------------\n");
+        printf(" | HP:  | %*d  | %*d  | %*d  |    ", 7, unit.base_hp, 7, unit.current_hp, 7, min_stats.hp);
+        if (requested_level == 0){
+            printf("Using runes at their current level\n");
+        }
+        else{
+            printf("Considering runes at level %d\n", requested_level);
+        }
+        printf(" | ATK: | %*d  | %*d  | %*d  |\n", 7, unit.base_atk, 7, unit.current_atk, 7, min_stats.atk);
+        printf(" | DEF: | %*d  | %*d  | %*d  |    Runes considered by slot:\n", 7, unit.base_def, 7, unit.current_def, 7, min_stats.def);
+        printf(" | SPD: | %*d  | %*d  | %*d  |      1:%*d    2:%*d    3:%*d\n", 7, unit.base_spd, 7, unit.current_spd, 7, min_stats.spd, 3, rune_count[1], 3, rune_count[2], 3, rune_count[3]);
+        printf(" | CRR: | %*d\% | %*d\% | %*d\% |      4:%*d    5:%*d    6:%*d\n", 7, unit.base_crr, 7, unit.current_crr, 7, min_stats.crr, 3, rune_count[4], 3, rune_count[5], 3, rune_count[6]);
+        printf(" | CRD: | %*d\% | %*d\% | %*d\% |\n", 7, unit.base_crd, 7, unit.current_crd, 7, min_stats.crd);
+        printf(" | RES: | %*d\% | %*d\% | %*d\% |    Total combinations: %d\n", 7, unit.base_res, 7, unit.current_res, 7, min_stats.res, max_combinations);
+        printf(" | ACC: | %*d\% | %*d\% | %*d\% |\n", 7, unit.base_acc, 7, unit.current_acc, 7, min_stats.acc);
+        printf(" | EHP: | %*d  | %*d  | %*d  |\n", 7, unit.base_ehp, 7, unit.current_ehp, 7, min_stats.ehp);
+        printf(" | DMG: | %*d  | %*d  | %*d  |\n", 7, unit.base_dmg, 7, unit.current_dmg, 7, min_stats.dmg);
+        printf(" -----------------------------------------\n");
+    }
+
+    // Now its time to loop all 6 'reels' of runes and try to match combos
+    if (gui != 1){
+        printf("\n\n__Optimization progress___________________________\n", max_combinations);
+    }
+    // Initialize arrys and some counters
+    int index[7] = {0, 0, 0, 0, 0, 0};
+    unsigned long tested_combinations = 0;
+    unsigned long valid_sets = 0;
+    unsigned long result_count = 0;
+    Result results[1000];
+    while(
+        index[1] < rune_count[1] &&
+        index[2] < rune_count[2] &&
+        index[3] < rune_count[3] &&
+        index[4] < rune_count[4] &&
+        index[5] < rune_count[5] &&
+        index[6] < rune_count[6]
+    ){
+        // Progress bar, 50 characters to 100%
+        if (
+          gui != 1 &&
+          (tested_combinations + 1) %
+          (unsigned long)(max_combinations / 50)
+          == 0
+        ){
+            printf("#");
+            // Line buffered! need to flush after every char.
+            fflush(stdout);
+        }
+
+        // Calculate rune sets at current indexes.
+        struct Rune_Set_Count set_count;
+        set_count.energy = 0;
+        set_count.guard = 0;
+        set_count.swift = 0;
+        set_count.blade = 0;
+        set_count.rage = 0;
+        set_count.focus = 0;
+        set_count.endure = 0;
+        set_count.fatal = 0;
+        set_count.despair = 0;
+        set_count.vampire = 0;
+        set_count.violent = 0;
+        set_count.nemesis = 0;
+        set_count.will = 0;
+        set_count.shield = 0;
+        set_count.revenge = 0;
+        set_count.destroy = 0;
+        set_count.fight = 0;
+        set_count.determination = 0;
+        set_count.enhance = 0;
+        set_count.accuracy = 0;
+        set_count.tolerance = 0;
+        for (int i = 1; i < 7; i ++){
+            switch (runes[i][index[i]].set){
+                case ENERGY:
+                    set_count.energy ++;
+                    break;
+                case GUARD:
+                    set_count.guard ++;
+                    break;
+                case SWIFT:
+                    set_count.swift ++;
+                    break;
+                case BLADE:
+                    set_count.blade ++;
+                    break;
+                case RAGE:
+                    set_count.rage ++;
+                    break;
+                case FOCUS:
+                    set_count.focus ++;
+                    break;
+                case ENDURE:
+                    set_count.endure ++;
+                    break;
+                case FATAL:
+                    set_count.fatal ++;
+                    break;
+                case DESPAIR:
+                    set_count.despair ++;
+                    break;
+                case VAMPIRE:
+                    set_count.vampire ++;
+                    break;
+                case VIOLENT:
+                    set_count.violent ++;
+                    break;
+                case NEMESIS:
+                    set_count.nemesis ++;
+                    break;
+                case WILL:
+                    set_count.will ++;
+                    break;
+                case SHIELD:
+                    set_count.shield ++;
+                    break;
+                case REVENGE:
+                    set_count.revenge ++;
+                    break;
+                case DESTROY:
+                    set_count.destroy ++;
+                    break;
+                case FIGHT:
+                    set_count.fight ++;
+                    break;
+                case DETERMINATION:
+                    set_count.determination ++;
+                    break;
+                case ENHANCE:
+                    set_count.enhance ++;
+                    break;
+                case ACCURACY:
+                    set_count.accuracy ++;
+                    break;
+                case TOLERANCE:
+                    set_count.tolerance ++;
+                    break;
+            }
+        }
+        // Compare with requested sets
+        if (
+            set_count.energy >= requested_set_count.energy &&
+            set_count.guard >= requested_set_count.guard &&
+            set_count.swift >= requested_set_count.swift &&
+            set_count.blade >= requested_set_count.blade &&
+            set_count.rage >= requested_set_count.rage &&
+            set_count.focus >= requested_set_count.focus &&
+            set_count.endure >= requested_set_count.endure &&
+            set_count.fatal >= requested_set_count.fatal &&
+            set_count.despair >= requested_set_count.despair &&
+            set_count.vampire >= requested_set_count.vampire &&
+            set_count.violent >= requested_set_count.violent &&
+            set_count.nemesis >= requested_set_count.nemesis &&
+            set_count.will >= requested_set_count.will &&
+            set_count.shield >= requested_set_count.shield &&
+            set_count.revenge >= requested_set_count.revenge &&
+            set_count.destroy >= requested_set_count.destroy &&
+            set_count.fight >= requested_set_count.fight &&
+            set_count.determination >= requested_set_count.determination &&
+            set_count.enhance >= requested_set_count.enhance &&
+            set_count.accuracy >= requested_set_count.accuracy &&
+            set_count.tolerance >= requested_set_count.tolerance
+        ){
+            // The current runes form a valid set.
+            valid_sets ++;
+
+            // Calculate new stats
+            struct Stats stats;
+            stats.hp = unit.base_hp;
+            stats.atk = unit.base_atk;
+            stats.def = unit.base_def;
+            stats.spd = unit.base_spd;
+            stats.crr = unit.base_crr;
+            stats.crd = unit.base_crd;
+            stats.res = unit.base_res;
+            stats.acc = unit.base_acc;
+            for (int i = 1; i < 7; i ++){
+                stats.hp += runes[i][index[i]].hp_flat;
+                stats.atk += runes[i][index[i]].atk_flat;
+                stats.def += runes[i][index[i]].def_flat;
+                stats.hp += unit.base_hp * runes[i][index[i]].hp_percent / 100;
+                stats.atk +=
+                  unit.base_hp * runes[i][index[i]].atk_percent / 100;
+                stats.def +=
+                  unit.base_hp * runes[i][index[i]].def_percent / 100;
+                stats.spd += runes[i][index[i]].spd;
+                stats.crr += runes[i][index[i]].crr;
+                stats.crd += runes[i][index[i]].crd;
+                stats.res += runes[i][index[i]].res;
+                stats.acc += runes[i][index[i]].acc;
+            }
+
+            // Calculated stats
+            stats.ehp = calculate_ehp(stats.hp, stats.def);
+            stats.dmg = calculate_dmg(stats.atk, stats.crr, stats.crd);
+
+            // Compare with minimum requeriments
+            if (
+                stats.hp >= min_stats.hp &&
+                stats.atk >= min_stats.atk &&
+                stats.def >= min_stats.def &&
+                stats.spd >= min_stats.spd &&
+                stats.crr >= min_stats.crr &&
+                stats.crd >= min_stats.crd &&
+                stats.res >= min_stats.res &&
+                stats.acc >= min_stats.acc &&
+                stats.ehp >= min_stats.ehp &&
+                stats.dmg >= min_stats.dmg
+            ){
+                // This is a valid sets and all stats are above the minimum.
+                // Create a result with rune indexes, stats, and rating.
+                for (int i = 1; i < 7; i ++){
+                    strcpy(
+                      results[result_count].rune_ids[i - 1],
+                      runes[i][index[i]].id
+                    );
+                }
+                results[result_count].stats.hp = stats.hp;
+                results[result_count].stats.atk = stats.atk;
+                results[result_count].stats.def = stats.def;
+                results[result_count].stats.spd = stats.spd;
+                results[result_count].stats.crr = stats.crr;
+                results[result_count].stats.crd = stats.crd;
+                results[result_count].stats.res = stats.res;
+                results[result_count].stats.acc = stats.acc;
+                results[result_count].stats.ehp = stats.ehp;
+                results[result_count].stats.dmg = stats.dmg;
+
+                // One rating point per stat increase over current stats.
+                // Save for HP, with takes 15 for a raing point
+                results[result_count].rating = 0;
+                results[result_count].rating += ((stats.hp - unit.current_hp) / 15);
+                results[result_count].rating += (stats.atk - unit.current_atk);
+                results[result_count].rating += (stats.def - unit.current_def);
+                results[result_count].rating += (stats.spd - unit.current_spd);
+                results[result_count].rating += (stats.crr - unit.current_crr);
+                results[result_count].rating += (stats.crd - unit.current_crd);
+                results[result_count].rating += (stats.res - unit.current_res);
+                results[result_count].rating += (stats.acc - unit.current_acc);
+                result_count ++;
+            }
+        }
+
+        // Loop control. Rotate the reels 'right to left'
+        tested_combinations ++;
+        index[6] ++;
+        if (index[6] == rune_count[6]){
+            index[6] = 0;
+            index[5] ++;
+        }
+        if (index[5] == rune_count[5]){
+            index[5] = 0;
+            index[4] ++;
+        }
+        if (index[4] == rune_count[4]){
+            index[4] = 0;
+            index[3] ++;
+        }
+        if (index[3] == rune_count[3]){
+            index[3] = 0;
+            index[2] ++;
+        }
+        if (index[2] == rune_count[2]){
+            index[2] = 0;
+            index[1] ++;
+        }
+
+        //DEBUG: force exit with some results TODO
+        //if (result_count > 11){
+        //    break;
+        //}
+    }
+    if (gui != 1){
+        printf("\n");
+    }
+
+    if (result_count > 0){
+        // Yay! Some combinations matched te criteria.
+        if (gui != 1){
+            printf("\n\n%d results found\n", result_count);
+        }
+        // Sort results
+        sort_results(results, result_count);
+
+        // Preview the best option
+        if (gui != 1){
+            printf("\nPresenting best option:\n\n");
+            printf(" ------------------------------\n");
+            printf(" | %*s |\n", -26, unit.name);
+            printf(" | ID: %*s |\n", -22, unit.id);
+            printf(" ------------------------------\n");
+            printf(" | STAT | CURR.    | NEW      |\n");
+            printf(" ------------------------------\n");
+            printf(" | HP:  | %*d  | %*d  |\n", 7, unit.current_hp, 7, results[0].stats.hp);
+            printf(" | ATK: | %*d  | %*d  |\n", 7, unit.current_atk, 7, results[0].stats.atk);
+            printf(" | DEF: | %*d  | %*d  |\n", 7, unit.current_def, 7, results[0].stats.def);
+            printf(" | SPD: | %*d  | %*d  |\n", 7, unit.current_spd, 7, results[0].stats.spd);
+            printf(" | CRR: | %*d\% | %*d\% |\n", 7, unit.current_crr, 7, results[0].stats.crr);
+            printf(" | CRD: | %*d\% | %*d\% |\n", 7, unit.current_crd, 7, results[0].stats.crd);
+            printf(" | RES: | %*d\% | %*d\% |\n", 7, unit.current_res, 7, results[0].stats.res);
+            printf(" | ACC: | %*d\% | %*d\% |\n", 7, unit.current_acc, 7, results[0].stats.acc);
+            printf(" | EHP: | %*d  | %*d  |\n", 7, unit.current_ehp, 7, results[0].stats.ehp);
+            printf(" | DMG: | %*d  | %*d  |\n", 7, unit.current_dmg, 7, results[0].stats.dmg);
+            printf(" ------------------------------\n");
+
+            // This bit may be hard to follow.
+            // I'm populating  8 lines of text with data, to display the runes in
+            // a nice table format.
+            //
+            // This is an output exaple:
+            //
+            // ------------------------------  ------------------------------  ------------------------------
+            // |6|RAGE         | 23285581330|  |1|BLADE        | 22677809846|  |2|BLADE        | 21432847749|
+            // ------------------------------  ------------------------------  ------------------------------
+            // | Storage       |        +12 |  | Storage       |        +12 |  | Perna         |        +15 |
+            // ------------------------------  ------------------------------  ------------------------------
+            // | ACC           48           |  | ATK_FLAT     118           |  | SPD           42           |
+            // | RES            6           |  |                            |  |                            |
+            // | ATK_FLAT      19           |  | RES           14           |  | CRD           16           |
+            // | CRR           12           |  | ACC            8           |  | CRR           10           |
+            // | HP            14           |  | HP_FLAT      580           |  | ATK            7 + 3       |
+            // | CRD           14           |  | CRR           10           |  | DEF            7 + 3       |
+            // ------------------------------  ------------------------------  ------------------------------
+            //
+            // ------------------------------  ------------------------------  ------------------------------
+            // |5|RAGE         | 26260912967|  |4|RAGE         | 27947761086|  |3|RAGE         | 27654723287|
+            // ------------------------------  ------------------------------  ------------------------------
+            // | Lushen        |        +15 |  | Lushen        |        +15 |  | Covenant      |        +12 |
+            // ------------------------------  ------------------------------  ------------------------------
+            // | HP_FLAT     2448           |  | CRD           80           |  | DEF_FLAT     118           |
+            // |                            |  |                            |  | HP_FLAT      167           |
+            // | CRR           16           |  | CRR            6           |  | RES            8           |
+            // | SPD            6 + 2       |  | RES           11           |  | DEF           11           |
+            // | CRD           11           |  | SPD           18           |  | CRD           18           |
+            // | ATK            6 + 5       |  | ATK            8           |  | CRR           12           |
+            // ------------------------------  ------------------------------  ------------------------------
+            //
+            // Again, Im breaking the 80-characters-line rule.
+
+            char present[8][130];
+            strcpy(present[0], "");
+            strcpy(present[1], "");
+            strcpy(present[2], "");
+            strcpy(present[4], "");
+            strcpy(present[5], "");
+            strcpy(present[6], "");
+            strcpy(present[7], "");
+
+            // Retrieve the rune stats from the database
+            printf("\n");
+            for (int i = 6; i != 0;){
+                char *rune_id = results[0].rune_ids[i - 1];
+                sqlite3_stmt *rune_res;
+                char *sql =
+                  "SELECT runes.id, runes.slot, runes.type, units.id, units.name, runes.level "
+                  "FROM runes LEFT JOIN units ON runes.unit = units.id "
+                  "WHERE runes.id = ?";
+                db_status = sqlite3_prepare_v2(db, sql, -1, &rune_res, 0);
+
+                if (db_status == SQLITE_OK) {
+                    sqlite3_bind_text(rune_res, 1, results[0].rune_ids[i - 1], strlen(results[0].rune_ids[i - 1]), NULL);
+                }
+                else {
+                    fprintf(stderr, "Failed to execute statement to select rune: %s\n", sqlite3_errmsg(db));
+                    return ERROR_DB_RUNES_RESULT;
+                }
+
+                int step = sqlite3_step(rune_res);
+                if (step == SQLITE_ROW) {
+                    char tmp[64];
+                    strcpy(tmp, "");
+                    strcat(present[0], "|");
+                    sprintf(tmp, "%*d", 1, sqlite3_column_int(rune_res, 1));
+                    strcat(present[0], tmp);
+                    strcat(present[0], "|");
+                    sprintf(tmp, "%*s|", -13, set_names[sqlite3_column_int(rune_res, 2)]);
+                    strcat(present[0], tmp);
+                    sprintf(tmp, "%*s|  ", 12, sqlite3_column_text(rune_res, 0));
+                    strcat(present[0], tmp);
+
+                    strcat(present[1], "|");
+                    if (sqlite3_column_type(rune_res, 3) == SQLITE_NULL){
+                        sprintf(tmp, " %*s", -14, "Storage");
+                        strcat(present[1], tmp);
+                        strcat(present[1], "|");
+                    }
+                    else{
+                        sprintf(tmp, " %*s", -14, sqlite3_column_text(rune_res, 4));
+                        strcat(present[1], tmp);
+                        strcat(present[1], "|");
+                    }
+
+                    // Rune level
+                    sprintf(tmp, "        +%*s |  ", 2, sqlite3_column_text(rune_res, 5));
+                    strcat(present[1], tmp);
+
+                    sqlite3_stmt *stats_res;
+                    char *sql_stats =
+                      "SELECT rune, slot, stat, value, enchant, grind "
+                      "FROM rune_stats "
+                      "WHERE rune = ?";
+                    db_status = sqlite3_prepare_v2(db, sql_stats, -1, &stats_res, 0);
+
+                    if (db_status == SQLITE_OK) {
+                        sqlite3_bind_text(stats_res, 1, sqlite3_column_text(rune_res, 0), strlen(sqlite3_column_text(rune_res, 0)), NULL);
+                    }
+                    else {
+                        fprintf(stderr, "Failed to execute statement to select rune stats: %s\n", sqlite3_errmsg(db));
+                        return ERROR_DB_STATS_RESULT;
+                    }
+
+                    int curr_slot = -1;
+                    while (1 == 1){
+                        int status = sqlite3_step(stats_res);
+                        //printf("STATUS:  %d   ", status);
+                        if (status == SQLITE_ROW){
+                            // Print empty lines for no-stats
+                            while (sqlite3_column_int(stats_res, 1) != curr_slot){
+                                sprintf(tmp, "|                            |  ");
+                                strcat(present[curr_slot + 3], tmp);
+                                curr_slot ++;
+                            }
+                            if (sqlite3_column_int(stats_res, 1) == curr_slot){
+                                strcat(present[curr_slot + 3], "| ");
+                                sprintf(tmp, "%*s", -9, stat_names[sqlite3_column_int(stats_res, 2)]);
+                                strcat(present[curr_slot + 3], tmp);
+                                sprintf(tmp, " %*d", 6, sqlite3_column_int(stats_res, 3));
+                                strcat(present[curr_slot + 3], tmp);
+
+                                // Display grinds
+                                if (sqlite3_column_int(stats_res, 5) > 0){
+                                    sprintf(tmp, " + %*d", -8, sqlite3_column_int(stats_res, 5));
+                                }
+                                else{
+                                    sprintf(tmp, "           ");
+                                }
+                                strcat(present[curr_slot + 3], tmp);
+                                strcat(present[curr_slot + 3], "|  ");
+                            }
+
+                            curr_slot ++;
+                        }
+                        else{
+                            break;
+                        }
+                    }
+                    sqlite3_finalize(stats_res);
+                }
+                else{
+                    fprintf(stderr, "BAD ROW: %s\n", sqlite3_errmsg(db));
+                }
+                sqlite3_finalize(rune_res);
+
+                // Loop control
+                // It's weird, but I wanna present the runes in the same format the
+                // game does:
+                //
+                // 6 1 2
+                // 5 4 3
+                // After slots 2 and 3, the generated strings are written.
+                if (i == 6){
+                    //printf("-6->1-");
+                    i = 1;
+                }
+                else if (i == 1){
+                    //printf("-1->2-");
+                    i = 2;
+                }
+                else if (i == 2){
+                    // Print and reinitialize the strings
+                    printf("------------------------------  ------------------------------  ------------------------------\n");
+                    printf(present[0]);
+                    printf("\n------------------------------  ------------------------------  ------------------------------\n");
+                    printf(present[1]);
+                    printf("\n------------------------------  ------------------------------  ------------------------------\n");
+                    printf(present[2]);
+                    printf("\n");
+                    printf(present[3]);
+                    printf("\n");
+                    printf(present[4]);
+                    printf("\n");
+                    printf(present[5]);
+                    printf("\n");
+                    printf(present[6]);
+                    printf("\n");
+                    printf(present[7]);
+                    printf("\n------------------------------  ------------------------------  ------------------------------\n");
+                    strcpy(present[0], "");
+                    strcpy(present[1], "");
+                    strcpy(present[2], "");
+                    strcpy(present[3], "");
+                    strcpy(present[4], "");
+                    strcpy(present[5], "");
+                    strcpy(present[6], "");
+                    strcpy(present[7], "");
+                    //printf("-2->5-");
+                    i = 5;
+                }
+                else if (i == 5){
+                    //printf("-5->4-");
+                    i = 4;
+                }
+                else if (i == 4){
+                    //printf("-4->3-");
+                    i = 3;
+                }
+                else if (i == 3){
+                    // Print and exit loop
+                    printf("\n------------------------------  ------------------------------  ------------------------------\n");
+                    printf(present[0]);
+                    printf("\n------------------------------  ------------------------------  ------------------------------\n");
+                    printf(present[1]);
+                    printf("\n------------------------------  ------------------------------  ------------------------------\n");
+                    printf(present[2]);
+                    printf("\n");
+                    printf(present[3]);
+                    printf("\n");
+                    printf(present[4]);
+                    printf("\n");
+                    printf(present[5]);
+                    printf("\n");
+                    printf(present[6]);
+                    printf("\n");
+                    printf(present[7]);
+                    printf("\n------------------------------  ------------------------------  ------------------------------\n");
+                    //printf("-3->0-");
+                    i = 0;
+                }
+            }
+        }
+        else{
+            //Print for GUI
+            char tmp[1000000];
+            strcpy(tmp, "");
+            char json[1000000] = "{\"results\":[";
+            for (int i = 0; i < result_count; i++){
+                //printf("RES LOOP %d\n", i);
+                sprintf(tmp, "{\"rating\":%d,", results[i].rating);
+                strcat(json, tmp);
+                sprintf(tmp, "\"hp\":%d,", results[i].stats.hp);
+                strcat(json, tmp);
+                sprintf(tmp, "\"atk\":%d,", results[i].stats.atk);
+                strcat(json, tmp);
+                sprintf(tmp, "\"dfc\":%d,", results[i].stats.def);
+                strcat(json, tmp);
+                sprintf(tmp, "\"spd\":%d,", results[i].stats.spd);
+                strcat(json, tmp);
+                sprintf(tmp, "\"crr\":%d,", results[i].stats.crr);
+                strcat(json, tmp);
+
+                sprintf(tmp, "\"crd\":%d,", results[i].stats.crd);
+                strcat(json, tmp);
+                sprintf(tmp, "\"res\":%d,", results[i].stats.res);
+                strcat(json, tmp);
+                sprintf(tmp, "\"acc\":%d,", results[i].stats.acc);
+                strcat(json, tmp);
+                sprintf(tmp, "\"ehp\":%d,", results[i].stats.ehp);
+                strcat(json, tmp);
+                sprintf(tmp, "\"dmg\":%d,", results[i].stats.dmg);
+                strcat(json, tmp);
+                strcat(json, "\"runes\":[");
+                //printf("    A %s\n", json);
+                for (int j = 0; j < 5; j++){
+                    //printf("    RUNE LOOP %d\n", j);
+                    sprintf(tmp, "\"%s\",", results[i].rune_ids[j]);
+                    strcat(json, tmp);
+                }
+                //printf("    B %s\n", json);
+                sprintf(tmp, "\"%s\"", results[i].rune_ids[5]);
+                strcat(json, tmp);
+                strcat(json, "]},");
+            }
+            // Remove last comma
+            json[strlen(json) - 1] = '\0';
+
+            // End and print
+            strcat(json, "]}\0");
+            printf("%s", json);
+        }
+    }
+    else{
+        if (gui != 1){
+            printf("No results found\n");
+        }
+        else{
+            fprintf(stderr, "No results found\n");
+        }
+    }
+
+    sqlite3_close(db);
+    return SUCCESS;
+}
+
+void sort_results(Result results[1000], int total){
+    // Bubble sort, by descending rating.
+    int i, j;
+    Result temp;
+
+    for (i = 0; i < total - 1; i++)
+    {
+        for (j = 0; j < (total - 1-i); j++)
+        {
+            if (results[j].rating < results[j + 1].rating)
+            {
+                temp = results[j];
+                results[j] = results[j + 1];
+                results[j + 1] = temp;
+            }
+        }
+    }
+}
+
+unsigned int calculate_ehp(unsigned int hp, unsigned short def){
+    unsigned int ehp = 0;
+    ehp = ceil((((((float) def) * 3.5f) + 1140.0f) * ((float) hp)) / 1000.0f);
+    return ehp;
+}
+
+unsigned short calculate_dmg(unsigned short atk, unsigned short crr, unsigned short crd){
+    unsigned short dmg = 0;
+    float crr_capped = (float) crr;
+    if (crr_capped > 100.0f){
+        crr_capped = 100.0f;
+    }
+    dmg = ceil((((float) atk) * (100.0f - crr_capped) / 100.0f) + ((((float) atk) + (((float) atk) * ((float) crd) / 100.0f)) * crr_capped / 100.0f));
+    return dmg;
+}

+ 186 - 0
src/RuneOptimizer/optimize/optimize.h

@@ -0,0 +1,186 @@
+/*
+ * This file is part of RuneOptimizer.
+ *
+ * RuneOptimizer 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, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * RuneOptimizer 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
+ * RuneOptimizer. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+// Rune set definitions
+#define ENERGY 1
+#define GUARD 2
+#define SWIFT 3
+#define BLADE 4
+#define RAGE 5
+#define FOCUS 6
+#define ENDURE 7
+#define FATAL 8
+#define DESPAIR 10
+#define VAMPIRE 11
+#define VIOLENT 13
+#define NEMESIS 14
+#define WILL 15
+#define SHIELD 16
+#define REVENGE 17
+#define DESTROY 18
+#define FIGHT 19
+#define DETERMINATION 20
+#define ENHANCE 21
+#define ACCURACY 22
+#define TOLERANCE 23
+
+// Rune stat definitions
+#define HP_FLAT 1
+#define HP_PERCENT 2
+#define ATK_FLAT 3
+#define ATK_PERCENT 4
+#define DEF_FLAT 5
+#define DEF_PERCENT 6
+#define SPD 8
+#define CRR 9
+#define CRD 10
+#define RES 11
+#define ACC 12
+
+struct Unit {
+    unsigned char id[12];
+    unsigned char name[50];
+    unsigned int base_hp;
+    unsigned short base_atk;
+    unsigned short base_def;
+    unsigned short base_spd;
+    unsigned short base_crr;
+    unsigned short base_crd;
+    unsigned short base_acc;
+    unsigned short base_res;
+    unsigned int base_ehp;
+    unsigned short base_dmg;
+    unsigned int current_hp;
+    unsigned short current_atk;
+    unsigned short current_def;
+    unsigned short current_spd;
+    unsigned short current_crr;
+    unsigned short current_crd;
+    unsigned short current_acc;
+    unsigned short current_res;
+    unsigned int current_ehp;
+    unsigned short current_dmg;
+};
+
+struct Rune {
+    unsigned char id[12];
+    unsigned char slot;
+    unsigned char set;
+    unsigned char unit[12];
+    unsigned char hp_percent;
+    unsigned char atk_percent;
+    unsigned char def_percent;
+    unsigned short hp_flat;
+    unsigned char atk_flat;
+    unsigned char def_flat;
+    unsigned char spd;
+    unsigned char crr;
+    unsigned char crd;
+    unsigned char acc;
+    unsigned char res;
+};
+
+struct Stats {
+    unsigned int hp;
+    unsigned short atk;
+    unsigned short def;
+    unsigned short spd;
+    unsigned short crr;
+    unsigned short crd;
+    unsigned short acc;
+    unsigned short res;
+    unsigned int ehp;
+    unsigned short dmg;
+};
+
+struct Rune_Set_Count {
+    unsigned short energy;
+    unsigned short guard;
+    unsigned short swift;
+    unsigned short blade;
+    unsigned short rage;
+    unsigned short focus;
+    unsigned short endure;
+    unsigned short fatal;
+    unsigned short despair;
+    unsigned short vampire;
+    unsigned short violent;
+    unsigned short nemesis;
+    unsigned short will;
+    unsigned short shield;
+    unsigned short revenge;
+    unsigned short destroy;
+    unsigned short fight;
+    unsigned short determination;
+    unsigned short enhance;
+    unsigned short accuracy;
+    unsigned short tolerance;
+};
+typedef struct Result {
+    unsigned char rune_ids[6][12];
+    signed int rating;
+    struct Stats stats;
+} Result;
+
+char stat_names[][13] = {
+  "NULL", "HP_FLAT", "HP",  "ATK_FLAT", "ATK", "DEF_FLAT",
+  "DEF",  "NULL",    "SPD", "CRR",      "CRD", "RES",      "ACC"
+};
+char set_names[][24] = {
+  "NULL",    "ENERGY",        "GUARD",   "SWIFT",    "BLADE",   "RAGE",
+  "FOCUS",   "ENDURE",        "FATAL",   "NULL",     "DESPAIR", "VAMPIRE",
+  "VIOLENT", "NEMESIS",       "WILL",    "SHIELD",   "REVENGE", "DESTROY",
+  "FIGHT",   "DETERMINATION", "ENHANCE", "ACCURACY", "TOLERANCE"
+};
+
+/**
+ * Starts the optimization process.
+ *
+ * @param argc Argument count.
+ * @param argv Argument list.
+ * @return 0 on success, other on error.
+ */
+int optimize(int argc, char *argv[]);
+
+/**
+ * Sorts the result array.
+ *
+ * Sorts them by rating.
+ *
+ * @param results List of results to sort.
+ * @param total Number of results.
+ */
+void sort_results(Result results[1000], int total);
+
+/**
+ * Calculates efficient HP.
+ *
+ * @param hp HP stat.
+ * @param def DEF stat.
+ * @return Calculated EHP.
+ */
+unsigned int calculate_ehp(unsigned int hp, unsigned short def);
+
+/**
+ * Calculates damage.
+ *
+ * @param atk ATK stat.
+ * @param crr CRR stat.
+ * @param crd CRD stat.
+ * @return Calculated DMG.
+ */
+unsigned short calculate_dmg(unsigned short atk, unsigned short crr, unsigned short crd);

+ 50 - 0
src/RuneOptimizer/team/team.c

@@ -0,0 +1,50 @@
+/*
+ * This file is part of RuneOptimizer.
+ *
+ * RuneOptimizer 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, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * RuneOptimizer 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
+ * RuneOptimizer. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+int list_teams(int argc, char *argv[]){
+    printf("A");
+    char id[8] = "\0";
+    char query[300] = "SELECT id, name, priority FROM teams ";
+    char tmp[64];
+    if (argc > 3){
+        strcpy(id, argv[3]);
+        strcpy(tmp, "");
+        sprintf(tmp, "WHERE id = '%s' OR name = '%s' ", id, id);
+        strcat(query, tmp);
+    }
+    printf("LISTING TEAMS %s\n%s\n", id, query);
+    if (SUCCESS != open_database()){
+        return ERROR_DB_CANT_OPEN;
+    }
+    sqlite3_stmt *stmt_teams;
+    db_status = sqlite3_prepare_v2(db, query, -1, &stmt_teams, 0);
+    if (db_status != SQLITE_OK) {
+        fprintf(stderr, "Error getting runes for slot 1: %s\n", sqlite3_errmsg(db));
+        return ERROR_DB_RUNES_SLOT;
+    }
+
+    while (1 == 1){
+        int status = sqlite3_step(stmt_teams);
+        if (status == SQLITE_ROW){
+            printf("OK %s\n", sqlite3_column_text(stmt_teams, 2));
+
+        }
+        else{
+            break;
+        }
+    }
+}

+ 25 - 0
src/RuneOptimizer/team/team.h

@@ -0,0 +1,25 @@
+/*
+ * This file is part of RuneOptimizer.
+ *
+ * RuneOptimizer 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, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * RuneOptimizer 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
+ * RuneOptimizer. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Displays teams.
+ *
+ * @param argc Argument count.
+ * @param argv Argument list.
+ * @return 0 on success, other on error.
+ */
+int list_teams(int argc, char *argv[]);

+ 16 - 0
src/RuneOptimizer/update/update.c

@@ -0,0 +1,16 @@
+/*
+ * This file is part of RuneOptimizer.
+ *
+ * RuneOptimizer 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, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * RuneOptimizer 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
+ * RuneOptimizer. If not, see <https://www.gnu.org/licenses/>.
+ */

+ 16 - 0
src/RuneOptimizer/update/update.h

@@ -0,0 +1,16 @@
+/*
+ * This file is part of RuneOptimizer.
+ *
+ * RuneOptimizer 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, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * RuneOptimizer 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
+ * RuneOptimizer. If not, see <https://www.gnu.org/licenses/>.
+ */

+ 5 - 1481
src/RuneOptimizerGUI/RuneOptimizer.py

@@ -1,8 +1,6 @@
 #!/usr/bin/env python
 
 """
-RuneOptimizer GUI.
-
 This file is part of RuneOptimizer.
 
 RuneOptimizer is free software: you can redistribute it and/or modify it
@@ -28,6 +26,9 @@ import subprocess
 import json
 from types import SimpleNamespace
 
+exec(compile(source=open('frames/RuneOptimizerFrame.py').read(), filename='frames/RuneOptimizerFrame.py', mode='exec'))
+exec(compile(source=open('frames/ResultsFrame.py').read(), filename='frames/ResultsFrame.py', mode='exec'))
+
 conn = None
 
 stat_names = [
@@ -41,1487 +42,10 @@ set_names = [
   "FIGHT",   "DETERMINATION", "ENHANCE", "ACCURACY", "TOLERANCE"
 ]
 
-class ResultsFrame(wx.Frame):
-    """
-    The frame uset to see and check results.
-
-    Parameters
-    ----------
-    unitId : str
-        ID of the unit being optimized (default "").
-    unitName : str
-        Name of the unit being optimized (default "").
-    data : Python Object
-        Results from RuneOptimizer (default None).
-    currentStats : int[10]
-        The current stats of the unit being optimized. (default is
-        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).
-    page : int
-        Currently displayed page, 0-index (default 0).
-    totalPages : int
-        Number of pages of results (default 0).
-    linesPerPage : int
-        Number of results to show per page (default 10).
-    pgPrevBt : wx.Button
-        Button to go to the previous page.
-    pgPrevBt : wx.Button
-        Button to go to the previous page.
-    resultsPageIndicator : wx.StaticText
-        Label to indicate the current and maximum pages.
-    resultGrid : wx.Grid.grid
-        Table of results.
-    resultContent : wx.BoxSizer
-        Holds every widget that is hidden until a result is selected.
-    statGrid : wx.Grid.grid
-        Table to show the new stats with the selected result.
-    runeIds : wx.StaticText[6]
-        Labels with the IDs of the runes in the current result.
-    runeLocations : wx.StaticText[6]
-        Labels with the locations of the runes in the current result.
-    runeSets : wx.StaticText[6]
-        Labels with the set names of the runes in the current result.
-    runeMains : wx.StaticText[6]
-        Labels with the main stats of the runes in the current result.
-    runeInnates : wx.StaticText[6]
-        Labels with the innates of the runes in the current result.
-    runeStats : wx.StaticText[6][4]
-        Labels with the stats of the runes in the current result.
-    selectedResultndex : int
-        Selected result index (default -1).
-
-    Methods
-    -------
-    processResults(jsonData)
-        Processes data obtained from RuneOptimizer.
-    pgPrev(event)
-        Goes to the previous result page.
-    pgNext(event)
-        Goes to the next result page.
-    closeWindow(event)
-        Closes the frame.
-    applyRunes(event)
-        Applies the runes in the currently selected result.
-    printResults()
-        Populates the results table with the results in the currently
-        selected page.
-    resultSelected(event)
-        Populates and shows the runes and effective stats with the
-        currently seleced result.
-
-    """
-
-    unitId = ""
-    unitName = ""
-    data = None
-    currentStats = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
-    page = 0
-    totalPages = 0
-    linesPerPage = 10
-    pgPrevBt = None
-    pgNextBt = None
-    resultsPageIndicator = None
-    resultGrid = None
-    resultContent = None
-    statGrid = None
-    runeIds = None
-    runeLocations = None
-    runeSets = None
-    runeMains = None
-    runeInnates = None
-    runeStats = None
-    selectedResultIndex = -1
-
-    def __init__(self, *args, **kw):
-        """Initializes the class.
-
-        Sets upt all the widgets.
-
-        """
-
-        global conn
-        # ensure the parent's __init__ is called
-        super(ResultsFrame, self).__init__(*args, **kw)
-
-        # create a panel in the frame
-        pnl = wx.Panel(self)
-
-        self.resultGrid = wx.grid.Grid(parent=pnl, id=-1, pos=(50, 30), size=(544, 220))
-        self.resultGrid.CreateGrid(numRows=10, numCols=11, selmode=wx.grid.Grid.GridSelectionModes.SelectRows)
-        self.resultGrid.EnableEditing(False)
-        self.resultGrid.SetDefaultCellAlignment(horiz=wx.ALIGN_RIGHT, vert=wx.ALIGN_CENTRE)
-        monospaceFont = wx.Font(10, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
-        self.resultGrid.SetDefaultCellFont(monospaceFont)
-        self.resultGrid.SetRowLabelSize(width=35)
-        self.resultGrid.SetColLabelValue(col=0, value="Rating")
-        self.resultGrid.SetColSize(col=0, width=50)
-        self.resultGrid.SetColLabelValue(col=1, value="HP")
-        self.resultGrid.SetColSize(col=1, width=50)
-        self.resultGrid.SetColLabelValue(col=2, value="ATK")
-        self.resultGrid.SetColSize(col=2, width=40)
-        self.resultGrid.SetColLabelValue(col=3, value="DEF")
-        self.resultGrid.SetColSize(col=3, width=40)
-        self.resultGrid.SetColLabelValue(col=4, value="SPD")
-        self.resultGrid.SetColSize(col=4, width=40)
-        self.resultGrid.SetColLabelValue(col=5, value="CRR")
-        self.resultGrid.SetColSize(col=5, width=40)
-        self.resultGrid.SetColLabelValue(col=6, value="CRD")
-        self.resultGrid.SetColSize(col=6, width=40)
-        self.resultGrid.SetColLabelValue(col=7, value="RES")
-        self.resultGrid.SetColSize(col=7, width=40)
-        self.resultGrid.SetColLabelValue(col=8, value="ACC")
-        self.resultGrid.SetColSize(col=8, width=40)
-        self.resultGrid.SetColLabelValue(col=9, value="EHP")
-        self.resultGrid.SetColSize(col=9, width=60)
-        self.resultGrid.SetColLabelValue(col=10, value="DMG")
-        self.resultGrid.SetColSize(col=10, width=50)
-        self.resultGrid.SetColLabelSize(height=20)
-        self.resultGrid.SetDefaultRowSize(height=20)
-        self.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.resultSelected, self.resultGrid)
-
-        # Paginator
-        self.pgPrevBt = wx.Button(parent=pnl, id=-1, pos=(580, 30), size=(40, 50), style=wx.LC_REPORT, label="Prev\npage")
-        self.resultsPageIndicator = wx.StaticText(parent=pnl, label="1/1",id=-1, pos=(580, 80), size=(40, 15), style=wx.ALIGN_CENTRE_HORIZONTAL)
-        self.pgNextBt = wx.Button(parent=pnl, id=-1, pos=(580, 100), size=(40, 50), style=wx.LC_REPORT, label="Next\npage")
-        self.Bind(wx.EVT_BUTTON, self.pgPrev, self.pgPrevBt)
-        self.Bind(wx.EVT_BUTTON, self.pgNext, self.pgNextBt)
-
-        self.resultContent = wx.BoxSizer(wx.VERTICAL)
-
-        # Stats table
-        self.statGrid = wx.grid.Grid(parent=pnl, id=-1, pos=(650, 30), size=(205, 220), style=wx.LC_REPORT)
-        self.statGrid.CreateGrid(numRows=10, numCols=2, selmode=wx.grid.Grid.GridSelectionModes.SelectRowsOrColumns)
-        self.statGrid.EnableEditing(False)
-        self.statGrid.SetDefaultCellAlignment(horiz=wx.ALIGN_RIGHT, vert=wx.ALIGN_CENTRE)
-        monospaceFont = wx.Font(10, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
-        self.statGrid.SetDefaultCellFont(monospaceFont)
-        self.statGrid.SetColSize(col=0, width=70)
-        self.statGrid.SetColLabelValue(col=0, value="Value")
-        self.statGrid.SetColLabelValue(col=1, value="Diff")
-        self.statGrid.SetRowLabelSize(width=35)
-        self.statGrid.SetColLabelSize(height=20)
-        self.statGrid.SetRowSize(row=0, height=20)
-        self.statGrid.SetRowSize(row=1, height=20)
-        self.statGrid.SetRowSize(row=2, height=20)
-        self.statGrid.SetRowSize(row=3, height=20)
-        self.statGrid.SetRowSize(row=4, height=20)
-        self.statGrid.SetRowSize(row=5, height=20)
-        self.statGrid.SetRowSize(row=6, height=20)
-        self.statGrid.SetRowSize(row=7, height=20)
-        self.statGrid.SetRowSize(row=8, height=20)
-        self.statGrid.SetRowSize(row=9, height=20)
-        self.statGrid.SetRowLabelValue(row=0, value=" HP")
-        self.statGrid.SetRowLabelValue(row=1, value="ATK")
-        self.statGrid.SetRowLabelValue(row=2, value="DEF")
-        self.statGrid.SetRowLabelValue(row=3, value="SPD")
-        self.statGrid.SetRowLabelValue(row=4, value="CRR")
-        self.statGrid.SetRowLabelValue(row=5, value="CRD")
-        self.statGrid.SetRowLabelValue(row=6, value="RES")
-        self.statGrid.SetRowLabelValue(row=7, value="ACC")
-        self.statGrid.SetRowLabelValue(row=8, value="EHP")
-        self.statGrid.SetRowLabelValue(row=9, value="DMG")
-        self.resultContent.Add(self.statGrid)
-
-        monospaceFont = wx.Font(10, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
-        monospaceFont.PointSize -= 2
-        monospaceFontBold = wx.Font(10, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)
-        monospaceFontBold.PointSize -= 2
-        monospaceFontItalic = wx.Font(10, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_ITALIC, wx.FONTWEIGHT_NORMAL)
-        monospaceFontItalic.PointSize -= 2
-
-        #Rune set list
-        runeListBox = [
-          wx.StaticBox(parent=pnl, label="Slot1:",id=-1, pos=(200, 255), size=(140, 160)),
-          wx.StaticBox(parent=pnl, label="Slot2:",id=-1, pos=(350, 255), size=(140, 160)),
-          wx.StaticBox(parent=pnl, label="Slot3:",id=-1, pos=(350, 420), size=(140, 160)),
-          wx.StaticBox(parent=pnl, label="Slot4:",id=-1, pos=(200, 420), size=(140, 160)),
-          wx.StaticBox(parent=pnl, label="Slot5:",id=-1, pos=(50, 420), size=(140, 160)),
-          wx.StaticBox(parent=pnl, label="Slot6:",id=-1, pos=(50, 255), size=(140, 160)),
-        ]
-        for i in range(0, 6):
-            runeListBox[i].SetFont(monospaceFont)
-            self.resultContent.Add(runeListBox[i])
-
-        self.runeIds = [
-          wx.StaticText(runeListBox[0], label="",id=-1, pos=(5, 0), size=(120, 10)),
-          wx.StaticText(runeListBox[1], label="",id=-1, pos=(5, 0), size=(120, 10)),
-          wx.StaticText(runeListBox[2], label="",id=-1, pos=(5, 0), size=(120, 10)),
-          wx.StaticText(runeListBox[3], label="",id=-1, pos=(5, 0), size=(120, 10)),
-          wx.StaticText(runeListBox[4], label="",id=-1, pos=(5, 0), size=(120, 10)),
-          wx.StaticText(runeListBox[5], label="",id=-1, pos=(5, 0), size=(120, 10)),
-        ]
-
-        self.runeLocations = [
-          wx.StaticText(runeListBox[0], label="",id=-1, pos=(5, 15), size=(120, 10)),
-          wx.StaticText(runeListBox[1], label="",id=-1, pos=(5, 15), size=(120, 10)),
-          wx.StaticText(runeListBox[2], label="",id=-1, pos=(5, 15), size=(120, 10)),
-          wx.StaticText(runeListBox[3], label="",id=-1, pos=(5, 15), size=(120, 10)),
-          wx.StaticText(runeListBox[4], label="",id=-1, pos=(5, 15), size=(120, 10)),
-          wx.StaticText(runeListBox[5], label="",id=-1, pos=(5, 15), size=(120, 10)),
-        ]
-
-        self.runeSets = [
-          wx.StaticText(runeListBox[0], label="",id=-1, pos=(5, 30), size=(120, 10)),
-          wx.StaticText(runeListBox[1], label="",id=-1, pos=(5, 30), size=(120, 10)),
-          wx.StaticText(runeListBox[2], label="",id=-1, pos=(5, 30), size=(120, 10)),
-          wx.StaticText(runeListBox[3], label="",id=-1, pos=(5, 30), size=(120, 10)),
-          wx.StaticText(runeListBox[4], label="",id=-1, pos=(5, 30), size=(120, 10)),
-          wx.StaticText(runeListBox[5], label="",id=-1, pos=(5, 30), size=(120, 10)),
-        ]
-
-        wx.StaticLine(parent=runeListBox[0], id=-1, pos=(5, 45), size=(130, 1), style=wx.LC_REPORT)
-        wx.StaticLine(parent=runeListBox[1], id=-1, pos=(5, 45), size=(130, 1), style=wx.LC_REPORT)
-        wx.StaticLine(parent=runeListBox[2], id=-1, pos=(5, 45), size=(130, 1), style=wx.LC_REPORT)
-        wx.StaticLine(parent=runeListBox[3], id=-1, pos=(5, 45), size=(130, 1), style=wx.LC_REPORT)
-        wx.StaticLine(parent=runeListBox[4], id=-1, pos=(5, 45), size=(130, 1), style=wx.LC_REPORT)
-        wx.StaticLine(parent=runeListBox[5], id=-1, pos=(5, 45), size=(130, 1), style=wx.LC_REPORT)
-
-        self.runeMains = [
-          wx.StaticText(runeListBox[0], label="",id=-1, pos=(5, 50), size=(120, 10)),
-          wx.StaticText(runeListBox[1], label="",id=-1, pos=(5, 50), size=(120, 10)),
-          wx.StaticText(runeListBox[2], label="",id=-1, pos=(5, 50), size=(120, 10)),
-          wx.StaticText(runeListBox[3], label="",id=-1, pos=(5, 50), size=(120, 10)),
-          wx.StaticText(runeListBox[4], label="",id=-1, pos=(5, 50), size=(120, 10)),
-          wx.StaticText(runeListBox[5], label="",id=-1, pos=(5, 50), size=(120, 10)),
-        ]
-        for i in range(0, 6):
-            self.runeMains[i].SetFont(monospaceFontBold)
-
-        self.runeInnates = [
-          wx.StaticText(runeListBox[0], label="",id=-1, pos=(5, 65), size=(120, 10)),
-          wx.StaticText(runeListBox[1], label="",id=-1, pos=(5, 65), size=(120, 10)),
-          wx.StaticText(runeListBox[2], label="",id=-1, pos=(5, 65), size=(120, 10)),
-          wx.StaticText(runeListBox[3], label="",id=-1, pos=(5, 65), size=(120, 10)),
-          wx.StaticText(runeListBox[4], label="",id=-1, pos=(5, 65), size=(120, 10)),
-          wx.StaticText(runeListBox[5], label="",id=-1, pos=(5, 65), size=(120, 10)),
-        ]
-        for i in range(0, 6):
-            self.runeInnates[i].SetFont(monospaceFontItalic)
-
-        self.runeStats = [
-          [
-            wx.StaticText(runeListBox[0], label="",id=-1, pos=(5, 80), size=(120, 10)),
-            wx.StaticText(runeListBox[0], label="",id=-1, pos=(5, 95), size=(120, 10)),
-            wx.StaticText(runeListBox[0], label="",id=-1, pos=(5, 110), size=(120, 10)),
-            wx.StaticText(runeListBox[0], label="",id=-1, pos=(5, 125), size=(120, 10))
-          ],
-          [
-            wx.StaticText(runeListBox[1], label="",id=-1, pos=(5, 80), size=(120, 10)),
-            wx.StaticText(runeListBox[1], label="",id=-1, pos=(5, 95), size=(120, 10)),
-            wx.StaticText(runeListBox[1], label="",id=-1, pos=(5, 110), size=(120, 10)),
-            wx.StaticText(runeListBox[1], label="",id=-1, pos=(5, 125), size=(120, 10))
-          ],
-          [
-            wx.StaticText(runeListBox[2], label="",id=-1, pos=(5, 80), size=(120, 10)),
-            wx.StaticText(runeListBox[2], label="",id=-1, pos=(5, 95), size=(120, 10)),
-            wx.StaticText(runeListBox[2], label="",id=-1, pos=(5, 110), size=(120, 10)),
-            wx.StaticText(runeListBox[2], label="",id=-1, pos=(5, 125), size=(120, 10))
-          ],
-          [
-            wx.StaticText(runeListBox[3], label="",id=-1, pos=(5, 80), size=(120, 10)),
-            wx.StaticText(runeListBox[3], label="",id=-1, pos=(5, 95), size=(120, 10)),
-            wx.StaticText(runeListBox[3], label="",id=-1, pos=(5, 110), size=(120, 10)),
-            wx.StaticText(runeListBox[3], label="",id=-1, pos=(5, 125), size=(120, 10))
-          ],
-          [
-            wx.StaticText(runeListBox[4], label="",id=-1, pos=(5, 80), size=(120, 10)),
-            wx.StaticText(runeListBox[4], label="",id=-1, pos=(5, 95), size=(120, 10)),
-            wx.StaticText(runeListBox[4], label="",id=-1, pos=(5, 110), size=(120, 10)),
-            wx.StaticText(runeListBox[4], label="",id=-1, pos=(5, 125), size=(120, 10))
-          ],
-          [
-            wx.StaticText(runeListBox[5], label="",id=-1, pos=(5, 80), size=(120, 10)),
-            wx.StaticText(runeListBox[5], label="",id=-1, pos=(5, 95), size=(120, 10)),
-            wx.StaticText(runeListBox[5], label="",id=-1, pos=(5, 110), size=(120, 10)),
-            wx.StaticText(runeListBox[5], label="",id=-1, pos=(5, 125), size=(120, 10))
-          ],
-        ]
-
-        # Action buttons
-        applyBt = wx.Button(parent=pnl, id=-1, pos=(650, 330), size=(165, 60), style=wx.LC_REPORT, label="Apply runes")
-        self.Bind(wx.EVT_BUTTON, self.applyRunes, applyBt)
-        self.resultContent.Add(applyBt)
-        closeBt = wx.Button(parent=pnl, id=-1, pos=(650, 430), size=(165, 60), style=wx.LC_REPORT, label="Close")
-        self.Bind(wx.EVT_BUTTON, self.closeWindow, closeBt)
-
-        # By default, hide everything
-        self.resultContent.ShowItems(False)
-
-    def processResults(self, jsonData):
-        """Processes data obtained from RuneOptimizer.
-
-        Reads the JSON data and initializes the property data.
-        Automatically calls printResults();
-
-        Parameters
-        ----------
-        jsonData : str
-            The data, as received from RuneOptimizer.
-
-        """
-
-        self.data = json.loads(jsonData, object_hook=lambda d: SimpleNamespace(**d))
-        self.totalPages = math.ceil(len(self.data.results) / self.linesPerPage)
-        self.printResults()
-
-    def pgPrev(self, event):
-        """Goes to the previous page of results.
-
-        Checks if there is a previous page to go to. If so, it
-        automatically calls printResults();
-
-        Parameters
-        ----------
-        event : wxEvent, optional
-            The event that triggered the call (default is None).
-
-        """
-
-        if self.page > 0:
-            self.page -= 1
-            self.printResults()
-
-    def pgNext(self, event):
-        """Goes to the next page of results.
-
-        Checks if there is a next page to go to. If so, it
-        automatically calls printResults();
-
-        Parameters
-        ----------
-        event : wxEvent, optional
-            The event that triggered the call (default is None).
-
-        """
-
-        if self.page < self.totalPages:
-            self.page += 1
-            self.printResults()
-
-    def closeWindow(self, event):
-        """Closes the window.
-
-        Parameters
-        ----------
-        event : wxEvent, optional
-            The event that triggered the call (default is None).
-
-        """
-
-        self.Close(True)
-
-    def applyRunes(self, event):
-        """Applies the selected results and saves data to the database.
-
-        Parameters
-        ----------
-        event : wxEvent, optional
-            The event that triggered the call (default is None).
-
-        """
-
-        global conn
-
-        if (self.selectedResultIndex < 0):
-            # TODO: Show error
-            return;
-        print("self.selectedResultIndex: " + str(self.selectedResultIndex))
-        for i in range(0, 6):
-            print(self.data.results[self.selectedResultIndex].runes[i])
-        # First, unassign all runes currently assigned to the unit
-        cursor = conn.execute(
-          """
-            UPDATE runes SET unit = null
-            WHERE unit = ?
-          """,
-          (
-            self.unitId,
-          )
-        )
-
-        # Next, mark units as modified
-        cursor = conn.execute(
-          """
-            UPDATE units SET modified = 1
-            WHERE id = ? OR id IN (SELECT unit FROM runes WHERE id IN (?, ?, ?, ?, ?, ?))
-          """,
-          (
-            self.unitId,
-            self.data.results[self.selectedResultIndex].runes[0],
-            self.data.results[self.selectedResultIndex].runes[1],
-            self.data.results[self.selectedResultIndex].runes[2],
-            self.data.results[self.selectedResultIndex].runes[3],
-            self.data.results[self.selectedResultIndex].runes[4],
-            self.data.results[self.selectedResultIndex].runes[5]
-          )
-        )
-
-        # Lastly, assign the runes
-        cursor = conn.execute(
-          """
-            UPDATE runes SET unit = ?
-            WHERE id IN (?, ?, ?, ?, ?, ?)
-          """,
-          (
-            self.unitId,
-            self.data.results[self.selectedResultIndex].runes[0],
-            self.data.results[self.selectedResultIndex].runes[1],
-            self.data.results[self.selectedResultIndex].runes[2],
-            self.data.results[self.selectedResultIndex].runes[3],
-            self.data.results[self.selectedResultIndex].runes[4],
-            self.data.results[self.selectedResultIndex].runes[5]
-          )
-        )
-        conn.commit()
-        # TODO: Recalculate all modified units stats from the database
-        print("Applied!")
-        recalculteStatsOfModifiedUnits()
-        print("All recalculated!")
-
-        # Fetch the new values for self.currentStats
-        cursor = conn.execute(
-          """
-            SELECT
-              current_hp,
-              current_atk,
-              current_def,
-              current_spd,
-              current_crr,
-              current_crd,
-              current_res,
-              current_acc
-            FROM units
-            WHERE id = ?
-          """,
-          (
-            self.unitId,
-          )
-        )
-        row = cursor.fetchone()
-        for i in range(0, 8):
-            self.currentStats[i] = int(row[i])
-        self.currentStats[9] = math.ceil((((self.currentStats[2] * 3.5) + 1140) * self.currentStats[0]) / 1000)
-        self.currentStats[10] = math.ceil((self.currentStats[1] * (100 - self.currentStats[4]) / 100) + ((self.currentStats[1] + (self.currentStats[1] * self.currentStats[5] / 100)) * self.currentStats[4] / 100));
-        self.resultSelected(None)
-
-    def printResults(self):
-        """Populates the results table with the results in the
-        currently selected page.
-
-        It doesn't change the selcted result. Automaticcaly called
-        after changing pages or processing data.
-        """
-
-        self.pgPrevBt.Enable(True)
-        self.pgNextBt.Enable(True)
-        if self.totalPages == 1:
-            self.pgPrevBt.Enable(False)
-            self.pgNextBt.Enable(False)
-        elif self.page == 0:
-            self.pgPrevBt.Enable(False)
-        elif self.page + 1 == self.totalPages:
-            self.pgNextBt.Enable(False)
-        self.resultsPageIndicator.SetLabel(str(self.page + 1) + "/" + str(self.totalPages))
-        #for result in self.data.results:
-        for i in range(0, 10):
-            rindex = i + (self.linesPerPage * self.page)
-            if (len(self.data.results) > rindex):
-                self.resultGrid.SetRowLabelValue(row=i, value=str(rindex + 1))
-                result = self.data.results[rindex]
-                self.resultGrid.SetCellValue(row=i, col=0, s=str(result.rating))
-                self.resultGrid.SetCellValue(row=i, col=1, s=str(result.hp))
-                self.resultGrid.SetCellValue(row=i, col=2, s=str(result.atk))
-                self.resultGrid.SetCellValue(row=i, col=3, s=str(result.dfc))
-                self.resultGrid.SetCellValue(row=i, col=4, s=str(result.spd))
-                self.resultGrid.SetCellValue(row=i, col=5, s=str(result.crr))
-                self.resultGrid.SetCellValue(row=i, col=6, s=str(result.crd))
-                self.resultGrid.SetCellValue(row=i, col=7, s=str(result.res))
-                self.resultGrid.SetCellValue(row=i, col=8, s=str(result.acc))
-                self.resultGrid.SetCellValue(row=i, col=9, s=str(result.ehp))
-                self.resultGrid.SetCellValue(row=i, col=10, s=str(result.dmg))
-            else:
-                self.resultGrid.SetRowLabelValue(row=i, value="")
-                for j in range(0, 11):
-                    self.resultGrid.SetCellValue(row=i, col=j, s="")
-
-    def resultSelected(self, event):
-        """Populates and shows the runes and effective stats with the
-        currently seleced result.
-
-        It also enables the apply button.
-
-        Parameters
-        ----------
-        event : wxEvent, optional
-            The event that triggered the call (default is None).
-
-        """
-
-        selectedLine = self.resultGrid.GetSelectedRows()[0]
-        self.selectedResultIndex = selectedLine + (self.linesPerPage * self.page)
-        self.statGrid.SetCellValue(row=0, col=0, s=str(self.data.results[self.selectedResultIndex].hp) + " ")
-        diff = self.data.results[self.selectedResultIndex].hp - self.currentStats[0]
-        if (diff < 0):
-            self.statGrid.SetCellValue(row=0, col=1, s="- " + str(abs(diff)) + " ")
-            self.statGrid.SetCellTextColour(row=0, col=1, colour=wx.Colour(red=255, green=0, blue=0))
-        elif (diff > 0):
-            self.statGrid.SetCellValue(row=0, col=1, s="+ " + str(diff) + " ")
-            self.statGrid.SetCellTextColour(row=0, col=1, colour=wx.Colour(red=0, green=255, blue=0))
-        else:
-            self.statGrid.SetCellValue(row=0, col=1, s="")
-
-        self.statGrid.SetCellValue(row=1, col=0, s=str(self.data.results[self.selectedResultIndex].atk) + " ")
-        diff = self.data.results[self.selectedResultIndex].atk - self.currentStats[1]
-        if (diff < 0):
-            self.statGrid.SetCellValue(row=1, col=1, s="- " + str(abs(diff)) + " ")
-            self.statGrid.SetCellTextColour(row=1, col=1, colour=wx.Colour(red=255, green=0, blue=0))
-        elif (diff > 0):
-            self.statGrid.SetCellValue(row=1, col=1, s="+ " + str(diff) + " ")
-            self.statGrid.SetCellTextColour(row=1, col=1, colour=wx.Colour(red=0, green=255, blue=0))
-        else:
-            self.statGrid.SetCellValue(row=1, col=1, s="")
-
-        self.statGrid.SetCellValue(row=2, col=0, s=str(self.data.results[self.selectedResultIndex].dfc) + " ")
-        diff = self.data.results[self.selectedResultIndex].atk - self.currentStats[2]
-        if (diff < 0):
-            self.statGrid.SetCellValue(row=2, col=1, s="- " + str(abs(diff)) + " ")
-            self.statGrid.SetCellTextColour(row=1, col=1, colour=wx.Colour(red=255, green=0, blue=0))
-        elif (diff > 0):
-            self.statGrid.SetCellValue(row=2, col=1, s="+ " + str(diff) + " ")
-            self.statGrid.SetCellTextColour(row=2, col=1, colour=wx.Colour(red=0, green=255, blue=0))
-        else:
-            self.statGrid.SetCellValue(row=2, col=1, s="")
-
-        self.statGrid.SetCellValue(row=3, col=0, s=str(self.data.results[self.selectedResultIndex].spd) + " ")
-        diff = self.data.results[self.selectedResultIndex].spd - self.currentStats[3]
-        if (diff < 0):
-            self.statGrid.SetCellValue(row=3, col=1, s="- " + str(abs(diff)) + " ")
-            self.statGrid.SetCellTextColour(row=3, col=1, colour=wx.Colour(red=255, green=0, blue=0))
-        elif (diff > 0):
-            self.statGrid.SetCellValue(row=3, col=1, s="+ " + str(diff) + " ")
-            self.statGrid.SetCellTextColour(row=3, col=1, colour=wx.Colour(red=0, green=255, blue=0))
-        else:
-            self.statGrid.SetCellValue(row=3, col=1, s="")
-
-
-        self.statGrid.SetCellValue(row=4, col=0, s=str(self.data.results[self.selectedResultIndex].crr) + "%")
-        diff = self.data.results[self.selectedResultIndex].crr - self.currentStats[4]
-        if (diff < 0):
-            self.statGrid.SetCellValue(row=4, col=1, s="- " + str(abs(diff)) + "%")
-            self.statGrid.SetCellTextColour(row=4, col=1, colour=wx.Colour(red=255, green=0, blue=0))
-        elif (diff > 0):
-            self.statGrid.SetCellValue(row=4, col=1, s="+ " + str(diff) + "%")
-            self.statGrid.SetCellTextColour(row=4, col=1, colour=wx.Colour(red=0, green=255, blue=0))
-        else:
-            self.statGrid.SetCellValue(row=4, col=1, s="")
-
-        self.statGrid.SetCellValue(row=5, col=0, s=str(self.data.results[self.selectedResultIndex].crd) + "%")
-        diff = self.data.results[self.selectedResultIndex].crd - self.currentStats[5]
-        if (diff < 0):
-            self.statGrid.SetCellValue(row=5, col=1, s="- " + str(abs(diff)) + "%")
-            self.statGrid.SetCellTextColour(row=5, col=1, colour=wx.Colour(red=255, green=0, blue=0))
-        elif (diff > 0):
-            self.statGrid.SetCellValue(row=5, col=1, s="+ " + str(diff) + "%")
-            self.statGrid.SetCellTextColour(row=5, col=1, colour=wx.Colour(red=0, green=255, blue=0))
-        else:
-            self.statGrid.SetCellValue(row=5, col=1, s="")
-
-        self.statGrid.SetCellValue(row=6, col=0, s=str(self.data.results[self.selectedResultIndex].res) + "%")
-        diff = self.data.results[self.selectedResultIndex].res - self.currentStats[6]
-        if (diff < 0):
-            self.statGrid.SetCellValue(row=6, col=1, s="- " + str(abs(diff)) + "%")
-            self.statGrid.SetCellTextColour(row=6, col=1, colour=wx.Colour(red=255, green=0, blue=0))
-        elif (diff > 0):
-            self.statGrid.SetCellValue(row=6, col=1, s="+ " + str(diff) + "%")
-            self.statGrid.SetCellTextColour(row=6, col=1, colour=wx.Colour(red=0, green=255, blue=0))
-        else:
-            self.statGrid.SetCellValue(row=6, col=1, s="")
-
-        self.statGrid.SetCellValue(row=7, col=0, s=str(self.data.results[self.selectedResultIndex].acc) + "%")
-        diff = self.data.results[self.selectedResultIndex].acc - self.currentStats[7]
-        if (diff < 0):
-            self.statGrid.SetCellValue(row=7, col=1, s="- " + str(abs(diff)) + "%")
-            self.statGrid.SetCellTextColour(row=7, col=1, colour=wx.Colour(red=255, green=0, blue=0))
-        elif (diff > 0):
-            self.statGrid.SetCellValue(row=7, col=1, s="+ " + str(diff) + "%")
-            self.statGrid.SetCellTextColour(row=7, col=1, colour=wx.Colour(red=0, green=255, blue=0))
-        else:
-            self.statGrid.SetCellValue(row=7, col=1, s="")
-
-        self.statGrid.SetCellValue(row=8, col=0, s=str(self.data.results[self.selectedResultIndex].ehp) + " ")
-        diff = self.data.results[self.selectedResultIndex].ehp - self.currentStats[8]
-        if (diff < 0):
-            self.statGrid.SetCellValue(row=8, col=1, s="- " + str(abs(diff)) + " ")
-            self.statGrid.SetCellTextColour(row=8, col=1, colour=wx.Colour(red=255, green=0, blue=0))
-        elif (diff > 0):
-            self.statGrid.SetCellValue(row=8, col=1, s="+ " + str(diff) + " ")
-            self.statGrid.SetCellTextColour(row=8, col=1, colour=wx.Colour(red=0, green=255, blue=0))
-        else:
-            self.statGrid.SetCellValue(row=8, col=1, s="")
-
-        self.statGrid.SetCellValue(row=9, col=0, s=str(self.data.results[self.selectedResultIndex].dmg) + " ")
-        diff = self.data.results[self.selectedResultIndex].dmg - self.currentStats[9]
-        if (diff < 0):
-            self.statGrid.SetCellValue(row=9, col=1, s="- " + str(abs(diff)) + " ")
-            self.statGrid.SetCellTextColour(row=9, col=1, colour=wx.Colour(red=255, green=0, blue=0))
-        elif (diff > 0):
-            self.statGrid.SetCellValue(row=9, col=1, s="+ " + str(diff) + " ")
-            self.statGrid.SetCellTextColour(row=9, col=1, colour=wx.Colour(red=0, green=255, blue=0))
-        else:
-            self.statGrid.SetCellValue(row=9, col=1, s="")
-
-        self.resultContent.ShowItems(True)
-
-        # Clean the runes
-        for i in range(0, 5):
-            self.runeIds[i].SetLabel("")
-            self.runeLocations[i].SetLabel("")
-            self.runeSets[i].SetLabel("")
-            self.runeMains[i].SetLabel("")
-            self.runeInnates[i].SetLabel("")
-            for j in range(0, 3):
-                self.runeStats[i][j].SetLabel("")
-
-        # Display the runes
-        cursor = conn.execute(
-          """
-            SELECT runes.id, runes.slot, runes.type, runes.level, units.id, units.name
-            FROM runes LEFT JOIN units ON runes.unit = units.id
-            WHERE runes.id IN (?, ?, ?, ?, ?, ?)
-            ORDER BY runes.slot;
-          """,
-          (
-            self.data.results[self.selectedResultIndex].runes[0],
-            self.data.results[self.selectedResultIndex].runes[1],
-            self.data.results[self.selectedResultIndex].runes[2],
-            self.data.results[self.selectedResultIndex].runes[3],
-            self.data.results[self.selectedResultIndex].runes[4],
-            self.data.results[self.selectedResultIndex].runes[5]
-          )
-        )
-        i = 0
-        for row in cursor:
-            # Rows are 21 charactes width
-            self.runeIds[i].SetLabel(("#" + str(row[0])).rjust(21, " "))
-            if row[4] == None:
-                self.runeLocations[i].SetLabel("Storage")
-            else:
-                self.runeLocations[i].SetLabel(str(row[5])[0:9].ljust(9, " ") + " #" + str(row[4]) + "")
-            self.runeSets[i].SetLabel(set_names[row[2]].ljust(18, " ") + "+" + str(row[3]))
-            #print(self.data.results[self.selectedResultIndex].runes[i])
-            cursorStats = conn.execute(
-              """
-                SELECT
-                  slot, stat, value, grind, enchant
-                FROM rune_stats
-                WHERE
-                  rune = ?
-                ORDER BY slot;
-              """,
-              (self.data.results[self.selectedResultIndex].runes[i],)
-            )
-            for rowStats in cursorStats:
-                slot = rowStats[0]
-                if rowStats[4] == 1: # if enchanted
-                    name = (stat_names[rowStats[1]].replace("%", "").replace(" ", "") + " * ").rjust(8, " ")
-                else:
-                    name = (stat_names[rowStats[1]].replace("%", "").replace(" ", "") + "   ").rjust(8, " ")
-                value = str(rowStats[2])
-                if rowStats[1] in [2, 4, 6, 9, 10, 11, 23]:
-                    value = value + "%"
-                else:
-                    value = value + " "
-                value = value.rjust(5)
-                if rowStats[3] > 0: # if grinded
-                    value = value + "  + " + str(rowStats[3])
-                    if rowStats[1] in [2, 4, 6, 9, 10, 11, 23]:
-                        value = value + "%"
-                line = name + value
-                if slot == -1: # main
-                    self.runeMains[i].SetLabel(line)
-                elif slot == 0: # innate
-                    self.runeInnates[i].SetLabel(line)
-                else: # normal stats
-                    self.runeStats[i][slot - 1].SetLabel(line)
-            i += 1
-
-
-class RuneOptimizerFrame(wx.Frame):
-    """
-    The main application window.
-
-    Parameters
-    ----------
-    unitList : wx.ListCtrl
-        Selectable unit list with priorities.
-    unitContent : wx.BoxSizer
-        Holds every widget that is hidden until a unit is selected.
-    unitName : wx.StaticText
-        Label with the unit name.
-    statGrid : wx.Grid.grid
-        Table with the unit base and current stats.
-    runeList : wx.StaticText[6]
-        Labels with all the info about the currently equipped runes.
-    minStatSlider : wx.Slider[6]
-        List of sliders for the minimum selectors for each stat.
-    minStatText : wx.StaticText[6]
-        List of text inputs for the minimum selectors for each stat.
-    runeSets : wx.Choice[3]
-        List of selector to pik rune sets.
-    stats : wx.CheckListBox[2]
-        Tho selctors to choose stats allowed in optimization.
-    level : wx.Choice
-        Selector to pick the level for the rune optimization.
-    currentStats : int[10]
-        The current stats of the unit being optimized. (default is
-        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).
-    filterName : wx.TextCtrl
-        Text input to filter units names.
-    filterNames : wx.CheckBox
-        Checkbox to include or exclude units in storage.
-    filterNoRunes : wx.CheckBox
-        Checkbox to include or exclude units without runes.
-    filterNoTeams : wx.CheckBox
-        Checkbox to include or exclude units in no teams.
-
-    Methods
-    -------
-    processResults(jsonData)
-        Processes data obtained from RuneOptimizer.
-    populateUnitList(event)
-        Populates the unit list.
-    startOptimization(event)
-        Prepares and runs a command optimization.
-    minStatChangeBySlider(event)
-        Changes text when a slider is changed.
-    minStatChangeByTExt(event)
-        Changes the slider when the text is changed.
-    unitSelected(event)
-        Loads a unit info and enables optimizaton options.
-    makeMenuBar(event)
-        Creates the app menu bar.
-    closeApp(event)
-        Closes the app.
-    showAbout(event)
-        Display an About dialog.
-    updateFromJson(event)
-        Updates data from a JSON file.
-    updateFromSwdb(event)
-        Updates data from a JSON file.
-    updateFromSwarfarm(event)
-        Updates data from a JSON file.
-    updateFromSqlite(event)
-        Updates data from a JSON file.
-    showUnimplemented(parent, event)
-        Displays a message for unimplemented features.
-
-    """
-
-    unitList = None
-    unitContent = None
-    unitName = None
-    statGrid = None
-    runeList = None
-    minStatSlid = None
-    minStatText = None
-    runeSets = None
-    stats = None
-    level = None
-    currentStats = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
-    filterName = None
-    filterStorage = None
-    filterNoRunes = None
-    filterNoTeams = None
-
-    def __init__(self, *args, **kw):
-        """Initializes the class.
-
-        Sets upt all the widgets.
-
-        """
-
-        global conn
-        super(RuneOptimizerFrame, self).__init__(*args, **kw)
-
-        # Create and configure a
-        pnl = wx.Panel(self)
-        self.makeMenuBar()
-        self.CreateStatusBar()
-        self.SetStatusText("Status: Updated, no pending changes")
-
-        # Show unit list
-        wx.StaticText(parent=pnl, id=-1, label="Name                           Prio.     Sto.", pos=(10, 10), size=(190, 20))
-        self.unitList = wx.ListCtrl(parent=pnl, id=-1, pos=(10, 30), size=(190, 490), style=wx.LC_REPORT|wx.LC_NO_HEADER)
-        self.unitList.InsertColumn(0, "Name", width=120)
-        self.unitList.InsertColumn(1, "Prio.", width=40)
-        self.unitList.InsertColumn(2, "Sto.", width=30)
-
-        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.unitSelected, self.unitList)
-
-        # List filters
-        filterBox = wx.StaticBox(pnl, label="Filters:",id=-1, pos=(10, 520), size=(190, 150))
-        wx.StaticText(parent=filterBox, label="Monster name", pos=(5, 5), size=(180, 20))
-        self.filterName = wx.TextCtrl(parent=filterBox, id=-1, value="", pos=(5, 25), size=(177, 20), style=wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx0")
-        self.Bind(wx.EVT_TEXT_ENTER, self.populateUnitList, self.filterName)
-        self.filterStorage = wx.CheckBox(parent=filterBox, id=-1, label="Monsters in storage", pos=(5, 55), size=(180, 20))
-        self.filterStorage.SetValue(True)
-        self.Bind(wx.EVT_CHECKBOX, self.populateUnitList, self.filterStorage)
-        self.filterNoRunes = wx.CheckBox(parent=filterBox, id=-1, label="Monsters without runes", pos=(5, 75), size=(180, 20))
-        self.filterNoRunes.SetValue(True)
-        self.Bind(wx.EVT_CHECKBOX, self.populateUnitList, self.filterNoRunes)
-        self.filterNoTeams = wx.CheckBox(parent=filterBox, id=-1, label="Monsters not in teams", pos=(5, 95), size=(180, 20))
-        self.filterNoTeams.SetValue(True)
-        self.Bind(wx.EVT_CHECKBOX, self.populateUnitList, self.filterNoTeams)
-
-        self.populateUnitList(None)
-
-        # Begin with unit-specifica content
-        self.unitContent = wx.BoxSizer(wx.VERTICAL)
-
-        # Unit name
-        self.unitName = wx.StaticText(pnl, label="", pos=(210, 0), size=(200, 20))
-        font = self.unitName.GetFont()
-        font.PointSize += 2
-        font = font.Bold()
-        self.unitName.SetFont(font)
-        self.unitContent.Add(self.unitName)
-
-        # Stats table
-        self.statGrid = wx.grid.Grid(parent=pnl, id=-1, pos=(210, 30), size=(165, 220))
-        self.statGrid.CreateGrid(numRows=10, numCols=2, selmode=wx.grid.Grid.GridSelectionModes.SelectRowsOrColumns)
-        self.statGrid.EnableEditing(False)
-        self.statGrid.SetDefaultCellAlignment(horiz=wx.ALIGN_RIGHT, vert=wx.ALIGN_CENTRE)
-        monospaceFont = wx.Font(10, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
-        self.statGrid.SetDefaultCellFont(monospaceFont)
-        self.statGrid.SetColSize(col=0, width=50)
-        self.statGrid.SetColLabelValue(col=0, value="Base")
-        self.statGrid.SetColSize(col=0, width=50)
-        self.statGrid.SetColLabelValue(col=1, value="Current")
-        self.statGrid.SetRowLabelSize(width=35)
-        self.statGrid.SetColLabelSize(height=20)
-        self.statGrid.SetRowSize(row=0, height=20)
-        self.statGrid.SetRowSize(row=1, height=20)
-        self.statGrid.SetRowSize(row=2, height=20)
-        self.statGrid.SetRowSize(row=3, height=20)
-        self.statGrid.SetRowSize(row=4, height=20)
-        self.statGrid.SetRowSize(row=5, height=20)
-        self.statGrid.SetRowSize(row=6, height=20)
-        self.statGrid.SetRowSize(row=7, height=20)
-        self.statGrid.SetRowSize(row=8, height=20)
-        self.statGrid.SetRowSize(row=9, height=20)
-        self.statGrid.SetRowLabelValue(row=0, value=" HP")
-        self.statGrid.SetRowLabelValue(row=1, value="ATK")
-        self.statGrid.SetRowLabelValue(row=2, value="DEF")
-        self.statGrid.SetRowLabelValue(row=3, value="SPD")
-        self.statGrid.SetRowLabelValue(row=4, value="CRR")
-        self.statGrid.SetRowLabelValue(row=5, value="CRD")
-        self.statGrid.SetRowLabelValue(row=6, value="RES")
-        self.statGrid.SetRowLabelValue(row=7, value="ACC")
-        self.statGrid.SetRowLabelValue(row=8, value="EHP")
-        self.statGrid.SetRowLabelValue(row=9, value="DMG")
-        self.unitContent.Add(self.statGrid)
-
-        #Rune set list
-        runeListBox = [
-          wx.StaticBox(pnl, label="Slot1:",id=-1, pos=(465, 30), size=(80, 115)),
-          wx.StaticBox(pnl, label="Slot2:",id=-1, pos=(550, 30), size=(80, 115)),
-          wx.StaticBox(pnl, label="Slot3:",id=-1, pos=(550, 150), size=(80, 115)),
-          wx.StaticBox(pnl, label="Slot4:",id=-1, pos=(465, 150), size=(80, 115)),
-          wx.StaticBox(pnl, label="Slot5:",id=-1, pos=(380, 150), size=(80, 115)),
-          wx.StaticBox(pnl, label="Slot6:",id=-1, pos=(380, 30), size=(80, 115)),
-        ]
-        self.runeList = [
-          wx.StaticText(runeListBox[0], label="",id=-1, pos=(0, 0), size=(80, 115)),
-          wx.StaticText(runeListBox[1], label="",id=-1, pos=(0, 0), size=(80, 115)),
-          wx.StaticText(runeListBox[2], label="",id=-1, pos=(0, 0), size=(80, 115)),
-          wx.StaticText(runeListBox[3], label="",id=-1, pos=(0, 0), size=(80, 115)),
-          wx.StaticText(runeListBox[4], label="",id=-1, pos=(0, 0), size=(80, 115)),
-          wx.StaticText(runeListBox[5], label="",id=-1, pos=(0, 0), size=(80, 115)),
-        ]
-        monospaceFont.PointSize -= 2
-        for i in range(0, 6):
-            runeListBox[i].SetFont(monospaceFont)
-            self.unitContent.Add(runeListBox[i])
-        monospaceFont.PointSize += 2
-
-        # Create an update button:
-        #btUpdate = wx.Button(parent=pnl, id=-1, label="Update data", pos=(10,10), size=(100,40))
-        #btOptimize = wx.Button(parent=pnl, id=-1, label="Optimize unit", pos=(10,60), size=(100,40))
-
-        # Line to separate optimization parameters
-        optimizationSeparator = wx.StaticLine(parent=pnl, id=-1, pos=(210, 290), size=(650, 3), style=wx.LC_REPORT)
-        self.unitContent.Add(optimizationSeparator)
-
-        # Min stats
-        minStatBox = wx.StaticBox(pnl, label="Min. stats:",id=-1, pos=(220, 300), size=(260, 380))
-        wx.StaticText(minStatBox, label="HP",id=-1, pos=(0, 5), size=(30, 25))
-        wx.StaticText(minStatBox, label="ATK",id=-1, pos=(0, 35), size=(30, 25))
-        wx.StaticText(minStatBox, label="DEF",id=-1, pos=(0, 65), size=(30, 25))
-        wx.StaticText(minStatBox, label="SPD",id=-1, pos=(0, 95), size=(30, 25))
-        wx.StaticText(minStatBox, label="CRR",id=-1, pos=(0, 125), size=(30, 25))
-        wx.StaticText(minStatBox, label="CRD",id=-1, pos=(0, 155), size=(30, 25))
-        wx.StaticText(minStatBox, label="RES",id=-1, pos=(0, 185), size=(30, 25))
-        wx.StaticText(minStatBox, label="ACC",id=-1, pos=(0, 215), size=(30, 25))
-        wx.StaticText(minStatBox, label="EHP",id=-1, pos=(0, 245), size=(30, 25))
-        wx.StaticText(minStatBox, label="DMG",id=-1, pos=(0, 275), size=(30, 25))
-        self.minStatSlid = [
-            wx.Slider(minStatBox, id=-1, pos=(30, 0), size=(150, 30), name="slid0"),
-            wx.Slider(minStatBox, id=-1, pos=(30, 30), size=(150, 30), name="slid1"),
-            wx.Slider(minStatBox, id=-1, pos=(30, 60), size=(150, 30), name="slid2"),
-            wx.Slider(minStatBox, id=-1, pos=(30, 90), size=(150, 30), name="slid3"),
-            wx.Slider(minStatBox, id=-1, pos=(30, 120), size=(150, 30), name="slid4"),
-            wx.Slider(minStatBox, id=-1, pos=(30, 150), size=(150, 30), name="slid5"),
-            wx.Slider(minStatBox, id=-1, pos=(30, 180), size=(150, 30), name="slid6"),
-            wx.Slider(minStatBox, id=-1, pos=(30, 210), size=(150, 30), name="slid7"),
-            wx.Slider(minStatBox, id=-1, pos=(30, 240), size=(150, 30), name="slid8"),
-            wx.Slider(minStatBox, id=-1, pos=(30, 270), size=(150, 30), name="slid9")
-        ]
-        for i in range(0, 9):
-            self.minStatSlid[i].SetMin(0)
-            self.minStatSlid[i].SetMax(0)
-            self.minStatSlid[i].SetValue(0)
-            self.Bind(wx.EVT_SCROLL, self.minStatChangeBySlider, self.minStatSlid[i])
-        self.minStatText = [
-            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 0), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx0"),
-            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 30), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx1"),
-            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 60), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx2"),
-            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 90), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx3"),
-            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 120), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx4"),
-            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 150), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx5"),
-            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 180), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx6"),
-            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 210), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx7"),
-            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 240), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx8"),
-            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 270), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx9")
-        ]
-        for i in range(0, 9):
-            self.Bind(wx.EVT_TEXT_ENTER, self.minStatChangeByText, self.minStatText[i])
-        minStatsReset = wx.Button(parent=minStatBox, id=-1, pos=(10, 300), size=(100, 40), style=wx.LC_REPORT, label="Reset all")
-        minStatsAdapt = wx.Button(parent=minStatBox, id=-1, pos=(120, 300), size=(100, 40), style=wx.LC_REPORT, label="Adapt all")
-        # TODO: Add binds
-        self.unitContent.Add(minStatBox)
-
-        # Rune sets
-        names = [
-          "",        "ENERGY ", "GUARD  ", "SWIFT  ", "BLADE  ", "RAGE   ",
-          "FOCUS  ", "ENDURE ", "FATAL  ", "DESPAIR", "VAMPIRE", "VIOLENT",
-          "NEMESIS", "WILL   ", "SHIELD ", "REVENGE", "DESTROY", "FIGHT  ",
-          "DETERMI", "ENHANCE", "ACCURAC", "TOLERAN"
-        ]
-        setBox = wx.StaticBox(pnl, label="Rune Sets:",id=-1, pos=(500, 300), size=(330, 65))
-        self.runeSets = [
-            wx.Choice(parent=setBox, id=-1, pos=(5, 0), choices=names),
-            wx.Choice(parent=setBox, id=-1, pos=(110, 0), choices=names),
-            wx.Choice(parent=setBox, id=-1, pos=(215, 0), choices=names)
-        ]
-        self.unitContent.Add(setBox)
-
-        # Allowed main stats for even slots
-        names = [
-          ["HP  ", "HP% ", "ATK ", "ATK%", "DEF ", "DEF%"],
-          ["SPD ", "CRR ", "CRD ", "RES ", "ACC "]
-        ]
-        statBox = wx.StaticBox(pnl, label="Main stats (2, 4, 6):",id=-1, pos=(500, 380), size=(150, 190))
-        self.stats = [
-          wx.CheckListBox(parent=statBox, id=-1, pos=(5, 5), size=(70, 155), choices=names[0]),
-          wx.CheckListBox(parent=statBox, id=-1, pos=(70, 5), size=(70, 155), choices=names[1])
-        ]
-        self.unitContent.Add(statBox)
-
-        levelBox = wx.StaticBox(pnl, label="Rune Level:",id=-1, pos=(700, 380), size=(100, 65))
-        self.level = wx.Choice(parent=levelBox, id=-1, pos=(5, 0), choices=["Current", "+ 12", " + 15"])
-        self.unitContent.Add(levelBox)
-
-        # Button to start
-        btOptimize = wx.Button(parent=pnl, id=-1, pos=(700, 480), size=(100, 40), style=wx.LC_REPORT, label="OPTIMIZE")
-        self.unitContent.Add(btOptimize)
-        self.Bind(wx.EVT_BUTTON, self.startOptimization, btOptimize)
-
-
-        self.unitContent.ShowItems(False)
-
-    def populateUnitList(self, event):
-        """Populates the unit list.
-
-        Uses the filters.
-
-        Parameters
-        ----------
-        event : wxEvent, optional
-            The event that triggered the call (default is None).
-
-        """
-
-        # Get units from db
-        name = self.filterName.GetValue()
-        query = """
-          SELECT
-            id,
-            name,
-            (
-              SELECT cast(total(teams.priority) as int)
-              FROM teams, units_teams
-              WHERE teams.id = units_teams.team AND units_teams.unit = units.id
-            ) as priority,
-            storage
-          FROM units
-          WHERE
-            name LIKE '%""" + name + """%'
-        """
-        if self.filterStorage.GetValue() == False:
-            query += " AND storage = 0 "
-        if self.filterNoRunes.GetValue() == False:
-            query += " AND id IN (SELECT DISTINCT unit FROM runes) "
-        if self.filterNoTeams.GetValue() == False:
-            query += " AND id IN (SELECT DISTINCT unit FROM units_teams) "
-        query += " ORDER BY priority DESC; ";
-        print(query)
-        cursor = conn.execute(query)
-        i = 0
-        self.unitList.DeleteAllItems()
-        for row in cursor:
-            self.unitList.InsertItem(i, row[1])
-            self.unitList.SetItem(i, 1, str(row[2]))
-            self.unitList.SetItem(i, 2, "")
-            self.unitList.SetItemData(i, int(row[0]))
-            if (int(row[3]) == 1):
-                self.unitList.SetItem(i, 2, "X")
-            else:
-                self.unitList.SetItem(i, 2, " ")
-            i = i + 1
-
-    def startOptimization(self, event):
-        """Prepares and runs a command optimization.
-
-        Once is done, opens a ResultsFrame.
-
-        Parameters
-        ----------
-        event : wxEvent, optional
-            The event that triggered the call (default is None).
-
-        """
-
-        # Example call:
-        # ../RuneOptimizer optimize 7223811472 -l 15 -e rage,blade --stats atk,crr,crd -h 10000 -f 10
-        command = "RuneOptimizer optimize "
-        #print("StartOptimization...")
-        unitId = str(self.unitList.GetItemData(self.unitList.GetFirstSelected()))
-        command += unitId
-        #print("    Unit ID: " + unitId)
-        level = self.level.GetSelection()
-        if level == 1:
-            level = "12"
-        elif level == 2:
-            level = "15"
-        else:
-            level = "current"
-        command += (" --level " + level)
-        #print("    Rune level: " + level)
-        sets = ""
-        for i in range (0, 2):
-            selected = self.runeSets[i].GetString(self.runeSets[i].GetSelection()).upper().replace(" ", "");
-            for j in range(0, 22):
-                name = set_names[j].upper()
-                if len(name) > 7:
-                    name = name[0:7]
-                #print(selected + " - " + name)
-                if selected == name:
-                    sets += set_names[j].lower() + ","
-        if len(sets) > 0:
-            sets = sets[:-1]
-            # TODO: ELSE ERROR
-        command += (" --sets " + sets)
-        stats = ""
-        selected_stats = self.stats[0].GetCheckedItems() + self.stats[1].GetCheckedItems()
-        for s in self.stats[0].GetCheckedItems():
-            if s == 0:
-                stats += "hpflat,"
-            elif s == 1:
-                stats += "hp,"
-            elif s == 2:
-                stats += "atkflat,"
-            elif s == 3:
-                stats += "atk,"
-            elif s == 4:
-                stats += "defflat,"
-            elif s == 5:
-                stats += "def,"
-        for s in self.stats[1].GetCheckedItems():
-            if s == 0:
-                stats += "spd,"
-            elif s == 1:
-                stats += "crr,"
-            elif s == 2:
-                stats += "crd,"
-            elif s == 3:
-                stats += "res,"
-            elif s == 4:
-                stats += "acc,"
-        if len(stats) > 0:
-            stats = stats[:-1]
-            # TODO: ELSE ERROR
-        command += (" --stats " + stats)
-        #print("    Main stats: " + stats)
-
-        command += (" --min-hp " + str(self.minStatSlid[0].GetValue()))
-        command += (" --min-atk " + str(self.minStatSlid[1].GetValue()))
-        command += (" --min-def " + str(self.minStatSlid[2].GetValue()))
-        command += (" --min-spd " + str(self.minStatSlid[3].GetValue()))
-        command += (" --min-crr " + str(self.minStatSlid[4].GetValue()))
-        command += (" --min-crd " + str(self.minStatSlid[5].GetValue()))
-        command += (" --min-res " + str(self.minStatSlid[6].GetValue()))
-        command += (" --min-acc " + str(self.minStatSlid[7].GetValue()))
-        command += (" --min-ehp " + str(self.minStatSlid[8].GetValue()))
-        command += (" --min-dmg " + str(self.minStatSlid[9].GetValue()))
-
-
-        command += (" --gui ")
-        print("Command: " + command)
-
-        command = "../../" + command
-        out = subprocess.check_output(command.split())
-        #print ("---- OUTPUT ------------------------------------------------------------------------------------------")
-        #print(out)
-        #print ("------------------------------------------------------------------------------------------------------")
-
-        resultsFrame = ResultsFrame(None, title='Options for Lushen (DEBUG)', pos=(100, 50), size=(900, 680))
-        resultsFrame.unitId = unitId
-        resultsFrame.unitName = self.unitNameValue
-        resultsFrame.currentStats = self.currentStats
-        resultsFrame.processResults(bytes.decode(out))
-        resultsFrame.Show()
-
-    def minStatChangeBySlider(self, event):
-        """Changes text when a slider is changed.
-
-        Doesn't do validation.
-
-        Parameters
-        ----------
-        event : wxEvent, optional
-            The event that triggered the call (default is None).
-
-        """
-
-        slidId = int(event.GetEventObject().GetName().replace("slid", ""))
-        self.minStatText[slidId].SetValue(str(event.GetEventObject().GetValue()))
-
-    def minStatChangeByText(self, event):
-        """Changes the slider when the text is changed.
-
-        Validates the text value.
-
-        Parameters
-        ----------
-        event : wxEvent, optional
-            The event that triggered the call (default is None).
-
-        """
-        textId = int(event.GetEventObject().GetName().replace("tx", ""))
-        if event.GetEventObject().GetValue().isdigit() == False:
-            event.GetEventObject().SetValue(str(self.minStatSlid[textId].GetValue()))
-        value = int(event.GetEventObject().GetValue())
-        minValue = self.minStatSlid[textId].GetMin()
-        maxValue = self.minStatSlid[textId].GetMax()
-        if value < minValue:
-            value = minValue
-            event.GetEventObject().SetValue(str(value))
-        elif value > maxValue:
-            value = maxValue
-            event.GetEventObject().SetValue(str(value))
-        self.minStatSlid[textId].SetValue(value)
-
-    def unitSelected(self, event):
-        """Loads a unit info and enables optimizaton options.
-
-        Called when a unit is selected from the list.
-
-        Parameters
-        ----------
-        event : wxEvent, optional
-            The event that triggered the call (default is None).
-
-        """
-
-        id = str(event.GetEventObject().GetItemData(event.GetEventObject().GetFirstSelected()))
-
-        # First, set sliders max values
-        self.minStatSlid[0].SetMax(50000)  #HP
-        self.minStatSlid[1].SetMax(5000)   #ATK
-        self.minStatSlid[2].SetMax(5000)   #DEF
-        self.minStatSlid[3].SetMax(500)    #SPD
-        self.minStatSlid[4].SetMax(100)    #CRR
-        self.minStatSlid[5].SetMax(500)    #CRD
-        self.minStatSlid[6].SetMax(100)    #RES
-        self.minStatSlid[7].SetMax(85)     #ACC
-        self.minStatSlid[8].SetMax(250000) #EHP
-        self.minStatSlid[9].SetMax(8000)   #DMG
-
-        cursor = conn.execute("""
-          SELECT
-            base_hp,
-            base_atk,
-            base_def,
-            base_spd,
-            base_crr,
-            base_crd,
-            base_res,
-            base_acc,
-            current_hp,
-            current_atk,
-            current_def,
-            current_spd,
-            current_crr,
-            current_crd,
-            current_res,
-            current_acc,
-            id,
-            name
-          FROM units
-          WHERE
-            id = """ + id + """;
-        """)
-        row = cursor.fetchone()
-        self.unitContent.ShowItems(True)
-        self.unitNameValue = str(row[17])
-        self.unitName.SetLabel(str(row[17]) + "    (# " + str(row[16]) + ")")
-        for i in range(0, 8):
-            value = str(row[i])
-            self.minStatSlid[i].SetMin(int(value))
-            if i > 3:
-                value = value + "%"
-            else:
-                value = value + " "
-            self.statGrid.SetCellValue(row=i, col=0, s=value)
-        for i in range(0, 8):
-            value = str(row[8 + i])
-            self.minStatSlid[i].SetValue(int(value))
-            self.minStatText[i].SetValue(value)
-            self.currentStats[i] = int(value)
-            if i > 3:
-                value = value + "%"
-            else:
-                value = value + " "
-            self.statGrid.SetCellValue(row=i, col=1, s=value)
-        # Galculate EHP and DMG
-        baseHp = int(self.statGrid.GetCellValue(row=0, col=0))
-        baseDef = int(self.statGrid.GetCellValue(row=2, col=0).replace("%", ""))
-        baseEhp = math.ceil((((baseDef * 3.5) + 1140) * baseHp) / 1000)
-        self.statGrid.SetCellValue(row=8, col=0, s=str(baseEhp))
-        self.minStatSlid[8].SetMin(baseEhp)
-        currentHp = int(self.statGrid.GetCellValue(row=0, col=1))
-        currentDef = int(self.statGrid.GetCellValue(row=2, col=1).replace("%", ""))
-        currentEhp = math.ceil((((currentDef * 3.5) + 1140) * currentHp) / 1000)
-        self.statGrid.SetCellValue(row=8, col=1, s=str(currentEhp))
-        self.minStatSlid[8].SetValue(currentEhp)
-        self.minStatText[8].SetValue(str(currentEhp))
-        baseAtk = int(self.statGrid.GetCellValue(row=1, col=0))
-        baseCrr = int(self.statGrid.GetCellValue(row=4, col=0).replace("%", ""))
-        baseCrd = int(self.statGrid.GetCellValue(row=5, col=0).replace("%", ""))
-        if (baseCrr > 100):
-            # Dont use crit rate over 100
-            baseCrr = 100
-        baseDmg = math.ceil((baseAtk * (100 - baseCrr) / 100) + ((baseAtk + (baseAtk * baseCrd / 100)) * baseCrr / 100));
-        self.statGrid.SetCellValue(row=9, col=0, s=str(baseDmg))
-        self.minStatSlid[9].SetMin(baseDmg)
-        currentAtk = int(self.statGrid.GetCellValue(row=1, col=1))
-        currentCrr = int(self.statGrid.GetCellValue(row=4, col=0).replace("%", ""))
-        currentCrd = int(self.statGrid.GetCellValue(row=5, col=1).replace("%", ""))
-        if (currentCrr > 100):
-            # Dont use crit rate over 100
-            currentCrr = 100
-        currentDmg = math.ceil((currentAtk * (100 - currentCrr) / 100) + ((currentAtk + (currentAtk * currentCrd / 100)) * currentCrr / 100));
-        self.statGrid.SetCellValue(row=9, col=1, s=str(currentDmg))
-        self.minStatSlid[9].SetValue(currentDmg)
-        self.minStatText[9].SetValue(str(currentDmg))
-
-        # Populate the runes
-        for i in range(0, 6):
-            self.runeList[i].SetLabel("")
-        cursor = conn.execute("""
-          SELECT
-            id, slot, type
-          FROM runes
-          WHERE
-            unit = """ + id + """
-          ORDER BY slot;
-        """)
-        i = 0
-        for row in cursor:
-            label = ""
-            label = label + set_names[row[2]] + "\n"
-            # Get all stats
-            cursorStats = conn.execute("""
-              SELECT
-                slot, stat, value, grind, enchant
-              FROM rune_stats
-              WHERE
-                rune = """ + row[0] + """
-              ORDER BY slot;
-                """)
-            j = -1
-            for rowStats in cursorStats:
-                while (j != rowStats[0]):
-                    label = label + "\n"
-                    j = j + 1;
-                label = label + stat_names[rowStats[1]] + str(rowStats[2]).rjust(4) + ""
-                if rowStats[3] > 0:
-                    label = label + " +" + str(rowStats[3])
-            self.runeList[i].SetLabel(label)
-            i = i + 1
-
-    def makeMenuBar(self):
-        """Sets up the application menu.
-        """
-
-        updateMenu = wx.Menu()
-        updateJson = updateMenu.Append(
-          -1,
-          "&Update from JSON file\tCtrl-J",
-          "Updates the database from a profile JSON file."
-        );
-        updateSwdb = updateMenu.Append(
-          -1,
-          "&Update from SWDB\tCtrl-W",
-          "Updates the database from data retrieved from a SWDB instance."
-        );
-        updateSwarfarm = updateMenu.Append(
-          -1,
-          "&Update from Sarfarm\tCtrl-F",
-          "Updates the database from data retrieved from Swarfarm."
-        );
-        updateSqlite = updateMenu.Append(
-          -1,
-          "&Update from a sqlite database\tCtrl-Q",
-          "Updates the database from a SWDB sqlite database."
-        );
-
-        # Make a file menu with Hello and Exit items
-        fileMenu = wx.Menu()
-        # The "\t..." syntax defines an accelerator key that also triggers
-        # the same event
-        aboutItem = fileMenu.Append(wx.ID_ABOUT)
-        fileMenu.AppendSeparator()
-        # When using a stock ID we don't need to specify the menu item's
-        # label
-        exitItem = fileMenu.Append(wx.ID_EXIT)
-
-
-
-        # Make the menu bar and add the two menus to it. The '&' defines
-        # that the next letter is the "mnemonic" for the menu item. On the
-        # platforms that support it those letters are underlined and can be
-        # triggered from the keyboard.
-        menuBar = wx.MenuBar()
-        menuBar.Append(fileMenu, "&File")
-        menuBar.Append(updateMenu, "&Update")
-
-        # Give the menu bar to the frame
-        self.SetMenuBar(menuBar)
-
-        # Finally, associate a handler function with the EVT_MENU event for
-        # each of the menu items. That means that when that menu item is
-        # activated then the associated handler function will be called.
-        #self.Bind(wx.EVT_MENU, self.OnHello, helloItem)
-        self.Bind(wx.EVT_MENU, self.closeApp, exitItem)
-        self.Bind(wx.EVT_MENU, self.showAbout, aboutItem)
-        self.Bind(wx.EVT_MENU, self.updateFromJson, updateJson)
-        self.Bind(wx.EVT_MENU, self.updateFromSwdb, updateSwdb)
-        self.Bind(wx.EVT_MENU, self.updateFromSwarfarm, updateSwarfarm)
-        self.Bind(wx.EVT_MENU, self.updateFromSqlite, updateSqlite)
-
-    def closeApp(self, event):
-        """Closes the app.
-
-        Parameters
-        ----------
-        event : wxEvent, optional
-            The event that triggered the call (default is None).
-
-        """
-
-        self.Close(True)
-
-    def showAbout(self, event):
-        """Display an About dialog.
-
-        Parameters
-        ----------
-        event : wxEvent, optional
-            The event that triggered the call (default is None).
-
-        """
-
-        wx.MessageBox("RuneOptimizerAbout", wx.OK | wx.ICON_INFORMATION)
-
-    def updateFromJson(self, event):
-        """Updates data from a JSON file.
-
-        TODO
-
-        Parameters
-        ----------
-        event : wxEvent, optional
-            The event that triggered the call (default is None).
-
-        """
-
-        self.showUnimplemented(wx.EVT_MENU)
-
-    def updateFromSwdb(self, event):
-        """Updates data from a SWDB instance.
-
-        TODO
-
-        Parameters
-        ----------
-        event : wxEvent, optional
-            The event that triggered the call (default is None).
-
-        """
-
-        self.showUnimplemented()
-
-    def updateFromSwarfarm(self, event):
-        """Updates data from Swarfarm.
-
-        TODO
-
-        Parameters
-        ----------
-        event : wxEvent, optional
-            The event that triggered the call (default is None).
-
-        """
-
-        self.showUnimplemented()
-
-    def updateFromSqlite(self, event):
-        """Updates data from a Sqlite file.
-
-        TODO
-
-        Parameters
-        ----------
-        event : wxEvent, optional
-            The event that triggered the call (default is None).
-
-        """
-
-        self.showUnimplemented()
-
-    def showUnimplemented(self, event):
-        """Displays a message for unimplemented features.
-
-        Parameters
-        ----------
-        event : wxEvent, optional
-            The event that triggered the call (default is None).
-
-        """
-
-        wx.MessageBox(parent=self, message="This functionality is not yet implemented", caption="Unimplemented")
 
 def recalculteStatsOfModifiedUnits():
+    """Recalculates the stats of all the units marked aas modified.
+    """
     global conn
     unitCursor = conn.execute("""
       SELECT

+ 721 - 0
src/RuneOptimizerGUI/frames/ResultsFrame.py

@@ -0,0 +1,721 @@
+"""
+This file is part of RuneOptimizer.
+
+RuneOptimizer 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, either version 3 of the License, or (at your option)
+any later version.
+
+RuneOptimizer 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
+RuneOptimizer. If not, see <https://www.gnu.org/licenses/>.
+
+"""
+
+
+class ResultsFrame(wx.Frame):
+    """
+    The frame uset to see and check results.
+
+    Parameters
+    ----------
+    unitId : str
+        ID of the unit being optimized (default "").
+    unitName : str
+        Name of the unit being optimized (default "").
+    data : Python Object
+        Results from RuneOptimizer (default None).
+    currentStats : int[10]
+        The current stats of the unit being optimized. (default is
+        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).
+    page : int
+        Currently displayed page, 0-index (default 0).
+    totalPages : int
+        Number of pages of results (default 0).
+    linesPerPage : int
+        Number of results to show per page (default 10).
+    pgPrevBt : wx.Button
+        Button to go to the previous page.
+    pgPrevBt : wx.Button
+        Button to go to the previous page.
+    resultsPageIndicator : wx.StaticText
+        Label to indicate the current and maximum pages.
+    resultGrid : wx.Grid.grid
+        Table of results.
+    resultContent : wx.BoxSizer
+        Holds every widget that is hidden until a result is selected.
+    statGrid : wx.Grid.grid
+        Table to show the new stats with the selected result.
+    runeIds : wx.StaticText[6]
+        Labels with the IDs of the runes in the current result.
+    runeLocations : wx.StaticText[6]
+        Labels with the locations of the runes in the current result.
+    runeSets : wx.StaticText[6]
+        Labels with the set names of the runes in the current result.
+    runeMains : wx.StaticText[6]
+        Labels with the main stats of the runes in the current result.
+    runeInnates : wx.StaticText[6]
+        Labels with the innates of the runes in the current result.
+    runeStats : wx.StaticText[6][4]
+        Labels with the stats of the runes in the current result.
+    selectedResultndex : int
+        Selected result index (default -1).
+
+    Methods
+    -------
+    processResults(jsonData)
+        Processes data obtained from RuneOptimizer.
+    pgPrev(event)
+        Goes to the previous result page.
+    pgNext(event)
+        Goes to the next result page.
+    closeWindow(event)
+        Closes the frame.
+    applyRunes(event)
+        Applies the runes in the currently selected result.
+    printResults()
+        Populates the results table with the results in the currently
+        selected page.
+    resultSelected(event)
+        Populates and shows the runes and effective stats with the
+        currently seleced result.
+
+    """
+
+    unitId = ""
+    unitName = ""
+    data = None
+    currentStats = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+    page = 0
+    totalPages = 0
+    linesPerPage = 10
+    pgPrevBt = None
+    pgNextBt = None
+    resultsPageIndicator = None
+    resultGrid = None
+    resultContent = None
+    statGrid = None
+    runeIds = None
+    runeLocations = None
+    runeSets = None
+    runeMains = None
+    runeInnates = None
+    runeStats = None
+    selectedResultIndex = -1
+
+    def __init__(self, *args, **kw):
+        """Initializes the class.
+
+        Sets upt all the widgets.
+
+        """
+
+        global conn
+        # ensure the parent's __init__ is called
+        super(ResultsFrame, self).__init__(*args, **kw)
+
+        # create a panel in the frame
+        pnl = wx.Panel(self)
+
+        self.resultGrid = wx.grid.Grid(parent=pnl, id=-1, pos=(50, 30), size=(544, 220))
+        self.resultGrid.CreateGrid(numRows=10, numCols=11, selmode=wx.grid.Grid.GridSelectionModes.SelectRows)
+        self.resultGrid.EnableEditing(False)
+        self.resultGrid.SetDefaultCellAlignment(horiz=wx.ALIGN_RIGHT, vert=wx.ALIGN_CENTRE)
+        monospaceFont = wx.Font(10, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
+        self.resultGrid.SetDefaultCellFont(monospaceFont)
+        self.resultGrid.SetRowLabelSize(width=35)
+        self.resultGrid.SetColLabelValue(col=0, value="Rating")
+        self.resultGrid.SetColSize(col=0, width=50)
+        self.resultGrid.SetColLabelValue(col=1, value="HP")
+        self.resultGrid.SetColSize(col=1, width=50)
+        self.resultGrid.SetColLabelValue(col=2, value="ATK")
+        self.resultGrid.SetColSize(col=2, width=40)
+        self.resultGrid.SetColLabelValue(col=3, value="DEF")
+        self.resultGrid.SetColSize(col=3, width=40)
+        self.resultGrid.SetColLabelValue(col=4, value="SPD")
+        self.resultGrid.SetColSize(col=4, width=40)
+        self.resultGrid.SetColLabelValue(col=5, value="CRR")
+        self.resultGrid.SetColSize(col=5, width=40)
+        self.resultGrid.SetColLabelValue(col=6, value="CRD")
+        self.resultGrid.SetColSize(col=6, width=40)
+        self.resultGrid.SetColLabelValue(col=7, value="RES")
+        self.resultGrid.SetColSize(col=7, width=40)
+        self.resultGrid.SetColLabelValue(col=8, value="ACC")
+        self.resultGrid.SetColSize(col=8, width=40)
+        self.resultGrid.SetColLabelValue(col=9, value="EHP")
+        self.resultGrid.SetColSize(col=9, width=60)
+        self.resultGrid.SetColLabelValue(col=10, value="DMG")
+        self.resultGrid.SetColSize(col=10, width=50)
+        self.resultGrid.SetColLabelSize(height=20)
+        self.resultGrid.SetDefaultRowSize(height=20)
+        self.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.resultSelected, self.resultGrid)
+
+        # Paginator
+        self.pgPrevBt = wx.Button(parent=pnl, id=-1, pos=(580, 30), size=(40, 50), style=wx.LC_REPORT, label="Prev\npage")
+        self.resultsPageIndicator = wx.StaticText(parent=pnl, label="1/1",id=-1, pos=(580, 80), size=(40, 15), style=wx.ALIGN_CENTRE_HORIZONTAL)
+        self.pgNextBt = wx.Button(parent=pnl, id=-1, pos=(580, 100), size=(40, 50), style=wx.LC_REPORT, label="Next\npage")
+        self.Bind(wx.EVT_BUTTON, self.pgPrev, self.pgPrevBt)
+        self.Bind(wx.EVT_BUTTON, self.pgNext, self.pgNextBt)
+
+        self.resultContent = wx.BoxSizer(wx.VERTICAL)
+
+        # Stats table
+        self.statGrid = wx.grid.Grid(parent=pnl, id=-1, pos=(650, 30), size=(205, 220), style=wx.LC_REPORT)
+        self.statGrid.CreateGrid(numRows=10, numCols=2, selmode=wx.grid.Grid.GridSelectionModes.SelectRowsOrColumns)
+        self.statGrid.EnableEditing(False)
+        self.statGrid.SetDefaultCellAlignment(horiz=wx.ALIGN_RIGHT, vert=wx.ALIGN_CENTRE)
+        monospaceFont = wx.Font(10, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
+        self.statGrid.SetDefaultCellFont(monospaceFont)
+        self.statGrid.SetColSize(col=0, width=70)
+        self.statGrid.SetColLabelValue(col=0, value="Value")
+        self.statGrid.SetColLabelValue(col=1, value="Diff")
+        self.statGrid.SetRowLabelSize(width=35)
+        self.statGrid.SetColLabelSize(height=20)
+        self.statGrid.SetRowSize(row=0, height=20)
+        self.statGrid.SetRowSize(row=1, height=20)
+        self.statGrid.SetRowSize(row=2, height=20)
+        self.statGrid.SetRowSize(row=3, height=20)
+        self.statGrid.SetRowSize(row=4, height=20)
+        self.statGrid.SetRowSize(row=5, height=20)
+        self.statGrid.SetRowSize(row=6, height=20)
+        self.statGrid.SetRowSize(row=7, height=20)
+        self.statGrid.SetRowSize(row=8, height=20)
+        self.statGrid.SetRowSize(row=9, height=20)
+        self.statGrid.SetRowLabelValue(row=0, value=" HP")
+        self.statGrid.SetRowLabelValue(row=1, value="ATK")
+        self.statGrid.SetRowLabelValue(row=2, value="DEF")
+        self.statGrid.SetRowLabelValue(row=3, value="SPD")
+        self.statGrid.SetRowLabelValue(row=4, value="CRR")
+        self.statGrid.SetRowLabelValue(row=5, value="CRD")
+        self.statGrid.SetRowLabelValue(row=6, value="RES")
+        self.statGrid.SetRowLabelValue(row=7, value="ACC")
+        self.statGrid.SetRowLabelValue(row=8, value="EHP")
+        self.statGrid.SetRowLabelValue(row=9, value="DMG")
+        self.resultContent.Add(self.statGrid)
+
+        monospaceFont = wx.Font(10, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
+        monospaceFont.PointSize -= 2
+        monospaceFontBold = wx.Font(10, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)
+        monospaceFontBold.PointSize -= 2
+        monospaceFontItalic = wx.Font(10, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_ITALIC, wx.FONTWEIGHT_NORMAL)
+        monospaceFontItalic.PointSize -= 2
+
+        #Rune set list
+        runeListBox = [
+          wx.StaticBox(parent=pnl, label="Slot1:",id=-1, pos=(200, 255), size=(140, 160)),
+          wx.StaticBox(parent=pnl, label="Slot2:",id=-1, pos=(350, 255), size=(140, 160)),
+          wx.StaticBox(parent=pnl, label="Slot3:",id=-1, pos=(350, 420), size=(140, 160)),
+          wx.StaticBox(parent=pnl, label="Slot4:",id=-1, pos=(200, 420), size=(140, 160)),
+          wx.StaticBox(parent=pnl, label="Slot5:",id=-1, pos=(50, 420), size=(140, 160)),
+          wx.StaticBox(parent=pnl, label="Slot6:",id=-1, pos=(50, 255), size=(140, 160)),
+        ]
+        for i in range(0, 6):
+            runeListBox[i].SetFont(monospaceFont)
+            self.resultContent.Add(runeListBox[i])
+
+        self.runeIds = [
+          wx.StaticText(runeListBox[0], label="",id=-1, pos=(5, 0), size=(120, 10)),
+          wx.StaticText(runeListBox[1], label="",id=-1, pos=(5, 0), size=(120, 10)),
+          wx.StaticText(runeListBox[2], label="",id=-1, pos=(5, 0), size=(120, 10)),
+          wx.StaticText(runeListBox[3], label="",id=-1, pos=(5, 0), size=(120, 10)),
+          wx.StaticText(runeListBox[4], label="",id=-1, pos=(5, 0), size=(120, 10)),
+          wx.StaticText(runeListBox[5], label="",id=-1, pos=(5, 0), size=(120, 10)),
+        ]
+
+        self.runeLocations = [
+          wx.StaticText(runeListBox[0], label="",id=-1, pos=(5, 15), size=(120, 10)),
+          wx.StaticText(runeListBox[1], label="",id=-1, pos=(5, 15), size=(120, 10)),
+          wx.StaticText(runeListBox[2], label="",id=-1, pos=(5, 15), size=(120, 10)),
+          wx.StaticText(runeListBox[3], label="",id=-1, pos=(5, 15), size=(120, 10)),
+          wx.StaticText(runeListBox[4], label="",id=-1, pos=(5, 15), size=(120, 10)),
+          wx.StaticText(runeListBox[5], label="",id=-1, pos=(5, 15), size=(120, 10)),
+        ]
+
+        self.runeSets = [
+          wx.StaticText(runeListBox[0], label="",id=-1, pos=(5, 30), size=(120, 10)),
+          wx.StaticText(runeListBox[1], label="",id=-1, pos=(5, 30), size=(120, 10)),
+          wx.StaticText(runeListBox[2], label="",id=-1, pos=(5, 30), size=(120, 10)),
+          wx.StaticText(runeListBox[3], label="",id=-1, pos=(5, 30), size=(120, 10)),
+          wx.StaticText(runeListBox[4], label="",id=-1, pos=(5, 30), size=(120, 10)),
+          wx.StaticText(runeListBox[5], label="",id=-1, pos=(5, 30), size=(120, 10)),
+        ]
+
+        wx.StaticLine(parent=runeListBox[0], id=-1, pos=(5, 45), size=(130, 1), style=wx.LC_REPORT)
+        wx.StaticLine(parent=runeListBox[1], id=-1, pos=(5, 45), size=(130, 1), style=wx.LC_REPORT)
+        wx.StaticLine(parent=runeListBox[2], id=-1, pos=(5, 45), size=(130, 1), style=wx.LC_REPORT)
+        wx.StaticLine(parent=runeListBox[3], id=-1, pos=(5, 45), size=(130, 1), style=wx.LC_REPORT)
+        wx.StaticLine(parent=runeListBox[4], id=-1, pos=(5, 45), size=(130, 1), style=wx.LC_REPORT)
+        wx.StaticLine(parent=runeListBox[5], id=-1, pos=(5, 45), size=(130, 1), style=wx.LC_REPORT)
+
+        self.runeMains = [
+          wx.StaticText(runeListBox[0], label="",id=-1, pos=(5, 50), size=(120, 10)),
+          wx.StaticText(runeListBox[1], label="",id=-1, pos=(5, 50), size=(120, 10)),
+          wx.StaticText(runeListBox[2], label="",id=-1, pos=(5, 50), size=(120, 10)),
+          wx.StaticText(runeListBox[3], label="",id=-1, pos=(5, 50), size=(120, 10)),
+          wx.StaticText(runeListBox[4], label="",id=-1, pos=(5, 50), size=(120, 10)),
+          wx.StaticText(runeListBox[5], label="",id=-1, pos=(5, 50), size=(120, 10)),
+        ]
+        for i in range(0, 6):
+            self.runeMains[i].SetFont(monospaceFontBold)
+
+        self.runeInnates = [
+          wx.StaticText(runeListBox[0], label="",id=-1, pos=(5, 65), size=(120, 10)),
+          wx.StaticText(runeListBox[1], label="",id=-1, pos=(5, 65), size=(120, 10)),
+          wx.StaticText(runeListBox[2], label="",id=-1, pos=(5, 65), size=(120, 10)),
+          wx.StaticText(runeListBox[3], label="",id=-1, pos=(5, 65), size=(120, 10)),
+          wx.StaticText(runeListBox[4], label="",id=-1, pos=(5, 65), size=(120, 10)),
+          wx.StaticText(runeListBox[5], label="",id=-1, pos=(5, 65), size=(120, 10)),
+        ]
+        for i in range(0, 6):
+            self.runeInnates[i].SetFont(monospaceFontItalic)
+
+        self.runeStats = [
+          [
+            wx.StaticText(runeListBox[0], label="",id=-1, pos=(5, 80), size=(120, 10)),
+            wx.StaticText(runeListBox[0], label="",id=-1, pos=(5, 95), size=(120, 10)),
+            wx.StaticText(runeListBox[0], label="",id=-1, pos=(5, 110), size=(120, 10)),
+            wx.StaticText(runeListBox[0], label="",id=-1, pos=(5, 125), size=(120, 10))
+          ],
+          [
+            wx.StaticText(runeListBox[1], label="",id=-1, pos=(5, 80), size=(120, 10)),
+            wx.StaticText(runeListBox[1], label="",id=-1, pos=(5, 95), size=(120, 10)),
+            wx.StaticText(runeListBox[1], label="",id=-1, pos=(5, 110), size=(120, 10)),
+            wx.StaticText(runeListBox[1], label="",id=-1, pos=(5, 125), size=(120, 10))
+          ],
+          [
+            wx.StaticText(runeListBox[2], label="",id=-1, pos=(5, 80), size=(120, 10)),
+            wx.StaticText(runeListBox[2], label="",id=-1, pos=(5, 95), size=(120, 10)),
+            wx.StaticText(runeListBox[2], label="",id=-1, pos=(5, 110), size=(120, 10)),
+            wx.StaticText(runeListBox[2], label="",id=-1, pos=(5, 125), size=(120, 10))
+          ],
+          [
+            wx.StaticText(runeListBox[3], label="",id=-1, pos=(5, 80), size=(120, 10)),
+            wx.StaticText(runeListBox[3], label="",id=-1, pos=(5, 95), size=(120, 10)),
+            wx.StaticText(runeListBox[3], label="",id=-1, pos=(5, 110), size=(120, 10)),
+            wx.StaticText(runeListBox[3], label="",id=-1, pos=(5, 125), size=(120, 10))
+          ],
+          [
+            wx.StaticText(runeListBox[4], label="",id=-1, pos=(5, 80), size=(120, 10)),
+            wx.StaticText(runeListBox[4], label="",id=-1, pos=(5, 95), size=(120, 10)),
+            wx.StaticText(runeListBox[4], label="",id=-1, pos=(5, 110), size=(120, 10)),
+            wx.StaticText(runeListBox[4], label="",id=-1, pos=(5, 125), size=(120, 10))
+          ],
+          [
+            wx.StaticText(runeListBox[5], label="",id=-1, pos=(5, 80), size=(120, 10)),
+            wx.StaticText(runeListBox[5], label="",id=-1, pos=(5, 95), size=(120, 10)),
+            wx.StaticText(runeListBox[5], label="",id=-1, pos=(5, 110), size=(120, 10)),
+            wx.StaticText(runeListBox[5], label="",id=-1, pos=(5, 125), size=(120, 10))
+          ],
+        ]
+
+        # Action buttons
+        applyBt = wx.Button(parent=pnl, id=-1, pos=(650, 330), size=(165, 60), style=wx.LC_REPORT, label="Apply runes")
+        self.Bind(wx.EVT_BUTTON, self.applyRunes, applyBt)
+        self.resultContent.Add(applyBt)
+        closeBt = wx.Button(parent=pnl, id=-1, pos=(650, 430), size=(165, 60), style=wx.LC_REPORT, label="Close")
+        self.Bind(wx.EVT_BUTTON, self.closeWindow, closeBt)
+
+        # By default, hide everything
+        self.resultContent.ShowItems(False)
+
+    def processResults(self, jsonData):
+        """Processes data obtained from RuneOptimizer.
+
+        Reads the JSON data and initializes the property data.
+        Automatically calls printResults();
+
+        Parameters
+        ----------
+        jsonData : str
+            The data, as received from RuneOptimizer.
+
+        """
+
+        self.data = json.loads(jsonData, object_hook=lambda d: SimpleNamespace(**d))
+        self.totalPages = math.ceil(len(self.data.results) / self.linesPerPage)
+        self.printResults()
+
+    def pgPrev(self, event):
+        """Goes to the previous page of results.
+
+        Checks if there is a previous page to go to. If so, it
+        automatically calls printResults();
+
+        Parameters
+        ----------
+        event : wxEvent, optional
+            The event that triggered the call (default is None).
+
+        """
+
+        if self.page > 0:
+            self.page -= 1
+            self.printResults()
+
+    def pgNext(self, event):
+        """Goes to the next page of results.
+
+        Checks if there is a next page to go to. If so, it
+        automatically calls printResults();
+
+        Parameters
+        ----------
+        event : wxEvent, optional
+            The event that triggered the call (default is None).
+
+        """
+
+        if self.page < self.totalPages:
+            self.page += 1
+            self.printResults()
+
+    def closeWindow(self, event):
+        """Closes the window.
+
+        Parameters
+        ----------
+        event : wxEvent, optional
+            The event that triggered the call (default is None).
+
+        """
+
+        self.Close(True)
+
+    def applyRunes(self, event):
+        """Applies the selected results and saves data to the database.
+
+        Parameters
+        ----------
+        event : wxEvent, optional
+            The event that triggered the call (default is None).
+
+        """
+
+        global conn
+
+        if (self.selectedResultIndex < 0):
+            # TODO: Show error
+            return;
+        print("self.selectedResultIndex: " + str(self.selectedResultIndex))
+        for i in range(0, 6):
+            print(self.data.results[self.selectedResultIndex].runes[i])
+        # First, unassign all runes currently assigned to the unit
+        cursor = conn.execute(
+          """
+            UPDATE runes SET unit = null
+            WHERE unit = ?
+          """,
+          (
+            self.unitId,
+          )
+        )
+
+        # Next, mark units as modified
+        cursor = conn.execute(
+          """
+            UPDATE units SET modified = 1
+            WHERE id = ? OR id IN (SELECT unit FROM runes WHERE id IN (?, ?, ?, ?, ?, ?))
+          """,
+          (
+            self.unitId,
+            self.data.results[self.selectedResultIndex].runes[0],
+            self.data.results[self.selectedResultIndex].runes[1],
+            self.data.results[self.selectedResultIndex].runes[2],
+            self.data.results[self.selectedResultIndex].runes[3],
+            self.data.results[self.selectedResultIndex].runes[4],
+            self.data.results[self.selectedResultIndex].runes[5]
+          )
+        )
+
+        # Lastly, assign the runes
+        cursor = conn.execute(
+          """
+            UPDATE runes SET unit = ?
+            WHERE id IN (?, ?, ?, ?, ?, ?)
+          """,
+          (
+            self.unitId,
+            self.data.results[self.selectedResultIndex].runes[0],
+            self.data.results[self.selectedResultIndex].runes[1],
+            self.data.results[self.selectedResultIndex].runes[2],
+            self.data.results[self.selectedResultIndex].runes[3],
+            self.data.results[self.selectedResultIndex].runes[4],
+            self.data.results[self.selectedResultIndex].runes[5]
+          )
+        )
+        conn.commit()
+        # TODO: Recalculate all modified units stats from the database
+        print("Applied!")
+        recalculteStatsOfModifiedUnits()
+        print("All recalculated!")
+
+        # Fetch the new values for self.currentStats
+        cursor = conn.execute(
+          """
+            SELECT
+              current_hp,
+              current_atk,
+              current_def,
+              current_spd,
+              current_crr,
+              current_crd,
+              current_res,
+              current_acc
+            FROM units
+            WHERE id = ?
+          """,
+          (
+            self.unitId,
+          )
+        )
+        row = cursor.fetchone()
+        for i in range(0, 8):
+            self.currentStats[i] = int(row[i])
+        self.currentStats[9] = math.ceil((((self.currentStats[2] * 3.5) + 1140) * self.currentStats[0]) / 1000)
+        self.currentStats[10] = math.ceil((self.currentStats[1] * (100 - self.currentStats[4]) / 100) + ((self.currentStats[1] + (self.currentStats[1] * self.currentStats[5] / 100)) * self.currentStats[4] / 100));
+        self.resultSelected(None)
+
+    def printResults(self):
+        """Populates the results table with the results in the
+        currently selected page.
+
+        It doesn't change the selcted result. Automaticcaly called
+        after changing pages or processing data.
+        """
+
+        self.pgPrevBt.Enable(True)
+        self.pgNextBt.Enable(True)
+        if self.totalPages == 1:
+            self.pgPrevBt.Enable(False)
+            self.pgNextBt.Enable(False)
+        elif self.page == 0:
+            self.pgPrevBt.Enable(False)
+        elif self.page + 1 == self.totalPages:
+            self.pgNextBt.Enable(False)
+        self.resultsPageIndicator.SetLabel(str(self.page + 1) + "/" + str(self.totalPages))
+        #for result in self.data.results:
+        for i in range(0, 10):
+            rindex = i + (self.linesPerPage * self.page)
+            if (len(self.data.results) > rindex):
+                self.resultGrid.SetRowLabelValue(row=i, value=str(rindex + 1))
+                result = self.data.results[rindex]
+                self.resultGrid.SetCellValue(row=i, col=0, s=str(result.rating))
+                self.resultGrid.SetCellValue(row=i, col=1, s=str(result.hp))
+                self.resultGrid.SetCellValue(row=i, col=2, s=str(result.atk))
+                self.resultGrid.SetCellValue(row=i, col=3, s=str(result.dfc))
+                self.resultGrid.SetCellValue(row=i, col=4, s=str(result.spd))
+                self.resultGrid.SetCellValue(row=i, col=5, s=str(result.crr))
+                self.resultGrid.SetCellValue(row=i, col=6, s=str(result.crd))
+                self.resultGrid.SetCellValue(row=i, col=7, s=str(result.res))
+                self.resultGrid.SetCellValue(row=i, col=8, s=str(result.acc))
+                self.resultGrid.SetCellValue(row=i, col=9, s=str(result.ehp))
+                self.resultGrid.SetCellValue(row=i, col=10, s=str(result.dmg))
+            else:
+                self.resultGrid.SetRowLabelValue(row=i, value="")
+                for j in range(0, 11):
+                    self.resultGrid.SetCellValue(row=i, col=j, s="")
+
+    def resultSelected(self, event):
+        """Populates and shows the runes and effective stats with the
+        currently seleced result.
+
+        It also enables the apply button.
+
+        Parameters
+        ----------
+        event : wxEvent, optional
+            The event that triggered the call (default is None).
+
+        """
+
+        selectedLine = self.resultGrid.GetSelectedRows()[0]
+        self.selectedResultIndex = selectedLine + (self.linesPerPage * self.page)
+        self.statGrid.SetCellValue(row=0, col=0, s=str(self.data.results[self.selectedResultIndex].hp) + " ")
+        diff = self.data.results[self.selectedResultIndex].hp - self.currentStats[0]
+        if (diff < 0):
+            self.statGrid.SetCellValue(row=0, col=1, s="- " + str(abs(diff)) + " ")
+            self.statGrid.SetCellTextColour(row=0, col=1, colour=wx.Colour(red=255, green=0, blue=0))
+        elif (diff > 0):
+            self.statGrid.SetCellValue(row=0, col=1, s="+ " + str(diff) + " ")
+            self.statGrid.SetCellTextColour(row=0, col=1, colour=wx.Colour(red=0, green=255, blue=0))
+        else:
+            self.statGrid.SetCellValue(row=0, col=1, s="")
+
+        self.statGrid.SetCellValue(row=1, col=0, s=str(self.data.results[self.selectedResultIndex].atk) + " ")
+        diff = self.data.results[self.selectedResultIndex].atk - self.currentStats[1]
+        if (diff < 0):
+            self.statGrid.SetCellValue(row=1, col=1, s="- " + str(abs(diff)) + " ")
+            self.statGrid.SetCellTextColour(row=1, col=1, colour=wx.Colour(red=255, green=0, blue=0))
+        elif (diff > 0):
+            self.statGrid.SetCellValue(row=1, col=1, s="+ " + str(diff) + " ")
+            self.statGrid.SetCellTextColour(row=1, col=1, colour=wx.Colour(red=0, green=255, blue=0))
+        else:
+            self.statGrid.SetCellValue(row=1, col=1, s="")
+
+        self.statGrid.SetCellValue(row=2, col=0, s=str(self.data.results[self.selectedResultIndex].dfc) + " ")
+        diff = self.data.results[self.selectedResultIndex].atk - self.currentStats[2]
+        if (diff < 0):
+            self.statGrid.SetCellValue(row=2, col=1, s="- " + str(abs(diff)) + " ")
+            self.statGrid.SetCellTextColour(row=1, col=1, colour=wx.Colour(red=255, green=0, blue=0))
+        elif (diff > 0):
+            self.statGrid.SetCellValue(row=2, col=1, s="+ " + str(diff) + " ")
+            self.statGrid.SetCellTextColour(row=2, col=1, colour=wx.Colour(red=0, green=255, blue=0))
+        else:
+            self.statGrid.SetCellValue(row=2, col=1, s="")
+
+        self.statGrid.SetCellValue(row=3, col=0, s=str(self.data.results[self.selectedResultIndex].spd) + " ")
+        diff = self.data.results[self.selectedResultIndex].spd - self.currentStats[3]
+        if (diff < 0):
+            self.statGrid.SetCellValue(row=3, col=1, s="- " + str(abs(diff)) + " ")
+            self.statGrid.SetCellTextColour(row=3, col=1, colour=wx.Colour(red=255, green=0, blue=0))
+        elif (diff > 0):
+            self.statGrid.SetCellValue(row=3, col=1, s="+ " + str(diff) + " ")
+            self.statGrid.SetCellTextColour(row=3, col=1, colour=wx.Colour(red=0, green=255, blue=0))
+        else:
+            self.statGrid.SetCellValue(row=3, col=1, s="")
+
+
+        self.statGrid.SetCellValue(row=4, col=0, s=str(self.data.results[self.selectedResultIndex].crr) + "%")
+        diff = self.data.results[self.selectedResultIndex].crr - self.currentStats[4]
+        if (diff < 0):
+            self.statGrid.SetCellValue(row=4, col=1, s="- " + str(abs(diff)) + "%")
+            self.statGrid.SetCellTextColour(row=4, col=1, colour=wx.Colour(red=255, green=0, blue=0))
+        elif (diff > 0):
+            self.statGrid.SetCellValue(row=4, col=1, s="+ " + str(diff) + "%")
+            self.statGrid.SetCellTextColour(row=4, col=1, colour=wx.Colour(red=0, green=255, blue=0))
+        else:
+            self.statGrid.SetCellValue(row=4, col=1, s="")
+
+        self.statGrid.SetCellValue(row=5, col=0, s=str(self.data.results[self.selectedResultIndex].crd) + "%")
+        diff = self.data.results[self.selectedResultIndex].crd - self.currentStats[5]
+        if (diff < 0):
+            self.statGrid.SetCellValue(row=5, col=1, s="- " + str(abs(diff)) + "%")
+            self.statGrid.SetCellTextColour(row=5, col=1, colour=wx.Colour(red=255, green=0, blue=0))
+        elif (diff > 0):
+            self.statGrid.SetCellValue(row=5, col=1, s="+ " + str(diff) + "%")
+            self.statGrid.SetCellTextColour(row=5, col=1, colour=wx.Colour(red=0, green=255, blue=0))
+        else:
+            self.statGrid.SetCellValue(row=5, col=1, s="")
+
+        self.statGrid.SetCellValue(row=6, col=0, s=str(self.data.results[self.selectedResultIndex].res) + "%")
+        diff = self.data.results[self.selectedResultIndex].res - self.currentStats[6]
+        if (diff < 0):
+            self.statGrid.SetCellValue(row=6, col=1, s="- " + str(abs(diff)) + "%")
+            self.statGrid.SetCellTextColour(row=6, col=1, colour=wx.Colour(red=255, green=0, blue=0))
+        elif (diff > 0):
+            self.statGrid.SetCellValue(row=6, col=1, s="+ " + str(diff) + "%")
+            self.statGrid.SetCellTextColour(row=6, col=1, colour=wx.Colour(red=0, green=255, blue=0))
+        else:
+            self.statGrid.SetCellValue(row=6, col=1, s="")
+
+        self.statGrid.SetCellValue(row=7, col=0, s=str(self.data.results[self.selectedResultIndex].acc) + "%")
+        diff = self.data.results[self.selectedResultIndex].acc - self.currentStats[7]
+        if (diff < 0):
+            self.statGrid.SetCellValue(row=7, col=1, s="- " + str(abs(diff)) + "%")
+            self.statGrid.SetCellTextColour(row=7, col=1, colour=wx.Colour(red=255, green=0, blue=0))
+        elif (diff > 0):
+            self.statGrid.SetCellValue(row=7, col=1, s="+ " + str(diff) + "%")
+            self.statGrid.SetCellTextColour(row=7, col=1, colour=wx.Colour(red=0, green=255, blue=0))
+        else:
+            self.statGrid.SetCellValue(row=7, col=1, s="")
+
+        self.statGrid.SetCellValue(row=8, col=0, s=str(self.data.results[self.selectedResultIndex].ehp) + " ")
+        diff = self.data.results[self.selectedResultIndex].ehp - self.currentStats[8]
+        if (diff < 0):
+            self.statGrid.SetCellValue(row=8, col=1, s="- " + str(abs(diff)) + " ")
+            self.statGrid.SetCellTextColour(row=8, col=1, colour=wx.Colour(red=255, green=0, blue=0))
+        elif (diff > 0):
+            self.statGrid.SetCellValue(row=8, col=1, s="+ " + str(diff) + " ")
+            self.statGrid.SetCellTextColour(row=8, col=1, colour=wx.Colour(red=0, green=255, blue=0))
+        else:
+            self.statGrid.SetCellValue(row=8, col=1, s="")
+
+        self.statGrid.SetCellValue(row=9, col=0, s=str(self.data.results[self.selectedResultIndex].dmg) + " ")
+        diff = self.data.results[self.selectedResultIndex].dmg - self.currentStats[9]
+        if (diff < 0):
+            self.statGrid.SetCellValue(row=9, col=1, s="- " + str(abs(diff)) + " ")
+            self.statGrid.SetCellTextColour(row=9, col=1, colour=wx.Colour(red=255, green=0, blue=0))
+        elif (diff > 0):
+            self.statGrid.SetCellValue(row=9, col=1, s="+ " + str(diff) + " ")
+            self.statGrid.SetCellTextColour(row=9, col=1, colour=wx.Colour(red=0, green=255, blue=0))
+        else:
+            self.statGrid.SetCellValue(row=9, col=1, s="")
+
+        self.resultContent.ShowItems(True)
+
+        # Clean the runes
+        for i in range(0, 5):
+            self.runeIds[i].SetLabel("")
+            self.runeLocations[i].SetLabel("")
+            self.runeSets[i].SetLabel("")
+            self.runeMains[i].SetLabel("")
+            self.runeInnates[i].SetLabel("")
+            for j in range(0, 3):
+                self.runeStats[i][j].SetLabel("")
+
+        # Display the runes
+        cursor = conn.execute(
+          """
+            SELECT runes.id, runes.slot, runes.type, runes.level, units.id, units.name
+            FROM runes LEFT JOIN units ON runes.unit = units.id
+            WHERE runes.id IN (?, ?, ?, ?, ?, ?)
+            ORDER BY runes.slot;
+          """,
+          (
+            self.data.results[self.selectedResultIndex].runes[0],
+            self.data.results[self.selectedResultIndex].runes[1],
+            self.data.results[self.selectedResultIndex].runes[2],
+            self.data.results[self.selectedResultIndex].runes[3],
+            self.data.results[self.selectedResultIndex].runes[4],
+            self.data.results[self.selectedResultIndex].runes[5]
+          )
+        )
+        i = 0
+        for row in cursor:
+            # Rows are 21 charactes width
+            self.runeIds[i].SetLabel(("#" + str(row[0])).rjust(21, " "))
+            if row[4] == None:
+                self.runeLocations[i].SetLabel("Storage")
+            else:
+                self.runeLocations[i].SetLabel(str(row[5])[0:9].ljust(9, " ") + " #" + str(row[4]) + "")
+            self.runeSets[i].SetLabel(set_names[row[2]].ljust(18, " ") + "+" + str(row[3]))
+            #print(self.data.results[self.selectedResultIndex].runes[i])
+            cursorStats = conn.execute(
+              """
+                SELECT
+                  slot, stat, value, grind, enchant
+                FROM rune_stats
+                WHERE
+                  rune = ?
+                ORDER BY slot;
+              """,
+              (self.data.results[self.selectedResultIndex].runes[i],)
+            )
+            for rowStats in cursorStats:
+                slot = rowStats[0]
+                if rowStats[4] == 1: # if enchanted
+                    name = (stat_names[rowStats[1]].replace("%", "").replace(" ", "") + " * ").rjust(8, " ")
+                else:
+                    name = (stat_names[rowStats[1]].replace("%", "").replace(" ", "") + "   ").rjust(8, " ")
+                value = str(rowStats[2])
+                if rowStats[1] in [2, 4, 6, 9, 10, 11, 23]:
+                    value = value + "%"
+                else:
+                    value = value + " "
+                value = value.rjust(5)
+                if rowStats[3] > 0: # if grinded
+                    value = value + "  + " + str(rowStats[3])
+                    if rowStats[1] in [2, 4, 6, 9, 10, 11, 23]:
+                        value = value + "%"
+                line = name + value
+                if slot == -1: # main
+                    self.runeMains[i].SetLabel(line)
+                elif slot == 0: # innate
+                    self.runeInnates[i].SetLabel(line)
+                else: # normal stats
+                    self.runeStats[i][slot - 1].SetLabel(line)
+            i += 1

+ 793 - 0
src/RuneOptimizerGUI/frames/RuneOptimizerFrame.py

@@ -0,0 +1,793 @@
+"""
+This file is part of RuneOptimizer.
+
+RuneOptimizer 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, either version 3 of the License, or (at your option)
+any later version.
+
+RuneOptimizer 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
+RuneOptimizer. If not, see <https://www.gnu.org/licenses/>.
+
+"""
+
+class RuneOptimizerFrame(wx.Frame):
+    """
+    The main application window.
+
+    Parameters
+    ----------
+    unitList : wx.ListCtrl
+        Selectable unit list with priorities.
+    unitContent : wx.BoxSizer
+        Holds every widget that is hidden until a unit is selected.
+    unitName : wx.StaticText
+        Label with the unit name.
+    statGrid : wx.Grid.grid
+        Table with the unit base and current stats.
+    runeList : wx.StaticText[6]
+        Labels with all the info about the currently equipped runes.
+    minStatSlider : wx.Slider[6]
+        List of sliders for the minimum selectors for each stat.
+    minStatText : wx.StaticText[6]
+        List of text inputs for the minimum selectors for each stat.
+    runeSets : wx.Choice[3]
+        List of selector to pik rune sets.
+    stats : wx.CheckListBox[2]
+        Tho selctors to choose stats allowed in optimization.
+    level : wx.Choice
+        Selector to pick the level for the rune optimization.
+    currentStats : int[10]
+        The current stats of the unit being optimized. (default is
+        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).
+    filterName : wx.TextCtrl
+        Text input to filter units names.
+    filterNames : wx.CheckBox
+        Checkbox to include or exclude units in storage.
+    filterNoRunes : wx.CheckBox
+        Checkbox to include or exclude units without runes.
+    filterNoTeams : wx.CheckBox
+        Checkbox to include or exclude units in no teams.
+
+    Methods
+    -------
+    processResults(jsonData)
+        Processes data obtained from RuneOptimizer.
+    populateUnitList(event)
+        Populates the unit list.
+    startOptimization(event)
+        Prepares and runs a command optimization.
+    minStatChangeBySlider(event)
+        Changes text when a slider is changed.
+    minStatChangeByTExt(event)
+        Changes the slider when the text is changed.
+    unitSelected(event)
+        Loads a unit info and enables optimizaton options.
+    makeMenuBar(event)
+        Creates the app menu bar.
+    closeApp(event)
+        Closes the app.
+    showAbout(event)
+        Display an About dialog.
+    updateFromJson(event)
+        Updates data from a JSON file.
+    updateFromSwdb(event)
+        Updates data from a JSON file.
+    updateFromSwarfarm(event)
+        Updates data from a JSON file.
+    updateFromSqlite(event)
+        Updates data from a JSON file.
+    showUnimplemented(parent, event)
+        Displays a message for unimplemented features.
+
+    """
+
+    unitList = None
+    unitContent = None
+    unitName = None
+    statGrid = None
+    runeList = None
+    minStatSlid = None
+    minStatText = None
+    runeSets = None
+    stats = None
+    level = None
+    currentStats = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+    filterName = None
+    filterStorage = None
+    filterNoRunes = None
+    filterNoTeams = None
+
+    def __init__(self, *args, **kw):
+        """Initializes the class.
+
+        Sets upt all the widgets.
+
+        """
+
+        global conn
+        super(RuneOptimizerFrame, self).__init__(*args, **kw)
+
+        # Create and configure a
+        pnl = wx.Panel(self)
+        self.makeMenuBar()
+        self.CreateStatusBar()
+        self.SetStatusText("Status: Updated, no pending changes")
+
+        # Show unit list
+        wx.StaticText(parent=pnl, id=-1, label="Name                           Prio.     Sto.", pos=(10, 10), size=(190, 20))
+        self.unitList = wx.ListCtrl(parent=pnl, id=-1, pos=(10, 30), size=(190, 490), style=wx.LC_REPORT|wx.LC_NO_HEADER)
+        self.unitList.InsertColumn(0, "Name", width=120)
+        self.unitList.InsertColumn(1, "Prio.", width=40)
+        self.unitList.InsertColumn(2, "Sto.", width=30)
+
+        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.unitSelected, self.unitList)
+
+        # List filters
+        filterBox = wx.StaticBox(pnl, label="Filters:",id=-1, pos=(10, 520), size=(190, 150))
+        wx.StaticText(parent=filterBox, label="Monster name", pos=(5, 5), size=(180, 20))
+        self.filterName = wx.TextCtrl(parent=filterBox, id=-1, value="", pos=(5, 25), size=(177, 20), style=wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx0")
+        self.Bind(wx.EVT_TEXT_ENTER, self.populateUnitList, self.filterName)
+        self.filterStorage = wx.CheckBox(parent=filterBox, id=-1, label="Monsters in storage", pos=(5, 55), size=(180, 20))
+        self.filterStorage.SetValue(True)
+        self.Bind(wx.EVT_CHECKBOX, self.populateUnitList, self.filterStorage)
+        self.filterNoRunes = wx.CheckBox(parent=filterBox, id=-1, label="Monsters without runes", pos=(5, 75), size=(180, 20))
+        self.filterNoRunes.SetValue(True)
+        self.Bind(wx.EVT_CHECKBOX, self.populateUnitList, self.filterNoRunes)
+        self.filterNoTeams = wx.CheckBox(parent=filterBox, id=-1, label="Monsters not in teams", pos=(5, 95), size=(180, 20))
+        self.filterNoTeams.SetValue(True)
+        self.Bind(wx.EVT_CHECKBOX, self.populateUnitList, self.filterNoTeams)
+
+        self.populateUnitList(None)
+
+        # Begin with unit-specifica content
+        self.unitContent = wx.BoxSizer(wx.VERTICAL)
+
+        # Unit name
+        self.unitName = wx.StaticText(pnl, label="", pos=(210, 0), size=(200, 20))
+        font = self.unitName.GetFont()
+        font.PointSize += 2
+        font = font.Bold()
+        self.unitName.SetFont(font)
+        self.unitContent.Add(self.unitName)
+
+        # Stats table
+        self.statGrid = wx.grid.Grid(parent=pnl, id=-1, pos=(210, 30), size=(165, 220))
+        self.statGrid.CreateGrid(numRows=10, numCols=2, selmode=wx.grid.Grid.GridSelectionModes.SelectRowsOrColumns)
+        self.statGrid.EnableEditing(False)
+        self.statGrid.SetDefaultCellAlignment(horiz=wx.ALIGN_RIGHT, vert=wx.ALIGN_CENTRE)
+        monospaceFont = wx.Font(10, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
+        self.statGrid.SetDefaultCellFont(monospaceFont)
+        self.statGrid.SetColSize(col=0, width=50)
+        self.statGrid.SetColLabelValue(col=0, value="Base")
+        self.statGrid.SetColSize(col=0, width=50)
+        self.statGrid.SetColLabelValue(col=1, value="Current")
+        self.statGrid.SetRowLabelSize(width=35)
+        self.statGrid.SetColLabelSize(height=20)
+        self.statGrid.SetRowSize(row=0, height=20)
+        self.statGrid.SetRowSize(row=1, height=20)
+        self.statGrid.SetRowSize(row=2, height=20)
+        self.statGrid.SetRowSize(row=3, height=20)
+        self.statGrid.SetRowSize(row=4, height=20)
+        self.statGrid.SetRowSize(row=5, height=20)
+        self.statGrid.SetRowSize(row=6, height=20)
+        self.statGrid.SetRowSize(row=7, height=20)
+        self.statGrid.SetRowSize(row=8, height=20)
+        self.statGrid.SetRowSize(row=9, height=20)
+        self.statGrid.SetRowLabelValue(row=0, value=" HP")
+        self.statGrid.SetRowLabelValue(row=1, value="ATK")
+        self.statGrid.SetRowLabelValue(row=2, value="DEF")
+        self.statGrid.SetRowLabelValue(row=3, value="SPD")
+        self.statGrid.SetRowLabelValue(row=4, value="CRR")
+        self.statGrid.SetRowLabelValue(row=5, value="CRD")
+        self.statGrid.SetRowLabelValue(row=6, value="RES")
+        self.statGrid.SetRowLabelValue(row=7, value="ACC")
+        self.statGrid.SetRowLabelValue(row=8, value="EHP")
+        self.statGrid.SetRowLabelValue(row=9, value="DMG")
+        self.unitContent.Add(self.statGrid)
+
+        #Rune set list
+        runeListBox = [
+          wx.StaticBox(pnl, label="Slot1:",id=-1, pos=(465, 30), size=(80, 115)),
+          wx.StaticBox(pnl, label="Slot2:",id=-1, pos=(550, 30), size=(80, 115)),
+          wx.StaticBox(pnl, label="Slot3:",id=-1, pos=(550, 150), size=(80, 115)),
+          wx.StaticBox(pnl, label="Slot4:",id=-1, pos=(465, 150), size=(80, 115)),
+          wx.StaticBox(pnl, label="Slot5:",id=-1, pos=(380, 150), size=(80, 115)),
+          wx.StaticBox(pnl, label="Slot6:",id=-1, pos=(380, 30), size=(80, 115)),
+        ]
+        self.runeList = [
+          wx.StaticText(runeListBox[0], label="",id=-1, pos=(0, 0), size=(80, 115)),
+          wx.StaticText(runeListBox[1], label="",id=-1, pos=(0, 0), size=(80, 115)),
+          wx.StaticText(runeListBox[2], label="",id=-1, pos=(0, 0), size=(80, 115)),
+          wx.StaticText(runeListBox[3], label="",id=-1, pos=(0, 0), size=(80, 115)),
+          wx.StaticText(runeListBox[4], label="",id=-1, pos=(0, 0), size=(80, 115)),
+          wx.StaticText(runeListBox[5], label="",id=-1, pos=(0, 0), size=(80, 115)),
+        ]
+        monospaceFont.PointSize -= 2
+        for i in range(0, 6):
+            runeListBox[i].SetFont(monospaceFont)
+            self.unitContent.Add(runeListBox[i])
+        monospaceFont.PointSize += 2
+
+        # Create an update button:
+        #btUpdate = wx.Button(parent=pnl, id=-1, label="Update data", pos=(10,10), size=(100,40))
+        #btOptimize = wx.Button(parent=pnl, id=-1, label="Optimize unit", pos=(10,60), size=(100,40))
+
+        # Line to separate optimization parameters
+        optimizationSeparator = wx.StaticLine(parent=pnl, id=-1, pos=(210, 290), size=(650, 3), style=wx.LC_REPORT)
+        self.unitContent.Add(optimizationSeparator)
+
+        # Min stats
+        minStatBox = wx.StaticBox(pnl, label="Min. stats:",id=-1, pos=(220, 300), size=(260, 380))
+        wx.StaticText(minStatBox, label="HP",id=-1, pos=(0, 5), size=(30, 25))
+        wx.StaticText(minStatBox, label="ATK",id=-1, pos=(0, 35), size=(30, 25))
+        wx.StaticText(minStatBox, label="DEF",id=-1, pos=(0, 65), size=(30, 25))
+        wx.StaticText(minStatBox, label="SPD",id=-1, pos=(0, 95), size=(30, 25))
+        wx.StaticText(minStatBox, label="CRR",id=-1, pos=(0, 125), size=(30, 25))
+        wx.StaticText(minStatBox, label="CRD",id=-1, pos=(0, 155), size=(30, 25))
+        wx.StaticText(minStatBox, label="RES",id=-1, pos=(0, 185), size=(30, 25))
+        wx.StaticText(minStatBox, label="ACC",id=-1, pos=(0, 215), size=(30, 25))
+        wx.StaticText(minStatBox, label="EHP",id=-1, pos=(0, 245), size=(30, 25))
+        wx.StaticText(minStatBox, label="DMG",id=-1, pos=(0, 275), size=(30, 25))
+        self.minStatSlid = [
+            wx.Slider(minStatBox, id=-1, pos=(30, 0), size=(150, 30), name="slid0"),
+            wx.Slider(minStatBox, id=-1, pos=(30, 30), size=(150, 30), name="slid1"),
+            wx.Slider(minStatBox, id=-1, pos=(30, 60), size=(150, 30), name="slid2"),
+            wx.Slider(minStatBox, id=-1, pos=(30, 90), size=(150, 30), name="slid3"),
+            wx.Slider(minStatBox, id=-1, pos=(30, 120), size=(150, 30), name="slid4"),
+            wx.Slider(minStatBox, id=-1, pos=(30, 150), size=(150, 30), name="slid5"),
+            wx.Slider(minStatBox, id=-1, pos=(30, 180), size=(150, 30), name="slid6"),
+            wx.Slider(minStatBox, id=-1, pos=(30, 210), size=(150, 30), name="slid7"),
+            wx.Slider(minStatBox, id=-1, pos=(30, 240), size=(150, 30), name="slid8"),
+            wx.Slider(minStatBox, id=-1, pos=(30, 270), size=(150, 30), name="slid9")
+        ]
+        for i in range(0, 9):
+            self.minStatSlid[i].SetMin(0)
+            self.minStatSlid[i].SetMax(0)
+            self.minStatSlid[i].SetValue(0)
+            self.Bind(wx.EVT_SCROLL, self.minStatChangeBySlider, self.minStatSlid[i])
+        self.minStatText = [
+            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 0), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx0"),
+            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 30), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx1"),
+            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 60), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx2"),
+            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 90), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx3"),
+            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 120), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx4"),
+            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 150), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx5"),
+            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 180), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx6"),
+            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 210), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx7"),
+            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 240), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx8"),
+            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 270), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx9")
+        ]
+        for i in range(0, 9):
+            self.Bind(wx.EVT_TEXT_ENTER, self.minStatChangeByText, self.minStatText[i])
+        minStatsReset = wx.Button(parent=minStatBox, id=-1, pos=(10, 300), size=(100, 40), style=wx.LC_REPORT, label="Reset all")
+        minStatsAdapt = wx.Button(parent=minStatBox, id=-1, pos=(120, 300), size=(100, 40), style=wx.LC_REPORT, label="Adapt all")
+        # TODO: Add binds
+        self.unitContent.Add(minStatBox)
+
+        # Rune sets
+        names = [
+          "",        "ENERGY ", "GUARD  ", "SWIFT  ", "BLADE  ", "RAGE   ",
+          "FOCUS  ", "ENDURE ", "FATAL  ", "DESPAIR", "VAMPIRE", "VIOLENT",
+          "NEMESIS", "WILL   ", "SHIELD ", "REVENGE", "DESTROY", "FIGHT  ",
+          "DETERMI", "ENHANCE", "ACCURAC", "TOLERAN"
+        ]
+        setBox = wx.StaticBox(pnl, label="Rune Sets:",id=-1, pos=(500, 300), size=(330, 65))
+        self.runeSets = [
+            wx.Choice(parent=setBox, id=-1, pos=(5, 0), choices=names),
+            wx.Choice(parent=setBox, id=-1, pos=(110, 0), choices=names),
+            wx.Choice(parent=setBox, id=-1, pos=(215, 0), choices=names)
+        ]
+        self.unitContent.Add(setBox)
+
+        # Allowed main stats for even slots
+        names = [
+          ["HP  ", "HP% ", "ATK ", "ATK%", "DEF ", "DEF%"],
+          ["SPD ", "CRR ", "CRD ", "RES ", "ACC "]
+        ]
+        statBox = wx.StaticBox(pnl, label="Main stats (2, 4, 6):",id=-1, pos=(500, 380), size=(150, 190))
+        self.stats = [
+          wx.CheckListBox(parent=statBox, id=-1, pos=(5, 5), size=(70, 155), choices=names[0]),
+          wx.CheckListBox(parent=statBox, id=-1, pos=(70, 5), size=(70, 155), choices=names[1])
+        ]
+        self.unitContent.Add(statBox)
+
+        levelBox = wx.StaticBox(pnl, label="Rune Level:",id=-1, pos=(700, 380), size=(100, 65))
+        self.level = wx.Choice(parent=levelBox, id=-1, pos=(5, 0), choices=["Current", "+ 12", " + 15"])
+        self.unitContent.Add(levelBox)
+
+        # Button to start
+        btOptimize = wx.Button(parent=pnl, id=-1, pos=(700, 480), size=(100, 40), style=wx.LC_REPORT, label="OPTIMIZE")
+        self.unitContent.Add(btOptimize)
+        self.Bind(wx.EVT_BUTTON, self.startOptimization, btOptimize)
+
+
+        self.unitContent.ShowItems(False)
+
+    def populateUnitList(self, event):
+        """Populates the unit list.
+
+        Uses the filters.
+
+        Parameters
+        ----------
+        event : wxEvent, optional
+            The event that triggered the call (default is None).
+
+        """
+
+        # Get units from db
+        name = self.filterName.GetValue()
+        query = """
+          SELECT
+            id,
+            name,
+            (
+              SELECT cast(total(teams.priority) as int)
+              FROM teams, units_teams
+              WHERE teams.id = units_teams.team AND units_teams.unit = units.id
+            ) as priority,
+            storage
+          FROM units
+          WHERE
+            name LIKE '%""" + name + """%'
+        """
+        if self.filterStorage.GetValue() == False:
+            query += " AND storage = 0 "
+        if self.filterNoRunes.GetValue() == False:
+            query += " AND id IN (SELECT DISTINCT unit FROM runes) "
+        if self.filterNoTeams.GetValue() == False:
+            query += " AND id IN (SELECT DISTINCT unit FROM units_teams) "
+        query += " ORDER BY priority DESC; ";
+        print(query)
+        cursor = conn.execute(query)
+        i = 0
+        self.unitList.DeleteAllItems()
+        for row in cursor:
+            self.unitList.InsertItem(i, row[1])
+            self.unitList.SetItem(i, 1, str(row[2]))
+            self.unitList.SetItem(i, 2, "")
+            self.unitList.SetItemData(i, int(row[0]))
+            if (int(row[3]) == 1):
+                self.unitList.SetItem(i, 2, "X")
+            else:
+                self.unitList.SetItem(i, 2, " ")
+            i = i + 1
+
+    def startOptimization(self, event):
+        """Prepares and runs a command optimization.
+
+        Once is done, opens a ResultsFrame.
+
+        Parameters
+        ----------
+        event : wxEvent, optional
+            The event that triggered the call (default is None).
+
+        """
+
+        # Example call:
+        # ../RuneOptimizer optimize 7223811472 -l 15 -e rage,blade --stats atk,crr,crd -h 10000 -f 10
+        command = "RuneOptimizer optimize "
+        #print("StartOptimization...")
+        unitId = str(self.unitList.GetItemData(self.unitList.GetFirstSelected()))
+        command += unitId
+        #print("    Unit ID: " + unitId)
+        level = self.level.GetSelection()
+        if level == 1:
+            level = "12"
+        elif level == 2:
+            level = "15"
+        else:
+            level = "current"
+        command += (" --level " + level)
+        #print("    Rune level: " + level)
+        sets = ""
+        for i in range (0, 2):
+            selected = self.runeSets[i].GetString(self.runeSets[i].GetSelection()).upper().replace(" ", "");
+            for j in range(0, 22):
+                name = set_names[j].upper()
+                if len(name) > 7:
+                    name = name[0:7]
+                #print(selected + " - " + name)
+                if selected == name:
+                    sets += set_names[j].lower() + ","
+        if len(sets) > 0:
+            sets = sets[:-1]
+            # TODO: ELSE ERROR
+        command += (" --sets " + sets)
+        stats = ""
+        selected_stats = self.stats[0].GetCheckedItems() + self.stats[1].GetCheckedItems()
+        for s in self.stats[0].GetCheckedItems():
+            if s == 0:
+                stats += "hpflat,"
+            elif s == 1:
+                stats += "hp,"
+            elif s == 2:
+                stats += "atkflat,"
+            elif s == 3:
+                stats += "atk,"
+            elif s == 4:
+                stats += "defflat,"
+            elif s == 5:
+                stats += "def,"
+        for s in self.stats[1].GetCheckedItems():
+            if s == 0:
+                stats += "spd,"
+            elif s == 1:
+                stats += "crr,"
+            elif s == 2:
+                stats += "crd,"
+            elif s == 3:
+                stats += "res,"
+            elif s == 4:
+                stats += "acc,"
+        if len(stats) > 0:
+            stats = stats[:-1]
+            # TODO: ELSE ERROR
+        command += (" --stats " + stats)
+        #print("    Main stats: " + stats)
+
+        command += (" --min-hp " + str(self.minStatSlid[0].GetValue()))
+        command += (" --min-atk " + str(self.minStatSlid[1].GetValue()))
+        command += (" --min-def " + str(self.minStatSlid[2].GetValue()))
+        command += (" --min-spd " + str(self.minStatSlid[3].GetValue()))
+        command += (" --min-crr " + str(self.minStatSlid[4].GetValue()))
+        command += (" --min-crd " + str(self.minStatSlid[5].GetValue()))
+        command += (" --min-res " + str(self.minStatSlid[6].GetValue()))
+        command += (" --min-acc " + str(self.minStatSlid[7].GetValue()))
+        command += (" --min-ehp " + str(self.minStatSlid[8].GetValue()))
+        command += (" --min-dmg " + str(self.minStatSlid[9].GetValue()))
+
+
+        command += (" --gui ")
+        print("Command: " + command)
+
+        command = "../../" + command
+        out = subprocess.check_output(command.split())
+        #print ("---- OUTPUT ------------------------------------------------------------------------------------------")
+        #print(out)
+        #print ("------------------------------------------------------------------------------------------------------")
+
+        resultsFrame = ResultsFrame(None, title='Options for Lushen (DEBUG)', pos=(100, 50), size=(900, 680))
+        resultsFrame.unitId = unitId
+        resultsFrame.unitName = self.unitNameValue
+        resultsFrame.currentStats = self.currentStats
+        resultsFrame.processResults(bytes.decode(out))
+        resultsFrame.Show()
+
+    def minStatChangeBySlider(self, event):
+        """Changes text when a slider is changed.
+
+        Doesn't do validation.
+
+        Parameters
+        ----------
+        event : wxEvent, optional
+            The event that triggered the call (default is None).
+
+        """
+
+        slidId = int(event.GetEventObject().GetName().replace("slid", ""))
+        self.minStatText[slidId].SetValue(str(event.GetEventObject().GetValue()))
+
+    def minStatChangeByText(self, event):
+        """Changes the slider when the text is changed.
+
+        Validates the text value.
+
+        Parameters
+        ----------
+        event : wxEvent, optional
+            The event that triggered the call (default is None).
+
+        """
+        textId = int(event.GetEventObject().GetName().replace("tx", ""))
+        if event.GetEventObject().GetValue().isdigit() == False:
+            event.GetEventObject().SetValue(str(self.minStatSlid[textId].GetValue()))
+        value = int(event.GetEventObject().GetValue())
+        minValue = self.minStatSlid[textId].GetMin()
+        maxValue = self.minStatSlid[textId].GetMax()
+        if value < minValue:
+            value = minValue
+            event.GetEventObject().SetValue(str(value))
+        elif value > maxValue:
+            value = maxValue
+            event.GetEventObject().SetValue(str(value))
+        self.minStatSlid[textId].SetValue(value)
+
+    def unitSelected(self, event):
+        """Loads a unit info and enables optimizaton options.
+
+        Called when a unit is selected from the list.
+
+        Parameters
+        ----------
+        event : wxEvent, optional
+            The event that triggered the call (default is None).
+
+        """
+
+        id = str(event.GetEventObject().GetItemData(event.GetEventObject().GetFirstSelected()))
+
+        # First, set sliders max values
+        self.minStatSlid[0].SetMax(50000)  #HP
+        self.minStatSlid[1].SetMax(5000)   #ATK
+        self.minStatSlid[2].SetMax(5000)   #DEF
+        self.minStatSlid[3].SetMax(500)    #SPD
+        self.minStatSlid[4].SetMax(100)    #CRR
+        self.minStatSlid[5].SetMax(500)    #CRD
+        self.minStatSlid[6].SetMax(100)    #RES
+        self.minStatSlid[7].SetMax(85)     #ACC
+        self.minStatSlid[8].SetMax(250000) #EHP
+        self.minStatSlid[9].SetMax(8000)   #DMG
+
+        cursor = conn.execute("""
+          SELECT
+            base_hp,
+            base_atk,
+            base_def,
+            base_spd,
+            base_crr,
+            base_crd,
+            base_res,
+            base_acc,
+            current_hp,
+            current_atk,
+            current_def,
+            current_spd,
+            current_crr,
+            current_crd,
+            current_res,
+            current_acc,
+            id,
+            name
+          FROM units
+          WHERE
+            id = """ + id + """;
+        """)
+        row = cursor.fetchone()
+        self.unitContent.ShowItems(True)
+        self.unitNameValue = str(row[17])
+        self.unitName.SetLabel(str(row[17]) + "    (# " + str(row[16]) + ")")
+        for i in range(0, 8):
+            value = str(row[i])
+            self.minStatSlid[i].SetMin(int(value))
+            if i > 3:
+                value = value + "%"
+            else:
+                value = value + " "
+            self.statGrid.SetCellValue(row=i, col=0, s=value)
+        for i in range(0, 8):
+            value = str(row[8 + i])
+            self.minStatSlid[i].SetValue(int(value))
+            self.minStatText[i].SetValue(value)
+            self.currentStats[i] = int(value)
+            if i > 3:
+                value = value + "%"
+            else:
+                value = value + " "
+            self.statGrid.SetCellValue(row=i, col=1, s=value)
+        # Galculate EHP and DMG
+        baseHp = int(self.statGrid.GetCellValue(row=0, col=0))
+        baseDef = int(self.statGrid.GetCellValue(row=2, col=0).replace("%", ""))
+        baseEhp = math.ceil((((baseDef * 3.5) + 1140) * baseHp) / 1000)
+        self.statGrid.SetCellValue(row=8, col=0, s=str(baseEhp))
+        self.minStatSlid[8].SetMin(baseEhp)
+        currentHp = int(self.statGrid.GetCellValue(row=0, col=1))
+        currentDef = int(self.statGrid.GetCellValue(row=2, col=1).replace("%", ""))
+        currentEhp = math.ceil((((currentDef * 3.5) + 1140) * currentHp) / 1000)
+        self.statGrid.SetCellValue(row=8, col=1, s=str(currentEhp))
+        self.minStatSlid[8].SetValue(currentEhp)
+        self.minStatText[8].SetValue(str(currentEhp))
+        baseAtk = int(self.statGrid.GetCellValue(row=1, col=0))
+        baseCrr = int(self.statGrid.GetCellValue(row=4, col=0).replace("%", ""))
+        baseCrd = int(self.statGrid.GetCellValue(row=5, col=0).replace("%", ""))
+        if (baseCrr > 100):
+            # Dont use crit rate over 100
+            baseCrr = 100
+        baseDmg = math.ceil((baseAtk * (100 - baseCrr) / 100) + ((baseAtk + (baseAtk * baseCrd / 100)) * baseCrr / 100));
+        self.statGrid.SetCellValue(row=9, col=0, s=str(baseDmg))
+        self.minStatSlid[9].SetMin(baseDmg)
+        currentAtk = int(self.statGrid.GetCellValue(row=1, col=1))
+        currentCrr = int(self.statGrid.GetCellValue(row=4, col=0).replace("%", ""))
+        currentCrd = int(self.statGrid.GetCellValue(row=5, col=1).replace("%", ""))
+        if (currentCrr > 100):
+            # Dont use crit rate over 100
+            currentCrr = 100
+        currentDmg = math.ceil((currentAtk * (100 - currentCrr) / 100) + ((currentAtk + (currentAtk * currentCrd / 100)) * currentCrr / 100));
+        self.statGrid.SetCellValue(row=9, col=1, s=str(currentDmg))
+        self.minStatSlid[9].SetValue(currentDmg)
+        self.minStatText[9].SetValue(str(currentDmg))
+
+        # Populate the runes
+        for i in range(0, 6):
+            self.runeList[i].SetLabel("")
+        cursor = conn.execute("""
+          SELECT
+            id, slot, type
+          FROM runes
+          WHERE
+            unit = """ + id + """
+          ORDER BY slot;
+        """)
+        i = 0
+        for row in cursor:
+            label = ""
+            label = label + set_names[row[2]] + "\n"
+            # Get all stats
+            cursorStats = conn.execute("""
+              SELECT
+                slot, stat, value, grind, enchant
+              FROM rune_stats
+              WHERE
+                rune = """ + row[0] + """
+              ORDER BY slot;
+                """)
+            j = -1
+            for rowStats in cursorStats:
+                while (j != rowStats[0]):
+                    label = label + "\n"
+                    j = j + 1;
+                label = label + stat_names[rowStats[1]] + str(rowStats[2]).rjust(4) + ""
+                if rowStats[3] > 0:
+                    label = label + " +" + str(rowStats[3])
+            self.runeList[i].SetLabel(label)
+            i = i + 1
+
+    def makeMenuBar(self):
+        """Sets up the application menu.
+        """
+
+        updateMenu = wx.Menu()
+        updateJson = updateMenu.Append(
+          -1,
+          "&Update from JSON file\tCtrl-J",
+          "Updates the database from a profile JSON file."
+        );
+        updateSwdb = updateMenu.Append(
+          -1,
+          "&Update from SWDB\tCtrl-W",
+          "Updates the database from data retrieved from a SWDB instance."
+        );
+        updateSwarfarm = updateMenu.Append(
+          -1,
+          "&Update from Sarfarm\tCtrl-F",
+          "Updates the database from data retrieved from Swarfarm."
+        );
+        updateSqlite = updateMenu.Append(
+          -1,
+          "&Update from a sqlite database\tCtrl-Q",
+          "Updates the database from a SWDB sqlite database."
+        );
+
+        # Make a file menu with Hello and Exit items
+        fileMenu = wx.Menu()
+        # The "\t..." syntax defines an accelerator key that also triggers
+        # the same event
+        aboutItem = fileMenu.Append(wx.ID_ABOUT)
+        fileMenu.AppendSeparator()
+        # When using a stock ID we don't need to specify the menu item's
+        # label
+        exitItem = fileMenu.Append(wx.ID_EXIT)
+
+
+
+        # Make the menu bar and add the two menus to it. The '&' defines
+        # that the next letter is the "mnemonic" for the menu item. On the
+        # platforms that support it those letters are underlined and can be
+        # triggered from the keyboard.
+        menuBar = wx.MenuBar()
+        menuBar.Append(fileMenu, "&File")
+        menuBar.Append(updateMenu, "&Update")
+
+        # Give the menu bar to the frame
+        self.SetMenuBar(menuBar)
+
+        # Finally, associate a handler function with the EVT_MENU event for
+        # each of the menu items. That means that when that menu item is
+        # activated then the associated handler function will be called.
+        #self.Bind(wx.EVT_MENU, self.OnHello, helloItem)
+        self.Bind(wx.EVT_MENU, self.closeApp, exitItem)
+        self.Bind(wx.EVT_MENU, self.showAbout, aboutItem)
+        self.Bind(wx.EVT_MENU, self.updateFromJson, updateJson)
+        self.Bind(wx.EVT_MENU, self.updateFromSwdb, updateSwdb)
+        self.Bind(wx.EVT_MENU, self.updateFromSwarfarm, updateSwarfarm)
+        self.Bind(wx.EVT_MENU, self.updateFromSqlite, updateSqlite)
+
+    def closeApp(self, event):
+        """Closes the app.
+
+        Parameters
+        ----------
+        event : wxEvent, optional
+            The event that triggered the call (default is None).
+
+        """
+
+        self.Close(True)
+
+    def showAbout(self, event):
+        """Display an About dialog.
+
+        Parameters
+        ----------
+        event : wxEvent, optional
+            The event that triggered the call (default is None).
+
+        """
+
+        wx.MessageBox("RuneOptimizerAbout", wx.OK | wx.ICON_INFORMATION)
+
+    def updateFromJson(self, event):
+        """Updates data from a JSON file.
+
+        TODO
+
+        Parameters
+        ----------
+        event : wxEvent, optional
+            The event that triggered the call (default is None).
+
+        """
+
+        self.showUnimplemented(wx.EVT_MENU)
+
+    def updateFromSwdb(self, event):
+        """Updates data from a SWDB instance.
+
+        TODO
+
+        Parameters
+        ----------
+        event : wxEvent, optional
+            The event that triggered the call (default is None).
+
+        """
+
+        self.showUnimplemented()
+
+    def updateFromSwarfarm(self, event):
+        """Updates data from Swarfarm.
+
+        TODO
+
+        Parameters
+        ----------
+        event : wxEvent, optional
+            The event that triggered the call (default is None).
+
+        """
+
+        self.showUnimplemented()
+
+    def updateFromSqlite(self, event):
+        """Updates data from a Sqlite file.
+
+        TODO
+
+        Parameters
+        ----------
+        event : wxEvent, optional
+            The event that triggered the call (default is None).
+
+        """
+
+        self.showUnimplemented()
+
+    def showUnimplemented(self, event):
+        """Displays a message for unimplemented features.
+
+        Parameters
+        ----------
+        event : wxEvent, optional
+            The event that triggered the call (default is None).
+
+        """
+
+        wx.MessageBox(parent=self, message="This functionality is not yet implemented", caption="Unimplemented")