/**********************************************
	limitations so far:
	- comments are still put on the next line.
			difficult to change the parser to look for same line comments
	- minor bug:  removes successive "/*" inside of comments
	- a few instances of a lot of nested do's will not quite work

	enhanced behaviour (aka "Cool Features"):
	- turns empty bracket blocks into {} with no lines in between
	- tracks parenthese depth, which will be useful for proper formatted conditionals
	- string buffers instead of string, MUCH faster
	- supports function spacing, using the StringBuffer.insert() (another advantage of buffers)
	- supports single line conditionals, as well as a number of combinations of switch statements
	- comments get indented with the code around them, including multiline /* comments

	plans
	- long line clipping?  probably non-trivial....
	- change creation/constructors so i don't need to create a new instance each time?
***************************************************/

import java.awt.*;
import java.util.*;
import java.io.*;

public class Update_Module {
	
	private final static int INDENT_BUFFER = 100;
	
	Yytoken token, next_token; 		// the tokens
	StringBuffer temp_code; 			// the output code
	Yylex lexAnalyzer;					// the lexer
	String indy = ""; 					// the indent string, stored to save multiple fetchs
	int indent_count = 0;				// the indent depth
	int paren_count = 0; 				// the parenthese depth
	int bracket_count = 0; 				// the bracket depth for arrays
	int i;									// obligatory counter
	int function_index;					// the index of the last function header; a new line is
												// inserted once it knows it is a funtion
	boolean in_conditional = false;	// if the parenthese expression is a conditional (while, if, etc.)
	boolean do_tail = false;			// if it is the end of a do while loop
	
	int code_chunk_type[] = new int[INDENT_BUFFER]; 		// the type of the code block
	private final static int NOTHING = 0;						// 	used to store the kind of code
	private final static int SINGLE_LINE_CONDITIONAL = 1;
	private final static int SINGLE_LINE_DO_WHILE = 2;
	private final static int CASE_BLOCK = 3;
	private final static int DO_WHILE_LOOP = 4;
	private final static int CLASS = 5;
	private final static int SWITCH = 6;
	private final static int ARRAY_ASSIGN = 7;
	
	// function to do "--" but stopping at 0, to prevent array errors if something goes wrong
	private int minus_minus(int input) {
		return (input > 0?input-1:input);
	}

	// returns a new line with the appropriate indent
	public String newLine() {
		StringBuffer temp = new StringBuffer("\n");
		for (int i = 0; i < indent_count; i++)
			temp.append(indy);
		return temp.toString();
	}
	
	// calls the parser, and updates the text area with the new code.
	// 1.1		public String updateCode(Reader reader,    // can only be used in 1.1, but should be faster
	public String updateCode(InputStream reader, 		// the input stream
		int parse_choice, 										// the choice of parsing
		boolean operator_space, 								// spacing around operators
		boolean bracket_new_line,								// the "{" on a new line or not
		boolean extra_line,										// extra line between functions
		String indent) 											// the indent string
						throws java.io.IOException {

		lexAnalyzer = new Yylex(reader); 					// create the lexer
		temp_code = new StringBuffer("");
		
		if (parse_choice == UI.STANDARD_PARSE) {			// the standard formatting option
			for (i = 0; i < INDENT_BUFFER; i++)
				code_chunk_type[i] = NOTHING;
			indy = indent;
			next_token = lexAnalyzer.yylex();				// i need to see the next token a lot
			while (next_token != null) {
				token = next_token;
				next_token = lexAnalyzer.yylex();
				
				// pre-processing
				switch (token.getType()) {
					case TokenType.CLASS:
						// used for function spacing
						code_chunk_type[indent_count] = CLASS;
						break;

					// loops and conditionals
					case TokenType.FOR:
					case TokenType.WHILE:
					case TokenType.IF:
						in_conditional = true;
						break;

					// switch-case stuff
					case TokenType.SWITCH:
						code_chunk_type[indent_count] = SWITCH;
						break;
					case TokenType.COLON:
						if (paren_count == 0)
							break;
					case TokenType.CASE:
					case TokenType.DEFAULT:
						code_chunk_type[indent_count] = CASE_BLOCK;
						break;

					// spaces aroung operators
					case TokenType.OPERATOR:
						if (operator_space && !(token.getText().equals("++")) && !(token.getText().equals("--")))
							temp_code.append(" ");
						if (token.getText().equals("=") 
									&& next_token != null && next_token.getType() == TokenType.LCURLY)
							code_chunk_type[indent_count] = ARRAY_ASSIGN;
						break;

					// strings and comments
					case TokenType.STRING:
						temp_code.append("\"");
						break;
					case TokenType.LINECOMMENT:
						temp_code.append("//");
						break;
					case TokenType.COMMENT:
						temp_code.append("/*");
						break;

					// curly brackets and semicolons.  this where most of the work is done
					case TokenType.LCURLY:
						// catches array assignment with "{}"
						if (code_chunk_type[indent_count] == ARRAY_ASSIGN) {
							break;
						}
						// inserts an extra line if it finds a { curly, since that means it is a funtion
						if (extra_line && ((indent_count > 0 && code_chunk_type[indent_count-1] == CLASS)
							|| (indent_count == 0 && function_index > 0)))
							temp_code.insert(function_index, newLine());
						if (bracket_new_line)
							temp_code.append(newLine());
						else
							temp_code.append(" ");
						break;
					case TokenType.RCURLY:
						// catches array assignment with "{}"
						if (code_chunk_type[indent_count] == ARRAY_ASSIGN) {
							break;
						}
					case TokenType.SEMICOLON:
						// if a code chunk was closed, and the next one over is a single line conditional
						//		continue to un-indent until there are no more, used for nested conditionals
						if (indent_count > 0 && code_chunk_type[indent_count-1] == SINGLE_LINE_DO_WHILE) {
							code_chunk_type[indent_count] = NOTHING;
							indent_count = minus_minus(indent_count);
						} else {
							while (indent_count > 0 && code_chunk_type[indent_count-1] == SINGLE_LINE_CONDITIONAL) {
								code_chunk_type[indent_count] = NOTHING;
								indent_count = minus_minus(indent_count);
							}
						}
						if (code_chunk_type[indent_count] != DO_WHILE_LOOP
									&& code_chunk_type[indent_count] != SINGLE_LINE_DO_WHILE)  
							code_chunk_type[indent_count] = NOTHING;  
						break;
				}
				
				
				temp_code.append(token.getText());
				
				
				// this line needs to be done, since i need to know the proper indent for the "{"
				// inserting a newLine (which automatically indents)
				if (token.getType() != TokenType.LCURLY && next_token != null
										&& next_token.getType() == TokenType.RCURLY
										&& code_chunk_type[indent_count] != ARRAY_ASSIGN) {
					code_chunk_type[indent_count] = NOTHING;
					indent_count = minus_minus(indent_count);
					if (code_chunk_type[indent_count] != DO_WHILE_LOOP
								&& code_chunk_type[indent_count] != SINGLE_LINE_DO_WHILE)  
						code_chunk_type[indent_count] = NOTHING;
					// this will close a switch if there is no break at the end
					if (indent_count > 0 && code_chunk_type[indent_count-1] == SWITCH) {
						indent_count = minus_minus(indent_count);
						code_chunk_type[indent_count] = NOTHING;
					}
				}

				// post-processing

				switch (token.getType()) {
					// loops and condtionals
					case TokenType.WHILE:
					case TokenType.IF:
					case TokenType.FOR:
						temp_code.append(" ");
						break;
					case TokenType.DO:
						if (next_token != null && next_token.getType() != TokenType.LCURLY) {
							code_chunk_type[indent_count] = SINGLE_LINE_DO_WHILE;
							indent_count++;
							temp_code.append(newLine());
						} else
							code_chunk_type[indent_count] = DO_WHILE_LOOP;
						break;
					case TokenType.ELSE:
						// this will put "{" or "IF" on the same line after an else, othersise, it is a single line conditional
						if (next_token != null) {
							if (next_token.getType() == TokenType.LCURLY || next_token.getType() == TokenType.IF) {
								temp_code.append(" ");
							} else {
								code_chunk_type[indent_count] = SINGLE_LINE_CONDITIONAL;
								indent_count++;
								temp_code.append(newLine());
							}
						}
						break;

					// switch-case stuff
					case TokenType.COLON:
						// for (A?B:C) colons
						if (paren_count > 0) {
							if (operator_space)
								temp_code.append(" ");
							break;
						}
						if (next_token != null && next_token.getType() != TokenType.CASE
														&& next_token.getType() != TokenType.DEFAULT
														&& next_token.getType() != TokenType.LCURLY)
							indent_count++;
						if (next_token != null && next_token.getType() != TokenType.LCURLY)
							temp_code.append(newLine());
						else
							temp_code.append(" ");
						break;
					case TokenType.BREAK:
						// only un-indent if the break is only one level inside of a case block.
						// to prevent indenting if a break is in an IF statement
						if (indent_count > 0 && code_chunk_type[indent_count-1] == CASE_BLOCK) {
							code_chunk_type[indent_count] = NOTHING;
							indent_count = minus_minus(indent_count);
							code_chunk_type[indent_count] = NOTHING;
						}
						break;

					// spaces aroung operators
					case TokenType.OPERATOR:
						if (operator_space && !(token.getText().equals("++")) && !(token.getText().equals("--"))
													&& !(token.getText().equals("!")) && next_token != null 
													&& next_token.getType() != TokenType.OPERATOR)
							temp_code.append(" ");
						break;

					// strings and comments
					case TokenType.STRING:
						temp_code.append("\"");
						break;
					case TokenType.COMMENT:
						temp_code.append("*/" + newLine());
						break;
					case TokenType.LINECOMMENT:
						temp_code.append(newLine());
						break;

					// parentheses and brackets
					case TokenType.LPARAN:
						paren_count++;
						break;
					case TokenType.RPARAN:
						// if we are closing a parenthese block
						if (paren_count == 1) {
							// if this is a conditional parenthese, and there is no "{" and it is not a do-while loop
							if (in_conditional && next_token != null && next_token.getType() != TokenType.LCURLY 
												&& !(do_tail)) {
								code_chunk_type[indent_count] = SINGLE_LINE_CONDITIONAL;
								indent_count++;
								temp_code.append(newLine());
							}
							// the end of the last line of a do-while loop:    "   } while (a>0);"
							if (do_tail) {
								do_tail = false;
								code_chunk_type[indent_count] = NOTHING;
							}
							paren_count = 0;
							in_conditional = false;
						} else if (paren_count > 0) 
							paren_count--;
						break;
					//no spacing after an "["    possibly a future setting?  spacing inside []'s and ()'s"
					case TokenType.LBRACKET:
						break;
					
					// curly brackets and semicolons.  this where most of the work is done
					case TokenType.LCURLY:
						// catches array assignment with "{}"
						if (code_chunk_type[indent_count] == ARRAY_ASSIGN) {
							bracket_count++;
							break;
						}
						// this closes empty brackets
						if (next_token != null && next_token.getType() == TokenType.RCURLY)
							break;
						indent_count++;
						temp_code.append(newLine());
						// if this is immediately in a class, remember the index, in case it is a function
						if (extra_line && indent_count > 0 && code_chunk_type[indent_count-1] == CLASS)
							function_index = temp_code.length();
						break;
					case TokenType.RCURLY:
						// catches array assignment with "{}"
						if (code_chunk_type[indent_count] == ARRAY_ASSIGN) {
							if (bracket_count <= 1) {
								code_chunk_type[indent_count] = NOTHING;
								bracket_count = 0;
							} else
								bracket_count--;
							break;
						}
						if (next_token != null) {
							// this will put else's on the same line as the "}" before it,
							//		if the brackets on new line is not set.  this may get a new variable.......
							if (next_token.getType() == TokenType.ELSE) {
								if (bracket_new_line)
									temp_code.append(newLine());
								else
									temp_code.append(" ");
								break;
							}
							// this will move the indent back over if another "CASE" token is seen while in a case block
							/*		example:
									case 1:
										i++;				right here, it sees the case, so it moves over
									case 2:
										yadda.....   */
							if ((next_token.getType() == TokenType.CASE || next_token.getType() == TokenType.DEFAULT)
											&& indent_count > 0 && code_chunk_type[indent_count-1] == CASE_BLOCK) {
								code_chunk_type[indent_count] = NOTHING;
								indent_count = minus_minus(indent_count);
								code_chunk_type[indent_count] = NOTHING;
								temp_code.append(newLine());
								break;
							}
							// this will catch the "while" token in a do-while loop, and set the "do-tail"
							if (next_token.getType() == TokenType.WHILE
										&& (code_chunk_type[indent_count] == DO_WHILE_LOOP || code_chunk_type[indent_count] == SINGLE_LINE_DO_WHILE)) {
								do_tail = true;
								code_chunk_type[indent_count] = NOTHING;
								temp_code.append(" ");
								break;
							}
						}
						// this will remember the next function, when you close up a function in a class
						if (extra_line && ((indent_count > 0 && code_chunk_type[indent_count-1] == CLASS)
							|| indent_count == 0))
							function_index = temp_code.length();
						temp_code.append(newLine());
						break;
					case TokenType.SEMICOLON:
						// for FOR loop semi colons
						if (in_conditional) {
							temp_code.append(" ");
							break;
						}
						// it will un-indent if it sees a ";" and the next is a "CASE" and it is in a case block
						if (next_token != null && (next_token.getType() == TokenType.CASE || next_token.getType() == TokenType.DEFAULT)
												&& indent_count > 0 && code_chunk_type[indent_count-1] == CASE_BLOCK) {
							code_chunk_type[indent_count] = NOTHING;
							indent_count = minus_minus(indent_count);
							code_chunk_type[indent_count] = NOTHING;
							temp_code.append(newLine());
							break;
						}                 
						// this will catch the "while" token in a do-while loop, and set the "do-tail"
						if (next_token != null && next_token.getType() == TokenType.WHILE
									&& (code_chunk_type[indent_count] == DO_WHILE_LOOP || code_chunk_type[indent_count] == SINGLE_LINE_DO_WHILE)) {
							do_tail = true;
							code_chunk_type[indent_count] = NOTHING;
							temp_code.append(newLine());
							break;
						}
						// this will remember the next function, when you close up a function in a class
						if (extra_line && ((indent_count > 0 && code_chunk_type[indent_count-1] == CLASS)
							|| indent_count == 0))
							function_index = temp_code.length();
						temp_code.append(newLine());
						break;

					// otherwise, check the next token for spacing suggestions
					default:
						if (next_token != null) {
							switch (next_token.getType()) {
								case TokenType.LPARAN:
								case TokenType.SEMICOLON:
								case TokenType.RPARAN:
								case TokenType.LBRACKET:
								case TokenType.RBRACKET:
								case TokenType.RCURLY:
								case TokenType.LCURLY:
								case TokenType.OPERATOR:
								case TokenType.COLON:
								case TokenType.COMMA:
									break;
								default:
									temp_code.append(" ");
							}
						}
				}
			} 
		// this is the other parsing choice, used for debugging the parser; prints a list of tokens
		} else if (parse_choice == UI.TOKEN_LIST) {
			token = lexAnalyzer.yylex();
			while (token != null) {
				temp_code.append("Token \""  + token.getText() + "\" has type " + token.getType() + "\n");
				token = lexAnalyzer.yylex();
			}
		}
		return temp_code.toString();
	}


}


