#include "Tag.h"
#include "Exception.h"
#include "Global.h"
#include "__mysql.h"
#include "__sql.h"
#include "__whilerow.h"
#include "__comment.h"
#include "__exclude.h"
#include "__for.h"
#include "__if.h"
#include "__ifrow.h"
#include "__include.h"
#include "__read.h"
#include "__write.h"
#include "__set.h"
#include "__call.h"
#include "__system.h"
#include "__jet.h"
#include "__while.h"
#include "__until.h"
#include "__header.h"
#include "__cookie.h"
#include "__whiledir.h"
#include "__tag.h"
#include "__dotag.h"
#include "__stream.h"
#include "__dump.h"
#include "Operand.h"
#include <iostream>

namespace jet {
   
   Tag::Tag(coreutils::ZString &in, coreutils::MString &parentOut, Global &global, Tag *parent, Tag *local, coreutils::ZString splitTagName)
     : ZString(in), parentOut(parentOut), global(global), parent(parent), local(local) {
	this->splitTagName = splitTagName;
	global.errorCursor = in.getCursor();
	if(parent && in.ifNext("<")) {
	   name = in.getTokenInclude("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_!");
	   if(in.startsWith(" ") || in.startsWith("/") || in.startsWith(">")) {
	      bool finished = false;
	      while(!finished) {
		 in.skipWhitespace();
		 if(in.ifNext(">")) {
		    hasContainer = true;
		    hasContainer2 = false;
		    finished = true;
		    break;
		 } else if(in.ifNext("/>")) {
		    hasContainer = false;
		    hasContainer2 = false;
		    finished = true;
		    break;
		 }
		 if(!finished) {
		    coreutils::ZString keywordName = in.getTokenInclude("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_");
		    if(in.ifNext("=\"")) {
		       if(keywords.count(keywordName) == 0)
			 keywords[keywordName] = in.getTokenExclude("\"");
		       else
			 throw coreutils::Exception("keyword name must be unique for tag.");
		    }
		    if(!in.ifNext("\"")) {}
		 }
	      }
	      if(keywordDefined("filterblanklines")) {
		 filterBlankLines = keywords["filterblanklines"] == "true" ? true: false;
	      }	      
	      if(keywordDefined("trimlines")) {
		 trimLines = keywords["trimlines"] == "true" ? true: false;
	      }	      
	      if(hasContainer) {
		 bool hasSplitTag = splitTagName == "" ? false: true;
		 char *start = in.getCursor();
		 while(!in.eod()) {              
		    char *end = in.getCursor();   
		    if(ifEndTagName(in)) {
		       if(hasContainer2)
			 container2 = coreutils::ZString(start, end - start);
		       else
			 container = coreutils::ZString(start, end - start);
		       break;		       
		    } else if(hasSplitTag && ifSplitTagName(in)) {
		       hasContainer2 = true;
		       container = coreutils::ZString(start, end - start);
		       in.ifNext("<else>");
		       start = in.getCursor();		 
		    } else if(ifNested(in)) {
		    } else
		      in.nextChar();
		 }		 
	      }
	      setZString(in.parsed());
	      if(keywordDefined("eval")) {
		 if(keywords["eval"] == "yes") {
		    evaluate = true;
		 } else if(keywords["eval"] == "no") {
		    evaluate = false;
		 } else 
		    throw coreutils::Exception("keyword 'eval' must be 'yes' or 'no'.");		       
	      }
	   }
	} else
	  parseContainer(in, out);
     }
   
   Tag::~Tag() {
      if(evaluate) 
	if(output) 
	  copyContainer(out, parentOut);
      else if(output)
	copyContainer(container, parentOut);
   }
   
   coreutils::MString Tag::resolveKeyword(coreutils::ZString keyword) {
      coreutils::MString resolved;
      keywords[keyword].reset();
      while(!keywords[keyword].eod()) {
	 if(keywords[keyword].startsWith("$[") || keywords[keyword].startsWith("#[")) {
	    resolved.write(getVariable(keywords[keyword]));
	 } else {
	    resolved.write(keywords[keyword].charAt(0));
	    keywords[keyword].nextChar();
	 }	 
      }
      keywords[keyword].reset();
      return resolved;
   }   

   void Tag::processContainer(coreutils::ZString &container, coreutils::ZString container2, bool topLevel) {
      if(hasContainer && evaluate)
	parseContainer(container, out, container2, topLevel);      
   }
      
   void Tag::parseContainer(coreutils::ZString &in, coreutils::MString &out, coreutils::ZString container2, bool topLevel) {
      coreutils::ZString tag;
      char *start = in.getCursor();
      while(!in.eod()) {
	 if(in.ifNext("|>")) {
	    if(!topLevel)
	      out.write("|>");
	    char ch = in.nextChar();
	    while(!in.ifNext("<|")) {		 
	      out.write(ch);
	      ch = in.nextChar();
	    }
	    out.write(ch);
	    if(!topLevel)
	      out.write("<|");	    
	    continue;
	 } else if(in.startsWith("<")) {
	    if(ifTagName(in, "mysql")) {
	       __mysql _mysql(in, out, global, this, local);
	       continue;
	    } else if(ifTagName(in, "comment")) {
	       __comment _comment(in, out, global, this, local);
	       continue;
	    } else if(ifTagName(in, "sql")) {
	       __sql _sql(in, out, global, this, local);
	       continue;
	    } else if(ifTagName(in, "whilerow")) {
	       __whilerow _whilerow(in, out, global, this, local);
	       continue;
	    } else if(ifTagName(in, "for")) {
	       __for _for(in, out, global, this, local);
	       continue;
	    } else if(ifTagName(in, "if")) {
	       __if _if(in, out, global, this, local);
	       continue;
	    } else if(ifTagName(in, "ifrow")) {
	       __ifrow _ifrow(in, out, global, this, local);
	       continue;
	    } else if(ifTagName(in, "include")) {
	       __include _include(in, out, global, this, local);
	       continue;
	    } else if(ifTagName(in, "exclude")) {
	       __exclude _exclude(in, out, global, this, local);
	       continue;
	    } else if(ifTagName(in, "jet")) {
	       __jet _jet(in, out, global, this, local);
	       continue;
	    } else if(ifTagName(in, "read")) {
	       __read _read(in, out, global, this, local);
	       continue;
	    } else if(ifTagName(in, "write")) {
	       __write _write(in, out, global, this, local);
	       continue;
	    } else if(ifTagName(in, "set")) {
	       __set _set(in, out, global, this, local);
	       continue;
	    } else if(ifTagName(in, "call")) {
	       __call _call(in, out, global, this, local);
	       continue;
	    } else if(ifTagName(in, "system")) {
	       __system _system(in, out, global, this, local);
	       continue;
	    } else if(ifTagName(in, "while")) {
	       __while _while(in, out, global, this, local);
	       continue;
	    } else if(ifTagName(in, "until")) {
	       __until _until(in, out, global, this, local);
	       continue;
	    } else if(ifTagName(in, "header")) {
	       __header _header(in, out, global, this, local);
	       continue;
	    } else if(ifTagName(in, "cookie")) {
	       __cookie _cookie(in, out, global, this, local);
	       continue;
	    } else if(ifTagName(in, "whiledir")) {
	       __whiledir _whiledir(in, out, global, this, local);
	       continue;
	    } else if(ifTagName(in, "stream")) {
	       __stream _stream(in, out, global, this, local);
	       continue;
	    } else if(ifTagName(in, "dump")) {
	       __dump _dump(in, out, global, this, local);
	       continue;
	    } else if(ifTagName(in, "tag")) {
	       __tag _tag(in, out, global, this, local);
	       continue;
	    } else if(ifTagDefined(in, tag)) {
	       __dotag _dotag(in, out, global, this, local);
	       continue;
	    } else if(ifTagName(in, "container")) {
	       processContainer(container2);
	       in.ifNext("<container");
	       in.skipWhitespace();
	       in.ifNext("/>");
	       continue;
	    } else {
	       out.write(in.nextChar());
	       continue;
	    }  
	 } else if(in.startsWith("$[") || in.startsWith("#[")) {
	    global.errorCursor = in.getCursor();
	    out.write(getVariable(in, true));
	 } else {
	    out.write(in.nextChar());
	 }	 
      }
   }
      
   void Tag::scanContainer(coreutils::ZString &in) {
      while(!in.eod()) {
	 if(ifEndTagName(in)) 
	   return;
	 else if(ifNested(in)) {}	 
	 else in.nextChar();
      }
   }
   
   bool Tag::ifTagName(coreutils::ZString &in, const char *tag) {
      in.push();
      if(in.ifNext("<"))
        if(in.getTokenInclude("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_").equals(tag)) {       
	   if(in.ifNext(" ")) {
	      in.pop();
	      return true;
	   } else if(in.ifNext("/>")) {
	      in.pop();
	      return true;
	   } else if(in.ifNext(">")) {
	      in.pop();
	      return true;
	   }	  
	}
      in.pop();
      return false;
   }
   
   bool Tag::ifTagName(coreutils::ZString &in) {
      in.push();
      if(in.ifNext("<"))
        if(in.getTokenInclude("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_").equals(name)) {
	   in.push();
	   if(in.ifNext(" ")) {
	      in.pop();
	      return true;
	   } else if(in.ifNext("/>")) {
	      in.pop();
	      return true;
	   } else if(in.ifNext(">")) {
	      in.pop();
	      return true;
	   }	   
	   in.pop();
	}
      in.pop();
      return false;
   }

   bool Tag::ifTagDefined(coreutils::ZString &in, coreutils::ZString &tag) {
      in.push();
      if(in.ifNext("<")) {
	 tag = in.getTokenInclude("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_");
	 if(global.tags.count(tag)) {
	    if(in.ifNext(" ")) {
	       in.pop();
	       return true;
	    } else if(in.ifNext("/>")) {
	       in.pop();
	       return true;
	    } else if(in.ifNext(">")) {
	       in.pop();
	       return true;
	    }	   
	    in.pop();
	 }
      }	 
      in.pop();
      return false;
   }
   
   bool Tag::ifEndTagName(coreutils::ZString &in) {
      in.push();
      if(in.ifNext("</"))
        if(in.getTokenInclude("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_").equals(name)) {
	   if(in.ifNext(">")) 
	     return true;
	}      
      in.pop();
      return false;
   }

   bool Tag::ifSplitTagName(coreutils::ZString &in) {
      in.push();
      if(in.ifNext("<"))
        if(in.getTokenInclude("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_").equals(splitTagName)) {
	   if(in.ifNext(">"))
	     in.pop();
	     return true;
	}      
      in.pop();
      return false;
   }
   
   int Tag::skipBlankLine(coreutils::ZString in) {
      ZString temp = in.getTokenInclude(" \t");
      if(ifNext("\n"))
        return temp.getLength() + 1;
      return 0;
   }
   
   void Tag::copyContainer(coreutils::ZString &in, coreutils::MString &out) {
      while(!in.eod()) {	 
         if(filterBlankLines) {
	    if(!in.lineIsWhitespace()) {
	       if(trimLines) 
		 out.write(in.goeol().trim());
	       else	       
		 out.write(in.goeol());
	       out.write('\n');
	    }
	    else {
	       in.goeol();
	    }	    
	 }
         else {     
	    out.write(in.charAt(0));
	    in.nextChar();
	 }	 
      }
   }
   
   bool Tag::keywordDefined(coreutils::ZString keyword) {
      return keywords.find(keyword) != keywords.end();
   }
   
   bool Tag::ifNested(coreutils::ZString &in) {
      bool hasContainer = false;
      if(ifTagName(in)) {
	 while(!in.eod()) {
	    in.skipWhitespace();
	    if(in.ifNext(">")) {
	       hasContainer = true;
	       break;
	    } else if(in.ifNext("/>")) {
	       hasContainer = false;
	       return true;
	    } else if(in.getTokenExclude("=").getLength() != 0) {
	       if(in.ifNext("=\"")) {
		  while(1) {
		     if(in.ifNext("\"")) {
			break;
		     }
		     in.nextChar();
		  }
	       }
	    }
	 }
	 if(hasContainer)
	   scanContainer(in);
      }
      return false;
   }

   coreutils::MString Tag::getVariable(coreutils::ZString &variable, bool inContainer) {
      if(variable.ifNext("$[")) {
	 coreutils::MString name;
	 coreutils::MString modifier;
	 if(variable.ifNext("!")) {
	    renderVariableName(variable, name, modifier);
	    return global.variables[name];
	 } else if(variable.ifNext("%")) {
	    renderVariableName(variable, name, modifier);
	    if(inContainer)
	      return keywords[name];
	    else
	      return parent->keywords[name];
	 } else if(variable.ifNext(":")) {
	    renderVariableName(variable, name, modifier);
	    if(name.find(":") == -1) {
	       name << ":0";
	    }
	    return processModifier(global.cgiData[name], modifier);
	 } else	if(variable.ifNext("@")) {
	    // TODO: should only allow session variables. Allow substitution.
	 } else if(variable.ifNext("%")) {
	    renderVariableName(variable, name, modifier);
	    return getenv(name.c_str());
	 } else if(variable.ifNext("^")) {
	    renderVariableName(variable, name, modifier);
	    return global.cookies.data[name];
	 } else {
	    renderVariableName(variable, name, modifier);
	    name.split(".");
	    if(name.getList().size() == 1) {
	       if(global.variables.find(name[0]) == global.variables.end())
		 throw coreutils::Exception("global variable is not initialized.");
	       return processModifier(global.variables[name[0]], modifier);
	    }
	    return global.getSessionVariable(name);
	 }
	 throw coreutils::Exception("expected variable name or type designator.");
      } else if(variable.ifNext("#[")) {
	 coreutils::MString name;
	 coreutils::MString modifier;
	 renderVariableName(variable, name, modifier);
	 if(local->variables.find(name) == local->variables.end())
	   throw coreutils::Exception("local variable is not initialized.");
	 return local->variables[name];
      }
      throw coreutils::Exception("Expecting a variable initializer ('$[' or '#[').");
   }

   void Tag::renderVariableName(coreutils::ZString &variable, coreutils::MString &name, coreutils::MString &modifier) {
      while(!variable.ifNext("]")) {
	 name << variable.getTokenInclude("?ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-");
	 if(variable.ifNext(";")) {
	    renderVariableName(variable, modifier, modifier);
	    return;
	 } else if(variable.ifNext(":")) {
	    name << ":";
	 } else if(variable.startsWith("$[") || variable.startsWith("#[")) {
	    name << getVariable(variable);
	 } else if(variable.ifNext("]"))
	      return;
	 else if(!variable.ifNextInclude("?ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-"))
	   throw coreutils::Exception("invalid variable name.");
      }      
      return;
   }

   void Tag::storeVariable(coreutils::ZString variable, coreutils::MString value, coreutils::ZString scope) {
      if((scope == "global") || (scope == ""))
	global.variables[variable] = value;
      else if(scope == "local")
	local->variables[variable] = value;
      else if(scope == "parent")
	local->parent->variables[variable] = value;
   }
      
   void Tag::storeVariable(coreutils::ZString variable) {
      if(keywordDefined("expr")) {
	 if(!keywordDefined("scope") || (resolveKeyword("scope") == "global"))
	   global.variables[variable] = Operand(keywords["expr"], *this).string;
	 else if(resolveKeyword("scope") == "local")
	   local->variables[variable] = Operand(keywords["expr"], *this).string;
	 else if(resolveKeyword("scope") == "parent")
	   local->parent->variables[variable] = Operand(keywords["expr"], *this).string;
      } else if(hasContainer) {
	 processContainer(container);
	 if(evaluate) {
	    if(!keywordDefined("scope") || (resolveKeyword("scope") == "global"))
	      global.variables[variable] = out;
	    else if(resolveKeyword("scope") == "local")
	      local->variables[variable] = out;
	    else if(resolveKeyword("scope") == "parent")
	      local->parent->variables[variable] = out;
	 } else {
	    if(!keywordDefined("scope") || (resolveKeyword("scope") == "global"))
	      global.variables[variable] = container;
	    else if(resolveKeyword("scope") == "local")
	      local->variables[variable] = container;
	    else if(resolveKeyword("scope") == "parent")
	      local->parent->variables[variable] = container;
	 }	 
      } else {
	 if(!keywordDefined("scope") || (resolveKeyword("scope") == "global"))
	   global.variables[variable] = resolveKeyword("value");
	 else if(resolveKeyword("scope") == "local")
	   local->variables[variable] = resolveKeyword("value");
	 else if(resolveKeyword("scope") == "parent")
	   local->parent->variables[variable] = resolveKeyword("value");
      }
   }

   coreutils::MString Tag::processModifier(coreutils::MString &value, coreutils::MString &modifier) {
      if(modifier == "")
	return value;
      if(modifier == "tobinary")
	return value.toBinary();
      else if(modifier == "frombinary")
	return value.fromBinary();
      else if(modifier == "tohex")
	return value.toHex();
      else if(modifier == "fromhex")
	return value.fromHex();
      else if(modifier == "tobase64")
	return value.toBase64();
      else if(modifier == "frombase64")
	return value.fromBase64();
      else if(modifier == "toupper")
	return value.toUpper();
      else if(modifier == "tolower")
	return value.toLower();
      else if(modifier == "tocgi")
	return value.toCGI();
      else if(modifier == "fromcgi")
	return value.fromCGI();
      throw coreutils::Exception("modifier not valid.");
   }
   
}
