import java.util.Scanner;

/**
 * Animate solving the Towers of Hanoi problem.
 * (This program doesn't work very well in NetBeans.
 * On the CS machine it looks like a little movie....)
 *
 * @author Mark Young (A00000000)
 */
public class TextHanoi {
    /** Maximum number of disks we can handle */
    public static final int MAX = 12;
    /** Default number of disks */
    public static final int DEFAULT = 3;
    /** The number of pags in the game */
    public static final int NUM_PEGS = 3;

    /** How many "frames" are left before we pause again */
    private static int storedEnters = 0;

    public static void main(String[] args) {
        // introduce yourself
        System.out.print("\n\n"
                + "Text Hanoi\n"
                + "----------\n\n"
                + "This application shows you how to solve "
                + "the Towers of Hanoi problem.\n\n");

        // get the number of disks from the user
        Scanner kbd = new Scanner(System.in);
        System.out.print("How many disks? ");
        int numDisks = kbd.nextInt();
        kbd.nextLine();

        // create and play the game
        TextHanoi game = new TextHanoi(numDisks);
        game.play();
    }

    // the game instance variable
    private int numDisks;   // how many disks there are
    private int[][] pegs;   // how those disks are arranged on the pegs
        /* IMPLEMENTATION NOTE
         * The rows of this array represent the pegs.
         * The elements of each row show the sizes of the disks on that peg,
         * with 0 indicating no disk.
         * Each row has enuf spaces for all the disks, plus one more space.
         * That extra space says how many disks are on this peg
         * (for convenience -- we *could* just count them).
         *
         * Thus the initial set up for the three disk game would be:
         * pegs == [[3,2,1, 3], [0,0,0, 0], [0,0,0, 0]]
         *         (all disks on first peg)
         * after the first move it'd be:
         * pegs == [[3,2,0, 2], [0,0,0, 0], [1,0,0, 1]]
         *         (smallest disk moved to end peg)
         * and after the second move it'd be:
         * pegs == [[3,0,0, 1], [2,0,0, 1], [1,0,0, 1]]
         *         (one disk on each peg)
         */

    /**
     * Create the game.
     *
     * @param reqNumDisks    the requested number of disks
     */
    public TextHanoi(int reqNumDisks) {
        // chack for #disks out of the range we can handle
        if (reqNumDisks > MAX) {
            System.out.println();
            System.err.println("TextHanoi can only go up to " + MAX 
                    + " disks!");
            reqNumDisks = MAX;
        }
        if (reqNumDisks <= 0) {
            System.out.println();
            System.err.println("TextHanoi needs a positive number of disks!");
            reqNumDisks = DEFAULT;
        }

        // set up the initial board
        numDisks = reqNumDisks;
        pegs = new int[NUM_PEGS][numDisks + 1];
        for (int i = numDisks; i > 0; i--) {
            pegs[0][numDisks-i] = i;
        }
        pegs[0][numDisks] = numDisks;
    }

    /**
     * Play the game.
     * Announce the moves as they're made,
     * and draw the resulting configuration of disks.
     */
    public void play() {
        // announce the start of the game
        System.out.println("\n\n"
                + "Towers of Hanoi -- " + numDisks + " disks\n\n");

        // show the board and all the moves
        showPegs();
        showMoves(numDisks, 0, 2, 1);

        // announce the end of the game
        System.out.println("\n\n"
                + "Towers of Hanoi -- solved\n\n");
    }

    // show all the moves
    /**
     * Show the moves required to solve the Towers of Hanoi problem.
     *
     * @param curNumDisks the number of disks
     * @param start the index of the starting peg
     * @param finish the index of the ending peg
     * @param extra the index of the extra peg
     */
    private void showMoves(int curNumDisks, int start, int finish, int extra) {
        // if there are any disks to move
        if (curNumDisks > 0) {
            // show all the moves required to clear the bottom disk
            showMoves(curNumDisks - 1, start, extra, finish);

            // show moving the bottom disk
            moveOneDisk(start, finish);

            // show all the moves required to restack onto the bottom disk
            showMoves(curNumDisks - 1, extra, finish, start);
        }
    }

    /**
     * Show moving a single disk.
     *
     * @param start     the index of the source peg
     * @param finish    the index of the destination peg
     */
    private void moveOneDisk(int start, int finish) {
        // announce the move
        System.out.println("\n\n"
                + "Move a disk from peg " + start + " to peg " + finish 
                + "\n");

        // update the configuration of the pegs
        push(pegs[finish], pop(pegs[start]));

        // show the new configuration
        showPegs();
    }

    /**
     * Remove the top disk from a peg.
     *
     * @param peg   the array representing this peg
     * @return  the number/size of the disk being moved
     */
    private int pop(int[] peg) {
        peg[numDisks]--;
        int disk = peg[peg[numDisks]];
        peg[peg[numDisks]] = 0;
        return disk;
    }

    /**
     * Put a disk onto a peg.
     *
     * @param peg   the array representing the target peg
     * @param disk  the number/size of the disk being moved
     */
    private void push(int[] peg, int disk) {
        peg[peg[numDisks]] = disk;
        peg[numDisks]++;
    }

    /**
     * Draw the pegs and disks on the screen.
     */
    public void showPegs() {
        // draw the pegs level by level (top to bottom)
        for (int n = numDisks; n > 0; n--) {
            showPeg(0, n);
            showPeg(1, n);
            showPeg(2, n);
            System.out.println();
        }
        // draw the bases of the pegs
        System.out.println("------------0------------"
                + "------------1------------"
                + "------------2------------\n");

        // pause
        pause();
    }

    /**
     * Pause briefly to let the user view the configuration.
     * If there are "frames" to be shown,
     * pause for a small amount of time
     * to let the full screen appear as a frame of the "movie".
     * If there are no "frames" remaining to be shown,
     * wait for the user to press the enter key,
     * noting whether the user has asked for multiple frames to be shown.
     */
    public void pause() {
        // NOTE: wait until user presses Enter key
        // BUT allow user to type a number
        //     and consider that as a number of Enter key presses

        // if there are stored Enter-key presses remaining
        if (storedEnters > 0) {
            // announce that you're using a stored enter-key press
            System.out.print("\nUsing a stored ENTER...");
            // use the press
            storedEnters--;
            // wait 500ms so we get an "animation" effect
            try {
                Thread.sleep(500);
            } catch (Exception e) {}
        }
        // otherwise (no stored enter-key presses)
        else {
            // wait for the user to press the enter key
            Scanner kbd = new Scanner(System.in);
            System.out.print("\nPress ENTER to continue...");
            String in = kbd.nextLine();

            // if there's anything on the line
            if (!in.equals("")) {
                // try to interprete it as a number
                try {
                    // and use that many key presses in total
                    storedEnters = Integer.parseInt(in);
                    storedEnters--;
                } catch (Exception e) {}
            }
        }
    }

    /**
     * Draw one layer of one peg on the screen.
     * One "layer" of a peg is the amount of space a disk would take up.
     * If there is a disk at that layer, draw it.
     * If there is no disk at that layer, draw a bit of the peg itself.
     *
     * @param peg     the number of the peg we're drawing
     * @param layer     the layer of the peg we're drawing
     */
    private void showPeg(int peg, int layer) {
        String spacer = "            ";
        String disker = "************";
        int disk = pegs[peg][layer - 1];

        // if there's no disk at this level of the peg
        if (disk == 0) {
            // print out an empty peg segment
            System.out.print(spacer + "|" + spacer);
        } else {
            // print out the disk with appropriate space
            String front = spacer.substring(disk) + disker.substring(0, disk);
            String back = disker.substring(0, disk) + spacer.substring(disk);
            System.out.print(front + "*" + back);
        }
    }

}
