/* Generated By:JavaCC: Do not edit this line. Token.java Version 3.0 */
/**
 * Do not regenerate by JavaCC; customized by Peter Kehl.
 * Describes the input token stream.
 */

package org.paneris.spindent;

import java.util.*;

public final class Engine implements ParserConstants {
	private Engine() {} //Not to be instantiated.

	static boolean isSpaceOrNewline(char z) { return " \t\n\r\f".indexOf(z)>=0; }
	static boolean isSpace(char z) { return " \t\f".indexOf(z)>=0; }
	
	private static final StringBuffer sharedBuf= new StringBuffer();
	/**Replaces multiple spaces/lines with one space.*/
	private static String TrimInsideSpaces(String orig) {
		boolean prevSpace=false;
		sharedBuf.setLength(0);
		for( int i=0, len=orig.length(); i<len; i++) {
			char z= orig.charAt(i);
			if( !isSpaceOrNewline(z) ) {
				sharedBuf.append(z);
				prevSpace=false;
			}
			else
			if( !prevSpace) {
				sharedBuf.append(' ');
				prevSpace=true;
			}
		}
		return sharedBuf.toString();
	}

	//public static final String NL= System.getProperty("line.separator","\n");
	/**
   * Returns the image with shortened whitespaces, tags and VBScript keywords in lowercase/uppercase.
	 * Note: these transformations are probably applied when tracing TokenManager.
   */
	private static final String transform(Token tok) {
		String image= tok.image;
		switch(tok.kind) {
			case WS:
				return tok.specialToken!=null ? " " : ""; //removing whitespace at beginning of first line

			case S_WS:
			case M_WS:
				return shortenCommentSpaces ? " " : image;

			case CR:
			case M_CR:
			case S_X_END: //Remove whitespaces from <CR>, <S_X_END>, <M_CR>
				sharedBuf.setLength(0);
				for( int i=0; i<image.length(); i++)
					if( !isSpace(image.charAt(i)))
						sharedBuf.append(image.charAt(i));
				return sharedBuf.toString();

			case H_BEGIN:
			case H_CLOSE:
			case H_HT_BEG:
			case H_J_BEG:
			case J_H_END:
			case HT_CHAR:
				if(tagsToUppercase)
					return image.toUpperCase();
				else if(tagsToLowercase)
					return image.toLowerCase();

			case C_BEGIN_VBSCRIPT:
			case C_CLOSE_VBSCRIPT:
			case J_BEGIN_VBSCRIPT:
			case J_CLOSE_VBSCRIPT:
				if(VBScript)
					return TrimInsideSpaces(image);
			//TODO: Add any non-indenting VBSCRIPT keywords if transforming to upper/lowercase - if in VBScript mode.
		}
		return image;
	}

	/**@param tok must be a SPECIAL token.*/
	static Token firstSpecialToken(Token tok) {
		while (tok.specialToken != null)
			tok = tok.specialToken;
		return tok;
	}

	private static final StringBuffer toStr= new StringBuffer(); //for result of toStringAll()

  final static String toStringAll(Token tok) throws Fault {
		Token firstSpecial= firstSpecialToken(tok);
		newLineStarted= false; //First line is never indented.
		for(Token t=firstSpecial; t!=null; t=t.next)
			indent(t);

		firstSpecial= firstSpecialToken(firstSpecial); //indentation token was inserted
		synchronized(toStr) {
			toStr.setLength(0);
			for(Token t=firstSpecial; t!=null; t=t.next)
				toStr.append( transform(t));
			String result= toStr.toString();
			toStr.setLength(0);
			return result;
		}
	}

 /**@param "where" must be a SPECIAL_TOKEN*/
	static void insertSpecialToken(Token where, Token newT) {
		Token tPrior= where.specialToken;
		Token tNext= where.next;
		if(tPrior!=null)
			tPrior.next= newT;
		where.specialToken= newT;
		newT.specialToken= tPrior;
		newT.next= where;
	}

 /**@param tok must be a SPECIAL_TOKEN*/
	static void deleteSpecialToken(Token tok) {
		Token tPrior= tok.specialToken;
		Token tNext= tok.next;
		if(tPrior!=null)
			tPrior.next= tNext;
		if(tNext!=null)
			tNext.specialToken= tPrior;
	}
	
	/**For synchronization on this class only.*/
	public static final Object synchro= new Object();

	/**Parser options:*/
	static boolean shortenCommentSpaces;
	static boolean VBScript;
	static boolean XMLindent; //Not good for common unpaired HTML as <img...>
	static boolean tagsToUppercase;
	static boolean tagsToLowercase;
	static boolean parenthIndent;
	static boolean unindentCase; //Whether to unindent "case" keyword on newlines.
	static boolean codeIndent; //Whether to indent code inside <% .. %>
	static boolean unindentOpenCode;
	static final int MAX_DEPTH=100;

	//Parser state variables:
	private static boolean newLineStarted;
	private static final int states[]= new int[MAX_DEPTH];
	private static int stateIndex;

	static { reInit(); }

	static void pushPrevState(int state) throws TransparentFault {
		if( stateIndex>=99) throw new TransparentFault(
			new Fault("A lot of nested HTML/CODE/QUOTATION levels (over " +MAX_DEPTH+ ")!") );
		else states[++stateIndex]= state;
	}

	static int popPrevState() throws TransparentFault {
		if(stateIndex<0) throw new TransparentFault(
			new InternalFault("negative state level.") );
		else return states[stateIndex--];
	}

	static void reInit() {
		shortenCommentSpaces=true;
		VBScript=false;
		XMLindent=false;
		tagsToUppercase=false;
		tagsToLowercase=false;
		parenthIndent=true;
		unindentCase=true;
		codeIndent=false;
		unindentOpenCode=true;

		newLineStarted=false;
		stateIndex= -1;

		actualLineIndent=new Indent();
		nextLineIndent=new Indent();
		wasOpener=false;
	}
	
	static Indent actualLineIndent=new Indent(), nextLineIndent=new Indent();
	
  /**
	 * Not an exact test; a fast not fully-restrictive one is sufficient.
	 * Suppose it is used by isIdentifier() to detect VBScript keywords which don't include digits.
	 */
	private static boolean isSeparator(char c) { return !( 'a'<=c && c<='z' || 'A'<=c && c<='Z' || c=='_' ); }

	/**
	 * Not an exact test; a fast not fully-restrictive one is sufficient.
	 * Suppose that tokens always have non-empty images.
	 */
	static final boolean isIdentifier(Token tok) {
		return !( tok.specialToken!=null && !isSeparator(tok.specialToken.image.charAt(tok.specialToken.image.length()-1)) ||
				tok.next!=null && !isSeparator(tok.next.image.charAt(0))
			);
	}

	/** Whether there is a nonwhite character on this line, starting at "tok" to right. Suppose CODE state.*/
	static boolean lineHasNonwhite(Token tok) throws Fault {
		for(; tok!=null; tok=tok.next) {
			switch( tok.kind) {
				case CR:
				case M_CR:
				case S_X_END: return false;
				case WS: break;
				default: return true;
			}
		}
		throw new InternalFault("nonwhite character not found on line");
	}

	private static boolean wasOpener; //True if actual tag indents text and tag's opener didn't finish yet.
	
	static final void indent(Token tok) throws Fault {
		Token next= tok.next;
		switch(tok.kind) {
			case CR:
			case M_CR:
			case S_X_END:
				newLineStarted= true;
				return;
		}

		if(newLineStarted) {
			actualLineIndent= new Indent(nextLineIndent);
			insertSpecialToken(tok, actualLineIndent);
		}

		boolean matched=false; //true when kind was already matched in following switch(kind){...}
		switch(tok.kind) {
			case H_HT_BEG:
				if(!matched && !XMLindent)
					break;
				wasOpener=true; //?
				matched=true;

			case H_C_BEG: //These can appear on newline.
			case HT_C_BEG:
			case J_C_BEG:
			case P_C_BEG:
				if(!matched && newLineStarted && unindentOpenCode && next!=null && lineHasNonwhite(next)
				&& actualLineIndent.getLevel()>0) {
					actualLineIndent.indentSpaces(-2); //unindent "<%" or "<?"
					if( next.kind==WS) {
						if(actualLineIndent.indentChars()>3)
							actualLineIndent.indentSpaces(-1);
						else
							deleteSpecialToken(next);
					}
				} //Don't set "matched" here; subprocessing not finished.
				
			case Q_C_BEG: //These two cannot appear on newline.
			case A_C_BEG:
				if(!matched && !codeIndent && !(unindentOpenCode && /*actual*/nextLineIndent.getLevel()==0/*ident when true*/) )
					break;
				matched=true;
				
			case C_BEGIN_PAREN:
			case J_BEGIN_PAREN:
				if(!matched && !parenthIndent)
					break;
				matched=true;

			case C_BEGIN_VBSCRIPT:
			case J_BEGIN_VBSCRIPT:
				if(!matched && (!VBScript || !isIdentifier(tok)/*indent VBScript identifiers*/) )
					break;
				matched=true;
					
			case H_BEGIN:
				if(!matched)
					wasOpener=true;
				matched=true;
				
			case C_BEGIN_BRACE:
			case H_HC_BEG:
			case H_J_BEG: //Suppose Javascript has paired brackets.
			case J_M_BEG:
			case J_BEGIN_BRACE:
			case C_M_BEG:
				nextLineIndent.open();
				break;

			/**/
			case C_CASE: //nextLineIndent is not affected.
				if(!matched && newLineStarted && unindentCase && !VBScript && isIdentifier(tok)/*not a part of larger word*/ )
					actualLineIndent.close();
				break;

			/**/
			case H_CLOSE_TAG:
				if(!matched && !XMLindent)
					break;
				matched=true;

			case HT_H_SINGLE_END:
				if(!matched && (!XMLindent && !wasOpener) ) //Pass if last tag was opener.
					break;
				wasOpener=false;
				matched=true;
				
			case C_X_END:
			case S_XX_END:
				if(!matched && !codeIndent && !(unindentOpenCode && nextLineIndent.getLevel()==1/*unindent back when true*/))
					break;
				matched=true;

			case C_CLOSE_PAREN:
			case J_CLOSE_PAREN:
				if(!matched && !parenthIndent)
					break;
				matched=true;
					
			case C_CLOSE_VBSCRIPT:
			case J_CLOSE_VBSCRIPT:
				if(!matched && (!VBScript || !isIdentifier(tok)/*unindent VBScript identifiers*/) )
					break;
				matched=true;

			case C_CLOSE_BRACE:
			case HC_H_END:
			case H_CLOSE:
			case J_H_END:
			case J_CLOSE_BRACE:
			case M_X_END:
				nextLineIndent.close();
				if(newLineStarted)
					actualLineIndent.close();
				break;

			case HT_H_END:
				wasOpener=false;
		}
		newLineStarted= false;		
	}
}