/**
 * @(#)Paginator.java
 *
 * <code>Paginator</code> class handles the pagination process 
 * of the current book. A <code>Paginator</code> object should 
 * also split the book to lines and put a line breaker at the 
 * end of every line so that the page printing algorithm in 
 * Display class can work rapidly. Once a <code>Paginator</code>
 * object is created, a copy of the book is stored in an array of 
 * <code>String</code>s. Every element of the array is a page.<br>
 * <code>Paginator</code> class also provides some methods that 
 * eases navigation.
 * 
 * @author Devrim Sahin
 * @version 1.00 21.12.2009
 */

package bin;
 
import java.awt.FontMetrics;
import java.util.StringTokenizer;
import java.util.ArrayList;

public class Paginator {

	/**
	 * <code>pages</code><br>
	 * An array of Strings which stores the pages.
	 */
	private String[] pages;
	
	/**
	 * <code>currentPage</code><br>
	 * Index of current page. Used by navigation methods.
	 */
    private int currentPage;
    
    /**
	 * Constructor. Takes 4 parameters and stores the pages in 
	 * an array.
     * @param bufferText 
     * <code>String</code> that holds the book.
     * @param metrics 
     * <code>FontMetrics</code> object of the current font.
     * @param areaWidth 
     * Width of the printable area of the page.<br>
     * <code>areaWidth</code> = pageWidth - (2*marginH)
     * @param areaHeight 
     * Height of the printable area of the page.<br>
     * <code>areaHeight</code> = pageHeight - (2*marginV)
	 */
    public Paginator(String bufferText,FontMetrics metrics,int areaWidth,int areaHeight) {
    	// Create a temporary ArrayList of Strings
   		ArrayList<String> temp = new ArrayList<String>();
   		// Tokenize the string, also add delimiters to the tokens
        StringTokenizer sT = new StringTokenizer(bufferText," \n\t",true);
        // Read the ascent of the current font from metrics object
        final int theAscent=metrics.getAscent();
        // Define spaceRemaining as the remaining space in the line
        // (Initially equals to areaWidth)
        int spaceRemaining = areaWidth;
        // Define h as position of the current line in y-axis
        // Initially equals to theAscent (NOT ZERO, BECAUSE 
        // drawString() METHOD TAKES ITS Y ARGUMENT AS THE 
        // BOTTOM LINE OF THE STRING. )
        int h=theAscent;
        // nT will store the next token which will be used more than once
        String nT="";
        // The string which will store the new page
        String newPage = "";
        
        // Do it until there is nothing left in the book
        while (sT.hasMoreTokens()) {
        	// Read next token
        	nT = sT.nextToken();
        	// Calculate the token width
        	int tokenWidth = metrics.stringWidth(nT);
        	// If token is space character
        	if (nT.equals(" ")) {
        		// If at the end of the line
        		if (spaceRemaining<=tokenWidth) {
        			// Advance to the next line
        			newPage += "\n";
        			// Increase h by theAscent
        			h+= theAscent;
        			// If h is greater than areaHeight
    				if (h>areaHeight) {
    					// Add newPage to temp
    					temp.add(newPage);
    					// Clear newPage
    					newPage="";
    					// Reset h
    					h=theAscent;
    				}
    				// Reset spaceRemaining
    				spaceRemaining = areaWidth;
        		}
        		// If not at the end of the line
        		else {
        			// Add a space to newPage
        			newPage +=" ";
        			// Decrease spaceRemaining by tokenWidth
        			spaceRemaining -= tokenWidth;
        		}
        	// If token is new line character
        	} else if (nT.equals("\n")) {
        		// Advance to the next line
        		newPage += "\n";
        		// Increase h by theAscent
        		h+= theAscent;
        		// If h is greater than areaHeight
				if (h>areaHeight) {
					// Add newPage to temp
					temp.add(newPage);
					// Clear newPage
					newPage="";
					// Reset h
					h=theAscent;
				}
				// Reset spaceRemaining
				spaceRemaining = areaWidth;
			// If token is tab character
        	} else if (nT.equals("\t")) {
        		// Add a tab character to newPage
        		newPage += "\t";
        		// Create a tab by aligning the word to one of 10 equal distances
        		spaceRemaining = ( (spaceRemaining*10-1)/(areaWidth) )*(areaWidth)/10;
        	// If token is a word and longer than areaWidth 
        	// (It means that the word can never fit the page in one piece)
        	} else if (tokenWidth > areaWidth) {
        		// For each letter of nT
        		for (int i=0;i<nT.length();i++) {
        			// Get the letter
        			String oneLetter = nT.substring(i,i+1);
        			// Get the width of this letter
        			int letterWidth = metrics.stringWidth(oneLetter);
        			// If the letter can't fit the line with a "-" sign with it,
        			if (metrics.stringWidth(oneLetter+"-")>spaceRemaining) {
        				// Put a "-" sign to the end of the line and advance to the next line
        				newPage +="-\n";
        				// Increase h by theAscent
        				h+= theAscent;
        				// If h is greater than areaHeight
        				if (h>areaHeight) {
        					// Add newPage to temp
        					temp.add(newPage);
        					// Clear newPage
        					newPage="";
        					// Reset h
        					h=theAscent;
        				}
        				// Reset spaceRemaining and
        				// Decrease spaceRemaining by letterWidth
        				spaceRemaining = areaWidth - letterWidth;
        				// Add the letter to newPage
        				newPage += oneLetter;
        			// If the letter fits to the current line
        			} else {
        				// Simply add the letter
        				newPage += oneLetter;
        				// And decrease spaceRemaining by letterWidth
        				spaceRemaining -= letterWidth;
        			}
        		}
        	// If the token can fit an empty line, but doesn't fit THIS line
        	} else if (tokenWidth > spaceRemaining) {
        		// Then the word doesn't have to be splitted
        		// Just add it to the next line (Word Wrap)
        		
        		// Advance to the next line
        		newPage += "\n";
        		// Increase theAscent by h
        		h += theAscent;
				// If h is greater than areaHeight
        		if (h>areaHeight) {
        			// Add newPage to temp
					temp.add(newPage);
					// Clear newPage
					newPage="";
					// Reset h
					h=theAscent;
        		}
				// Reset spaceRemaining and
        		// Decrease spaceRemaining by tokenWidth
        		spaceRemaining = areaWidth - tokenWidth;
        		// Add the token to newPage
        		newPage += nT;
        	// If token fits the line
        	} else {
        		// Add the token to newPage
        		newPage += nT;
        		// Decrease spaceRemaining by tokenWidth
        		spaceRemaining -= tokenWidth;
        	}
        }
        // If not blank, add the last page to temp
        if (newPage.length()>0) temp.add(newPage);
        // Convert the ArrayList temp to array of Strings
	    pages = new String[temp.size()];
	    pages = temp.toArray(pages);
	    // Reset the current page
	    currentPage = 0;
    }
    
    /**
	 * <code>getPages</code><br>
	 * Returns a clone of the <code>pages</code> array.
	 * @return The pages array.
	 */
    public String[] getPages() {
    	// Return a clone of the pages array;
    	return pages.clone();
    }
    
    /**
	 * <code>getPage</code><br>
	 * Returns the page in the specified index number.
     * @param num Index of the page. 
     * @return The specified page.
	 */
    public String getPage(int num) {
    	// If there are no pages
    	if (pages.length == 0)
    		// Return an empty string
    		return "";
    	// If a negative value is given
    	if (num < 0)
    		// Consider it like zero
    		num = 0;
    	// Else if it is larger than the size of the array
    	else if (num>=pages.length)
    		// Consider it like the last element
    		num = pages.length-1;
    	// Set the current page to num
    	currentPage = num;
    	// Return the "num"th page
    	return pages[currentPage];
    }
    
    /**
	 * <code>getNextPage</code><br>
	 * Returns the next page. If current page is already 
	 * the last page, do not change the page.
	 * @return The next page.
	 */
    public String getNextPage() {
    	// If it is not the last page
    	if (currentPage < pages.length-1)
    		// Increase the page counter by 1
    		currentPage++;
    	// Return the page
    	return pages[currentPage];
    }
    
    /**
	 * <code>getPrevPage</code><br>
	 * Returns the previous page. If current page is already 
	 * the first page, do not change the page.
	 * @return The previous page.
	 */
    public String getPrevPage() {
    	// If it is not the first page
    	if (currentPage>0)
    		// Decrease the page counter by 1
    		currentPage--;
    	// Return the page
    	return pages[currentPage];
    }
    
    /**
	 * <code>getPageCount</code><br>
	 * Returns the length of <code>pages</code>.
	 * @return The length of <code>pages</code>.
	 */
    public int getPageCount() {
    	// Return the page count
    	return pages.length;
    }
    
    /**
	 * <code>getCurrentPageNum</code><br>
	 * Returns the index of the current page.
	 * @return The index of the current page.
	 */
    public int getCurrentPageNum () {
    	// Return the current page index
    	return currentPage;
    }
}