import java.util.stream.IntStream;

/**
 * SUPER STARTREK - MAY 16,1978
 * ****        **** STAR TREK ****        ****
 * **** SIMULATION OF A MISSION OF THE STARSHIP ENTERPRISE,
 * **** AS SEEN ON THE STAR TREK TV SHOW.
 * **** ORIGINAL PROGRAM BY MIKE MAYFIELD, MODIFIED VERSION
 * **** PUBLISHED IN DEC'S "101 BASIC GAMES", BY DAVE AHL.
 * **** MODIFICATIONS TO THE LATTER (PLUS DEBUGGING) BY BOB
 * *** LEEDOM - APRIL & DECEMBER 1974,
 * *** WITH A LITTLE HELP FROM HIS FRIENDS . . .
 *
 * Ported to Java in Jan-Mar 2022 by
 * Taciano Dreckmann Perez (taciano.perez@gmail.com)
 */
public class SuperStarTrekGame implements GameCallback {

    // commands
    static final int COMMAND_NAV = 1;
    static final int COMMAND_SRS = 2;
    static final int COMMAND_LRS = 3;
    static final int COMMAND_PHA = 4;
    static final int COMMAND_TOR = 5;
    static final int COMMAND_SHE = 6;
    static final int COMMAND_DAM = 7;
    static final int COMMAND_COM = 8;
    static final int COMMAND_XXX = 9;

    // computer commands
    static final int COMPUTER_COMMAND_CUMULATIVE_GALACTIC_RECORD = 1;
    static final int COMPUTER_COMMAND_STATUS_REPORT = 2;
    static final int COMPUTER_COMMAND_PHOTON_TORPEDO_DATA = 3;
    static final int COMPUTER_COMMAND_STARBASE_NAV_DATA = 4;
    static final int COMPUTER_COMMAND_DIR_DIST_CALC = 5;
    static final int COMPUTER_COMMAND_GALAXY_MAP = 6;

    // other constants
    static final String COMMANDS = "NAVSRSLRSPHATORSHEDAMCOMXXX";

    // game state
    final GalaxyMap galaxyMap = new GalaxyMap();
    double stardate = Util.toInt(Util.random() * 20 + 20);
    int missionDuration = Math.max((25 + Util.toInt(Util.random() * 10)), galaxyMap.getKlingonsInGalaxy()+1);    // T9 (mission duration in stardates)
    boolean restart = false;

    // initial values
    final double initialStardate = stardate;

    public static void main(String[] args) {
        final SuperStarTrekGame game = new SuperStarTrekGame();
        printBanner();
        while (true) {
            game.orders();
            game.enterNewQuadrant();
            game.restart = false;
            game.commandLoop();
        }
    }

    static void printBanner() {
        IntStream.range(1, 10).forEach(i -> {
            Util.println("");
        });
        Util.println(
                """
                                                            ,------*------,
                                            ,-------------   '---  ------'
                                             '-------- --'      / /
                                                 ,---' '-------/ /--,
                                                  '----------------'

                                            THE USS ENTERPRISE --- NCC-1701"

                        """
        );
    }

    void orders() {
        Util.println("YOUR ORDERS ARE AS FOLLOWS:\n" +
                "     DESTROY THE " + galaxyMap.getKlingonsInGalaxy() + " KLINGON WARSHIP" + ((galaxyMap.getKlingonsInGalaxy() == 1) ? "" : "S") + " WHICH HAVE INVADED\n" +
                "   THE GALAXY BEFORE THEY CAN ATTACK FEDERATION HEADQUARTERS\n" +
                "   ON STARDATE " + initialStardate + missionDuration + "  THIS GIVES YOU " + missionDuration + " DAYS.  THERE " + ((galaxyMap.getBasesInGalaxy() == 1) ? "IS" : "ARE") + "\n" +
                "  " + galaxyMap.getBasesInGalaxy() + " STARBASE" + ((galaxyMap.getBasesInGalaxy() == 1) ? "" : "S") + " IN THE GALAXY FOR RESUPPLYING YOUR SHIP");
    }

    public void enterNewQuadrant() {
        galaxyMap.newQuadrant(stardate, initialStardate);
        shortRangeSensorScan();
    }

    void commandLoop() {
        while (!this.restart) {
            checkShipEnergy();
            String cmdStr = "";
            while ("".equals(cmdStr)) cmdStr = Util.inputStr("COMMAND");
            boolean foundCommand = false;
            for (int i = 1; i <= 9; i++) {
                if (Util.leftStr(cmdStr, 3).equals(Util.midStr(COMMANDS, 3 * i - 2, 3))) {
                    switch (i) {
                        case COMMAND_NAV:
                            navigation();
                            foundCommand = true;
                            break;
                        case COMMAND_SRS:
                            shortRangeSensorScan();
                            foundCommand = true;
                            break;
                        case COMMAND_LRS:
                            longRangeSensorScan();
                            foundCommand = true;
                            break;
                        case COMMAND_PHA:
                            firePhasers();
                            foundCommand = true;
                            break;
                        case COMMAND_TOR:
                            firePhotonTorpedo();
                            foundCommand = true;
                            break;
                        case COMMAND_SHE:
                            shieldControl();
                            foundCommand = true;
                            break;
                        case COMMAND_DAM:
                            galaxyMap.getEnterprise().damageControl(this);
                            foundCommand = true;
                            break;
                        case COMMAND_COM:
                            libraryComputer();
                            foundCommand = true;
                            break;
                        case COMMAND_XXX:
                            endGameFail(false);
                            foundCommand = true;
                            break;
                        default:
                            printCommandOptions();
                            foundCommand = true;
                    }
                }
            }
            if (!foundCommand) printCommandOptions();
        }
    }

    void checkShipEnergy() {
        final Enterprise enterprise = galaxyMap.getEnterprise();
        if (enterprise.getTotalEnergy() < 10 && (enterprise.getEnergy() <= 10 || enterprise.getDeviceStatus()[Enterprise.DEVICE_SHIELD_CONTROL] != 0)) {
            Util.println("\n** FATAL ERROR **   YOU'VE JUST STRANDED YOUR SHIP IN ");
            Util.println("SPACE");
            Util.println("YOU HAVE INSUFFICIENT MANEUVERING ENERGY,");
            Util.println(" AND SHIELD CONTROL");
            Util.println("IS PRESENTLY INCAPABLE OF CROSS");
            Util.println("-CIRCUITING TO ENGINE ROOM!!");
            endGameFail(false);
        }
    }

    void printCommandOptions() {
        Util.println("ENTER ONE OF THE FOLLOWING:");
        Util.println("  NAV  (TO SET COURSE)");
        Util.println("  SRS  (FOR SHORT RANGE SENSOR SCAN)");
        Util.println("  LRS  (FOR LONG RANGE SENSOR SCAN)");
        Util.println("  PHA  (TO FIRE PHASERS)");
        Util.println("  TOR  (TO FIRE PHOTON TORPEDOES)");
        Util.println("  SHE  (TO RAISE OR LOWER SHIELDS)");
        Util.println("  DAM  (FOR DAMAGE CONTROL REPORTS)");
        Util.println("  COM  (TO CALL ON LIBRARY-COMPUTER)");
        Util.println("  XXX  (TO RESIGN YOUR COMMAND)\n");
    }

    void navigation() {
        float course = Util.toInt(Util.inputFloat("COURSE (0-9)"));
        if (course == 9) course = 1;
        if (course < 1 || course >= 9) {
            Util.println("   LT. SULU REPORTS, 'INCORRECT COURSE DATA, SIR!'");
            return;
        }
        final Enterprise enterprise = galaxyMap.getEnterprise();
        final double[] deviceStatus = enterprise.getDeviceStatus();
        Util.println("WARP FACTOR (0-" + ((deviceStatus[Enterprise.DEVICE_WARP_ENGINES] < 0) ? "0.2" : "8") + ")");
        float warp = Util.inputFloat("");
        if (deviceStatus[Enterprise.DEVICE_WARP_ENGINES] < 0 && warp > .2) {
            Util.println("WARP ENGINES ARE DAMAGED.  MAXIMUM SPEED = WARP 0.2");
            return;
        }
        if (warp == 0) return;
        if (warp > 0 && warp <= 8) {
            int n = Util.toInt(warp * 8);
            if (enterprise.getEnergy() - n >= 0) {
                galaxyMap.klingonsMoveAndFire(this);
                repairDamagedDevices(course, warp, n);
                galaxyMap.moveEnterprise(course, warp, n, stardate, initialStardate, missionDuration, this);
            } else {
                Util.println("ENGINEERING REPORTS   'INSUFFICIENT ENERGY AVAILABLE");
                Util.println("                       FOR MANEUVERING AT WARP " + warp + "!'");
                if (enterprise.getShields() < n - enterprise.getEnergy() || deviceStatus[Enterprise.DEVICE_SHIELD_CONTROL] < 0) return;
                Util.println("DEFLECTOR CONTROL ROOM ACKNOWLEDGES " + enterprise.getShields() + " UNITS OF ENERGY");
                Util.println("                         PRESENTLY DEPLOYED TO SHIELDS.");
            }
        } else {
            Util.println("   CHIEF ENGINEER SCOTT REPORTS 'THE ENGINES WON'T TAKE");
            Util.println(" WARP " + warp + "!'");
        }
    }

    void repairDamagedDevices(final float course, final float warp, final int N) {
        final Enterprise enterprise = galaxyMap.getEnterprise();
        // repair damaged devices and print damage report
        enterprise.repairDamagedDevices(warp);
        if (Util.random() > .2) return;  // 80% chance no damage nor repair
        int randomDevice = Util.fnr();    // random device
        final double[] deviceStatus = enterprise.getDeviceStatus();
        if (Util.random() >= .6) {   // 40% chance of repair of random device
            enterprise.setDeviceStatus(randomDevice, deviceStatus[randomDevice] + Util.random() * 3 + 1);
            Util.println("DAMAGE CONTROL REPORT:  " + Enterprise.printDeviceName(randomDevice) + " STATE OF REPAIR IMPROVED\n");
        } else {            // 60% chance of damage of random device
            enterprise.setDeviceStatus(randomDevice, deviceStatus[randomDevice] - (Util.random() * 5 + 1));
            Util.println("DAMAGE CONTROL REPORT:  " + Enterprise.printDeviceName(randomDevice) + " DAMAGED");
        }
    }

    void longRangeSensorScan() {
        // LONG RANGE SENSOR SCAN CODE
        galaxyMap.longRangeSensorScan();
    }

    void firePhasers() {
        galaxyMap.firePhasers(this);
    }

    void firePhotonTorpedo() {
        galaxyMap.firePhotonTorpedo(stardate, initialStardate, missionDuration, this);
    }

    void shieldControl() {
        galaxyMap.getEnterprise().shieldControl();
    }

    void shortRangeSensorScan() {
        // SHORT RANGE SENSOR SCAN & STARTUP SUBROUTINE
        galaxyMap.shortRangeSensorScan(stardate);
    }

    void libraryComputer() {
        // REM LIBRARY COMPUTER CODE
        if (galaxyMap.getEnterprise().getDeviceStatus()[Enterprise.DEVICE_LIBRARY_COMPUTER] < 0) {
            Util.println("COMPUTER DISABLED");
            return;
        }
        while (true) {
            final float commandInput = Util.inputFloat("COMPUTER ACTIVE AND AWAITING COMMAND");
            if (commandInput < 0) return;
            Util.println("");
            int command = Util.toInt(commandInput) + 1;
            if (command >= COMPUTER_COMMAND_CUMULATIVE_GALACTIC_RECORD && command <= COMPUTER_COMMAND_GALAXY_MAP) {
                switch (command) {
                    case COMPUTER_COMMAND_CUMULATIVE_GALACTIC_RECORD:
                        galaxyMap.cumulativeGalacticRecord(true);
                        return;
                    case COMPUTER_COMMAND_STATUS_REPORT:
                        statusReport();
                        return;
                    case COMPUTER_COMMAND_PHOTON_TORPEDO_DATA:
                        galaxyMap.photonTorpedoData();
                        return;
                    case COMPUTER_COMMAND_STARBASE_NAV_DATA:
                        galaxyMap.starbaseNavData();
                        return;
                    case COMPUTER_COMMAND_DIR_DIST_CALC:
                        galaxyMap.directionDistanceCalculator();
                        return;
                    case COMPUTER_COMMAND_GALAXY_MAP:
                        galaxyMap.cumulativeGalacticRecord(false);
                        return;
                }
            } else {
                // invalid command
                Util.println("FUNCTIONS AVAILABLE FROM LIBRARY-COMPUTER:");
                Util.println("   0 = CUMULATIVE GALACTIC RECORD");
                Util.println("   1 = STATUS REPORT");
                Util.println("   2 = PHOTON TORPEDO DATA");
                Util.println("   3 = STARBASE NAV DATA");
                Util.println("   4 = DIRECTION/DISTANCE CALCULATOR");
                Util.println("   5 = GALAXY 'REGION NAME' MAP");
                Util.println("");
            }
        }
    }

    void statusReport() {
        Util.println("   STATUS REPORT:");
        Util.println("KLINGON" + ((galaxyMap.getKlingonsInGalaxy() > 1)? "S" : "")  + " LEFT: " + galaxyMap.getKlingonsInGalaxy());
        Util.println("MISSION MUST BE COMPLETED IN " + .1 * Util.toInt((initialStardate + missionDuration - stardate) * 10) + " STARDATES");
        if (galaxyMap.getBasesInGalaxy() >= 1) {
            Util.println("THE FEDERATION IS MAINTAINING " + galaxyMap.getBasesInGalaxy() + " STARBASE" + ((galaxyMap.getBasesInGalaxy() > 1)? "S" : "") + " IN THE GALAXY");
        } else {
            Util.println("YOUR STUPIDITY HAS LEFT YOU ON YOUR OWN IN");
            Util.println("  THE GALAXY -- YOU HAVE NO STARBASES LEFT!");
        }
        galaxyMap.getEnterprise().damageControl(this);
    }

    public void incrementStardate(double increment) {
        this.stardate += increment;
    }

    public void endGameFail(final boolean enterpriseDestroyed) {    // 6220
        if (enterpriseDestroyed) {
            Util.println("\nTHE ENTERPRISE HAS BEEN DESTROYED.  THEN FEDERATION ");
            Util.println("WILL BE CONQUERED");
        }
        Util.println("\nIT IS STARDATE " + stardate);
        Util.println("THERE WERE " + galaxyMap.getKlingonsInGalaxy() + " KLINGON BATTLE CRUISERS LEFT AT");
        Util.println("THE END OF YOUR MISSION.");
        repeatGame();
    }

    public void endGameSuccess() {
        Util.println("CONGRATULATION, CAPTAIN!  THE LAST KLINGON BATTLE CRUISER");
        Util.println("MENACING THE FEDERATION HAS BEEN DESTROYED.\n");
        Util.println("YOUR EFFICIENCY RATING IS " + (Math.sqrt(1000 * (galaxyMap.getRemainingKlingons() / (stardate - initialStardate)))));
        repeatGame();
    }

    void repeatGame() {
        Util.println("\n");
        if (galaxyMap.getBasesInGalaxy() != 0) {
            Util.println("THE FEDERATION IS IN NEED OF A NEW STARSHIP COMMANDER");
            Util.println("FOR A SIMILAR MISSION -- IF THERE IS A VOLUNTEER,");
            final String reply = Util.inputStr("LET HIM STEP FORWARD AND ENTER 'AYE'");
            if ("AYE".equals(reply)) {
                this.restart = true;
            } else {
                System.exit(0);
            }
        }
    }

}
