/*
 * MegaMek -
 * Copyright (C) 2007 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.client.commands;

import megamek.client.Client;
import megamek.client.ui.swing.MovementDisplay;
import megamek.common.Coords;
import megamek.common.Entity;
import megamek.common.IEntityMovementMode;
import megamek.common.MovePath;

/**
 * @author dirk
 */
public class MoveCommand extends ClientCommand {

    public static final int GEAR_LAND = 0;
    public static final int GEAR_BACKUP = 1;
    public static final int GEAR_JUMP = 2;
    public static final int GEAR_CHARGE = 3;
    public static final int GEAR_DFA = 4;
    public static final int GEAR_TURN = 5;
    public static final int GEAR_SWIM = 6;

    // considering movement data
    private MovePath cmd;
    // considered ce()
    private int cen = Entity.NONE;
    int gear;

    public MoveCommand(Client client) {
        super(client, "move",
                "Move your units. Use #move HELP for more information.");
    }

    public String run(String[] args) {
        if (args.length > 1) {
            Coords target = null;
            if (args[1].equalsIgnoreCase("ABORT")) {
                clearAllMoves();
                cen = Entity.NONE;
                return "Move aborted, all movement data cleared.";
            } else if (args[1].equalsIgnoreCase("HELP")) {
                return "Available commands:\n"
                        + "#move ABORT = aborts planed move and deselect unit.\n"
                        + "#move SELECT unitID = Selects thhe unit named unit ID for movement. This is a prerequisite for all commands listed after this.\n"
                        + "#move COMMIT = comits the planed movement.\n"
                        + "#move JUMP = clears all movement and starts jump movement. Eiether the entire move is a jump or the entire move is a walk. switching gears will cancel all planned movement (but leave the unit selected).\n"
                        + "#move BACK [x y] = Start walking backwards, can be followed by a coordiate.\n"
                        + "#move WALK [x y] = Start walking/running forwards, this is the default. Can be followed by a coordiate.\n"
                        + "#move TURN [x y] = Starts turning towards target coordinate. Can be followed by a coordiate.\n"
                        + "#move CLIP = Clips to path to what is actually possible, and reports on what will happen if commited.\n"
                        + "#move GETUP = Attempt to stand up. Will require a piloting roll.\n"
                        + "#move CAREFUL = Attempt to stand up. Will require a piloting roll.\n"
                        + "#move x y = move towards coordinate in the current gear. It will do pathfinding for least cost path. Note that the entity will try to move to each coordinate supplied in order.\n";
            } else if (args[1].equalsIgnoreCase("SELECT")) {
                try {
                    clearAllMoves();
                    cen = Integer.parseInt(args[2]);
                    if (ce() == null) {
                        cen = Entity.NONE;
                        return "Not an entity ID or valid number.";
                    }
                    cmd = new MovePath(client.game, ce());

                    return "Entity " + ce().toString()
                            + " selected for movement.";
                } catch (Exception e) {
                    return "Not an entity ID or valid number." + e.toString();
                }
            } else if (ce() != null) {
                if (args[1].equalsIgnoreCase("JUMP")) {

                    clearAllMoves();
                    if (!cmd.isJumping()) {
                        cmd.addStep(MovePath.STEP_START_JUMP);
                    }
                    gear = GEAR_JUMP;

                    return "Entity " + ce().toString() + " is going to jump.";
                } else if (args[1].equalsIgnoreCase("COMMIT")) {
                    moveTo(cmd);
                    return "Move sent.";
                } else if (args[1].equalsIgnoreCase("BACK")) {
                    if (gear == MovementDisplay.GEAR_JUMP) {
                        clearAllMoves();
                    }
                    gear = GEAR_BACKUP;
                } else if (args[1].equalsIgnoreCase("WALK")) {
                    if (gear == MovementDisplay.GEAR_JUMP) {
                        clearAllMoves();
                    }
                    gear = GEAR_LAND;
                } else if (args[1].equalsIgnoreCase("TURN")) {
                    gear = GEAR_TURN;
                } else if (args[1].equalsIgnoreCase("CLIP")) {
                    cmd.clipToPossible();
                    return "Path cliped to whats actually possible. "
                            + ce().toString() + " is now in gear "
                            + gearName(gear) + " heading towards "
                            + cmd.getFinalCoords().toFriendlyString()
                            + " with a final facing of "
                            + getDirection(cmd.getFinalFacing())
                            + ". Total mp used: " + cmd.getMpUsed()
                            + " for a movement of: " + cmd.getHexesMoved();
                } else if (args[1].equalsIgnoreCase("GETUP")) {
                    if (cmd.getFinalProne() || cmd.getFinalHullDown()) {
                        cmd.addStep(MovePath.STEP_GET_UP);
                        return "Mech will try to stand up. this requieres a piloting roll.";
                    }

                    return "Trying to get up but the mech is not prone.";
                } else if (args[1].equalsIgnoreCase("CAREFULSTAND")) {
                    if (cmd.getFinalProne() || cmd.getFinalHullDown()
                            && client.game.getOptions().booleanOption("tacops_careful_stand")) {
                        cmd.addStep(MovePath.STEP_CAREFUL_STAND);
                        return "Mech will try to stand up. this requieres a piloting roll.";
                    }

                    return "Trying to get up but the mech is not prone.";
                } else {
                    target = new Coords(Integer.parseInt(args[1]) - 1, Integer
                            .parseInt(args[2]) - 1);
                }

                if (target == null && args.length > 3) {
                    target = new Coords(Integer.parseInt(args[2]) - 1, Integer
                            .parseInt(args[3]) - 1);
                }

                currentMove(target);

                return "Commands accepted " + ce().toString()
                        + " is now in gear " + gearName(gear)
                        + " heading towards "
                        + cmd.getFinalCoords().toFriendlyString()
                        + ". Total mp used: " + cmd.getMpUsed()
                        + " for a movement of: " + cmd.getHexesMoved();
            }
        }
        clearAllMoves();
        return "No arguments given, or there was an error parsing the arguments. All movement data cleared.";
    }

    /**
     * Returns new MovePath for the currently selected movement type
     */
    private void currentMove(Coords dest) {
        if (dest != null) {
            if (gear == GEAR_TURN) {
                cmd.rotatePathfinder(cmd.getFinalCoords().direction(dest), false);
            } else if (gear == GEAR_LAND || gear == GEAR_JUMP) {
                cmd.findPathTo(dest, MovePath.STEP_FORWARDS);
            } else if (gear == GEAR_BACKUP) {
                cmd.findPathTo(dest, MovePath.STEP_BACKWARDS);
            } else if (gear == GEAR_CHARGE) {
                cmd.findPathTo(dest, MovePath.STEP_CHARGE);
            } else if (gear == GEAR_DFA) {
                cmd.findPathTo(dest, MovePath.STEP_DFA);
            } else if (gear == GEAR_SWIM) {
                cmd.findPathTo(dest, MovePath.STEP_SWIM);
            }
        }
    }

    private String gearName(int gear) {
        if (gear == GEAR_TURN) {
            return "turning";
        } else if (gear == GEAR_LAND) {
            return "walking";
        } else if (gear == GEAR_BACKUP) {
            return "backup";
        } else if (gear == GEAR_CHARGE) {
            return "charging";
        } else if (gear == GEAR_DFA) {
            return "death from aboveing";
        } else if (gear == GEAR_SWIM) {
            return "swiming";
        } else if (gear == GEAR_JUMP) {
            return "jumping";
        }

        return "Unknown";
    }

    /**
     * Clears out the currently selected movement data and resets it.
     */
    private void clearAllMoves() {
        // switch back from swimming to normal mode.
        if (ce() != null) {
            if (ce().getMovementMode() == IEntityMovementMode.BIPED_SWIM)
                ce().setMovementMode(IEntityMovementMode.BIPED);
            else if (ce().getMovementMode() == IEntityMovementMode.QUAD_SWIM)
                ce().setMovementMode(IEntityMovementMode.QUAD);

            cmd = new MovePath(client.game, ce());
        } else {
            cmd = null;
        }

        gear = GEAR_LAND;
    }

    /**
     * Sends a data packet indicating the chosen movement.
     */
    private synchronized void moveTo(MovePath md) {
        md.clipToPossible();

        if (ce().hasUMU()) {
            client.sendUpdateEntity(ce());
        }
        client.moveEntity(cen, md);
    }

    /**
     * Returns the current Entity.
     */
    public Entity ce() {
        return client.game.getEntity(cen);
    }
}
