//todo:  napisi main() i runProgram()

//napomena za rad sa vektorima :
//deklaracija vektora(vektor je objekat koji predstavlja niz(moze se tako 
//posmatrati): std::vector<type> id;
//dodavanje na kraj vektora preko funkcije: id.push_back()
//id.size()vraca velicinu niza
//id.resize(int) menja velicinu niza 
//id[n] - pristup n-tom elementu niza 
#include<fstream.h>
#include<iostream.h>
#include<string.h>
#include<malloc.h>
#include<ctype.h>
#include<vector.h>

//vraca 0-ti argument i-te instrukcije

#define argument(i,a) program[i].arguments[a]  

struct komanda
{
	char instr;
	int *arguments;
};

class URM
{
	private:      
	std::vector<int> traka;  //niz sa registrima trake
	int duzinaPrograma;  //broj instrukcija u programu,pomocna promenljiva
	int lineNo;  //broj linije do koje se stiglo pri parsiranju (zbog gresaka)
	std::vector<komanda> program; //niz struktura u koje se smestaju instrukcije  
	ifstream ulaz; //tok podataka odakle se cita(parsira) source
	
	//stanja automata koji parsira ulazni fajl (source)
	void readInstruction(char c);
	void readComments();
	void readArguments(char instr);
	void readSpaces();
	void greska(int lineNo,char c);
	
	public:
   //destruktor
   ~URM();
   //mode moze da bude 'f' za fajl ili 's' za string 
    void loadProgram(char *s); 
	//mode moze da bude 'p' za program(instrukcije i memorija)
	// 'i' za instrukcije i 'm' za samo stampanje trake
	void runProgram(char mode,int duzinaTrake,int* traka);	  
	//stampa instrukcije i njihove argumente
	void printProgram(); 
	//stampa stanje memorije
	void printMemory();
	//vraca dubinu programa, tj najvisi indeks registara koji se koriste
	int getPrDepth();
};

//parsiranje, tj ucitavanje programa u 'memoriju' je zamisljeno kao
//konacni automat sa 4 stanja: 
//1.citanje komande(readInstruction)
//2.primanje argumenata (readArguments)
//3.citanje beline (readSpaces)
//4.citanje komentara (readComments)
//5. greska koja je u isto vreme i zavrsno stanje ( error)
// u zavisnosti od tekuceg karaktera pri parsiranju prelazi se iz jednog 
// stanja u drugo

void URM::loadProgram(char *s)
{
	char c;
	ulaz.open(s);
	if(!ulaz)
	{
		cout << "Greska pri otvaranju datoteke " << s <<"!\n";
		exit(1);
	}
	duzinaPrograma = 0;
	lineNo=1;
	ulaz.get(c);
	switch(c)
	{
		case ' ':
			readSpaces();
			break;
		case '@':
			readComments();
			break;
		case 'Z':
		case 'S':
		case 'J':
		case 'T':
		    readInstruction(c);
			break;
		case '\n':
			lineNo++;
			readSpaces();
			break;
		default:
			greska(lineNo,c);
	}
	cout << endl << "Datoteka " << s << " uspesno ucitana u memoriju!\n";
}

void URM::greska(int lineNo,char c)
{
	cout << " Ilegalan karakter: "<< c <<" na liniji " << lineNo << endl;
	exit(2);
}

void URM::readInstruction(char c)
{
	program.resize(duzinaPrograma+1);
	program[duzinaPrograma].instr = c;
	readArguments(c);
}

void URM::readArguments(char instr)
{
	char c;      
	int n=0,i;
	ulaz.get(c);
	if(c!='(') // kupimo prvi argument instrukcije koji imaju sve 4 instrukcije
		greska(lineNo,c);
	ulaz.get(c);
	if(!isdigit(c))
		greska(lineNo,c);
	while(isdigit(c))
	{
		n = n*10 + (c - '0');
		ulaz.get(c);
	}
	switch(instr)   //zuzimamo potrebnu memoriju za argumente svake instr.
	{
		case 'S': case 'Z':
			program[duzinaPrograma].arguments = new int;
		case 'T':
			program[duzinaPrograma].arguments = new int[2];
		case 'J':
			program[duzinaPrograma].arguments = new int[3];
	}
	program[duzinaPrograma++].arguments[0] = n;
	switch(instr)
	{
		
		case 'S':  //ako je instrukcija S ili Z, proveravamo sintaksu i
		case 'Z':  //prelazimo u sledece stanje u zavisnosti od sledeceg karaktera
			if(c!=')')
				greska(lineNo,c);
			ulaz.get(c);
			switch(c)
			{
				case ' ':
					readSpaces();
					break;
				case '@':
					readComments();
					break;
				case '\n':
					lineNo++;
					readSpaces();
					break;
				default:
					greska(lineNo,c);
			}
			break;
		case 'T':  //u slucaju T instrukcije potrebna su dva argumenta
			if(c!=',')
				greska(lineNo,c);
			ulaz.get(c);
			if(!isdigit(c))
				greska(lineNo,c);
			n = 0; //vracamo argumetn na 0
			while(isdigit(c))
			{
				n = n*10 + (c - '0');
				ulaz.get(c);
			}
	        program[duzinaPrograma-1].arguments[1] = n;
			if(c!=')')
				greska(lineNo,c);
			ulaz.get(c);
			switch(c)
			{
				case ' ':
					readSpaces();
					break;
				case '@':
					readComments();
					break;
				case '\n':
					lineNo++;
					readSpaces();
					break;
				default:
					greska(lineNo,c);
			}
			break;
		case 'J': // a u J instrukciji 3 argumenta, pa 2x proveravamo sintaksu
			for(i=1;i<3;i++)  //za argumetne, i prelazimo u sledece stanje
			{
				if(c!=',')
					greska(lineNo,c);
				ulaz.get(c);
				if(!isdigit(c))
					greska(lineNo,c);
				n = 0; //posto n sadrzi prethodno ucitani argument, opet setujemo na 0
				while(isdigit(c))
				{
					n = n*10 + (c - '0');
					ulaz.get(c);
				}		
				program[duzinaPrograma-1].arguments[i] = n;				
			}
			if(c!=')')
				greska(lineNo,c);
			ulaz.get(c);
			if(ulaz.eof())
				return;
			switch(c)
			{
				case ' ':
					readSpaces();
					break;
				case '@':
					readComments();
					break;
				case '\n':
					lineNo++;
					readSpaces();
					break;
				default:
					greska(lineNo,c);
			}
			break;
	}
}

void URM::readSpaces()
{
	char c = ' ';
	while(c == ' ' || c == '\n')
	{
		if(c == '\n')
			lineNo++;		
		ulaz.get(c);
		if(ulaz.eof()) 
		return;
	}
	switch(c)
	{		
		case 'S' : case 'Z' : case 'T' : case 'J':
			readInstruction(c);
			break;
		case '@':		
			readComments();
			break;
		default:
			greska(lineNo,c);
	}
}

void URM::readComments()
{
	char c;
	while(c != '\n')
	{
		ulaz.get(c);
		lineNo++;
		if(ulaz.eof()) 
			return;
	}
	ulaz.get(c); //uzimamo prvi karakter nakon line feed-a
	switch(c)
	{		
		case 'S' : case 'Z' : case 'T' : case 'J':
			readInstruction(c);
			break;
		case '@':		
			readComments();
			break;
		default:
			greska(lineNo,c);
	}
}

void URM::printProgram()
{
	int i;
	for(i = 0; i < duzinaPrograma ; i++)
	{
		cout << "I" << i <<": " ;
		cout << program[i].instr << " ";
		switch(program[i].instr)
		{
			case 'S': case 'Z': cout << program[i].arguments[0] << " " << endl;
				break;
			case 'T': cout << program[i].arguments[0] << " ";
					cout << program[i].arguments[1] << " " << endl;
				break;
			case 'J': cout << program[i].arguments[0] << " ";
					cout << program[i].arguments[1] << " ";
					cout << program[i].arguments[2] << " " << endl;
				break;
		}
	}
}	

URM::~URM()
{
   int i;
   //delete[] traka;
   for(i = 0; i<duzinaPrograma ; i++)
      switch(program[i].instr)
      {
         case 'Z': case 'S': 
            delete program[i].arguments;
            break;
         case 'T': case 'J':
            delete[] program[i].arguments;
            break;
      }
   program.clear();				
}
//ovde se sve desava
void URM::runProgram(char mode,int argNo,int* args)
{
     int i,j;
     traka.resize(getPrDepth());   //oslobadajamo dovoljno memorije za traku
     for(i=0;i<argNo;i++)
     	traka[i] = args[i];          //popunjavamo traku zadatim argumentima
     cout << "Pocetno stanje memorije: ";
     printMemory();
     
     for(i=0;i<duzinaPrograma;i++)
     {
      j = i; 
		//stampam koja se instrukcija izvrsava kad je potrebno
		switch(mode)
		{
			case 'i': case 'p':
				cout << endl << "Izvrsava se " << program[i].instr <<'(';
				cout << program[i].arguments[0];				
				switch(program[i].instr)
				{
					case 'S': case 'Z':
						cout << ')';
						break;
					case 'T':
						cout <<','<<program[i].arguments[1];
						break;
					case 'J':
						cout <<','<<program[i].arguments[1] ;
						cout <<','<<program[i].arguments[2] << ')';
						break;
				}
				cout <<" (" << i << ". instrukcija)";
		}
		
		//izvrsavam tu instrukciju
		switch(program[i].instr)
		{
			case 'Z':
				traka[argument(i,0)-1]=0; //umanjujemo sve za jedan jer URM 
				break;				      //masina nema 0-ti registar
			case 'T':
				traka[argument(i,1)-1] = traka[argument(i,0)-1];
				break;
			case 'J':
				if(traka[argument(i,1)-1] == traka[argument(i,0)-1])
					i = argument(i,2)-2; //umanjujemo za 2(1 zbog i++,1 
				break;					 //jer je I[0] prva instrukcija
			case 'S':
				traka[argument(i,0)-1]++;
				break;
		}
		//stampam stanje memorije ako je potrebno
	 	if((mode == 'p' || mode == 'm') && program[j].instr!='J')
	 		printMemory();	 	
	}
	cout << endl << "Stanje memorije nakon izvrsenja programa : ";
	printMemory();
	cout << endl << "Rezultat(prvi registar) je: " << traka[0] << endl;
}

void URM::printMemory()
{
	int i;
	cout << endl;
	for(i=0;i<traka.size();i++)
		cout << traka[i] << " ";
}

int URM::getPrDepth()
{
	int i,max=0;
	for(i=0;i<duzinaPrograma;i++)
	{
	 	if(argument(i,0)>max)
			max = argument(i,0);
		if((program[i].instr == 'T') || (program[i].instr == 'J'))
		   if(argument(i,1)>max)
				max = argument(i,1);	
   }					
	return max;
}

int main(int argc, char *argv[])
{
	URM masina;
	int argNo,*args,i;
	if(argc != 2)
	{
		cout << "Pogresan broj argumenata komandne linije!!" <<endl;
		cout << "syntax: urm fname" << endl ;
		cout << "gde je fname ime fajla sa URM instrukcijama";
		exit(3);
	}
	masina.loadProgram(argv[1]);
	masina.printProgram();
	cout << "Unesite broj argumenata vaseg programa:\n";
	cin >> argNo;
	args = new int[argNo];
	cout << endl << "Unos argumenata URM programa:";
	for(i=0;i<argNo;i++)
	{
		cout << endl << "Unesite " << i << "ti argument URM programa:";
		cin >> args[i];
	}
	cout << endl << "Molimo izaberite jednu od sledecih opcija(defult = 1)" ;
	cout << endl << "1.Stampanje svake instrukcije i sadrzaja trake";
	cout << endl << "2.Stampanje samo redosleda izvrsavanja instrukcija";
	cout << endl << "3.Samo stampanje sadrzaja trake" << endl ;
	cin >> i ;
	switch(i)
	{
		case 2: 
			masina.runProgram('i',argNo,args);
			break;
		case 3: 
			masina.runProgram('m',argNo,args);
			break;
		default:
			masina.runProgram('p',argNo,args);	
	}
}

