/**
    Christopher Scaglione
    SW409
    Midterm
*/

package rtf;

import java.io.*;
import java.util.StringTokenizer;
import futils.ReaderUtil;
import futils.WriterUtil;

public class RtfUtil implements JavaText {

    public static final String HEADER = "{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033" +
                                        "{\\fonttbl{\\f0\\fswiss\\fcharset0 Arial;}}" +
                                        "\\viewkind4\\uc1\\pard\\f0\\fs20";
    
    private boolean javaDocComment = false;
    private boolean cStyleComment = false;
    private boolean cppStyleComment = false;
    private int quoteCount = 0;
    private BufferedWriter bw = null;                                        

    /**
        Converts a Java source file to a RTF file.
        @author Christopher Scaglione
        @param br is the BufferedReader to read from.
        @param _bw is the BufferedWriter to write to.
    */
    public void javaToRtf(BufferedReader br, BufferedWriter _bw) {
        String line = null;
        
        try {
            bw = _bw;           
            
            write(HEADER);
            
            while ((line = ReaderUtil.readLine(br)) != null) {              
                processLine(line);              
            }
            
            write("}");
            bw.flush();
            
            ReaderUtil.close(br);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    /**
        Converts a Java source file to a RTF file.
        @author Christopher Scaglione
        @param javaFile is the Java source file to convert.
        @param rtfFile is the RTF file to convert to.
    */
    public void javaToRtf(File javaFile, File rtfFile) {
        javaToRtf(ReaderUtil.getBufferedReader(javaFile),
                  WriterUtil.getBufferedWriter(rtfFile));
    }
    
    /**
        Converts a line of Java source to RTF format.
        @author Christopher Scaglione
        @param line is the line of Java source to convert.
    */
    private void processLine(String line) {     
        String token = null;
        
        // The delimiters have special codes in RTF, so we want
        // them to be returned by the StringTokenizer.
        StringTokenizer st = new StringTokenizer(line, " \t\n\r\f", true);
        
        try
        {
            while (st.hasMoreTokens()) {
                token = st.nextToken();             
                if (token.equals(" ")) {
                    write(" ");
                }
                else {
                    if (token.equals("\t")) {
                        write(getTab());
                    } else {
                        if (token.equals("\n") ||
                            token.equals("\r") ||
                            token.equals("\f")) {
                                write(getPar());
                        } else {                                            
                            processToken(token);                
                        }
                    }
                }
            }                               
            
            write(getPar());
            
            // C++-style comments can only span a single line,
            // so reset the flag after the line is parsed.
            cppStyleComment = false;        
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
        Converts a token of Java source to RTF format.
        @author Christopher Scaglione
        @param _token is the token of Java source to convert.
    */
    private void processToken(String _token) {
        boolean reservedWord = false;       
        String token = null;
        String previousToken = null;
        String previousPreviousToken = null;
        
        // The token needs to be parsed again to determine if it is a reserved
        // word since reserved words may be right next to other characters.
        // For example, "for(i = 0", "boolean b = true;".
        StringTokenizer st = new StringTokenizer(_token, "\";><=&|!(){}[],\\.:", true);             
                
        try {
            while (st.hasMoreTokens()) {
                reservedWord = false;
                
                token = st.nextToken();
                
                checkForStartComment(token);                
                
                // JavaDoc comments will be italic.         
                if (javaDocComment == true) {                           
                    write(getItalic(token));
                    
                    // Check if we have reached the end of the
                    // JavaDoc comment.  If we have, reset the
                    // flag.
                    if (checkForEndComment(token) == true) {                            
                        javaDocComment = false;                         
                    }   
                } else {
                    // C-style comments will be plain.
                    if (cStyleComment == true) {            
                        write(getPlain(token));
                        
                        // Check if we have reached the end of the
                        // C-style comment.  If we have, reset the
                        // flag.
                        if (checkForEndComment(token) == true) {                            
                            cStyleComment = false;                      
                        }                               
                    } else {                        
                        // C++-style comments will be plain.
                        if (cppStyleComment == true) {
                            write(getPlain(token));
                        } else {                            
                            if (checkForStringQuote(previousPreviousToken, previousToken, token) == true) {                         
                                quoteCount++;                   
                            }
                           
                            // If quoteCount is 1, then we are within a string                         
                            // and reserved words do not need to be made bold.
                            if (quoteCount != 1) {                                        
                                // Check if this token is a reserved word.
                                if (isJavaReservedWord(token) == true) {
                                    reservedWord = true;
                                    write(getBold(token));
                                }
                            }                                                       
                                                            
                            if (reservedWord == false) {
                                write(getPlain(token));
                            }
                            
                            // If quoteCount is 2, we have found the
                            // start and end parentheses for a string,
                            // so reset quoteCount.                     
                            if (quoteCount == 2) {
                               quoteCount = 0;
                            }   
                        }
                    }
                }
                
                // Keep track of the 2 previous tokens.
                // This is needed to determine a string quote.
                previousPreviousToken = previousToken;
                previousToken = token;                          
            }
        } catch (Exception e) {
            e.printStackTrace();
        }       
    }
    
    /**
        Determines if the token contains the start of comment
        designator for a JavaDoc comment, C-style comment, or
        C++-style comment and sets the appropriate flag.
        @author Christopher Scaglione
        @param The token to search.     
    */
    private void checkForStartComment(String token) {       
        // If the quote count is 1, then we are currently
        // inside a string and we do not care if we find
        // the start of comment designator since it is part
        // of the string.
        if (quoteCount != 1) {
            if (token.indexOf("/**") != -1) {
                javaDocComment = true;
            } else {
                if (token.indexOf("/*") != -1) {
                    cStyleComment = true;
                } else {
                    if (token.indexOf("//") != -1) {
                        cppStyleComment = true;
                    }
                }
            }
        }
    }

    /**
        Determines if the token contains the end of comment
        designator.
        @author Christopher Scaglione
        @param The token to search.
        @return True if the end of comment designator was found,
                false otherwise.
    */
    private boolean checkForEndComment(String token) {
        boolean endComment = false;
        
        // If the quote count is 1, then we are currently
        // inside a string and we do not care if we find
        // the end of comment designator since it is part
        // of the string.
        if (quoteCount != 1) {
            if (token.indexOf("*/") != -1) {
                endComment = true;
            }
        }           
        
        return endComment;
    }
    
    /**
        Determines if the token contains a string quote. To make
        this determination requires the 2 previous tokens.
        @author Christopher Scaglione
        @param previousPreviousToken is the token immediately
               before previousToken.
        @param previousToken is the token immediately before token.
        @param token is the token to search.        
    */
    private boolean checkForStringQuote(String previousPreviousToken, String previousToken, String token) {
        boolean stringQuote = false;
        
        // If one of the comment flags is true, then
        // the quote is part of the comment.
        if (javaDocComment == false && 
            cStyleComment == false && 
            cppStyleComment == false) {
                                
            if (token.indexOf("\"") != -1) {
                stringQuote = true;
                
                if (previousToken != null) {
                    // Check if the quote is preceded by a backslash.
                    // If it is, then this could be a quote within
                    // a string (\").
                    if (previousToken.equals("\\") == true) {
                        stringQuote = false;
                        
                        if (previousPreviousToken != null) {
                            // Check if the backslash is preceded by another
                            // backslash.  If it is, then this is a quote since
                            // the 2 previous tokens make up a backslash in
                            // a string.
                            if (previousPreviousToken.equals("\\") == true) {
                                stringQuote = true;
                            }
                        }
                    }               
                }
            }               
        }
        
        return stringQuote;
    }
    
    /**
        Checks if the given token is a Java reserved word.
        @author Christopher Scaglione
        @param The token to check.
        @return True if the token is a reserved word,
                false otherwise.
    */
    private boolean isJavaReservedWord(String token) {   
        boolean reservedWord = false;
           
        for (int i = 0; i < javaReservedWords.length; i++) {
            if (token.compareTo(javaReservedWords[i]) == 0) {
                reservedWord = true;                
                break;
            }
        }
        
        return reservedWord;
    }
    
    /**
        Writes a string to the BufferedWriter.
        @author Christopher Scaglione
        @param s is the string to write to the BufferedWriter.
    */
    private void write(String s) {
        try {
            bw.write(s, 0, s.length());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }   
    
    /**
        Returns a string in italic RTF format.
        @author Christopher Scaglione
        @param s is the string to format.
        @return A string in italic RTF format.
    */      
    public static String getItalic(String s) {
        return "{\\i " + formatCurlyBracesAndBackslashes(s) + "}";
    }
    
    /**
        Returns a string in bold RTF format.
        @author Christopher Scaglione
        @param s is the string to format.
        @return A string in bold RTF format.
    */  
    public static String getBold(String s) {
        return "{\\b " + formatCurlyBracesAndBackslashes(s) + "}";
    }
    
    /**
        Returns a string in plain RTF format.
        @author Christopher Scaglione
        @param s is the string to format.
        @return A string in plain RTF format.
    */  
    public static String getPlain(String s) {
        return "{" + formatCurlyBracesAndBackslashes(s) + "}"; 
    }
    
    /**
        Returns a paragraph break in RTF format.
        @author Christopher Scaglione
        @return A paragraph break in RTF format.
    */  
    public static String getPar() {
        return "{\\par}";
    }
    
    /**
        Returns a tab in RTF format.
        @author Christopher Scaglione
        @return A tab in RTF format.
    */  
    public static String getTab() {
        return "{\\tab}";
    }
    
    /**
        Returns a string with curly braces and backslashes in RTF format.
        Any displayed curly braces or backslashes in RTF must be preceded by
        a "\" since "{", "}", and "\" are used for control characters.
        @author Christopher Scaglione
        @param s is the string to format.
        @return A string with curly braces in RTF format.
    */
    public static String formatCurlyBracesAndBackslashes(String s) {
        String formattedString = "";
        char ch;
        
        for (int i = 0; i < s.length(); i++) {
            ch = s.charAt(i);
            if (ch == '{' || ch == '}' || ch == '\\') {
                formattedString = formattedString.concat("\\");         
            }
            
            formattedString = formattedString.concat(String.valueOf(ch));
        }
        
        return formattedString;
    }                   
}