/*
 * MegaMek - Copyright (C) 2000-2004 Ben Mazur (bmazur@sev.org)
 *
 *  This program 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 2 of the License, or (at your option)
 *  any later version.
 *
 *  This program 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.
 */

/*
 * TdbFile.java
 *  -based on MtfFile.java, modifications by Ryan McConnell
 * Created on April 1, 2003, 2:48 PM
 */

package megamek.common.loaders;

import gd.xml.ParseException;
import gd.xml.tiny.ParsedXML;
import gd.xml.tiny.TinyParser;

import java.io.InputStream;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import megamek.common.BipedMech;
import megamek.common.CriticalSlot;
import megamek.common.Engine;
import megamek.common.Entity;
import megamek.common.EquipmentType;
import megamek.common.LocationFullException;
import megamek.common.Mech;
import megamek.common.MiscType;
import megamek.common.Mounted;
import megamek.common.QuadMech;
import megamek.common.TechConstants;
import megamek.common.WeaponType;

public class TdbFile implements IMechLoader {

    private ParsedXML root = null;

    /**
     * The names of the various elements recognized by this parser.
     */
    private static final String CREATOR_SECTION = "creator";
    private static final String BASICS_SECTION = "basics";
    private static final String ITEM_DEFS_SECTION = "itemdefs";
    private static final String MOUNTED_ITEMS_SECTION = "mounteditems";
    private static final String CRIT_DEFS_SECTION = "critdefs";

    private static final String NAME = "name";
    private static final String VERSION = "version";
    private static final String MODEL = "model";
    private static final String VARIANT = "variant";
    private static final String TECHNOLOGY = "technology";
    private static final String MOVEMECHMOD = "movemechmod";
    private static final String TONNAGE = "tonnage"; // also attribute
    private static final String TYPE = "type";
    private static final String OMNI = "isomni";
    private static final String WALK = "walk";
    private static final String JUMP = "jump";
    private static final String HEAT_SINKS = "heatsinks";
    private static final String ARMOR = "armor"; // also attribute
    private static final String ENGINE = "engine";
    private static final String GYRO = "gyro";
    private static final String COCKPIT = "cockpit";
    private static final String STRUCTURE = "internal"; // also attribute
    private static final String MOUNTED_ITEM = "mounteditem";
    private static final String LOCATION = "location";
    private static final String TARGSYS = "targsys";

    /**
     * The names of the attributes recognized by this parser.
     */
    private static final String LEVEL = "level";
    private static final String COUNT = "count";
    //private static final String POINTS = "points";
    private static final String REAR_MOUNTED = "rearmounted";
    private static final String IS_SPREAD = "isspread";
    private static final String ITEM_INDEX = "itemindex";
    private static final String REAR_ARMOR = "reararmor";
    private static final String RATING = "rating";

    /**
     * Special values recognized by this parser.
     */
    private static final String TRUE = "True";
    //private static final String FALSE = "False";
    private static final String DOUBLE = "Double";
    //private static final String LASER = "Laser";
    //private static final String COMPACT = "Compact (2)";
    private static final String TRUE_LOWER = "true";

    private String creatorName = "Unknown";
    private String creatorVersion = "Unknown";
    private String name;
    private boolean isOmni = false;
    private String model;
    private String variant;

    private String chassisConfig;
    private String techBase;
    private static final String techYear = "3068"; // TDB doesn't have era
    private String rulesLevel;
    private String LAMTonnage;

    private String tonnage;

    private String heatSinks;
    private boolean dblSinks;
    // don't need this, we get it from the engine rating
    //private String walkMP;
    private String jumpMP;

    private int larmArmor;
    private int rarmArmor;
    private int ltArmor;
    private int rtArmor;
    private int ctArmor;
    private int headArmor;
    private int llegArmor;
    private int rlegArmor;
    private int ltrArmor;
    private int rtrArmor;
    private int ctrArmor;

    private String[][][] critData;
    private boolean isRearMounted[];
    private boolean isSplit[];

    private String armorType;
    private String engineType;
    private int engineRating;
    private String structureType;
    private String targSysStr;
    private String gyroType = "Standard";
    private String cockpitType = "Standard";
    private boolean clanTC = false;

    private Hashtable<EquipmentType, Mounted> hSharedEquip = new Hashtable<EquipmentType, Mounted>();
    private Vector<Mounted> vSplitWeapons = new Vector<Mounted>();

    /** Creates new TdbFile */
    public TdbFile(InputStream is) throws EntityLoadingException {
        try {
            root = TinyParser.parseXML(is);
        } catch (ParseException e) {
            throw new EntityLoadingException("   Failure to parse XML ("
                    + e.getLocalizedMessage() + ")");
        }
        // Arbitrarily sized static arrays suck, or so a computer
        // science teacher once told me.
        isRearMounted = new boolean[256];
        isSplit = new boolean[256];

        critData = new String[8][12][2];
        parseNode((ParsedXML) root.elements().nextElement());
    }

    private void parseNode(ParsedXML node) throws EntityLoadingException {
        if (!node.getTypeName().equals("tag")) {
            // We only want to parse element nodes, text nodes
            // are implicitly parsed when needed.
            return;
        }

        Enumeration<?> children = node.elements();

        if (node.getName().equals(CREATOR_SECTION)) {
            parseCreatorNode(node);
        } else if (node.getName().equals(BASICS_SECTION)) {
            parseBasicsNode(node);
        } else if (node.getName().equals(ITEM_DEFS_SECTION)) {
            return; // don't need item defs section of xml
        } else if (node.getName().equals(MOUNTED_ITEMS_SECTION)) {
            parseMountedNode(node);
        } else if (node.getName().equals(CRIT_DEFS_SECTION)) {
            parseCritNode(node);
        } else if (children != null) {
            // Use recursion to process all the children
            while (children.hasMoreElements()) {
                parseNode((ParsedXML) children.nextElement());
            }
        }
    }

    private void parseCreatorNode(ParsedXML node) throws EntityLoadingException {
        if (!node.getTypeName().equals("tag")) {
            // We only want to parse element nodes, text nodes
            // are directly parsed below.
            return;
        }

        Enumeration<?> children = node.elements();

        if (node.getName().equals(NAME)) {
            creatorName = ((ParsedXML) children.nextElement()).getContent();
        } else if (node.getName().equals(VERSION)) {
            creatorVersion = ((ParsedXML) children.nextElement()).getContent();
        } else if (children != null) {
            // Use recursion to process all the children
            while (children.hasMoreElements()) {
                parseCreatorNode((ParsedXML) children.nextElement());
            }
        }
        // Other tags (that don't match any if blocks above)
        // are simply ignored.
    }

    private void parseBasicsNode(ParsedXML node) throws EntityLoadingException {
        if (!node.getTypeName().equals("tag")) {
            // We only want to parse element nodes, text nodes
            // are directly parsed below.
            return;
        }

        Enumeration<?> children = node.elements();

        if (node.getName().equals(NAME)) {
            name = ((ParsedXML) children.nextElement()).getContent();
        } else if (node.getName().equals(MODEL)) {
            model = ((ParsedXML) children.nextElement()).getContent();
        } else if (node.getName().equals(OMNI)) {
            isOmni = ((ParsedXML) children.nextElement()).getContent().equals(
                    TRUE_LOWER);
        } else if (node.getName().equals(VARIANT)) {
            variant = ((ParsedXML) children.nextElement()).getContent();
        } else if (node.getName().equals(TECHNOLOGY)) {
            techBase = ((ParsedXML) children.nextElement()).getContent();
            rulesLevel = node.getAttribute(LEVEL);
        } else if (node.getName().equals(TONNAGE)) {
            tonnage = ((ParsedXML) children.nextElement()).getContent();
        } else if (node.getName().equals(TYPE)) {
            chassisConfig = ((ParsedXML) children.nextElement()).getContent();
        } else if (node.getName().equals(MOVEMECHMOD)) {
            // This tag seems to indicate the pod space on an omnimech
            // or the tonnage of the conversion equipment for
            // a LAM (Land Air Mech).
            LAMTonnage = node.getAttribute(TONNAGE);
        } else if (node.getName().equals(WALK)) {
            // we don't actually need this, because it's calculated from the
            // engine rating
            //walkMP = ((ParsedXML) children.nextElement()).getContent();
        } else if (node.getName().equals(JUMP)) {
            jumpMP = ((ParsedXML) children.nextElement()).getContent();
        } else if (node.getName().equals(HEAT_SINKS)) {
            if (((ParsedXML) children.nextElement()).getContent().indexOf(
                    DOUBLE) != -1) {
                dblSinks = true;
            } else {
                dblSinks = false;
            }
            heatSinks = node.getAttribute(COUNT);
        } else if (node.getName().equals(ARMOR)) {
            armorType = ((ParsedXML) children.nextElement()).getContent();
        } else if (node.getName().equals(ENGINE)) {
            engineType = ((ParsedXML) children.nextElement()).getContent();
            engineRating = Integer.parseInt(node.getAttribute(RATING));
        } else if (node.getName().equals(STRUCTURE)) {
            structureType = ((ParsedXML) children.nextElement()).getContent();
        } else if (node.getName().equals(GYRO)) {
            gyroType = ((ParsedXML) children.nextElement()).getContent();
        } else if (node.getName().equals(COCKPIT)) {
            cockpitType = ((ParsedXML) children.nextElement()).getContent();
        } else if (node.getName().equals(TARGSYS)) {
            targSysStr = ((ParsedXML) children.nextElement()).getContent()
                    .trim();
            if ((targSysStr.length() >= 3)
                    && (targSysStr.substring(0, 3).equals("(C)"))) {
                clanTC = true;
            }
        } else if (children != null) {
            // Use recursion to process all the children
            while (children.hasMoreElements()) {
                parseBasicsNode((ParsedXML) children.nextElement());
            }
        }
        // Other tags (that don't match any if blocks above)
        // are simply ignored.
    }

    private void parseMountedNode(ParsedXML node) throws EntityLoadingException {
        if (!node.getTypeName().equals("tag")) {
            // We only want to parse element nodes, text nodes
            // are directly parsed below.
            return;
        }

        Enumeration<?> children = node.elements();

        if (node.getName().equals(MOUNTED_ITEM)) {
            if (node.getAttribute(REAR_MOUNTED).equals(TRUE)) {
                isRearMounted[Integer.parseInt(node.getAttribute(ITEM_INDEX))] = true;
            } else {
                isRearMounted[Integer.parseInt(node.getAttribute(ITEM_INDEX))] = false;
            }
            if (node.getAttribute(IS_SPREAD).equals(TRUE)) {
                isSplit[Integer.parseInt(node.getAttribute(ITEM_INDEX))] = true;
            } else {
                isSplit[Integer.parseInt(node.getAttribute(ITEM_INDEX))] = false;
            }
        } else if (children != null) {
            // Use recursion to process all the children
            while (children.hasMoreElements()) {
                parseMountedNode((ParsedXML) children.nextElement());
            }
        }
        // Other tags (that don't match any if blocks above)
        // are simply ignored.
    }

    private void parseCritNode(ParsedXML node) throws EntityLoadingException {
        if (!node.getTypeName().equals("tag")) {
            // We only want to parse element nodes, text nodes
            // are directly parsed below.
            return;
        }

        Enumeration<?> children = node.elements();

        if (node.getName().equals(LOCATION)) {
            int loc = -1;
            int i = 0;
            int armor = -1;
            int rearArmor = -1;
            if (node.getAttribute(ARMOR) != null) {
                armor = Integer.parseInt(node.getAttribute(ARMOR));
            }
            if (node.getAttribute(REAR_ARMOR) != null) {
                rearArmor = Integer.parseInt(node.getAttribute(REAR_ARMOR));
            }
            if (node.getAttribute(NAME).equals("LA")
                    || node.getAttribute(NAME).equals("FLL")) {
                loc = Mech.LOC_LARM;
                larmArmor = armor;
            } else if (node.getAttribute(NAME).equals("RA")
                    || node.getAttribute(NAME).equals("FRL")) {
                loc = Mech.LOC_RARM;
                rarmArmor = armor;
            } else if (node.getAttribute(NAME).equals("LT")) {
                loc = Mech.LOC_LT;
                ltArmor = armor;
                ltrArmor = rearArmor;
            } else if (node.getAttribute(NAME).equals("RT")) {
                loc = Mech.LOC_RT;
                rtArmor = armor;
                rtrArmor = rearArmor;
            } else if (node.getAttribute(NAME).equals("CT")) {
                loc = Mech.LOC_CT;
                ctArmor = armor;
                ctrArmor = rearArmor;
            } else if (node.getAttribute(NAME).equals("H")) {
                loc = Mech.LOC_HEAD;
                headArmor = armor;
            } else if (node.getAttribute(NAME).equals("LL")
                    || node.getAttribute(NAME).equals("RLL")) {
                loc = Mech.LOC_LLEG;
                llegArmor = armor;
            } else if (node.getAttribute(NAME).equals("RL")
                    || node.getAttribute(NAME).equals("RRL")) {
                loc = Mech.LOC_RLEG;
                rlegArmor = armor;
            }

            if (loc == -1) {
                throw new EntityLoadingException("   Bad Mech location: "
                        + node.getAttribute(NAME));
            }
            while (children.hasMoreElements()) {
                ParsedXML critSlotNode = (ParsedXML) children.nextElement();
                critData[loc][i][0] = ((ParsedXML) critSlotNode.elements()
                        .nextElement()).getContent();
                if (clanTC && critData[loc][i][0].equals("Targeting Computer")) {
                    critData[loc][i][0] = "(C) " + critData[loc][i][0];
                }
                critData[loc][i++][1] = critSlotNode.getAttribute(ITEM_INDEX);
            }
        } else if (children != null) {
            // Use recursion to process all the children
            while (children.hasMoreElements()) {
                parseCritNode((ParsedXML) children.nextElement());
            }
        }
        // Other tags (that don't match any if blocks above)
        // are simply ignored.
    }

    public Entity getEntity() throws EntityLoadingException {
        try {
            Mech mech;

            if ((creatorName == "Unknown")
                    || !creatorName.equals("The Drawing Board")
                    || (Integer.parseInt(creatorVersion) != 2)) {
                // MegaMek no longer supports older versions of The
                // Drawing Board (pre 2.0.23) due to incomplete xml
                // file information in those versions.
                throw new EntityLoadingException(
                        "This xml file is not a valid Drawing Board mech.  Make sure you are using version 2.0.23 or later of The Drawing Board.");
            }

            if (gyroType.equals("Extra-Light")) {
                gyroType = "XL";
            } else if (gyroType.equals("Heavy-Duty")) {
                gyroType = "Heavy Duty";
            }
            if (cockpitType.equals("Torso-Mounted")) {
                cockpitType = "Torso Mounted";
            }
            if (chassisConfig.equals("Quad")) {
                mech = new QuadMech(gyroType, cockpitType);
            } else {
                mech = new BipedMech(gyroType, cockpitType);
            }

            // aarg! those stupid sub-names in parenthesis screw everything up
            // we may do something different in the future, but for now, I'm
            // going to strip them out
            int pindex = name.indexOf("(");
            if (pindex == -1) {
                mech.setChassis(name);
            } else {
                mech.setChassis(name.substring(0, pindex - 1));
            }

            if (variant != null) {
                mech.setModel(variant);
            } else if (model != null) {
                mech.setModel(model);
            } else {
                // Do mechs need a model?
                mech.setModel("");
            }
            mech.setYear(Integer.parseInt(techYear));
            mech.setOmni(isOmni);

            if (structureType.substring(0, 3).equals("(C)")) {
                structureType = structureType.substring(4);
            }
            mech.setStructureType(structureType);

            if (armorType.substring(0, 3).equals("(C)")) {
                armorType = armorType.substring(4);
            }
            mech.setArmorType(armorType);

            if (LAMTonnage != null) {
                // throw new EntityLoadingException("Unsupported tech: LAM?");
            }
            if (techBase.equals("Inner Sphere")) {
                switch (Integer.parseInt(rulesLevel)) {
                    case 1:
                        mech.setTechLevel(TechConstants.T_INTRO_BOXSET);
                        break;
                    case 2:
                        mech.setTechLevel(TechConstants.T_IS_TW_NON_BOX);
                        break;
                    case 3:
                        mech.setTechLevel(TechConstants.T_IS_ADVANCED);
                        break;
                    default:
                        throw new EntityLoadingException(
                                "Unsupported tech level: " + rulesLevel);
                }
            } else if (techBase.equals("Clan")) {
                switch (Integer.parseInt(rulesLevel)) {
                    case 2:
                        mech.setTechLevel(TechConstants.T_CLAN_TW);
                        break;
                    case 3:
                        mech.setTechLevel(TechConstants.T_CLAN_ADVANCED);
                        break;
                    default:
                        throw new EntityLoadingException(
                                "Unsupported tech level: " + rulesLevel);
                }
            } else if (techBase.equals("Mixed (IS Chassis)")
                    || techBase.equals("Inner Sphere 'C'")) {
                mech.setTechLevel(TechConstants.T_IS_ADVANCED);
                mech.setMixedTech(true);
            } else if (techBase.equals("Mixed (Clan Chassis)")) {
                mech.setTechLevel(TechConstants.T_CLAN_ADVANCED);
                mech.setMixedTech(true);
            } else {
                throw new EntityLoadingException("Unsupported tech base: "
                        + techBase);
            }
            mech.setWeight(Integer.parseInt(tonnage));
            if (jumpMP != null) {
                mech.setOriginalJumpMP(Integer.parseInt(jumpMP));
            }
            int engineFlags = 0;
            if ((mech.isClan() && !mech.isMixedTech())
                    || (mech.isMixedTech() && mech.isClan() && !mech
                            .itemOppositeTech(engineType))) {
                engineFlags = Engine.CLAN_ENGINE;
            }
            mech.setEngine(new Engine(engineRating, Engine
                    .getEngineTypeByString(engineType), engineFlags));
            int expectedSinks = Integer.parseInt(heatSinks);

            mech.autoSetInternal();

            mech.initializeArmor(larmArmor, Mech.LOC_LARM);
            mech.initializeArmor(rarmArmor, Mech.LOC_RARM);
            mech.initializeArmor(ltArmor, Mech.LOC_LT);
            mech.initializeArmor(rtArmor, Mech.LOC_RT);
            mech.initializeArmor(ctArmor, Mech.LOC_CT);
            mech.initializeArmor(headArmor, Mech.LOC_HEAD);
            mech.initializeArmor(llegArmor, Mech.LOC_LLEG);
            mech.initializeArmor(rlegArmor, Mech.LOC_RLEG);
            mech.initializeRearArmor(ltrArmor, Mech.LOC_LT);
            mech.initializeRearArmor(rtrArmor, Mech.LOC_RT);
            mech.initializeRearArmor(ctrArmor, Mech.LOC_CT);

            // oog, crits.
            compactCriticals(mech);
            // we do these in reverse order to get the outermost
            // locations first, which is necessary for split crits to work
            for (int i = mech.locations() - 1; i >= 0; i--) {
                parseCrits(mech, i);
            }

            if (mech.isClan()) {
                mech.addClanCase();
            }

            // add any heat sinks not allocated
            mech.addEngineSinks(expectedSinks - mech.heatSinks(), dblSinks);

            // set targeting system
            if (targSysStr.startsWith("Long-Range")) {
                mech.setTargSysType(MiscType.T_TARGSYS_LONGRANGE);
            } else if (targSysStr.startsWith("Short-Range")) {
                mech.setTargSysType(MiscType.T_TARGSYS_SHORTRANGE);
            } else if (targSysStr.startsWith("Variable-Range")) {
                mech.setTargSysType(MiscType.T_TARGSYS_VARIABLE_RANGE);
            } else if (targSysStr.startsWith("Anti-Aircraft")) {
                mech.setTargSysType(MiscType.T_TARGSYS_ANTI_AIR);
            } else if (targSysStr.startsWith("Multi-Trac")) {
                mech.setTargSysType(MiscType.T_TARGSYS_MULTI_TRAC);
            } else if (targSysStr.startsWith("Multi-Trac II")) {
                mech.setTargSysType(MiscType.T_TARGSYS_MULTI_TRAC_II);
            } else if (targSysStr.startsWith("Targeting Computer")) {
                mech.setTargSysType(MiscType.T_TARGSYS_TARGCOMP);
            }

            return mech;
        } catch (NumberFormatException ex) {
            throw new EntityLoadingException(
                    "NumberFormatException parsing file");
        } catch (NullPointerException ex) {
            throw new EntityLoadingException(
                    "NullPointerException parsing file");
        } catch (StringIndexOutOfBoundsException ex) {
            throw new EntityLoadingException(
                    "StringIndexOutOfBoundsException parsing file");
        }
    }

    private void parseCrits(Mech mech, int loc) throws EntityLoadingException {
        // check for removed arm actuators
        if (!(mech instanceof QuadMech)) {
            if ((loc == Mech.LOC_LARM) || (loc == Mech.LOC_RARM)) {
                if (!critData[loc][3][0].equals("Hand Actuator")) {
                    mech.setCritical(loc, 3, null);
                }
                if (!critData[loc][2][0].equals("Lower Arm Actuator")) {
                    mech.setCritical(loc, 2, null);
                }
            }
        }

        // go thru file, add weapons
        for (int i = 0; i < mech.getNumberOfCriticals(loc); i++) {

            // if the slot's full already, skip it.
            if (mech.getCritical(loc, i) != null) {
                continue;
            }

            // parse out and add the critical
            String critName = critData[loc][i][0];
            boolean rearMounted = true;
            if ((critData[loc][i][1] == null)
                    || !isRearMounted[Integer.parseInt(critData[loc][i][1])]) {
                rearMounted = false;
            }
            // boolean split = true;
            if ((critData[loc][i][1] == null)
                    || !isSplit[Integer.parseInt(critData[loc][i][1])]) {
                // split = false;
            }
            if (critName.equalsIgnoreCase("Armored Cowl")) {
                mech.setCowl(5);
            }
            if (critName.indexOf("Engine") != -1) {
                mech.setCritical(loc, i, new CriticalSlot(
                        CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_ENGINE));
                continue;
            }
            if (critName.indexOf("Gyro") != -1) {
                mech.setCritical(loc, i, new CriticalSlot(
                        CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_GYRO));
                continue;
            }
            if (critName.indexOf("Life Support") != -1) {
                mech.setCritical(loc, i, new CriticalSlot(
                        CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_LIFE_SUPPORT));
                continue;
            }
            if (critName.indexOf("Sensors") != -1) {
                mech.setCritical(loc, i, new CriticalSlot(
                        CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_SENSORS));
                continue;
            }
            if (critName.indexOf("Cockpit") != -1) {
                mech.setCritical(loc, i, new CriticalSlot(
                        CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_COCKPIT));
                continue;
            }
            if (critName.endsWith("[LRM]") || critName.endsWith("[SRM]")) {
                // This is a lame kludge for The Drawing Board, which
                // identifies which type of missle weapon an
                // Artemis IV system goes with.
                critName = critName.substring(0, 14);
            }
            if (critName.endsWith("- Artemis IV")) {
                // Ugh, another lame kludge to allow for loading of
                // The Drawing Board's specially marked Artemis IV
                // missle ammo. The only real game difference is
                // c-bill cost, which we don't care about anyway.
                critName = critName.substring(0, critName
                        .indexOf(" - Artemis IV"));
            }
            if (critName.endsWith("- Narc")) {
                // Yet another lame kludge to allow for loading of
                // The Drawing Board's specially marked Narc
                // missle ammo.
                critName = critName.substring(0, critName.indexOf(" - Narc"));
            }
            if (critName.equals("(C) Endosteel")) {
                // MegaMek determines whether Endo Steel is IS or Clan
                // type by techbase of mech.
                critName = critName.substring(4);
            }
            if (critName.equals("(C) Ferro-Fibrous Armor")) {
                // MegaMek determines whether FF Armor is IS or Clan
                // type by techbase of mech.
                critName = critName.substring(4);
            }
            try {
                String hashPrefix;
                if (critName.startsWith("(C)")) {
                    // equipment specifically marked as clan
                    hashPrefix = "Clan ";
                    critName = critName.substring(4);
                } else if (critName.startsWith("(IS)")) {
                    // equipment specifically marked as inner sphere
                    hashPrefix = "IS ";
                    critName = critName.substring(5);
                } else if (mech.isClan()) {
                    // assume equipment is clan because mech is clan
                    hashPrefix = "Clan ";
                } else {
                    // assume equipment is inner sphere
                    hashPrefix = "IS ";
                }
                EquipmentType etype = EquipmentType.get(hashPrefix + critName);
                if (etype == null) {
                    // try without prefix
                    etype = EquipmentType.get(critName);
                }
                if (etype != null) {
                    if (etype.isSpreadable()) {
                        // do we already have one of these? Key on Type
                        Mounted m = hSharedEquip.get(etype);
                        if (m != null) {
                            // use the existing one
                            mech.addCritical(loc, new CriticalSlot(
                                    CriticalSlot.TYPE_EQUIPMENT, mech
                                            .getEquipmentNum(m), etype
                                            .isHittable(), m));
                            continue;
                        }
                        m = mech.addEquipment(etype, loc, rearMounted);
                        hSharedEquip.put(etype, m);
                    } else if ((etype instanceof WeaponType)
                            && etype.hasFlag(WeaponType.F_SPLITABLE)) {
                        // do we already have this one in this or an outer
                        // location?
                        Mounted m = null;
                        boolean bFound = false;
                        for (int x = 0, n = vSplitWeapons.size(); x < n; x++) {
                            m = vSplitWeapons.elementAt(x);
                            int nLoc = m.getLocation();
                            if (((nLoc == loc) || (loc == Mech
                                    .getInnerLocation(nLoc)))
                                    && (m.getType() == etype)) {
                                bFound = true;
                                break;
                            }
                        }
                        if (bFound) {
                            m.setFoundCrits(m.getFoundCrits() + 1);
                            if (m.getFoundCrits() >= etype.getCriticals(mech)) {
                                vSplitWeapons.removeElement(m);
                            }
                            // if we're in a new location, set the
                            // weapon as split
                            if (loc != m.getLocation()) {
                                m.setSplit(true);
                            }
                            // give the most restrictive location for arcs
                            int help = m.getLocation();
                            m.setLocation(Mech.mostRestrictiveLoc(loc, help));
                            if (loc != help) {
                                m.setSecondLocation(Mech.leastRestrictiveLoc(
                                        loc, help));
                            }
                        } else {
                            // make a new one
                            m = new Mounted(mech, etype);
                            m.setFoundCrits(1);
                            vSplitWeapons.addElement(m);
                        }
                        mech.addEquipment(m, loc, rearMounted);
                    } else {
                        mech.addEquipment(etype, loc, rearMounted);
                    }
                } else {
                    if (!critName.equals("Empty")) {
                        // Can't load this piece of equipment!
                        // Add it to the list so we can show the user.
                        mech.addFailedEquipment(critName);
                        // Make the failed equipment an empty slot
                        critData[loc][i][0] = "Empty";
                        critData[loc][i][1] = null;
                        // Compact criticals again
                        compactCriticals(mech, loc);
                        // Re-parse the same slot, since the compacting
                        // could have moved new equipment to this slot
                        i--;
                    }
                }
            } catch (LocationFullException ex) {
                throw new EntityLoadingException(ex.getMessage());
            }
        }
    }

    /**
     * This function moves all "empty" slots to the end of a location's critical
     * list. MegaMek adds equipment to the first empty slot available in a
     * location. This means that any "holes" (empty slots not at the end of a
     * location), will cause the file crits and MegaMek's crits to become out of
     * sync.
     */
    private void compactCriticals(Mech mech) {
        for (int loc = 0; loc < mech.locations(); loc++) {
            compactCriticals(mech, loc);
        }
    }

    private void compactCriticals(Mech mech, int loc) {
        if (loc == Mech.LOC_HEAD) {
            // This location has an empty slot inbetween systems crits
            // which will mess up parsing if compacted.
            return;
        }
        int firstEmpty = -1;
        for (int slot = 0; slot < mech.getNumberOfCriticals(loc); slot++) {
            if ((firstEmpty == -1) && critData[loc][slot][0].equals("Empty")) {
                firstEmpty = slot;
            }
            if ((firstEmpty != -1) && !critData[loc][slot][0].equals("Empty")) {
                // move this to the first empty slot
                critData[loc][firstEmpty][0] = critData[loc][slot][0];
                critData[loc][firstEmpty][1] = critData[loc][slot][1];
                // mark the old slot empty
                critData[loc][slot][0] = "Empty";
                critData[loc][slot][1] = null;
                // restart just after the moved slot's new location
                slot = firstEmpty;
                firstEmpty = -1;
            }
        }
    }

}
