public class CustomNamesSet {

    private List<String> trainerNames;
    private List<String> trainerClasses;
    private List<String> doublesTrainerNames;
    private List<String> doublesTrainerClasses;
    private List<String> pokemonNicknames;

    private static final int CUSTOM_NAMES_VERSION = 1;

    // Standard constructor: read binary data from an input stream.
    public CustomNamesSet(InputStream data) throws IOException {

        if (data.read() != CUSTOM_NAMES_VERSION) {
            throw new IOException("Invalid custom names file provided.");
        }

        trainerNames = readNamesBlock(data);
        trainerClasses = readNamesBlock(data);
        doublesTrainerNames = readNamesBlock(data);
        doublesTrainerClasses = readNamesBlock(data);
        pokemonNicknames = readNamesBlock(data);
    }

    // Alternate constructor: blank all lists
    // Used for importing old names and on the editor dialog.
    public CustomNamesSet() {
        trainerNames = new ArrayList<String>();
        trainerClasses = new ArrayList<String>();
        doublesTrainerNames = new ArrayList<String>();
        doublesTrainerClasses = new ArrayList<String>();
        pokemonNicknames = new ArrayList<String>();
    }

    private List<String> readNamesBlock(InputStream in) throws IOException {
        // Read the size of the block to come.
        byte[] szData = FileFunctions.readFullyIntoBuffer(in, 4);
        int size = FileFunctions.readFullInt(szData, 0);
        if (in.available() < size) {
            throw new IOException("Invalid size specified.");
        }

        // Read the block and translate it into a list of names.
        byte[] namesData = FileFunctions.readFullyIntoBuffer(in, size);
        List<String> names = new ArrayList<String>();
        Scanner sc = new Scanner(new ByteArrayInputStream(namesData), "UTF-8");
        while (sc.hasNextLine()) {
            String name = sc.nextLine().trim();
            if (!name.isEmpty()) {
                names.add(name);
            }
        }
        sc.close();

        return names;
    }

    public byte[] getBytes() throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        
        baos.write(CUSTOM_NAMES_VERSION);

        writeNamesBlock(baos, trainerNames);
        writeNamesBlock(baos, trainerClasses);
        writeNamesBlock(baos, doublesTrainerNames);
        writeNamesBlock(baos, doublesTrainerClasses);
        writeNamesBlock(baos, pokemonNicknames);

        return baos.toByteArray();
    }

    private void writeNamesBlock(OutputStream out, List<String> names) throws IOException {
        String newln = SysConstants.LINE_SEP;
        StringBuffer outNames = new StringBuffer();
        boolean first = true;
        for (String name : names) {
            if (!first) {
                outNames.append(newln);
            }
            first = false;
            outNames.append(name);
        }
        byte[] namesData = outNames.toString().getBytes("UTF-8");
        byte[] szData = new byte[4];
        FileFunctions.writeFullInt(szData, 0, namesData.length);
        out.write(szData);
        out.write(namesData);
    }

    public List<String> getTrainerNames() {
        return Collections.unmodifiableList(trainerNames);
    }

    public List<String> getTrainerClasses() {
        return Collections.unmodifiableList(trainerClasses);
    }

    public List<String> getDoublesTrainerNames() {
        return Collections.unmodifiableList(doublesTrainerNames);
    }

    public List<String> getDoublesTrainerClasses() {
        return Collections.unmodifiableList(doublesTrainerClasses);
    }

    public List<String> getPokemonNicknames() {
        return Collections.unmodifiableList(pokemonNicknames);
    }
    
    public void setTrainerNames(List<String> names) {
        trainerNames.clear();
        trainerNames.addAll(names);
    }
    
    public void setTrainerClasses(List<String> names) {
        trainerClasses.clear();
        trainerClasses.addAll(names);
    }
    
    public void setDoublesTrainerNames(List<String> names) {
        doublesTrainerNames.clear();
        doublesTrainerNames.addAll(names);
    }
    
    public void setDoublesTrainerClasses(List<String> names) {
        doublesTrainerClasses.clear();
        doublesTrainerClasses.addAll(names);
    }
    
    public void setPokemonNicknames(List<String> names) {
        pokemonNicknames.clear();
        pokemonNicknames.addAll(names);
    }

    public static CustomNamesSet importOldNames() throws FileNotFoundException {
        CustomNamesSet cns = new CustomNamesSet();

        // Trainer Names
        if (FileFunctions.configExists(SysConstants.tnamesFile)) {
            Scanner sc = new Scanner(FileFunctions.openConfig(SysConstants.tnamesFile), "UTF-8");
            while (sc.hasNextLine()) {
                String trainername = sc.nextLine().trim();
                if (trainername.isEmpty()) {
                    continue;
                }
                if (trainername.startsWith("\uFEFF")) {
                    trainername = trainername.substring(1);
                }
                if (trainername.contains("&")) {
                    cns.doublesTrainerNames.add(trainername);
                } else {
                    cns.trainerNames.add(trainername);
                }
            }
            sc.close();
        }

        // Trainer Classes
        if (FileFunctions.configExists(SysConstants.tclassesFile)) {
            Scanner sc = new Scanner(FileFunctions.openConfig(SysConstants.tclassesFile), "UTF-8");
            while (sc.hasNextLine()) {
                String trainerClassName = sc.nextLine().trim();
                if (trainerClassName.isEmpty()) {
                    continue;
                }
                if (trainerClassName.startsWith("\uFEFF")) {
                    trainerClassName = trainerClassName.substring(1);
                }
                String checkName = trainerClassName.toLowerCase();
                int idx = (checkName.endsWith("couple") || checkName.contains(" and ") || checkName.endsWith("kin")
                        || checkName.endsWith("team") || checkName.contains("&") || (checkName.endsWith("s") && !checkName
                        .endsWith("ss"))) ? 1 : 0;
                if (idx == 1) {
                    cns.doublesTrainerClasses.add(trainerClassName);
                } else {
                    cns.trainerClasses.add(trainerClassName);
                }
            }
            sc.close();
        }

        // Nicknames
        if (FileFunctions.configExists(SysConstants.nnamesFile)) {
            Scanner sc = new Scanner(FileFunctions.openConfig(SysConstants.nnamesFile), "UTF-8");
            while (sc.hasNextLine()) {
                String nickname = sc.nextLine().trim();
                if (nickname.isEmpty()) {
                    continue;
                }
                if (nickname.startsWith("\uFEFF")) {
                    nickname = nickname.substring(1);
                }
                cns.pokemonNicknames.add(nickname);
            }
            sc.close();
        }

        return cns;
    }

} public class MiscTweak implements Comparable<MiscTweak> {

    private static final ResourceBundle bundle = ResourceBundle.getBundle("com/dabomstew/pkrandom/gui/Bundle");

    public static List<MiscTweak> allTweaks = new ArrayList<MiscTweak>();

    /* @formatter:off */
    // Higher priority value (third argument) = run first
    public static final MiscTweak BW_EXP_PATCH = new MiscTweak(1, "bwPatch", 0);
    public static final MiscTweak NERF_X_ACCURACY = new MiscTweak(2, "nerfXAcc", 0);
    public static final MiscTweak FIX_CRIT_RATE = new MiscTweak(4, "critRateFix", 0);
    public static final MiscTweak FASTEST_TEXT = new MiscTweak(8, "fastestText", 0);
    public static final MiscTweak RUNNING_SHOES_INDOORS = new MiscTweak(16, "runningShoes", 0);
    public static final MiscTweak RANDOMIZE_PC_POTION = new MiscTweak(32, "pcPotion", 0);
    public static final MiscTweak ALLOW_PIKACHU_EVOLUTION = new MiscTweak(64, "pikachuEvo", 0);
    public static final MiscTweak NATIONAL_DEX_AT_START = new MiscTweak(128, "nationalDex", 0);
    public static final MiscTweak UPDATE_TYPE_EFFECTIVENESS = new MiscTweak(256, "typeEffectiveness", 0);
    public static final MiscTweak RANDOMIZE_HIDDEN_HOLLOWS = new MiscTweak(512, "hiddenHollows", 0);
    public static final MiscTweak LOWER_CASE_POKEMON_NAMES = new MiscTweak(1024, "lowerCaseNames", 0);
    public static final MiscTweak RANDOMIZE_CATCHING_TUTORIAL = new MiscTweak(2048, "catchingTutorial", 0);
    public static final MiscTweak BAN_LUCKY_EGG = new MiscTweak(4096, "luckyEgg", 1);
    /* @formatter:on */

    private final int value;
    private final String tweakName;
    private final String tooltipText;
    private final int priority;

    private MiscTweak(int value, String tweakID, int priority) {
        this.value = value;
        this.tweakName = bundle.getString("CodeTweaks." + tweakID + ".name");
        this.tooltipText = bundle.getString("CodeTweaks." + tweakID + ".toolTipText");
        this.priority = priority;
        allTweaks.add(this);
    }

    public int getValue() {
        return value;
    }

    public String getTweakName() {
        return tweakName;
    }

    public String getTooltipText() {
        return tooltipText;
    }

    @Override
    public int compareTo(MiscTweak o) {
        // Order according to reverse priority, so higher priority = earlier in
        // ordering
        return o.priority - priority;
    }

} public class RomFunctions {

    public static Set<Pokemon> getBasicOrNoCopyPokemon(RomHandler baseRom) {
        List<Pokemon> allPokes = baseRom.getPokemon();
        Set<Pokemon> dontCopyPokes = new TreeSet<Pokemon>();
        for (Pokemon pkmn : allPokes) {
            if (pkmn != null) {
                if (pkmn.evolutionsTo.size() != 1) {
                    dontCopyPokes.add(pkmn);
                } else {
                    Evolution onlyEvo = pkmn.evolutionsTo.get(0);
                    if (!onlyEvo.carryStats) {
                        dontCopyPokes.add(pkmn);
                    }
                }
            }
        }
        return dontCopyPokes;
    }

    public static Set<Pokemon> getMiddleEvolutions(RomHandler baseRom) {
        List<Pokemon> allPokes = baseRom.getPokemon();
        Set<Pokemon> middleEvolutions = new TreeSet<Pokemon>();
        for (Pokemon pkmn : allPokes) {
            if (pkmn != null) {
                if (pkmn.evolutionsTo.size() == 1 && pkmn.evolutionsFrom.size() > 0) {
                    Evolution onlyEvo = pkmn.evolutionsTo.get(0);
                    if (onlyEvo.carryStats) {
                        middleEvolutions.add(pkmn);
                    }
                }
            }
        }
        return middleEvolutions;
    }

    public static Set<Pokemon> getFinalEvolutions(RomHandler baseRom) {
        List<Pokemon> allPokes = baseRom.getPokemon();
        Set<Pokemon> finalEvolutions = new TreeSet<Pokemon>();
        for (Pokemon pkmn : allPokes) {
            if (pkmn != null) {
                if (pkmn.evolutionsTo.size() == 1 && pkmn.evolutionsFrom.size() == 0) {
                    Evolution onlyEvo = pkmn.evolutionsTo.get(0);
                    if (onlyEvo.carryStats) {
                        finalEvolutions.add(pkmn);
                    }
                }
            }
        }
        return finalEvolutions;
    }

    /**
     * Get the 4 moves known by a Pokemon at a particular level.
     * 
     * @param pkmn
     * @param movesets
     * @param level
     * @return
     */
    public static int[] getMovesAtLevel(Pokemon pkmn, Map<Pokemon, List<MoveLearnt>> movesets, int level) {
        return getMovesAtLevel(pkmn, movesets, level, 0);
    }

    public static int[] getMovesAtLevel(Pokemon pkmn, Map<Pokemon, List<MoveLearnt>> movesets, int level, int emptyValue) {
        int[] curMoves = new int[4];

        if (emptyValue != 0) {
            Arrays.fill(curMoves, emptyValue);
        }

        int moveCount = 0;
        List<MoveLearnt> movepool = movesets.get(pkmn);
        for (MoveLearnt ml : movepool) {
            if (ml.level > level) {
                // we're done
                break;
            }

            boolean alreadyKnownMove = false;
            for (int i = 0; i < moveCount; i++) {
                if (curMoves[i] == ml.move) {
                    alreadyKnownMove = true;
                    break;
                }
            }

            if (!alreadyKnownMove) {
                // add this move to the moveset
                if (moveCount == 4) {
                    // shift moves up and add to last slot
                    for (int i = 0; i < 3; i++) {
                        curMoves[i] = curMoves[i + 1];
                    }
                    curMoves[3] = ml.move;
                } else {
                    // add to next available slot
                    curMoves[moveCount++] = ml.move;
                }
            }
        }

        return curMoves;
    }

    public static String camelCase(String original) {
        char[] string = original.toLowerCase().toCharArray();
        boolean docap = true;
        for (int j = 0; j < string.length; j++) {
            char current = string[j];
            if (docap && Character.isLetter(current)) {
                string[j] = Character.toUpperCase(current);
                docap = false;
            } else {
                if (!docap && !Character.isLetter(current) && current != '\'') {
                    docap = true;
                }
            }
        }
        return new String(string);
    }

    public static int freeSpaceFinder(byte[] rom, byte freeSpace, int amount, int offset) {
        // by default align to 4 bytes to make sure things don't break
        return freeSpaceFinder(rom, freeSpace, amount, offset, true);
    }

    public static int freeSpaceFinder(byte[] rom, byte freeSpace, int amount, int offset, boolean longAligned) {
        if (!longAligned) {
            // Find 2 more than necessary and return 2 into it,
            // to preserve stuff like FF terminators for strings
            // 161: and FFFF terminators for movesets
            byte[] searchNeedle = new byte[amount + 2];
            for (int i = 0; i < amount + 2; i++) {
                searchNeedle[i] = freeSpace;
            }
            return searchForFirst(rom, offset, searchNeedle) + 2;
        } else {
            // Find 5 more than necessary and return into it as necessary for
            // 4-alignment,
            // to preserve stuff like FF terminators for strings
            // 161: and FFFF terminators for movesets
            byte[] searchNeedle = new byte[amount + 5];
            for (int i = 0; i < amount + 5; i++) {
                searchNeedle[i] = freeSpace;
            }
            return (searchForFirst(rom, offset, searchNeedle) + 5) & ~3;
        }
    }

    public static List<Integer> search(byte[] haystack, byte[] needle) {
        return search(haystack, 0, haystack.length, needle);
    }

    public static List<Integer> search(byte[] haystack, int beginOffset, byte[] needle) {
        return search(haystack, beginOffset, haystack.length, needle);
    }

    public static List<Integer> search(byte[] haystack, int beginOffset, int endOffset, byte[] needle) {
        int currentMatchStart = beginOffset;
        int currentCharacterPosition = 0;

        int docSize = endOffset;
        int needleSize = needle.length;

        int[] toFillTable = buildKMPSearchTable(needle);
        List<Integer> results = new ArrayList<Integer>();

        while ((currentMatchStart + currentCharacterPosition) < docSize) {

            if (needle[currentCharacterPosition] == (haystack[currentCharacterPosition + currentMatchStart])) {
                currentCharacterPosition = currentCharacterPosition + 1;

                if (currentCharacterPosition == (needleSize)) {
                    results.add(currentMatchStart);
                    currentCharacterPosition = 0;
                    currentMatchStart = currentMatchStart + needleSize;

                }

            } else {
                currentMatchStart = currentMatchStart + currentCharacterPosition
                        - toFillTable[currentCharacterPosition];

                if (toFillTable[currentCharacterPosition] > -1) {
                    currentCharacterPosition = toFillTable[currentCharacterPosition];
                }

                else {
                    currentCharacterPosition = 0;

                }

            }
        }
        return results;
    }

    public static int searchForFirst(byte[] haystack, int beginOffset, byte[] needle) {
        int currentMatchStart = beginOffset;
        int currentCharacterPosition = 0;

        int docSize = haystack.length;
        int needleSize = needle.length;

        int[] toFillTable = buildKMPSearchTable(needle);

        while ((currentMatchStart + currentCharacterPosition) < docSize) {

            if (needle[currentCharacterPosition] == (haystack[currentCharacterPosition + currentMatchStart])) {
                currentCharacterPosition = currentCharacterPosition + 1;

                if (currentCharacterPosition == (needleSize)) {
                    return currentMatchStart;
                }

            } else {
                currentMatchStart = currentMatchStart + currentCharacterPosition
                        - toFillTable[currentCharacterPosition];

                if (toFillTable[currentCharacterPosition] > -1) {
                    currentCharacterPosition = toFillTable[currentCharacterPosition];
                }

                else {
                    currentCharacterPosition = 0;

                }

            }
        }
        return -1;
    }

    private static int[] buildKMPSearchTable(byte[] needle) {
        int[] stable = new int[needle.length];
        int pos = 2;
        int j = 0;
        stable[0] = -1;
        stable[1] = 0;
        while (pos < needle.length) {
            if (needle[pos - 1] == needle[j]) {
                stable[pos] = j + 1;
                pos++;
                j++;
            } else if (j > 0) {
                j = stable[j];
            } else {
                stable[pos] = 0;
                pos++;
            }
        }
        return stable;
    }

    public static String rewriteDescriptionForNewLineSize(String moveDesc, String newline, int lineSize,
            StringSizeDeterminer ssd) {
        // We rewrite the description we're given based on some new chars per
        // line.
        moveDesc = moveDesc.replace("-" + newline, "").replace(newline, " ");
        // Keep spatk/spdef as one word on one line
        moveDesc = moveDesc.replace("Sp. Atk", "Sp__Atk");
        moveDesc = moveDesc.replace("Sp. Def", "Sp__Def");
        moveDesc = moveDesc.replace("SP. ATK", "SP__ATK");
        moveDesc = moveDesc.replace("SP. DEF", "SP__DEF");
        String[] words = moveDesc.split(" ");
        StringBuilder fullDesc = new StringBuilder();
        StringBuilder thisLine = new StringBuilder();
        int currLineWC = 0;
        int currLineCC = 0;
        int linesWritten = 0;
        for (int i = 0; i < words.length; i++) {
            // Reverse the spatk/spdef preservation from above
            words[i] = words[i].replace("SP__", "SP. ");
            words[i] = words[i].replace("Sp__", "Sp. ");
            int reqLength = ssd.lengthFor(words[i]);
            if (currLineWC > 0) {
                reqLength++;
            }
            if (currLineCC + reqLength <= lineSize) {
                // add to current line
                if (currLineWC > 0) {
                    thisLine.append(' ');
                }
                thisLine.append(words[i]);
                currLineWC++;
                currLineCC += reqLength;
            } else {
                // Save current line, if applicable
                if (currLineWC > 0) {
                    if (linesWritten > 0) {
                        fullDesc.append(newline);
                    }
                    fullDesc.append(thisLine.toString());
                    linesWritten++;
                    thisLine = new StringBuilder();
                }
                // Start the new line
                thisLine.append(words[i]);
                currLineWC = 1;
                currLineCC = ssd.lengthFor(words[i]);
            }
        }

        // If the last line has anything add it
        if (currLineWC > 0) {
            if (linesWritten > 0) {
                fullDesc.append(newline);
            }
            fullDesc.append(thisLine.toString());
            linesWritten++;
        }

        return fullDesc.toString();
    }

    public static String formatTextWithReplacements(String text, Map<String, String> replacements, String newline,
            String extraline, String newpara, int maxLineLength, StringSizeDeterminer ssd) {
        // Ends with a paragraph indicator?
        boolean endsWithPara = false;
        if (text.endsWith(newpara)) {
            endsWithPara = true;
            text = text.substring(0, text.length() - newpara.length());
        }
        // Replace current line endings with spaces
        text = text.replace(newline, " ").replace(extraline, " ");
        // Replace words if replacements are set
        // do it in two stages so the rules don't conflict
        if (replacements != null) {
            int index = 0;
            for (Map.Entry<String, String> toReplace : replacements.entrySet()) {
                index++;
                text = text.replace(toReplace.getKey(), "<tmpreplace" + index + ">");
            }
            index = 0;
            for (Map.Entry<String, String> toReplace : replacements.entrySet()) {
                index++;
                text = text.replace("<tmpreplace" + index + ">", toReplace.getValue());
            }
        }
        // Split on paragraphs and deal with each one individually
        String[] oldParagraphs = text.split(newpara.replace("\\", "\\\\"));
        StringBuilder finalResult = new StringBuilder();
        int sentenceNewLineSize = Math.max(10, maxLineLength / 2);
        for (int para = 0; para < oldParagraphs.length; para++) {
            String[] words = oldParagraphs[para].split(" ");
            StringBuilder fullPara = new StringBuilder();
            StringBuilder thisLine = new StringBuilder();
            int currLineWC = 0;
            int currLineCC = 0;
            int linesWritten = 0;
            char currLineLastChar = 0;
            for (int i = 0; i < words.length; i++) {
                int reqLength = ssd.lengthFor(words[i]);
                if (currLineWC > 0) {
                    reqLength++;
                }
                if ((currLineCC + reqLength > maxLineLength)
                        || (currLineCC >= sentenceNewLineSize && (currLineLastChar == '.' || currLineLastChar == '?'
                                || currLineLastChar == '!' || currLineLastChar == '…' || currLineLastChar == ','))) {
                    // new line
                    // Save current line, if applicable
                    if (currLineWC > 0) {
                        if (linesWritten > 1) {
                            fullPara.append(extraline);
                        } else if (linesWritten == 1) {
                            fullPara.append(newline);
                        }
                        fullPara.append(thisLine.toString());
                        linesWritten++;
                        thisLine = new StringBuilder();
                    }
                    // Start the new line
                    thisLine.append(words[i]);
                    currLineWC = 1;
                    currLineCC = ssd.lengthFor(words[i]);
                    if (words[i].length() == 0) {
                        currLineLastChar = 0;
                    } else {
                        currLineLastChar = words[i].charAt(words[i].length() - 1);
                    }
                } else {
                    // add to current line
                    if (currLineWC > 0) {
                        thisLine.append(' ');
                    }
                    thisLine.append(words[i]);
                    currLineWC++;
                    currLineCC += reqLength;
                    if (words[i].length() == 0) {
                        currLineLastChar = 0;
                    } else {
                        currLineLastChar = words[i].charAt(words[i].length() - 1);
                    }
                }
            }

            // If the last line has anything add it
            if (currLineWC > 0) {
                if (linesWritten > 1) {
                    fullPara.append(extraline);
                } else if (linesWritten == 1) {
                    fullPara.append(newline);
                }
                fullPara.append(thisLine.toString());
                linesWritten++;
            }
            if (para > 0) {
                finalResult.append(newpara);
            }
            finalResult.append(fullPara.toString());
        }
        if (endsWithPara) {
            finalResult.append(newpara);
        }
        return finalResult.toString();
    }

    public interface StringSizeDeterminer {
        public int lengthFor(String encodedText);
    }

    public static class StringLengthSD implements StringSizeDeterminer {

        @Override
        public int lengthFor(String encodedText) {
            return encodedText.length();
        }

    }

} public class GlobalConstants {

    public static final boolean[] bannedRandomMoves = new boolean[560], bannedForDamagingMove = new boolean[560];
    static {
        bannedRandomMoves[144] = true; // Transform, glitched in RBY
        bannedRandomMoves[165] = true; // Struggle, self explanatory

        bannedForDamagingMove[120] = true; // SelfDestruct
        bannedForDamagingMove[138] = true; // Dream Eater
        bannedForDamagingMove[153] = true; // Explosion
        bannedForDamagingMove[173] = true; // Snore
        bannedForDamagingMove[206] = true; // False Swipe
        bannedForDamagingMove[248] = true; // Future Sight
        bannedForDamagingMove[252] = true; // Fake Out
        bannedForDamagingMove[264] = true; // Focus Punch
        bannedForDamagingMove[353] = true; // Doom Desire
        bannedForDamagingMove[364] = true; // Feint
        bannedForDamagingMove[387] = true; // Last Resort
        bannedForDamagingMove[389] = true; // Sucker Punch

        // new 160
        bannedForDamagingMove[132] = true; // Constrict, overly weak
        bannedForDamagingMove[99] = true; // Rage, lock-in in gen1
        bannedForDamagingMove[205] = true; // Rollout, lock-in
        bannedForDamagingMove[301] = true; // Ice Ball, Rollout clone

        // make sure these cant roll
        bannedForDamagingMove[39] = true; // Sonicboom
        bannedForDamagingMove[82] = true; // Dragon Rage
        bannedForDamagingMove[32] = true; // Horn Drill
        bannedForDamagingMove[12] = true; // Guillotine
        bannedForDamagingMove[90] = true; // Fissure
        bannedForDamagingMove[329] = true; // Sheer Cold

    }

    /* @formatter:off */
    public static final List<Integer> normalMultihitMoves = Arrays.asList(
            292, // Arm Thrust
            140, // Barrage
            198, // Bone Rush
            331, // Bullet Seed
            4, // Comet Punch
            3, // DoubleSlap
            31, // Fury Attack
            154, // Fury Swipes
            333, // Icicle Spear
            42, // Pin Missile
            350, // Rock Blast
            131, // Spike Cannon
            541 // Tail Slap
            );
    
    public static final List<Integer> doubleHitMoves = Arrays.asList(
            155, // Bonemerang
            458, // Double Hit
            24, // Double Kick
            530, // Dual Chop
            544, // Gear Grind
            41 // Twineedle
            );
    
    
    /* @formatter:on */

    public static final List<Integer> battleTrappingAbilities = Arrays.asList(23, 42, 71);

    public static final List<Integer> negativeAbilities = Arrays.asList(129, 112, 54, 59, 161);
    // Defeatist, Slow Start, Truant, Forecast, Zen Mode
    // To test: Illusion, Imposter

    public static final int WONDER_GUARD_INDEX = 25;

    public static final int MIN_DAMAGING_MOVE_POWER = 50;

    public static final int METRONOME_MOVE = 118;

    public static final int TRIPLE_KICK_INDEX = 167;

} public class RandomizationException extends RuntimeException {
    public RandomizationException(String text) {
        super(text);
    }

    private static final long serialVersionUID = -771695719222719664L;

} public class EncounterSet {

    public int rate;
    public List<Encounter> encounters = new ArrayList<Encounter>();
    public Set<Pokemon> bannedPokemon = new HashSet<Pokemon>();
    public String displayName;
    public int offset;

    public String toString() {
        return "Encounter [Rate = " + rate + ", Encounters = " + encounters + "]";
    }

} public class Evolution implements Comparable<Evolution> {

    public Pokemon from;
    public Pokemon to;
    public boolean carryStats;
    public EvolutionType type;
    public int extraInfo;

    public Evolution(Pokemon from, Pokemon to, boolean carryStats, EvolutionType type, int extra) {
        this.from = from;
        this.to = to;
        this.carryStats = carryStats;
        this.type = type;
        this.extraInfo = extra;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + from.number;
        result = prime * result + to.number;
        result = prime * result + type.ordinal();
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Evolution other = (Evolution) obj;
        if (from != other.from)
            return false;
        if (to != other.to)
            return false;
        if (type != other.type)
            return false;
        return true;
    }

    @Override
    public int compareTo(Evolution o) {
        if (this.from.number < o.from.number) {
            return -1;
        } else if (this.from.number > o.from.number) {
            return 1;
        } else if (this.to.number < o.to.number) {
            return -1;
        } else if (this.to.number > o.to.number) {
            return 1;
        } else if (this.type.ordinal() < o.type.ordinal()) {
            return -1;
        } else if (this.type.ordinal() > o.type.ordinal()) {
            return 1;
        } else {
            return 0;
        }
    }

} public enum EvolutionType {
    /* @formatter:off */
    LEVEL(1, 1, 4, 4, 4),
    STONE(2, 2, 7, 7, 8),
    TRADE(3, 3, 5, 5, 5),
    TRADE_ITEM(-1, 3, 6, 6, 6),
    HAPPINESS(-1, 4, 1, 1, 1),
    HAPPINESS_DAY(-1, 4, 2, 2, 2),
    HAPPINESS_NIGHT(-1, 4, 3, 3, 3),
    LEVEL_ATTACK_HIGHER(-1, 5, 8, 8, 9),
    LEVEL_DEFENSE_HIGHER(-1, 5, 10, 10, 11),
    LEVEL_ATK_DEF_SAME(-1, 5, 9, 9, 10),
    LEVEL_LOW_PV(-1, -1, 11, 11, 12),
    LEVEL_HIGH_PV(-1, -1, 12, 12, 13),
    LEVEL_CREATE_EXTRA(-1, -1, 13, 13, 14),
    LEVEL_IS_EXTRA(-1, -1, 14, 14, 15),
    LEVEL_HIGH_BEAUTY(-1, -1, 15, 15, 16),
    STONE_MALE_ONLY(-1, -1, -1, 16, 17),
    STONE_FEMALE_ONLY(-1, -1, -1, 17, 18),
    LEVEL_ITEM_DAY(-1, -1, -1, 18, 19),
    LEVEL_ITEM_NIGHT(-1, -1, -1, 19, 20),
    LEVEL_WITH_MOVE(-1, -1, -1, 20, 21),
    LEVEL_WITH_OTHER(-1, -1, -1, 21, 22),
    LEVEL_MALE_ONLY(-1, -1, -1, 22, 23),
    LEVEL_FEMALE_ONLY(-1, -1, -1, 23, 24),
    LEVEL_ELECTRIFIED_AREA(-1, -1, -1, 24, 25),
    LEVEL_MOSS_ROCK(-1, -1, -1, 25, 26),
    LEVEL_ICY_ROCK(-1, -1, -1, 26, 27),
    TRADE_SPECIAL(-1, -1, -1, -1, 7),
    NONE(-1, -1, -1, -1, -1);
    /* @formatter:on */

    private int[] indexNumbers;
    private static EvolutionType[][] reverseIndexes = new EvolutionType[5][30];

    static {
        for (EvolutionType et : EvolutionType.values()) {
            for (int i = 0; i < et.indexNumbers.length; i++) {
                if (et.indexNumbers[i] > 0 && reverseIndexes[i][et.indexNumbers[i]] == null) {
                    reverseIndexes[i][et.indexNumbers[i]] = et;
                }
            }
        }
    }

    private EvolutionType(int... indexes) {
        this.indexNumbers = indexes;
    }

    public int toIndex(int generation) {
        return indexNumbers[generation - 1];
    }

    public static EvolutionType fromIndex(int generation, int index) {
        return reverseIndexes[generation - 1][index];
    }

    public boolean usesLevel() {
        return (this == LEVEL) || (this == LEVEL_ATTACK_HIGHER) || (this == LEVEL_DEFENSE_HIGHER)
                || (this == LEVEL_ATK_DEF_SAME) || (this == LEVEL_LOW_PV) || (this == LEVEL_HIGH_PV)
                || (this == LEVEL_CREATE_EXTRA) || (this == LEVEL_IS_EXTRA) || (this == LEVEL_MALE_ONLY)
                || (this == LEVEL_FEMALE_ONLY);
    }
} public class ItemList {

    private boolean[] items;
    private boolean[] tms;

    public ItemList(int highestIndex) {
        items = new boolean[highestIndex + 1];
        tms = new boolean[highestIndex + 1];
        for (int i = 1; i <= highestIndex; i++) {
            items[i] = true;
        }
    }

    public boolean isTM(int index) {
        if (index < 0 || index >= tms.length) {
            return false;
        }
        return tms[index];
    }

    public boolean isAllowed(int index) {
        if (index < 0 || index >= tms.length) {
            return false;
        }
        return items[index];
    }

    public void banSingles(int... indexes) {
        for (int index : indexes) {
            items[index] = false;
        }
    }

    public void banRange(int startIndex, int length) {
        for (int i = 0; i < length; i++) {
            items[i + startIndex] = false;
        }
    }

    public void tmRange(int startIndex, int length) {
        for (int i = 0; i < length; i++) {
            tms[i + startIndex] = true;
        }
    }

    public int randomItem(Random random) {
        int chosen = 0;
        while (!items[chosen]) {
            chosen = random.nextInt(items.length);
        }
        return chosen;
    }

    public int randomNonTM(Random random) {
        int chosen = 0;
        while (!items[chosen] || tms[chosen]) {
            chosen = random.nextInt(items.length);
        }
        return chosen;
    }

    public int randomTM(Random random) {
        int chosen = 0;
        while (!tms[chosen]) {
            chosen = random.nextInt(items.length);
        }
        return chosen;
    }

    public ItemList copy() {
        ItemList other = new ItemList(items.length - 1);
        System.arraycopy(items, 0, other.items, 0, items.length);
        System.arraycopy(tms, 0, other.tms, 0, tms.length);
        return other;
    }

} public class Move {

    public String name;
    public int number;
    public int internalId;
    public int power;
    public int pp;
    public double hitratio;
    public Type type;
    public int effectIndex;
    public MoveCategory category;
    public double hitCount = 1; // not saved, only used in randomized move powers.

    public String toString() {
        return "#" + number + " " + name + " - Power: " + power + ", Base PP: " + pp + ", Type: " + type + ", Hit%: "
                + (hitratio) + ", Effect: " + effectIndex;
    }

} public enum MoveCategory {
    PHYSICAL, SPECIAL, STATUS;
} public class MoveLearnt {

    public int move;
    public int level;

    public String toString() {
        return "move " + move + " at level " + level;
    }

} public class Pokemon implements Comparable<Pokemon> {

    public String name;
    public int number;

    public Type primaryType, secondaryType;

    public int hp, attack, defense, spatk, spdef, speed, special;

    public int ability1, ability2, ability3;

    public int catchRate, expYield;

    public int guaranteedHeldItem, commonHeldItem, rareHeldItem, darkGrassHeldItem;

    public int genderRatio;

    public int frontSpritePointer, picDimensions;

    public ExpCurve growthCurve;

    public List<Evolution> evolutionsFrom = new ArrayList<Evolution>();
    public List<Evolution> evolutionsTo = new ArrayList<Evolution>();

    public List<Integer> shuffledStatsOrder = null;

    // A flag to use for things like recursive stats copying.
    // Must not rely on the state of this flag being preserved between calls.
    public boolean temporaryFlag;

    public Pokemon() {
        shuffledStatsOrder = Arrays.asList(0, 1, 2, 3, 4, 5);
    }

    public void shuffleStats(Random random) {
        Collections.shuffle(shuffledStatsOrder, random);
        applyShuffledOrderToStats();
    }
    
    public void copyShuffledStatsUpEvolution(Pokemon evolvesFrom) {
        shuffledStatsOrder = evolvesFrom.shuffledStatsOrder;
        applyShuffledOrderToStats();
    }

    private void applyShuffledOrderToStats() {
        List<Integer> stats = Arrays.asList(hp, attack, defense, spatk, spdef, speed);

        // Copy in new stats
        hp = stats.get(shuffledStatsOrder.get(0));
        attack = stats.get(shuffledStatsOrder.get(1));
        defense = stats.get(shuffledStatsOrder.get(2));
        spatk = stats.get(shuffledStatsOrder.get(3));
        spdef = stats.get(shuffledStatsOrder.get(4));
        speed = stats.get(shuffledStatsOrder.get(5));

        // make special the average of spatk and spdef
        special = (int) Math.ceil((spatk + spdef) / 2.0f);
    }

    public void randomizeStatsWithinBST(Random random) {
        if (number == 292) {
            // Shedinja is horribly broken unless we restrict him to 1HP.
            int bst = bst() - 51;

            // Make weightings
            double atkW = random.nextDouble(), defW = random.nextDouble();
            double spaW = random.nextDouble(), spdW = random.nextDouble(), speW = random.nextDouble();

            double totW = atkW + defW + spaW + spdW + speW;

            hp = 1;
            attack = (int) Math.max(1, Math.round(atkW / totW * bst)) + 10;
            defense = (int) Math.max(1, Math.round(defW / totW * bst)) + 10;
            spatk = (int) Math.max(1, Math.round(spaW / totW * bst)) + 10;
            spdef = (int) Math.max(1, Math.round(spdW / totW * bst)) + 10;
            speed = (int) Math.max(1, Math.round(speW / totW * bst)) + 10;

            // Fix up special too
            special = (int) Math.ceil((spatk + spdef) / 2.0f);

        } else {
            // Minimum 20 HP, 10 everything else
            int bst = bst() - 70;

            // Make weightings
            double hpW = random.nextDouble(), atkW = random.nextDouble(), defW = random.nextDouble();
            double spaW = random.nextDouble(), spdW = random.nextDouble(), speW = random.nextDouble();

            double totW = hpW + atkW + defW + spaW + spdW + speW;

            hp = (int) Math.max(1, Math.round(hpW / totW * bst)) + 20;
            attack = (int) Math.max(1, Math.round(atkW / totW * bst)) + 10;
            defense = (int) Math.max(1, Math.round(defW / totW * bst)) + 10;
            spatk = (int) Math.max(1, Math.round(spaW / totW * bst)) + 10;
            spdef = (int) Math.max(1, Math.round(spdW / totW * bst)) + 10;
            speed = (int) Math.max(1, Math.round(speW / totW * bst)) + 10;

            // Fix up special too
            special = (int) Math.ceil((spatk + spdef) / 2.0f);
        }

        // Check for something we can't store
        if (hp > 255 || attack > 255 || defense > 255 || spatk > 255 || spdef > 255 || speed > 255) {
            // re roll
            randomizeStatsWithinBST(random);
        }

    }

    public void copyRandomizedStatsUpEvolution(Pokemon evolvesFrom) {
        double ourBST = bst();
        double theirBST = evolvesFrom.bst();

        double bstRatio = ourBST / theirBST;

        hp = (int) Math.min(255, Math.max(1, Math.round(evolvesFrom.hp * bstRatio)));
        attack = (int) Math.min(255, Math.max(1, Math.round(evolvesFrom.attack * bstRatio)));
        defense = (int) Math.min(255, Math.max(1, Math.round(evolvesFrom.defense * bstRatio)));
        speed = (int) Math.min(255, Math.max(1, Math.round(evolvesFrom.speed * bstRatio)));
        spatk = (int) Math.min(255, Math.max(1, Math.round(evolvesFrom.spatk * bstRatio)));
        spdef = (int) Math.min(255, Math.max(1, Math.round(evolvesFrom.spdef * bstRatio)));

        special = (int) Math.ceil((spatk + spdef) / 2.0f);
    }

    public int bst() {
        return hp + attack + defense + spatk + spdef + speed;
    }

    public int bstForPowerLevels() {
        // Take into account Shedinja's purposefully nerfed HP
        if (number == 292) {
            return (attack + defense + spatk + spdef + speed) * 6 / 5;
        } else {
            return hp + attack + defense + spatk + spdef + speed;
        }
    }

    @Override
    public String toString() {
        return "Pokemon [name=" + name + ", number=" + number + ", primaryType=" + primaryType + ", secondaryType="
                + secondaryType + ", hp=" + hp + ", attack=" + attack + ", defense=" + defense + ", spatk=" + spatk
                + ", spdef=" + spdef + ", speed=" + speed + "]";
    }

    public String toStringRBY() {
        return "Pokemon [name=" + name + ", number=" + number + ", primaryType=" + primaryType + ", secondaryType="
                + secondaryType + ", hp=" + hp + ", attack=" + attack + ", defense=" + defense + ", special=" + special
                + ", speed=" + speed + "]";
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + number;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Pokemon other = (Pokemon) obj;
        if (number != other.number)
            return false;
        return true;
    }

    @Override
    public int compareTo(Pokemon o) {
        return number - o.number;
    }

    private static final List<Integer> legendaries = Arrays.asList(144, 145, 146, 150, 151, 243, 244, 245, 249, 250,
            251, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488,
            489, 490, 491, 492, 493, 494, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649);

    public boolean isLegendary() {
        return legendaries.contains(this.number);
    }

} public class TrainerPokemon {

    public Pokemon pokemon;
    public int level;

    public int move1;
    public int move2;
    public int move3;
    public int move4;

    public int AILevel;
    public int heldItem;
    public int ability;
    
    public boolean resetMoves = false;

    public String toString() {
        return pokemon.name + " Lv" + level;
    }

} public enum Type {

    NORMAL, FIGHTING, FLYING, GRASS, WATER, FIRE, ROCK, GROUND, PSYCHIC, BUG, DRAGON, ELECTRIC, GHOST, POISON, ICE, STEEL, DARK, GAS(
            true), FAIRY(true), WOOD(true), ABNORMAL(true), WIND(true), SOUND(true), LIGHT(true), TRI(true);

    public boolean isHackOnly;

    private Type() {
        this.isHackOnly = false;
    }

    private Type(boolean isHackOnly) {
        this.isHackOnly = isHackOnly;
    }

    private static final List<Type> VALUES = Collections.unmodifiableList(Arrays.asList(values()));
    private static final int SIZE = VALUES.size();

    public static Type randomType(Random random) {
        return VALUES.get(random.nextInt(SIZE));
    }

    public String camelCase() {
        return RomFunctions.camelCase(this.toString());
    }

}
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;
import java.util.TreeSet;
import com.dabomstew.pkrandom.CustomNamesSet;
import com.dabomstew.pkrandom.MiscTweak;
import com.dabomstew.pkrandom.RomFunctions;
import com.dabomstew.pkrandom.constants.GlobalConstants;
import com.dabomstew.pkrandom.exceptions.RandomizationException;
import com.dabomstew.pkrandom.pokemon.Encounter;
import com.dabomstew.pkrandom.pokemon.EncounterSet;
import com.dabomstew.pkrandom.pokemon.Evolution;
import com.dabomstew.pkrandom.pokemon.EvolutionType;
import com.dabomstew.pkrandom.pokemon.ExpCurve;
import com.dabomstew.pkrandom.pokemon.GenRestrictions;
import com.dabomstew.pkrandom.pokemon.IngameTrade;
import com.dabomstew.pkrandom.pokemon.ItemList;
import com.dabomstew.pkrandom.pokemon.Move;
import com.dabomstew.pkrandom.pokemon.MoveCategory;
import com.dabomstew.pkrandom.pokemon.MoveLearnt;
import com.dabomstew.pkrandom.pokemon.Pokemon;
import com.dabomstew.pkrandom.pokemon.Trainer;
import com.dabomstew.pkrandom.pokemon.TrainerPokemon;
import com.dabomstew.pkrandom.pokemon.Type;
                    int ctl = intStrLen;
                    if (pickFrom != null && pickFrom.size() > 0 && intStrLen > 1) {
                        int innerTries = 0;
                        changeTo = pickFrom.get(this.random.nextInt(pickFrom.size()));
                        ctl = this.internalStringLength(changeTo);
                        while ((mode == TrainerNameMode.MAX_LENGTH && ctl > maxLength)
                                || (mode == TrainerNameMode.MAX_LENGTH_WITH_CLASS && ctl + tcNameLengths.get(tnIndex) > maxLength)) {
                            innerTries++;
                            if (innerTries == 100) {
                                changeTo = trainerName;
                                ctl = intStrLen;
                                break;
                            }
                            changeTo = pickFrom.get(this.random.nextInt(pickFrom.size()));
                            ctl = this.internalStringLength(changeTo);
                        }
                    }
                    translation.put(trainerName, changeTo);
                    newTrainerNames.add(changeTo);
                    totalLength += ctl;
                }

                if (totalLength > totalMaxLength) {
                    success = false;
                    tries++;
                    break;
                }
            }
        }

        if (!success) {
            throw new RandomizationException("Could not randomize trainer names in a reasonable amount of attempts."
                    + "\nPlease add some shorter names to your custom trainer names.");
        }

        // Done choosing, save
        this.setTrainerNames(newTrainerNames);
    }

    @SuppressWarnings("unchecked")
    @Override
    public void randomizeTrainerClassNames(CustomNamesSet customNames) {
        if (!this.canChangeTrainerText()) {
            return;
        }

        // index 0 = singles, index 1 = doubles
        List<String> allTrainerClasses[] = new List[] { new ArrayList<String>(), new ArrayList<String>() };
        Map<Integer, List<String>> trainerClassesByLength[] = new Map[] { new HashMap<Integer, List<String>>(),
                new HashMap<Integer, List<String>>() };

        // Read names data
        for (String trainerClassName : customNames.getTrainerClasses()) {
            allTrainerClasses[0].add(trainerClassName);
            int len = this.internalStringLength(trainerClassName);
            if (trainerClassesByLength[0].containsKey(len)) {
                trainerClassesByLength[0].get(len).add(trainerClassName);
            } else {
                List<String> namesOfThisLength = new ArrayList<String>();
                namesOfThisLength.add(trainerClassName);
                trainerClassesByLength[0].put(len, namesOfThisLength);
            }
        }

        for (String trainerClassName : customNames.getDoublesTrainerClasses()) {
            allTrainerClasses[1].add(trainerClassName);
            int len = this.internalStringLength(trainerClassName);
            if (trainerClassesByLength[1].containsKey(len)) {
                trainerClassesByLength[1].get(len).add(trainerClassName);
            } else {
                List<String> namesOfThisLength = new ArrayList<String>();
                namesOfThisLength.add(trainerClassName);
                trainerClassesByLength[1].put(len, namesOfThisLength);
            }
        }

        // Get the current trainer names data
        List<String> currentClassNames = this.getTrainerClassNames();
        boolean mustBeSameLength = this.fixedTrainerClassNamesLength();
        int maxLength = this.maxTrainerClassNameLength();

        // Init the translation map and new list
        Map<String, String> translation = new HashMap<String, String>();
        List<String> newClassNames = new ArrayList<String>();

        int numTrainerClasses = currentClassNames.size();
        List<Integer> doublesClasses = this.getDoublesTrainerClasses();

        // Start choosing
        for (int i = 0; i < numTrainerClasses; i++) {
            String trainerClassName = currentClassNames.get(i);
            if (translation.containsKey(trainerClassName)) {
                // use an already picked translation
                newClassNames.add(translation.get(trainerClassName));
            } else {
                int idx = doublesClasses.contains(i) ? 1 : 0;
                List<String> pickFrom = allTrainerClasses[idx];
                int intStrLen = this.internalStringLength(trainerClassName);
                if (mustBeSameLength) {
                    pickFrom = trainerClassesByLength[idx].get(intStrLen);
                }
                String changeTo = trainerClassName;
                if (pickFrom != null && pickFrom.size() > 0) {
                    changeTo = pickFrom.get(this.random.nextInt(pickFrom.size()));
                    while (changeTo.length() > maxLength) {
                        changeTo = pickFrom.get(this.random.nextInt(pickFrom.size()));
                    }
                }
                translation.put(trainerClassName, changeTo);
                newClassNames.add(changeTo);
            }
        }

        // Done choosing, save
        this.setTrainerClassNames(newClassNames);
    }

    @Override
    public void randomizeWildHeldItems(boolean banBadItems) {
        List<Pokemon> pokemon = allPokemonWithoutNull();
        ItemList possibleItems = banBadItems ? this.getNonBadItems() : this.getAllowedItems();