/*
 * MegaMek -
 * Copyright (C) 2000,2001,2002,2003,2004,2005 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.
 */

/*
 * Author: Reinhard Vicinus
 */

package megamek.common.verifier;

import java.io.Serializable;
import java.util.Enumeration;
import java.util.Vector;

import megamek.common.AmmoType;
import megamek.common.CriticalSlot;
import megamek.common.Entity;
import megamek.common.EquipmentType;
import megamek.common.Mech;
import megamek.common.MiscType;
import megamek.common.Mounted;
import megamek.common.WeaponType;
import megamek.common.util.StringUtil;

public class TestMech extends TestEntity {
    private Mech mech = null;

    public TestMech(Mech mech, TestEntityOption option, String fileString) {
        super(option, mech.getEngine(), getArmor(mech), getStructure(mech));
        this.mech = mech;
        this.fileString = fileString;
    }

    private static Structure getStructure(Mech mech) {
        int type = mech.getStructureType();
        int flag = 0;
        if (mech.isClan()) {
            flag |= Structure.CLAN_STRUCTURE;
        }
        return new Structure(type, flag);
    }

    private static Armor getArmor(Mech mech) {
        int type = mech.getArmorType();
        int flag = 0;
        if (mech.isClanArmor()) {
            flag |= Armor.CLAN_ARMOR;
        }
        return new Armor(type, flag);
    }

    @Override
    public Entity getEntity() {
        return mech;
    }

    @Override
    public boolean isTank() {
        return false;
    }

    @Override
    public boolean isMech() {
        return true;
    }

    @Override
    public float getWeightMisc() {
        return 0.0f;
    }

    public float getWeightCockpit() {
        float weight = 3.0f;
        if (mech.getCockpitType() == Mech.COCKPIT_SMALL) {
            weight = 2.0f;
        } else if (mech.getCockpitType() == Mech.COCKPIT_TORSO_MOUNTED) {
            weight = 4.0f;
        } else if (mech.getCockpitType() == Mech.COCKPIT_COMMAND_CONSOLE) {
            // Technically, it's two separate 3-ton pieces of equipment.
            // We're ignoring that and returning the total, because it's easier.
            weight = 6.0f;
        } else if (mech.getCockpitType() == Mech.COCKPIT_DUAL) {
            // This is wrong; I just don't remember the correct weight.
            // FIXME
            weight = 3.0f;
        } else if (mech.getCockpitType() == Mech.COCKPIT_PRIMITIVE) {
            weight = 5.0f;
        } else if (mech.getCockpitType() == Mech.COCKPIT_PRIMITIVE_INDUSTRIAL) {
            weight = 5.0f;
        }

        return weight;
    }

    public float getWeightGyro() {
        float retVal = (float) Math.ceil(engine.getRating() / 100.0f);
        if (mech.getGyroType() == Mech.GYRO_XL) {
            retVal /= 2;
        } else if (mech.getGyroType() == Mech.GYRO_COMPACT) {
            retVal *= 1.5;
        } else if (mech.getGyroType() == Mech.GYRO_HEAVY_DUTY) {
            retVal *= 2;
        }
        retVal = ceil(retVal, getWeightCeilingGyro());
        return retVal;
    }

    @Override
    public float getWeightControls() {
        return getWeightCockpit() + getWeightGyro();
    }

    @Override
    public int getCountHeatSinks() {
        return mech.heatSinks();
    }

    @Override
    public int getWeightHeatSinks() {
        return mech.heatSinks() - engine.getWeightFreeEngineHeatSinks();
    }

    @Override
    public boolean hasDoubleHeatSinks() {
        if (mech.heatSinks() != mech.getHeatCapacity()) {
            return true;
        }
        return false;
    }

    @Override
    public String printWeightMisc() {
        return "";
    }

    @Override
    public String printWeightControls() {
        StringBuffer retVal = new StringBuffer(StringUtil.makeLength(mech
                .getCockpitTypeString()
                + ":", getPrintSize() - 5));
        retVal.append(makeWeightString(getWeightCockpit()));
        retVal.append("\n");
        retVal.append(StringUtil.makeLength(mech.getGyroTypeString() + ":",
                getPrintSize() - 5));
        retVal.append(makeWeightString(getWeightGyro()));
        retVal.append("\n");
        return retVal.toString();
    }

    public Mech getMech() {
        return mech;
    }

    public int countCriticalSlotsFromEquipInLocation(Entity entity, int eNum,
            int location) {
        int count = 0;
        for (int slots = 0; slots < entity.getNumberOfCriticals(location); slots++) {
            CriticalSlot slot = entity.getCritical(location, slots);
            if ((slot == null) || (slot.getType() == CriticalSlot.TYPE_SYSTEM)) {
                continue;
            } else if (slot.getType() == CriticalSlot.TYPE_EQUIPMENT) {
                if (slot.getIndex() == eNum) {
                    count++;
                }
            } else {
                // Ignore this?
            }
        }
        return count;
    }

    public boolean criticalSlotsAllocated(Entity entity, Mounted mounted,
            Vector<Serializable> allocation) {
        int eNum = entity.getEquipmentNum(mounted);
        int location = mounted.getLocation();
        EquipmentType et = mounted.getType();
        int criticals = 0;
        if (et instanceof MiscType) {
            criticals = calcMiscCrits((MiscType) et);
        } else {
            criticals = et.getCriticals(entity);
        }
        int count = 0;

        if (location == Entity.LOC_NONE) {
            return true;
        }

        if (et.isSpreadable() && !et.getName().equals("Targeting Computer")) {
            if ((et instanceof MiscType) && et.hasFlag(MiscType.F_STEALTH)) {
                // stealth needs to have 2 crits in legs arm and side torso
                if (countCriticalSlotsFromEquipInLocation(entity, eNum,
                        Mech.LOC_LARM) != 2) {
                    return false;
                }
                if (countCriticalSlotsFromEquipInLocation(entity, eNum,
                        Mech.LOC_RARM) != 2) {
                    return false;
                }
                if (countCriticalSlotsFromEquipInLocation(entity, eNum,
                        Mech.LOC_LLEG) != 2) {
                    return false;
                }
                if (countCriticalSlotsFromEquipInLocation(entity, eNum,
                        Mech.LOC_RLEG) != 2) {
                    return false;
                }
                if (countCriticalSlotsFromEquipInLocation(entity, eNum,
                        Mech.LOC_LT) != 2) {
                    return false;
                }
                if (countCriticalSlotsFromEquipInLocation(entity, eNum,
                        Mech.LOC_RT) != 2) {
                    return false;
                }
            }
            if ((et instanceof MiscType) && et.hasFlag(MiscType.F_ENVIRONMENTAL_SEALING)) {
                // environmental sealing needs to have 1 crit per location
                for (int locations = 0; locations < entity.locations(); locations++) {
                    if (countCriticalSlotsFromEquipInLocation(entity, eNum,
                            locations) != 1) {
                        return false;
                    }
                }
            }
            for (int locations = 0; locations < entity.locations(); locations++) {
                count += countCriticalSlotsFromEquipInLocation(entity, eNum,
                        locations);
            }
        } else {
            count = countCriticalSlotsFromEquipInLocation(entity, eNum,
                    location);
        }

        if ((et instanceof WeaponType) && mounted.isSplit()) {
            int secCound = 0;
            for (int locations = 0; locations < entity.locations(); locations++) {
                if (locations == location) {
                    continue;
                }

                secCound = countCriticalSlotsFromEquipInLocation(entity, eNum,
                        locations);
                if ((secCound != 0)
                        && (location == Mech.mostRestrictiveLoc(locations,
                                location))) {
                    count += secCound;
                    break;
                }
            }
        }

        if (count == criticals) {
            return true;
        }

        allocation.addElement(mounted);
        allocation.addElement(new Integer(criticals));
        allocation.addElement(new Integer(count));
        return false;
    }

    public void checkCriticalSlotsForEquipment(Entity entity,
            Vector<Mounted> unallocated, Vector<Serializable> allocation,
            Vector<Integer> heatSinks) {
        int countInternalHeatSinks = 0;
        for (Mounted m : entity.getEquipment()) {
            if (m.getLocation() == Entity.LOC_NONE) {
                if ((m.getType() instanceof AmmoType) && (m.getShotsLeft() <= 1)) {
                    continue;
                }
                if (!(m.getType() instanceof MiscType)) {
                    unallocated.addElement(m);
                    continue;
                }
                MiscType mt = (MiscType) m.getType();
                if (mt.hasFlag(MiscType.F_HEAT_SINK)
                        || mt.hasFlag(MiscType.F_DOUBLE_HEAT_SINK)) {
                    countInternalHeatSinks++;
                } else {
                    unallocated.addElement(m);
                    continue;
                }

            } else if (!criticalSlotsAllocated(entity, m, allocation)) {
            }
        }
        if ((countInternalHeatSinks > engine.integralHeatSinkCapacity())
                || ((countInternalHeatSinks < engine.integralHeatSinkCapacity())
                        && (countInternalHeatSinks != ((Mech) entity)
                                .heatSinks()) && !entity.isOmni())) {
            heatSinks.addElement(new Integer(countInternalHeatSinks));
        }
    }

    public void checkCriticals() {
        Vector<Mounted> unallocated = new Vector<Mounted>();
        Vector<Serializable> allocation = new Vector<Serializable>();
        Vector<Integer> heatSinks = new Vector<Integer>();
        checkCriticalSlotsForEquipment(mech, unallocated, allocation, heatSinks);
    }

    public boolean correctCriticals(StringBuffer buff) {
        Vector<Mounted> unallocated = new Vector<Mounted>();
        Vector<Serializable> allocation = new Vector<Serializable>();
        Vector<Integer> heatSinks = new Vector<Integer>();
        checkCriticalSlotsForEquipment(mech, unallocated, allocation, heatSinks);
        boolean correct = true;
        if (!unallocated.isEmpty()) {
            buff.append("Unallocated Equipment:\n");
            for (Mounted m : unallocated) {
            buff.append(m.getType().getInternalName()).append("\n");
         }
            correct = false;
        }
        if (!allocation.isEmpty()) {
            buff.append("Allocated Equipment:\n");
            for (Enumeration<Serializable> serializableEnum = allocation.elements();serializableEnum.hasMoreElements();) {
                Mounted m = (Mounted) serializableEnum.nextElement();
                int needCrits = ((Integer) serializableEnum.nextElement()).intValue();
                int aktCrits = ((Integer) serializableEnum.nextElement()).intValue();
                buff.append(m.getType().getInternalName()).append(" has ")
                .append(needCrits).append(" Slots, but ").append(
                        aktCrits).append(" Slots are allocated!")
                        .append("\n");
            }
            correct = false;
        }
        if (!heatSinks.isEmpty()) {
            int sinks = heatSinks.elements().nextElement().intValue();
            buff.append(sinks).append(" of ").append(
                    engine.integralHeatSinkCapacity()).append(
                    " possible Internal Heat Sinks!").append("\n");
            correct = false;
        }
        if (!checkSystemCriticals(buff)) {
            correct = false;
        }

        return correct;
    }

    private boolean checkSystemCriticals(StringBuffer buff) {
        // Engine criticals
        boolean engineCorrect = true;
        int requiredSideCrits = engine.getSideTorsoCriticalSlots().length;
        if ((requiredSideCrits != mech.getNumberOfCriticals(
                CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_ENGINE, Mech.LOC_LT))
                || (requiredSideCrits != mech.getNumberOfCriticals(
                        CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_ENGINE,
                        Mech.LOC_RT))) {
            engineCorrect = false;
        }
        if (engine.getCenterTorsoCriticalSlots(mech.getGyroType()).length != mech
                .getNumberOfCriticals(CriticalSlot.TYPE_SYSTEM,
                        Mech.SYSTEM_ENGINE, Mech.LOC_CT)) {
            engineCorrect = false;
        }
        if (!engineCorrect) {
            buff.append("Engine: Incorrect number of criticals allocated.\n");
        }

        if (!engineCorrect) {
            return false;
        }

        return true;
    }

    public String printArmorLocProp(int loc, int wert) {
        return " is greater then " + Integer.toString(wert) + "!";
    }

    public boolean correctArmor(StringBuffer buff) {
        boolean correct = true;
        for (int loc = 0; loc < mech.locations(); loc++) {
            if (loc == Mech.LOC_HEAD) {
                if (mech.getOArmor(Mech.LOC_HEAD) > 9) {
                    buff.append(printArmorLocation(Mech.LOC_HEAD)).append(
                            printArmorLocProp(Mech.LOC_HEAD, 9)).append("\n");
                    correct = false;
                }

            } else if ((mech.getOArmor(loc) + (mech.hasRearArmor(loc) ? mech
                    .getOArmor(loc, true) : 0)) > 2 * mech.getOInternal(loc)) {
                buff.append(printArmorLocation(loc)).append(
                        printArmorLocProp(loc, 2 * mech.getOInternal(loc)))
                        .append("\n");
                correct = false;
            }
        }

        return correct;
    }

    public boolean correctMovement(StringBuffer buff) {
        // Mechanical Jump Boosts can be greater then Running as long as
        // the unit can handle the weight.
        // talons also increase jump MP by 1
        if ((mech.getJumpMP(false) > mech.getOriginalRunMPwithoutMASC())
                && !mech.hasJumpBoosters() && !mech.hasWorkingMisc(MiscType.F_TALON, -1)) {
            buff.append("Jump MP exceeds run MP\n");
            return false;
        }
        if ((mech.getJumpMP(false) > mech.getOriginalWalkMP())
                && (mech.getJumpType() != Mech.JUMP_IMPROVED)
                && !mech.hasWorkingMisc(MiscType.F_TALON, -1)) {
          buff.append("Jump MP exceeds walk MP without IJJs");
          return false;
        }
        return true;
    }

    @Override
    public boolean correctEntity(StringBuffer buff) {
        return correctEntity(buff, true);
    }

    @Override
    public boolean correctEntity(StringBuffer buff, boolean ignoreAmmo) {
        boolean correct = true;
        if (skip()) {
            return true;
        }
        if (!correctWeight(buff)) {
            buff.insert(0, printTechLevel() + printShortMovement());
            buff.append(printWeightCalculation());
            correct = false;
        }
        if (!engine.engineValid) {
            buff.append(engine.problem.toString()).append("\n\n");
            correct = false;
        }
        if (getCountHeatSinks() < engine.getWeightFreeEngineHeatSinks()) {
            buff.append("Heat Sinks:\n");
            buff.append(" Engine    "+engine.integralHeatSinkCapacity()+"\n");
            buff.append(" Total     "+getCountHeatSinks()+"\n");
            buff.append(" Required  "+engine.getWeightFreeEngineHeatSinks()+"\n");
            correct = false;
        }
        if (showCorrectArmor() && !correctArmor(buff)) {
            correct = false;
        }
        if (showCorrectCritical() && !correctCriticals(buff)) {
            correct = false;
        }
        if (showFailedEquip() && hasFailedEquipment(buff)) {
            correct = false;
        }
        if (hasIllegalTechLevels(buff, ignoreAmmo)) {
            correct = false;
        }
        if (hasIllegalEquipmentCombinations(buff)) {
            correct = false;
        }
        correct = correct && correctMovement(buff);
        return correct;
    }

    @Override
    public StringBuffer printEntity() {
        StringBuffer buff = new StringBuffer();
        buff.append("Mech: ").append(mech.getDisplayName()).append("\n");
        buff.append("Found in: ").append(fileString).append("\n");
        buff.append(printTechLevel());
        buff.append(printShortMovement());
        if (correctWeight(buff, true, true)) {
            buff.append("Weight: ").append(getWeight()).append(" (").append(
                    calculateWeight()).append(")\n");
        }
        buff.append(printWeightCalculation()).append("\n");
        buff.append(printArmorPlacement());
        correctArmor(buff);
        buff.append(printLocations());
        correctCriticals(buff);

        // printArmor(buff);
        printFailedEquipment(buff);
        return buff;
    }

    @Override
    public String getName() {
        return "Mech: " + mech.getDisplayName();
    }

    /**
     * calculates the total weight of all armored components.
     */
    @Override
    public float getArmoredComponentWeight() {
        float weight = 0.0f;

        for (int location = Mech.LOC_HEAD; location <= Mech.LOC_LLEG; location++) {
            for (int slot = 0; slot < mech.getNumberOfCriticals(location); slot++) {
                CriticalSlot cs = mech.getCritical(location, slot);
                if ((cs != null) && cs.isArmored()) {
                    weight += 0.5f;

                    if ((cs.getType() == CriticalSlot.TYPE_SYSTEM) && (cs.getIndex() == Mech.SYSTEM_COCKPIT)) {
                        weight += 0.5f;
                    }
                }
            }
        }
        return weight;
    }


}
