/**
 * MegaMek - Copyright (C) 2000-2002 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.
 */

package megamek.common.loaders;

import java.io.*;
import java.util.Hashtable;

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.Mounted;
import megamek.common.QuadMech;
import megamek.common.TechConstants;

// Known bug!  Split-location weapons are not supported.

public class MepFile implements IMechLoader {
    String version;
    String name;
    
    String mechYear;
    String innerSphere;
    String techYear;
    String chassisType;
    String tonnage;
    
    String engineType;
    String heatSinkType;
    String armorType;
    String internalType;
    
    String walkMP;
    String jumpMP;
    String heatSinks;
    
    String armorPoints;
    String armorPoints1;
    String armorPoints2;
    
    String headArmor;
    String larmArmor;
    String ltArmor;
    String ltrArmor;
    String ctArmor;
    String ctrArmor;
    String rtArmor;
    String rtrArmor;
    String rarmArmor;
    String llegArmor;
    String rlegArmor;
    
    String eqCount;
    String[] equipData;
    
    String eqWeight;
    String eqSlots;
    
    String[] critData;

    Hashtable hSharedEquip = new Hashtable();

    public MepFile(InputStream is) throws EntityLoadingException {
        try {
            BufferedReader r = new BufferedReader(new InputStreamReader(is));
            
            version = r.readLine();
            name = r.readLine();
            
            r.readLine(); // don't know what these are
            r.readLine();
            r.readLine();
            r.readLine();
            r.readLine();
            
            mechYear = r.readLine();
            innerSphere = r.readLine();
            techYear = r.readLine();
            chassisType = r.readLine();
            tonnage = r.readLine();
            
            engineType = r.readLine();
            heatSinkType = r.readLine();
            internalType = r.readLine();
            armorType = r.readLine();
            
            walkMP = r.readLine();
            jumpMP = r.readLine();
            heatSinks = r.readLine();
            
            r.readLine(); // weapons table descriptor -- useless?
            
            armorPoints = r.readLine();
            armorPoints1 = r.readLine(); // what are these two?
            armorPoints2 = r.readLine();
            
            headArmor = r.readLine();
            larmArmor = r.readLine();
            ltArmor = r.readLine();
            ltrArmor = r.readLine();
            ctArmor = r.readLine();
            ctrArmor = r.readLine();
            rtArmor = r.readLine();
            rtrArmor = r.readLine();
            rarmArmor = r.readLine();
            llegArmor = r.readLine();
            rlegArmor = r.readLine();
            
            eqCount = r.readLine();
            
            int eqs = Integer.parseInt(eqCount.substring(1));
            equipData = new String[eqs];
            for(int i = 0; i < eqs; i++) {
                equipData[i] = r.readLine();
            }
            
            eqWeight = r.readLine();
            eqSlots = r.readLine();
            
            r.readLine(); // mystery number
            
            int crits = 78;
            critData = new String[crits];
            for(int i = 0; i < crits; i++) {
                critData[i] = r.readLine();
            }
            
            r.close();
        } catch (IOException ex) {
            throw new EntityLoadingException("I/O error occured during file read");
        } catch (StringIndexOutOfBoundsException ex) {
            throw new EntityLoadingException("StringIndexOutOfBoundsException reading file (format error)");
        } catch (NumberFormatException ex) {
            throw new EntityLoadingException("NumberFormatException reading file (format error)");
        }
    }
    
    public Entity getEntity() throws EntityLoadingException {
        try {
            Mech mech;
            
            if ("Quad".equals(chassisType.trim())) {
                mech = new QuadMech();
            } else {
                mech = new BipedMech();
            }
            
            int firstSpace = this.name.indexOf(" ");
            if (firstSpace != -1) {
                mech.setChassis(this.name.substring(firstSpace).trim());
                mech.setModel(this.name.substring(5, firstSpace).trim());
            } else {
                mech.setChassis(this.name.substring(5).trim());
                mech.setModel(this.name.substring(5).trim());
            }
            
            mech.setWeight(Integer.decode(this.tonnage.trim()).intValue());
            mech.setYear(Integer.parseInt(this.techYear.trim()));
            mech.setOmni("OmniMech".equals(this.chassisType.trim()));
            
            //TODO: this ought to be a better test
            if ("InnerSphere".equals(this.innerSphere.trim())) {
                if (mech.getYear() == 3025) {
                    mech.setTechLevel(TechConstants.T_IS_LEVEL_1);
                } else {
                    mech.setTechLevel(TechConstants.T_IS_LEVEL_2);
                }
            } else {
                mech.setTechLevel(TechConstants.T_CLAN_LEVEL_2);
            }
            
            int engineFlags = 0;
            if (mech.isClan())
                engineFlags = Engine.CLAN_ENGINE;
            int engineRating = Integer.parseInt(this.walkMP.trim()) * (int)mech.getWeight();
            mech.setEngine(new Engine(engineRating,
                                      Engine.getEngineTypeByString(engineType),
                                      engineFlags));
            //No support for moveable system crits due to goofy critical
            // format.  Could be fixed, but I don't think anyone uses
            // MEP for level 3 designs.
            mech.addEngineCrits();
            mech.addCockpit();
            mech.addGyro();

            mech.setOriginalJumpMP(Integer.parseInt(this.jumpMP.trim()));
            
            boolean dblSinks = "Double".equals(this.heatSinkType.trim());
            mech.addEngineSinks(Integer.parseInt(this.heatSinks.trim()), dblSinks);

            mech.setStructureType(internalType);

            mech.setArmorType(armorType);

            decodeArmorAndInternals(mech, Mech.LOC_HEAD, headArmor);
            decodeArmorAndInternals(mech, Mech.LOC_LARM, larmArmor);
            decodeArmorAndInternals(mech, Mech.LOC_LT, ltArmor);
            decodeRearArmor(mech, Mech.LOC_LT, ltrArmor);
            decodeArmorAndInternals(mech, Mech.LOC_CT, ctArmor);
            decodeRearArmor(mech, Mech.LOC_CT, ctrArmor);
            decodeArmorAndInternals(mech, Mech.LOC_RT, rtArmor);
            decodeRearArmor(mech, Mech.LOC_RT, rtrArmor);
            decodeArmorAndInternals(mech, Mech.LOC_RARM, rarmArmor);
            decodeArmorAndInternals(mech, Mech.LOC_RLEG, rlegArmor);
            decodeArmorAndInternals(mech, Mech.LOC_LLEG, llegArmor);
            
            // remove arm actuators
            for (int i = 0; i < equipData.length; i++) {
                String eqName = new String(equipData[i]);
                eqName = eqName.substring(5, 28).trim();
                
                if (eqName.equals("No Lower Right Arm")) {
                    mech.removeCriticals(Mech.LOC_RARM, new CriticalSlot(CriticalSlot.TYPE_SYSTEM, Mech.ACTUATOR_LOWER_ARM));
                    mech.removeCriticals(Mech.LOC_RARM, new CriticalSlot(CriticalSlot.TYPE_SYSTEM, Mech.ACTUATOR_HAND));
                } else if (eqName.equals("No Lower Left Arm")) {
                    mech.removeCriticals(Mech.LOC_LARM, new CriticalSlot(CriticalSlot.TYPE_SYSTEM, Mech.ACTUATOR_LOWER_ARM));
                    mech.removeCriticals(Mech.LOC_LARM, new CriticalSlot(CriticalSlot.TYPE_SYSTEM, Mech.ACTUATOR_HAND));
                } else if (eqName.equals("No Right Hand")) {
                    mech.removeCriticals(Mech.LOC_RARM, new CriticalSlot(CriticalSlot.TYPE_SYSTEM, Mech.ACTUATOR_HAND));
                } else if (eqName.equals("No Left Hand")) {
                    mech.removeCriticals(Mech.LOC_LARM, new CriticalSlot(CriticalSlot.TYPE_SYSTEM, Mech.ACTUATOR_HAND));
                }
            }
            
            // hmm, what to do with the rest of equipment list... I dunno.
            
            // prefix is "Clan " or "IS "
            String prefix;
            if (mech.getTechLevel() == TechConstants.T_CLAN_LEVEL_2) {
                prefix = "Clan ";
            } else {
                prefix = "IS ";
            }
            
            // parse the critical hit slots
            for (int i = 0; i < critData.length; i++) {
                int loc = mech.getLocationFromAbbr(critData[i].substring(3, 5));
                int slot = Integer.parseInt(critData[i].substring(5, 7));
                boolean rearMounted = false;
                String critName = critData[i].substring(7).trim();

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

                if (critName.startsWith("(R)")) {
                    rearMounted = true;
                    critName = critName.substring(3).trim();
                }
                if (critName.equalsIgnoreCase("Armored Cowl")) {
                    mech.setCowl(5);
                }
                // this is a bit a kludge, but MEP is stupid
                if (critName.equals("Heat Sink") && dblSinks) {
                    critName = "Double Heat Sink";
                }
                
                EquipmentType etype = EquipmentType.get(prefix + critName);
                if (etype == null) {
                    // try w/o prefix
                    etype = EquipmentType.get(critName);
                }
                if (etype != null) {
                    try {
                        if (etype.isSpreadable()) {
                            // do we already have one of these?  Key on Type
                            Mounted m = (Mounted)hSharedEquip.get(etype);
                            if (m != null) {
                                // use the existing one
                                mech.addCritical(loc, new CriticalSlot(CriticalSlot.TYPE_EQUIPMENT, mech.getEquipmentNum(m), etype.isHittable()));
                                continue;
                            }
							m = mech.addEquipment(etype, loc, rearMounted);
							hSharedEquip.put(etype, m);
                        } else {
                            mech.addEquipment(etype, loc, rearMounted);
                        }
                    } catch (LocationFullException ex) {
                       throw new EntityLoadingException(ex.getMessage());
                    }
                } else {
                    if (!critName.equals("-----------------")
                        && !critName.equals("''")) {
                        // Can't load this piece of equipment!
                        mech.addFailedEquipment(critName);
                    }
                }
            }
            
            if (mech.isClan()) {
                mech.addClanCase();
            }
            
            return mech;
        } catch (NumberFormatException ex) {
            throw new EntityLoadingException("NumberFormatException parsing file");
        } catch (NullPointerException ex) {
            throw new EntityLoadingException("NullPointerException parsing file");
        }
    }
    
    
    
    /**
     * Decodes and sets the mech's armor and internal structure values
     */
    private void decodeArmorAndInternals(Mech mech, int loc, String s) {
        mech.initializeArmor(Integer.parseInt(s.substring(2, 4)), loc);
        mech.initializeInternal(Integer.parseInt(s.substring(12)), loc);
    }
    
    /**
     * Decodes and sets the mech's rear armor values
     */
    private void decodeRearArmor(Mech mech, int loc, String string) {
        mech.initializeRearArmor(Integer.parseInt(string.substring(2, 4)), loc);
    }
    
}
