ShadeX91 / Materia inserter for Ariyala

// ==UserScript==
// @name         Materia inserter for Ariyala
// @version      0.1.5
// @description  Provides buttons to automatically insert materia into slots
// @author       ShadeX91
// @match        http://ffxiv.ariyala.com/*
// @require https://code.jquery.com/jquery-3.2.1.slim.js
// @grant        none
// ==/UserScript==



(function() {
    'use strict';

    var healers = ["White Mage", "Scholar", "Astrologian"];
    var tanks = ["Dark Knight", "Warrior", "Paladin"];
    var dpsDex = ["Bard", "Machinist", "Ninja"];
    var dpsStr = ["Monk", "Samurai", "Dragoon"];
    var dpsInt = ["Black Mage", "Summoner", "Red Mage"];

    var prio1 = ["STR", "DHT", "DET", "CRT", "TEN"];

    var materiaSelector = $("#materiaWindow .placer:nth-child(2)");

    // Materia chances: $("#materiaWindow tr td.right")

    var prioInput = "<label for='prioInput'>Priorities:</label><input type='text' value='STR, DHT, DET, CRT, SKS, TEN' id='prioInput' style='display:block; margin: 5px auto 15px auto; width: 200px'>";
    materiaSelector.append(prioInput);
    
    //var debugButton = "<input type='submit' value='Show Debug Info' id='myDebugButton'>";
    //materiaSelector.append(debugButton);

    var testButton = "<input type='submit' value='Fill Materia' id='myTestButton'>";
    materiaSelector.append(testButton);

    //var resetButton = "<input type='submit' value='Reset Materia' id='myResetButton'>";
    //materiaSelector.append(resetButton);

    //document.getElementById("materiaSelect1").selectedIndex=5;
    //document.getElementById("materiaSelect1").dispatchEvent(new Event("change"));

    $("#myTestButton").click(function() {
        $("#materiaTableClearButton").click(); 

        // Use maximum or safe melds
        var doMaximum = false;

        // Whether this is a piece of accessory
        var isAccessory = false;

        // Total number of available slots
        var numTotal = 0;

        // Number of safe materia slots
        var materiaChances = $("#materiaWindow tr td.right");
        var numSafe = 0;

        // Get number of "safe" materia slots
        $.each(materiaChances, function(index, value) {
            if($(value).html() === "100%")
                numSafe++;
        });

        // If the second select box does not offer Vit, then it is an accessory
        var vitIndex = getIndexByValue("#materiaSelect2", "VIT:0");
        if(vitIndex === -1) {
            isAccessory = true;
        }

        var materiaSelectors = $(".materiaTable select");
        $.each(materiaSelectors, function(index, value) {
            if(! $(value).is(":disabled") ) 
                numTotal++;
        });
        
        var prios = getPriosFromInput();

        // Example values: isAccessory = false, numTotal = 5, numSafe = 2, doMaximum = false
        var res = calculateMelds(getAttributeOrder(), getAttributesToMax(), prios, numTotal, numSafe, isAccessory, true);

        
        // Insert our values into the website
        for(var i = 0; i < res.length; i++) {
            $("#materiaSelect"+(i+1)).val(res[i]).change();
        }

    });

    // Calculate optimal melds with the given parameters.
    function calculateMelds(attributes, toMax, priorities, numTotal, numSafe, isAccessory, doMax) {

        var result = [];
        var maxVals = toMax;
        var pIndex = 0;
        var targetGrade = 5;

        // TODO: This needs serious cleanup
        
        var i=0;
        while(i < numTotal) {
            pIndex = 0;
            targetGrade = 5;
            if(numSafe - i < 0) {
                targetGrade = 4;
            }


            var attrString = priorities[pIndex];

            if(isMainStat(attrString) && isAccessory && i > 0) {
                while(isMainStat(attrString)) {
                    pIndex++;
                    if(pIndex >= priorities.length) {
                        return result;
                    }
                    attrString = priorities[pIndex];

                }

            }

            var attrIndex = $.inArray(attrString, attributes);
            var attrToMax = maxVals[attrIndex];
            var matGrade = calculateMateriaGrade(attrString, attrToMax, doMax);

            if(matGrade >= targetGrade) {
                matGrade = Math.min(matGrade, targetGrade);
                result[i] = attrString + ":" + matGrade;
                maxVals[attrIndex] = attrToMax - getStatsFromMateriaGrade(attrString, matGrade);
            } else {
                pIndex++;
                var found = false;
                do {
                    attrString = priorities[pIndex];
                    attrIndex = $.inArray(attrString, attributes);
                    attrToMax = maxVals[attrIndex];
                    matGrade = calculateMateriaGrade(attrString, attrToMax, doMax);

                    if(matGrade >= targetGrade) {
                        matGrade = Math.min(matGrade, targetGrade);
                        result[i] = attrString + ":" + matGrade;
                        maxVals[attrIndex] = attrToMax - getStatsFromMateriaGrade(attrString, matGrade);
                        found = true;
                    } else if(pIndex+1 < priorities.length) {
                        pIndex++;
                    } else {
                        pIndex = 0;
                        targetGrade--;
                    }
                } while(targetGrade > 0 && !found);
            }


            i++;
        }

        return result;
    }

    function getPriosFromInput() {
        var text = $("#prioInput").val().replace(/ /g, '');
        var arr = text.split(",");
        return arr;

    }
    
    function getStatsFromMateriaGrade(attribute, grade) {

        var values = [2,4,6,9,12,40];
        if(isMainStat(attribute)) {
            values = [1,2,4,7,15,25];
        }
        return values[grade];
    }

    // Calculate which grade materia (0-5) to use depending on the stat we give it
    function calculateMateriaGrade(attribute, toMax, overfill) {
        var grade = 0;
        var values = [2,4,6,9,12,40];
        if(isMainStat(attribute)) {
            values = [1,2,4,7,15,25];
        }
        
        $.each(values,function(index, value) {
            if(toMax >= value || (overfill && index==values.length-1 && toMax-value > -15) ) {
                grade++;
            } else
                return false;
        });

        return grade-1;
    }

    function isMainStat(attribute) {
        return $.inArray(attribute, ["STR","DEX","INT","MND","VIT"]) !== -1;
    }

    $("#myResetButton").click(function() {
        $("#materiaTableClearButton").click(); 
    });

    $("#myDebugButton").click(function() {

        var attributes = getAttributesToMax();

        // Debug info, current value of attributes and so on.
        console.log(attributes);
        console.log(getAttributeOrder());

    });

    // Not really necessary since we can change selected values via .val(value)
    function getIndexByValue(selectbox, value) {
        var count = 0;
        $(selectbox +" option").each(
            function(){
                if($(this).attr("value") === value)
                    return false;
                else
                    count++;
            }
        );
        if(count == $(selectbox +" option").length) return -1;

        return count;
    }

    function getAttributesToMax() {
        var table = $("#materiaWindow .placer:nth-child(1) tr");
        table = table.splice(1,table.length-1);
        var attributes = [];
        $(table).each(function(index, value) {
            var text = $(this).find("td").eq(2).html();
            attributes[index] = parseInt(text);
        });

        return attributes;
    }


    // Create an array with the order of all attributes for this class
    function getAttributeOrder() {
        var playerClass = getClassName();
        console.log(playerClass);
        if($.inArray(playerClass, healers) !== -1) {
            return ["MND", "DHT", "CRT", "DET", "SPS", "PIE", "VIT"];
        } else if($.inArray(playerClass, tanks) !== -1) {
            return ["STR", "DHT", "CRT", "DET", "SKS", "VIT", "TEN"];
        } else if($.inArray(playerClass, dpsStr) !== -1) {
            return ["STR", "DHT", "CRT", "DET", "SKS", "VIT"];
        } else if($.inArray(playerClass, dpsDex) !== -1) {
            return ["DEX", "DHT", "CRT", "DET", "SKS", "VIT"];
        } else if($.inArray(playerClass, dpsInt) !== -1) {
            return ["INT", "DHT", "CRT", "DET", "SPS", "VIT"];
        } else {
            return [];
        }
    }

    function getClassName() {
        var playerClass = $("#categoryBoxContentName").html();
        return playerClass;
    }

})();