package commons.script;

import java.util.ArrayList;
import java.util.List;
import basics.utl.Empty;
import basics.utl.SUtl;
import basics.utl.SysUtl;

/**
 * Tokenizes the input, makes a list of string where each recognized token is preceded 
 * by a token type.
 * @author ks
 */
public class InputTokenizer
{
private static final String E0                      = "Invalid input, token incomplete (%).";
private static final String E1a                     = "Invalid clause, missing left parenthesis.";
private static final String E1b                     = "Invalid clause, missing right parenthesis.";
//
List<String>                tokens                  = null;
Token                       token                   = new Token();
int                         mark                    = 0;
int                         index                   = 0;
int                         size                    = 0;
String                      input;
String                      cchar;
int                         accepted                = 0;
boolean                     iscomment               = false;
//
static final String         COMMENT1                = "#";
//static final String         COMMENT2                = "//";
static final String         EMPTY                   = "";
static final String         TOKEN_MAKRO_DEF         = "$";
static final String         TOKEN_CLAUSE_IF         = "if";
static final String         TOKEN_CLAUSE_WHILE      = "while";
static final String         TOKEN_CLAUSE_END        = "end";
static final String         TOKEN_CLAUSE_ELSE       = "else";
static final String         TOKEN_CALL              = "call";
static final String         TOKEN_CMD               = "cmd";
static final String         TOKEN_STOP              = "stop";
static final String         TOKEN_EXIT              = "exit";
static final String         TOKEN_SUSPEND           = "suspend";
static final String         TOKEN_RESUME            = "resume";
static final String         TOKEN_GOTO              = "goto";
static final String         TOKEN_LABEL             = ":";
public static final String  SPACE                   = " ";
public static final String  COLON                   = ":";
public static final String  SEMICOLON               = ";";
static final String         PARENTHESIS_LEFT        = "(";
static final String         PARENTHESIS_RIGHT       = ")";
static final String         SEPARATOR_LIST_ELEMENTS = ",";
static final String         QUOTATION_MARK          = "\"";
static final String         TAB                     = "\t";
static final String         NL                      = "\n";
static final String         CR                      = "\r";
static final String         TOKTYPE_QUOTE           = "QUOTE";
static final String         TOKTYPE_MAKRO           = "MAKRO";
static final String         TOKTYPE_CLAUSE          = "CLAUSE";
static final String         TOKTYPE_INTERR          = "ITR";
static final String         TOKTYPE_IF              = "IF";
static final String         TOKTYPE_WHILE           = "WHILE";
static final String         TOKTYPE_END             = "END";
static final String         TOKTYPE_ELSE            = "ELSE";
static final String         TOKTYPE_CALL            = "CALL";
static final String         TOKTYPE_CMD             = "CMD";
static final String         TOKTYPE_ARGS            = "ARGS";
static final String []      terminators             = { SPACE, TAB, COLON, SEMICOLON,
   SEPARATOR_LIST_ELEMENTS, PARENTHESIS_LEFT, PARENTHESIS_RIGHT };
static final String []      interrupts              = { TOKEN_STOP, TOKEN_EXIT, TOKEN_SUSPEND,
   TOKEN_RESUME, TOKEN_GOTO                        };

private InputTokenizer(String input)
{
   tokens = new ArrayList<String>();
   if(input == null)
   {
      size = 0;
      this.input = EMPTY;
   }
   else
   {
      size = input.length();
      this.input = input;
   }
}

static List<String> parse(String input)
{
   InputTokenizer tok = new InputTokenizer(input);
   tok.parse();
   return tok.tokens;
}

void parse()
{
   if(forward())
   {
      do
      {
         if(!(tokens.size() % 2 == 0))
            SysUtl.trace("% tokens: %", tokens.size(), SUtl.join(tokens));
         //remove space under current position:
         trim();
         if(comment())
            break;
         args();
         if(comment())
            break;
         if(accepted == 0 & makrodef())
            continue;
         if(!ifclause())
            if(whileclause())
               continue;
            else
               if(elseclause())
                  continue;
         trim();
         if(iscomment)
            break;
         if(endclause())
            continue;
         else
            if(call())
               continue;
         if(interrupt())
            continue;
         if(!command())
            if(!forward())
               break;
      } while(!iscomment);
   }
}

void nextToken(String type)
{
   tokens.add(type);
   tokens.add(token.getToken());
   token.clear();
}

void insertTokenBeforeLastToken(String type)
{
   int x = tokens.size();
   if(x == 0)
      nextToken(type);
   else
   {
      tokens.add(tokens.get(x - 2));
      tokens.add(tokens.get(x - 1));
      tokens.set(x - 2, type);
      tokens.set(x - 1, token.getToken());
      token.clear();
   }
}

void consumeChar()
{
   accepted++;
   token.append(cchar);
}

void consumeString(String t)
{
   accepted += t.length();
   token.append(t);
}

boolean forward()
{
   if(index >= size)
   {
      cchar = "";
      return false;
   }
   cchar = input.substring(index, ++index);
   return true;
}

boolean isReading(String t)
{
   return isReading(t, false);
}

boolean isReading(String expectedToken, boolean moveIfTrue)
{
   boolean match = false;
   int len = expectedToken.length();
   if(len == 0)
      return false;
   if(len == 1)
      match = cchar.equalsIgnoreCase(expectedToken);
   else
      if(index + len - 1 > size)
         return false;
      else
      {
         String test = (cchar + input.substring(index, index + len - 1));
         match = test.equalsIgnoreCase(expectedToken) && endOfToken(index + len - 1);
      }
   if(match && moveIfTrue)
      while(len-- > 0)
         forward();
   return match;
}

boolean endOfToken(int x)
{
   if(x >= input.length())
      return true;
   String c = input.substring(x, x + 1);
   for(String t : terminators)
      if(t.equals(c))
         return true;
   return false;
}

void resetToMark()
{
   index = mark;
}

void setMark()
{
   mark = index;
}

void trim()
{
   while(whiteSpace())
   {
      if(!forward())
         break;
      else
         setMark();
   }
}

boolean whiteSpace()
{
   if(cchar.equals(SPACE))
      return true;
   if(cchar.equals(TAB))
      return true;
   if(cchar.equals(NL))
      return true;
   if(cchar.equals(CR))
      return true;
   return false;
}

boolean comment()
{
   boolean t = (accepted == 0 && isReading(COMMENT1)); // || isReading(COMMENT2);
   if(t)
      iscomment = true;
   return t;
}

boolean terminated()
{
   for(String t : terminators)
      if(isReading(t, true))
         return true;
   return false;
}

void quotation()
{
   if(isReading(QUOTATION_MARK))
   {
      boolean closed = false;
      while(forward())
      {
         if(isReading(QUOTATION_MARK))
         {
            closed = true;
            break;
         }
         consumeChar();
         setMark();
      }
      if(!closed)
         throw new ParserFailure(E0);
      forward(); // skip closing quotation.
      nextToken(TOKTYPE_QUOTE);
   }
}

void args()
{
   int parenthesisCounter = 0;
   boolean quoting = false;
   if(isReading(PARENTHESIS_LEFT))
   {
      consumeChar();
      setMark();
      parenthesisCounter++;
      while(forward())
      {
         trim();
         if(!quoting && isReading(PARENTHESIS_LEFT))
         {
            parenthesisCounter++;
         }
         else
            if(!quoting && isReading(PARENTHESIS_RIGHT))
            {
               parenthesisCounter--;
            }
         if(isReading(QUOTATION_MARK))
            quoting = !quoting;
         else
            consumeChar();
         setMark();
         if(!quoting && parenthesisCounter == 0)
         {
            forward();
            break;
         }
      }
      if(parenthesisCounter != 0)
         throw new ParserFailure(SUtl.sprint(E0, TOKTYPE_ARGS));
      setMark();
      insertTokenBeforeLastToken(TOKTYPE_ARGS);
   }
}

boolean makrodef()
{
   if(isReading(TOKEN_MAKRO_DEF, true))
   {
      do
      {
         trim();
         //         if(comment())
         //            break;
         consumeChar();
         setMark();
      } while(forward());
      nextToken(TOKTYPE_MAKRO);
      return true;
   }
   else
      return false;
}

boolean ifclause()
{
   if(isReading(TOKEN_CLAUSE_IF, true))
   {
      //we are in the <f> in <if>
      if(!isReading(PARENTHESIS_LEFT, false))
         forward();
      boolean closed = false;
      trim();
      if(!isReading(PARENTHESIS_LEFT, true))
         throw new ParserFailure(E1a);
      do
      {
         trim();
         //         if(comment())
         //            break;
         if(isReading(PARENTHESIS_RIGHT, true))
         {
            closed = true;
            break;
         }
         consumeChar();
         setMark();
      } while(forward());
      if(!closed)
         throw new ParserFailure(E1b);
      nextToken(TOKTYPE_IF);
      return true;
   }
   else
      return false;
}

boolean whileclause()
{
   if(isReading(TOKEN_CLAUSE_WHILE, true))
   {
      //we are in the <e> in <while>
      if(!isReading(PARENTHESIS_LEFT, false))
         forward();
      boolean closed = false;
      trim();
      if(!isReading(PARENTHESIS_LEFT, true))
         throw new ParserFailure(E1a);
      do
      {
         trim();
         //         if(comment())
         //            break;
         if(isReading(PARENTHESIS_RIGHT, true))
         {
            closed = true;
            break;
         }
         consumeChar();
         setMark();
      } while(forward());
      if(!closed)
         throw new ParserFailure(E1b);
      nextToken(TOKTYPE_WHILE);
      return true;
   }
   else
      return false;
}

boolean endclause()
{
   if(isReading(TOKEN_CLAUSE_END, true))
   {
      nextToken(TOKTYPE_END);
      return true;
   }
   else
      return false;
}

boolean elseclause()
{
   if(isReading(TOKEN_CLAUSE_ELSE, true))
   {
      nextToken(TOKTYPE_ELSE);
      return true;
   }
   else
      return false;
}

boolean call()
{
   if(isReading(TOKEN_CALL, true))
   {
      forward();
      trim();
      do
      {
         //         if(comment())
         //            break;
         if(whiteSpace())
            break;
         consumeChar();
         setMark();
      } while(forward());
      nextToken(TOKTYPE_CALL);
      return true;
   }
   else
      return false;
}

boolean interrupt()
{
   boolean matched = false;
   for(String ir : interrupts)
      if(isReading(ir, true))
      {
         matched = true;
         consumeString(ir);
         nextToken(TOKTYPE_INTERR);
         break;
      }
   return matched;
}

boolean command()
{
   boolean noread = false;
   boolean mayHaveOneArg = false;
   do
   {
      if(whiteSpace())
      {
         trim();
         mayHaveOneArg = true;
         break;
      }
      //      if(comment())
      //         break;
      consumeChar();
      setMark();
   } while(forward());
   if(!Empty.is(token.getToken()))
      nextToken(TOKTYPE_CMD);
   if(mayHaveOneArg)
   {
      trim();
      //      if(comment())
      //         return false;
      if(!(isReading(PARENTHESIS_LEFT, false) || isReading(TOKEN_CLAUSE_ELSE, false) || isReading(
         TOKEN_CLAUSE_END, false)))
      {
         boolean quoting = isReading(QUOTATION_MARK);
         if(!quoting)
            consumeChar();
         while(forward())
         {
            if(isReading(QUOTATION_MARK))
            {
               quoting = !quoting;
               continue;
            }
            if(!quoting)
            {
               if(whiteSpace())
               {
                  trim();
                  noread = true;
                  break;
               }
               //               if(comment())
               //                  break;
            }
            consumeChar();
            setMark();
         }
         if(!Empty.is(token.getToken()))
         {
            token.insertAt(0, PARENTHESIS_LEFT);
            token.append(PARENTHESIS_RIGHT);
            insertTokenBeforeLastToken(TOKTYPE_ARGS);
         }
      }
      else
         noread = true;
   }
   return noread;
}
}
