/*
* Copyright (c) 1996-1999 Bill Venners. All Rights Reserved.
*
* This Java source file is part of the Interactive Illustrations Web
* Site, which is delivered in the applets directory of the CD-ROM
* that accompanies the book "Inside the Java 2 Virtual Machine" by Bill
* Venners, published by McGraw-Hill, 1999, ISBN: 0-07-135093-4. This
* source file is provided for evaluation purposes only, but you can
* redistribute it under certain conditions, described in the full
* copyright notice below.
*
* Full Copyright Notice:
*
* All the web pages and Java applets delivered in the applets
* directory of the CD-ROM, consisting of ".html," ".gif," ".class,"
* and ".java" files, are copyrighted (c) 1996-1999 by Bill
* Venners, and all rights are reserved.  This material may be copied
* and placed on any commercial or non-commercial web server on any
* network (including the internet) provided that the following
* guidelines are followed:
*
* a. All the web pages and Java Applets (".html," ".gif," ".class,"
* and ".java" files), including the source code, that are delivered
* in the applets directory of the CD-ROM that
* accompanies the book must be published together on the same web
* site.
*
* b. All the web pages and Java Applets (".html," ".gif," ".class,"
* and ".java" files) must be published "as is" and may not be altered
* in any way.
*
* c. All use and access to this web site must be free, and no fees
* can be charged to view these materials, unless express written
* permission is obtained from Bill Venners.
*
* d. The web pages and Java Applets may not be distributed on any
* media, other than a web server on a network, and may not accompany
* any book or publication.
*
* BILL VENNERS MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE
* SUITABILITY OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING
* BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR PARTICULAR PURPOSE, OR NON-INFRINGEMENT.  BILL VENNERS
* SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY A LICENSEE AS A
* RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES.
*/
import java.awt.*;

/**
* This class is a canvas upon which a JVM is shown consuming a stream
* of bytes that makes up a particular Java class file.
*
* @author  Bill Venners
*/
class JVMPacman extends Canvas {

    private String theString;
    private boolean stringValid = false;
    private int currentGobblePosition;
    private int interestingCharsCount;
    private int charsThatFitBetweenRectanglesCount = 2;

    JVMPacman() {
        setBackground(Color.cyan);
    }

    void setText(String passedText) {

        theString = passedText;
        stringValid = true;
    }

    public Dimension minimumSize() {
        return new Dimension(110, 60);
    }
    public Dimension preferredSize() {
        return new Dimension(110, 60);
    }

    public void setGobblePosition(int pos, int interesting) {
        // Multiply the passed position by two because the passed position represents
        // a byte position whereas we want currentGobblePosition to represent a
        // character position, and there are two hex characters for each byte.
        currentGobblePosition = pos * 2;
        interestingCharsCount = interesting * 2;
        repaint();
    }

    public void gobbleToPosition(int pos, int interesting) {
        currentGobblePosition = pos * 2;
        interestingCharsCount = interesting * 2;
        repaint();
    }

    public void paint(Graphics g) {

        Font font = getFont();
        FontMetrics fm = getFontMetrics(font);
        int heightOfOneLine = fm.getHeight();

        // Calculate x starting point
        Dimension dim = new Dimension();

        dim = size();

        int xStartingPoint = 5;

        // Calculate y starting point
        int totalHeight = heightOfOneLine * 2;
        int yStartingPoint = (dim.height - totalHeight) / 2;
        if (yStartingPoint < 5) {
            yStartingPoint = 5;
        }

        // Calculate width of JVM rectangle. This will be heightOfOneLine more than
        // the stringWidth of "JVM" which I'll write in the middle of the rectangle.
        // This will make the border around the "JVM" the same width and height on
        // all sides and will equal heightOfOneLine / 2. I'll make the height of the
        // rectangle heightOfOneLine *2.
        int jvmRectangleWidth = fm.stringWidth("JVM") + heightOfOneLine;

        // Draw the filled rectangle
        g.setColor(Color.green);
        g.fillRoundRect(xStartingPoint, yStartingPoint, jvmRectangleWidth, totalHeight,
            5, 5);

        // Give it a handsome black outline
        g.setColor(Color.black);
        g.drawRoundRect(xStartingPoint, yStartingPoint, jvmRectangleWidth - 1, totalHeight - 1,
            5, 5);

        // Calculate width of Server rectangle. This will be heightOfOneLine more than
        // the stringWidth of "Server" which I'll write in the middle of the rectangle.
        // This will make the border around the "Server" the same width and height on
        // all sides and will equal heightOfOneLine / 2. I'll make the height of the
        // rectangle heightOfOneLine *2.
        int serverRectangleWidth = fm.stringWidth("Server") + heightOfOneLine;

        // Draw the filled rectangle. The x starting point is the width of the
        // canvas minus the width of the server rectangle minus the 5 pixel margin.
        int xStartingPointServerRect = dim.width - serverRectangleWidth - 5;
        g.setColor(Color.green);
        g.fillRoundRect(xStartingPointServerRect, yStartingPoint,
            serverRectangleWidth, totalHeight, 5, 5);

        // Give this rectangle a handsome black outline
        g.setColor(Color.black);
        g.drawRoundRect(xStartingPointServerRect, yStartingPoint,
            serverRectangleWidth - 1, totalHeight - 1, 5, 5);

        int whiteRectangleWidth = xStartingPointServerRect - jvmRectangleWidth - 5;
        if (whiteRectangleWidth > 0) {

            g.setColor(Color.white);
            g.fillRect(jvmRectangleWidth + 5, yStartingPoint + (heightOfOneLine / 2),
                whiteRectangleWidth, heightOfOneLine);
        }

        // Draw "JVM" inside the rectangle
        g.setColor(Color.black);
        xStartingPoint += (heightOfOneLine / 2);
        int ascent = fm.getAscent();
        yStartingPoint += ascent + (heightOfOneLine / 2);
        g.drawString("JVM", xStartingPoint, yStartingPoint);

        // Draw "Server" inside the rectangle
        int xStartingPointServerText = xStartingPointServerRect + (heightOfOneLine / 2);
        g.drawString("Server", xStartingPointServerText, yStartingPoint);

        // The string should be written so that it fits between the JVM and Server
        // rectangles, leaving at least 5 pixels space between the rectangle and
        // the string.
        if (stringValid && currentGobblePosition < theString.length()) {

            // First need to figure out how many characters will fit in
            // the space between the two rectangles.
            int xTextStartingPoint = jvmRectangleWidth + 10;
            int xTextEndingPoint = xStartingPointServerRect - 5;
            int pixelsAvailableBetweenRectangles = xTextEndingPoint - xTextStartingPoint;
            if (pixelsAvailableBetweenRectangles < 0) {
                pixelsAvailableBetweenRectangles = 0;
            }

            // Initialize the number of characters to write as the number of
            // remaining characters. This will be reduced below if this amount of
            // characters doesn't fit.
            int charsToWriteCount = theString.length() - currentGobblePosition;

            // Check to see if the string to be displayed already fits between the
            // two rectangles. If so, we'll just use the total number of characters
            // remaining as the number of characters to write.
            int pixelWidthOfRemainingString = fm.stringWidth(theString.substring(currentGobblePosition));
            if (pixelWidthOfRemainingString > pixelsAvailableBetweenRectangles) {

                // The first while loop increments the charsThatFitBetweenTwoRectanglesCount
                // until the width of the string in pixels just exceeds the available space.
                String tryThisString = theString.substring(currentGobblePosition,
                    currentGobblePosition + charsThatFitBetweenRectanglesCount);
                int pixelsEaten = fm.stringWidth(tryThisString);
                while (pixelsEaten <= pixelsAvailableBetweenRectangles) {
                    ++charsThatFitBetweenRectanglesCount;
                    tryThisString = theString.substring(currentGobblePosition,
                        currentGobblePosition + charsThatFitBetweenRectanglesCount);
                    pixelsEaten = fm.stringWidth(tryThisString);
                }

                // The second while loop decreases the charsThatFit variable until the
                // width of the string in pixels is just under the available width.
                while (pixelsEaten > pixelsAvailableBetweenRectangles) {
                    --charsThatFitBetweenRectanglesCount;
                    tryThisString = theString.substring(currentGobblePosition,
                        currentGobblePosition + charsThatFitBetweenRectanglesCount);
                    pixelsEaten = fm.stringWidth(tryThisString);
                }

                charsToWriteCount = charsThatFitBetweenRectanglesCount;
            }

            // Draw the interesting characters in red.
            g.setColor(Color.red);
            int redCharsCount = interestingCharsCount;
            if (redCharsCount > charsToWriteCount) {
                redCharsCount = charsToWriteCount;
            }
            String redString = theString.substring(currentGobblePosition,
                currentGobblePosition + redCharsCount);
            g.drawString(redString, xTextStartingPoint, yStartingPoint);

            // Draw the remaining characters in black.
            int blackStringStartingPosition = currentGobblePosition + redCharsCount;
            int blackCharsCount = charsToWriteCount - redCharsCount;
            if (blackStringStartingPosition < theString.length()
                && blackCharsCount > 0) {

                xTextStartingPoint += fm.stringWidth(redString);
                g.setColor(Color.black);
                g.drawString(theString.substring(blackStringStartingPosition,
                    blackStringStartingPosition + blackCharsCount),
                    xTextStartingPoint, yStartingPoint);
            }
        }
    }
}