/**
 * Provides a dice rolling command specialized for MLP: RiM season 4 edition.
 * The syntax for the command is 
 * !r [{Character name}:]{skill name} [+/- Advantages/Drawbacks]
 * 
 * e.g.
 * !r Spectrum Square:Energy Weapons +4 -2
 * 
 * The character name and skill name are case-insensitive. You should only
 * need to enter the character name if you control more than one character,
 * otherwise it automatically use the first character you control (which for
 * most players is just one).
 * 
 * There are a couple other assumptions the script makes about a character's 
 * attributes in order for it to work:
 * - All attributes related to skills have the word 'skill' in them somewhere.
 * - Skill attributes are divided into 4 parts:
 *      repeating_skills{mind|body|heart}_{repeating skills index}_{SkillAttributeName}
 * 
 *      * The 1st part is just a literal used by roll20 for all repeating fieldset attributes
 *      on a character sheet.
 *      * The 2nd part is the name of the repeating field set. The script assumes that
 *      your character sheets have skillsmind, skillsbody, and skillsheart repeating
 *      fieldsets, which are identical accept for the primary attribute they're 
 *      based off of.
 *      * The 3rd part is the index of the skill's group of attributes in the 
 *      repeating fieldset. All attributes for one skill share this index. It too
 *      is automatically generated by Roll20's character sheet API.
 *      * The 4th part is the name of an attribute for the skill. This script assumes
 *      that each skill's repeating field set contains fields with these names:
 *      skillX, skillXT, skillXI, skillXG, skillXMisc, where X is M, B, or H for
 *      mind, body, and heart respectively.
 * 
 *      skillX is the text field actually containing the skill's name.
 * 
 *      skillXT is a checkbox field marking whether the skill is trained. I.E.
 *      The character took the Skill Training edge for that skill. 
 *      The checkbox's value when checked is 1.
 * 
 *      skillXI is a checkbox for Improved Skill Training for the skill, again 
 *      with a checked value of 1.
 *      
 *      skillXG is a checkbox for Greater Skill Training for the skill, again
 *      with a checked value of 1.
 * 
 *      skillXMisc is a number field for the total of any other modifiers for
 *      the skill, which aren't Advantages and Drawbacks.
 */
(function() {
    
    var cmd = "!r "
    
    /**
     * Gets a character controlled by the player.
     * @param {String} playerId     The player's ID.
     * @param {String} [name]       The character's name. If not provided, 
     *                              the player's first controlled token is returned.
     * @return {Character}
     */
    function getCharacter(playerId, name) {
        var allCharacters = findObjs({
            _type: "character"
        });
        
        if(name) {
            // If a name was provided, get that character if it exists and
            // is controlled by playerId.
            return _.find(allCharacters, function(ch) {
                return (ch.get("name").toLowerCase().trim().indexOf(name) !== -1 && controlledBy(ch, playerId));
            });
        }
        else {
            // If no name was provided, get the first character controlled by
            // playerId.
            return _.find(allCharacters, function(ch) {
                return controlledBy(ch, playerId);
            });
        }
    };
    
    /**
     * Checks if a character is controllable by a player.
     * @param {Character} ch        The character
     * @param {String} playerId     The ID of the player.
     * @return {boolean} true iff the character is controllable by the player.
     */
    function controlledBy(ch, playerId) {
        return (ch.get("controlledby").indexOf(playerId) !== -1);
    };
    
    /**
     * Gets all the skill attributes for a character.
     * @param {Character} character
     * @return {Attribute[]}
     */
    function getAllSkills(character) {
        return _.filter(findObjs({
                _type: "attribute",
                _characterid: character.id
            }), function(attr) {
                return (attr.get("name").indexOf("skill") !== -1);
            });
    };
    
    /**
     * Gets information about a skill.
     * @param {Character} character
     * @param {String} name     The name of the skill.
     * @return {Object}
     */
    function getSkill(character, name) {
        var skills = getAllSkills(character);
        var skill = _.find(skills, function(attr) {
            var value = getSkillName(attr);
            return (value.indexOf(name) !== -1);
        });
        
        if(skill) {
            var name = getSkillName(skill);
            var toks = splitSkill(skill);
            var index = toks[0];
            var type = toks[1];
            
            var trained = (parseInt(getSkillField(skills, type, index, "T")) == 1);
            var improved = (parseInt(getSkillField(skills, type, index, "I")) == 1);
            var greater = (parseInt(getSkillField(skills, type, index, "G")) == 1);
            var bonus = parseInt(getSkillField(skills, type, index, "Misc")) || 0;
            
            var attr = {};
            if(type === "skillM")
                attr.name = "mind";
            if(type === "skillB")
                attr.name = "body";
            if(type === "skillH")
                attr.name = "heart";
            
            attr.value = getAttrByName(character.id, attr.name); 
            
            return {
                name: name,
                attr: attr,
                trained: trained,
                improved: improved,
                greater: greater,
                bonus: bonus
            };
        }
    };
    
    function splitSkill(skill) {
        var toks = skill.get("name").split("_");
        return [toks[2], toks[3]];
    };
    
    /**
     * Gets the value of the attribute for a skill's name.
     */
    function getSkillName(skill) {
        return skill.get("current").toLowerCase();
    };
    
    /**
     * Extracts a field value for a skill.
     */
    function getSkillField(skills, type, index, field) {
        var field = _.find(skills, function(skill) {
            var toks = splitSkill(skill);
            return (toks[0] === index && toks[1] === (type + field));
        });
        if(field)    
            return field.get("current");
        else
            return undefined;
    };
    
    
    function getSkillRoll(skill, advDis) {
        advDis = advDis || 0;
        
        var name = skill.name;
        var attr = skill.attr;
        var dice = "2d6";
        var tier = "untrained";
        var bonus = skill.bonus;
        
        if(skill.trained) {
            dice = "3d6d1";
            tier = "trained"; 
        }
        if(skill.improved) {
            dice = "4d6d2";
            tier = "improved";
        }
        if(skill.greater) {
            tier = "greater";
        }
        
        var roll = "/r [" + name + " " + tier +"]" + " {{" + dice + " + " + advDis + ", 12 + 1d0}kl1, 2 + 1d0}kh1";
        if(skill.greater)
            roll += " + 1[G]";
        roll += " + " + attr.value + "[" + attr.name + "]";
        roll += " + " + bonus;
        return roll;
    }
    
    
    function getAdvantages(args) {
        args = _.drop(args, 1);
        return _.reduce(args, function(memo, arg) {
            arg = arg.toLowerCase();
            var value = 0;
            if(arg.indexOf("a") !== -1) {
                arg.replace("a", "").trim();
                value = parseInt(arg) || 0;
            }
            return memo + value;
        }, 0);
    }
    
    function getDrawbacks(args) {
        args = _.drop(args, 1);
        return _.reduce(args, function(memo, arg) {
            arg = arg.toLowerCase();
            var value = 0;
            if(arg.indexOf("d") !== -1) {
                arg.replace("d", "").trim();
                value = Math.abs(parseInt(arg) || 0);
            }
            return memo + value;
        }, 0);
    }
    

    on("chat:message", function(msg) {
        
        if(msg.type == "api" && msg.content.indexOf(cmd) !== -1) {
            var str = msg.content.replace(cmd, "");
            var args;
            var sign;
            if(str.indexOf("+") !== -1) {
                args = str.split("+");
                sign = "";
            }
            else if(str.indexOf("-") !== -1) {
                args = str.split("-");
                sign = "-";
            }
            else {
                args = [str];
            }
            
            args = _.map(args, function(arg) {
                return arg.trim().toLowerCase();
            });
            
            var playerId = msg.playerid;
            
            var charName = "";
            var skillName = args[0];
            var adv = sign + args[1];
            
            var character;
            if(skillName && skillName.indexOf(":") !== -1) {
                var toks = _.map(skillName.split(":"), function(tok) {
                    return tok.trim();
                });
                
                charName = toks[0];
                character = getCharacter(playerId, charName);
                skillName = toks[1];
            }
            else {
                character = getCharacter(playerId);
            }
            
            if(character) {
                var skill = getSkill(character, skillName);
                if(skill) {
                    var roll = getSkillRoll(skill, adv);
                    sendChat(msg.who, roll);
                } 
                else
                    sendChat("ERROR", "/w " + msg.who + " Could not find skill: " + skillName);
            }
            else
                sendChat("ERROR", "/w " + msg.who + " Could not find character: " + charName);
        }
    });
})()