#include <assert.h>
#include <ctype.h>
#include <sstream>
#include "plalexan.h"
#include "nodes.h"
#include "sTable.h"

#define L_END 0
#define L_ERR 1
#define L_DIGIT 2
#define L_TERM 3
#define L_IDENT 4
#define L_NUMBER 5
#define L_IMARKER 6
#define L_OMARKER 7
#define L_EMARKER 8
#define L_INAMEMARKER 9
#define L_ONAMEMARKER 10
#define L_ENDMARKER 11
#define L_DEVIDER 12
#define L_PMARKER 13
#define L_DASH 14

#define NRERRSTRINGS 4
#define NRKEYWORDS 6
#define LENGHTKEYW 20
#define MAXSTRLENGTH 100

#define S_NOT 0
#define S_AND 1
#define S_OR 2
#define S_NAND 3
#define S_NOR 4
#define S_XOR 5
#define S_XNOR 6
#define S_ASSIGN 7


static string varname = "x";
static string ovarname = "y";
static unsigned int L_Value;
//static int L_VLength;
static istream * in_file;
//static ofstream * out_file;
static string lex_str;
static unsigned long long line = 1;
static bool err = false;
static bool space = false;

static string statement [NRERRSTRINGS]=
{
  "Invalid lexical element at line:",
  "Too long identifier at line:",
  "Syntax error at line:",
  "Warning: Different vector lengths at line:"
  "Warning: Different number of inputs at line:"
  "Warning: Different number of outputs at line:"
};


struct Keyword
{
 int token;
 string str; 
 //char str[LENGHTKEYW]; //pro borland win32 prekladac
};

static Keyword Keywords[NRKEYWORDS] =
{
 {L_IMARKER, ".i"},
 {L_OMARKER, ".o"},
 {L_INAMEMARKER, ".ilb"},
 {L_ONAMEMARKER, ".ob"},
 {L_PMARKER, ".p"},
 {L_ENDMARKER, ".e"},
};


 
//varovne hlaseni
/*static void warning(int j)
{
	cout<<statement[j]<<line<<endl;
}*/

//chybove hlaseni
static void error(int i)
{
	cout<<statement[i]<<line<<endl;
	err = true;
}

/*test zda je dany retezec klicove slovo*/
static int iskeyword(string& s)
{
  for (int i=0;i<NRKEYWORDS;i++)
  {
    if (Keywords[i].str == s) return Keywords[i].token;
  }

  return -1;
}

/*==========================================*/
/*lexikalni analyza-cteni lexikalniho tokenu*/
static int gettoken ()
{
 int state=0, act_char=0;
 lex_str="";
 L_Value=0;
 //L_VLength=0; presunuto

 while(1)
 {
   act_char = in_file->get();

   switch (state)
   {
   	 //pocatecni stav
     case 0:
     {
	   //whitespace
       if ( act_char == ' ' || act_char == '\n' || act_char == '\t' || act_char == '|')
       {
       	 space = true;
         if (act_char == '\n') {line++;}
         break;
       }
       
       //komentar
       if (act_char == '#')
       {
    	   state = 3;
    	   break;
       }

       //cislo
       /*if ( isdigit(act_char) )
       {
       	 L_Value = act_char - '0';
       	 state = 2;
       	 break;
       }*/
       
       //cislice
       if ( isdigit(act_char) )
       {
       	 L_Value = act_char - '0';
       	 return L_DIGIT;
       }
       
       //pomlcka
       if ( act_char == '-' )
       {
       	 return L_DASH;
       }
      
       //klicove slovo
       /*if ( act_char == '.' )
       {
       	state = 1;
       	break;
       }*/
		
	   //identifikator, klicove slovo
       if ( isalpha(act_char) || (act_char=='_') || (act_char == '.') )
       {
         state = 1;
         lex_str = lex_str + char(act_char);
         break;
       }
       
	   //konec souboru
       if ( act_char == EOF ) return ( L_END );

       error(0);
       return ( L_ERR );
     }

	 //identifikator: napr. a_11	
     case 1:
     {
       if ( isalnum(act_char) || (act_char=='_') )
       {
         if (lex_str.length()<=MAXSTRLENGTH)
         {lex_str=lex_str+(char)act_char;}
         break;
       }

       in_file->unget();
       int keywcode = iskeyword(lex_str);
       if (lex_str.length()<=MAXSTRLENGTH)
       {
         if (keywcode!=-1) return (keywcode);
         return (L_IDENT);
       }
       error(1);
       return (L_ERR);
     }
      
     //desitkove cislo
     /*case 2:
	 {
	   if ( isdigit(act_char) )
	   {
	   	 L_Value = (L_Value*10) + act_char - '0';
	   	 break;
	   }
	   
	   in_file->unget();
	   return (L_NUMBER);
	 }*/
	 
     case 3:
     { 
       if (act_char == '\n') {line++; state = 0; break;}
       if (act_char == EOF) return (L_END);
       break;
     }
	 

     default:
     {
       assert ( "Invalid state" == NULL );
     }
   }



 }

}



/*===========================================================================*/
/*syntakticka analyza - pravidla pro budovani stromu abstraktni syntaxe*/

static void S();
int N( int v );
void I(int i);
void O(int i);
static Node * T(Node* parent, int pos);

static int Token;

//1. S->.keyw N
//2. S->.i I
//3. S->.o O
//4. S-> T
//5. S->.e
static void S()
{
	Node * parent = NULL;
	int val = 0;
	
	switch (Token)
	{

		case L_IMARKER:
		{ 
			Token = gettoken();
			space = false;
			val = N(0);
			treeArray->invars = val;
			
			//konverze int->string
			for (int i=0; i<val; i++)
			{
				stringstream ss;
				string str;
				
				ss<<i; 
				ss>>str;
				str = varname + str;
				treeArray->symbNames->add(str);
			}
			return;
		}
		
		case L_OMARKER:
		{
			Token = gettoken();
			space = false;
			val = N(0);
			
			//treeArray->outvars = val;
			
			//konverze int->string
			for (int i=0; i<val; i++)
			{
				stringstream ss;
				string str;
				
				ss<<i; 
				ss>>str;
				str = ovarname + str;
				treeArray->add(NULL,NULL,str);
			}
			return;
		}
		
		case L_PMARKER:
		{
			Token = gettoken();
			space = false;
			val = N(0);
			treeArray->terms = val;
			return;
		}
		
		case L_ENDMARKER:
		{
			return;
		}
		
		case L_INAMEMARKER:
		{
			treeArray->namedi = true; 
			Token = gettoken();
			I(0);
			return;
		}
		
		case L_ONAMEMARKER:
		{
			treeArray->namedo = true;
			Token = gettoken();
			O(0);
			return;
		}
		
		
		default:
		{
			parent = T(NULL,0);
			if (parent != NULL)
			{
				/*parent->print(cout);*/
				delete parent;
				return;
			}
			error(2);
			return;
		}
	}
		
}

//6. N->n N
//7. N->eps
int N ( int v )
{
	if (Token == L_DIGIT)
	{
		if (space) return v;
		v = v*10 + L_Value;
		Token = gettoken();
		return N(v);
	}
	
	return v;
	
}

//8. O->ident O
void O(int i)
{
	
	switch (Token)
	{
		case L_IDENT:
		{
			elem = treeArray->find(i);
			if (elem != NULL) elem->name = lex_str;
			Token = gettoken();
			i++;
			O(i);
			return;
		}
		
		default: return;
		
	}
}

//9. I->ident I
void I(int i)
{
	Symbol * smb = NULL;
	
	switch (Token)
	{
		case L_IDENT:
		{
			smb = treeArray->symbNames->find(i);
			if (smb != NULL) smb->name = lex_str;
			Token = gettoken();
			i++;
			I(i);
			return;
		}
		
		default: return;
		
	}
}

//10. T->n T
//11. T->- T
//12. T->eps
static Node * T( Node * ptr, int pos )
{
	//je pozice v radku vetsi nez pocet vstupu+vystupu?
	if (pos >= (treeArray->invars + treeArray->outvars) )
	{
		//terms++; 
		return ptr;
	}
	
	string name="";
	Symbol * smb = NULL;
	Node * newptr = NULL;
	
	//znaky vstupu
	if (pos<treeArray->invars)
	{
		
		switch (Token)
		{
			//pomlcka - preskocit
			case L_DASH:
			{
				
				pos++;
				Token = gettoken();
				return T(ptr,pos);
			}
			
			//1 nebo 0, vytvorit novy node
			case L_DIGIT:
			{
				if ( !( (L_Value == 0) || (L_Value == 1 ) ) ) break; 
				
				smb = treeArray->symbNames->find(pos);
					
				if (smb != NULL)
				{
					newptr = new IdentNode(smb->name);
					if (L_Value == 0) {newptr = new UNode (newptr, S_NOT );}
				}
				
				if (ptr != NULL) {ptr = new BNode (ptr, newptr, S_AND);}
				else {ptr = newptr;}
				pos++;
				Token = gettoken();
				return T(ptr,pos);
			}
			
			default: {return ptr;}
		}
		
	}
	//znaky vystupu
	else
	{
		if (L_Value != 1) 
		{
			pos++;
			Token = gettoken();
			return T(ptr,pos);
			
		}
		int actpos = pos - treeArray->invars;
		elem = treeArray->find(actpos);
		if (elem != NULL)
		{
			if (ptr != NULL)
			{
				if (elem->root == NULL) elem->root = ptr->copy();
				else elem->root = new BNode (elem->root, ptr->copy(), S_OR);
			}
			else
			{
				ptr = new VectNode(1,1);
				elem->root = ptr->copy();
			}
		}
		pos++;
		Token = gettoken();
		return T(ptr,pos);
	}
	
	return ptr;
}

//vytvoreni stromu abstraktni syntaxe
bool analyzepla (istream * input)
{
	in_file = input;
	if (line == 1) {Token = gettoken();}
	S();
	if ( (Token != L_END) &&  (Token != L_ENDMARKER) && (!err) ) {return true;}
	return false;
}
