/*
*C-Function Mapper v1.1.1
*Description: Parses C or C++ code to track the functions.
*Use: Pass "--help" as a parameter, or once all files are parsed, "help" as a command for help on using this program. The structure of the filelist or the input of filenames is one path per line and an empty line to mark the end of input.
*/

#include <cstdlib>
#include <cstdio>
#include <vector>
#include <string>
#include <fstream>
#include <iostream>
#include <cerrno>
#include <cstring>
using namespace std;

#define SINGLE_LINE 1
#define MULTI_LINE 2
#define SINGLE_QUOTE 4
#define DOUBLE_QUOTE 8
#define INCOMMENT(x) (x)&3
#define INQUOTE(x) (x)&12

class FuncStruct;

struct ParentInf
{
	FuncStruct *parentFunc;
	int row;
};
	
class FuncStruct
{
public:
	string fileNom;
	string funcNom;
	vector<ParentInf> callingFuncs;
	bool canPrint;
	
	FuncStruct(string &newFuncNom)
	{
		canPrint=true;
		funcNom=newFuncNom;
	}
	
	void printShortPaths(string strApp="")
	{
		unsigned int i;
		char tmp[12];
		if(callingFuncs.size()==0)
			cout<<funcNom+strApp<<endl;
		else
		{
			canPrint=false;
			for(i=0;i<callingFuncs.size();i++)
			{
				sprintf(tmp, "%d", callingFuncs[i].row);
				if(callingFuncs[i].parentFunc->canPrint)
					callingFuncs[i].parentFunc->printShortPaths("("+string(tmp)+") -> "+funcNom+strApp);
				else
					cout<<"Possible recursivity found: "<<callingFuncs[i].parentFunc->funcNom<<"("+string(tmp)+") -> "+funcNom+strApp<<endl;
			}
			canPrint=true;
		}
	}
};

bool isWhitespace(const char &);
bool isVarChar(const char &);
string getFuncNom(const char *, int);
void printUse(char *);

int main(int argc, char *argv[])
{
	vector<FuncStruct*> funcsCollection;
	string currentFile;
	string currentFunc;
	string initFuncNom;
	ifstream fileList;
	ParentInf pInf;
	FuncStruct *tmpFunc=NULL;
	unsigned int openCbr=0;
	unsigned int startCbr=0;
	unsigned int openPar=0;
	unsigned int flags=0;
	unsigned int j;
	int row=1;
	int tmpRow;
	int readLen;
	int fileLen;
	int i;
	FILE *currfd;
	char *fileCont;
	char newLine;
	pInf.parentFunc=NULL;
	if(argc==2)
	{
		if(strcmp(argv[1], "--help")==0)
		{
			printUse(argv[0]);
			return 0;
		}
		fileList.open(argv[1]);
		if(fileList.fail())
		{
			cout<<"Failed to open "<<argv[1]<<" for reading."<<endl;
			return -1;
		}
	}
	else if(argc>2)
	{
		printUse(argv[0]);
		return -1;
	}
	while(true)
	{
		if(argc==2)
		{
			if(fileList.eof())
				break;
			getline(fileList, currentFile);
		}
		else
			getline(cin, currentFile);
		if(currentFile.empty())
			break;
		currfd=fopen(currentFile.c_str(), "rb");
		if(currfd==NULL)
		{
			cout<<"Could not open "<<currentFile<<" for reading."<<endl;
			continue;
		}
		if(fseek(currfd, 0, SEEK_END))
		{
			cout<<"fseek failed, skipping file \""<<currentFile<<"\"."<<endl;
			fclose(currfd);
			continue;
		}
		fileLen=ftell(currfd)+1;
		if(fileLen==-1)
		{
			cout<<"Could not read file length of file \""<<currentFile<<"\". Skipping."<<endl;
			fclose(currfd);
			continue;
		}
		if(fseek(currfd, 0, SEEK_SET))
		{
			cout<<"Could not reset internal pointer for file \""<<currentFile<<"\". Skipping."<<endl;
			fclose(currfd);
			continue;
		}
		fileCont=(char*)malloc(fileLen);
		if(fileCont==NULL)
		{
			cout<<"Could not allocate the necessary memory for file \""<<currentFile<<"\". Skipping."<<endl;
			fclose(currfd);
			continue;
		}
		readLen=0;
		errno=0;
		do
		{
			readLen+=fread(fileCont+readLen, 1, fileLen-readLen, currfd);
			if(readLen<fileLen)
			{
				if(ferror(currfd))
				{
					cout<<"Error when reading from file \""<<currentFile<<"\". Skipping."<<endl;
					fclose(currfd);
					free(fileCont);
					errno=1;
					break;
				}
				if(feof(currfd))
				{
					fileLen=readLen;
					break;
				}
			}
		}while(readLen<fileLen);
		if(errno)
			continue;
		fclose(currfd);
		
		/*Find newline denominator*/
		newLine=0;
		for(i=0;i<fileLen;i++)
		{
			if(fileCont[i]=='\r')
			{
				if(i+1!=fileLen && fileCont[i+1]=='\n')
				{
					newLine='\n';
				}
				else
					newLine='\r';
				break;
			}
		}
		if(newLine==0)
			newLine='\n';
		
		/*Begin parsing*/
		if(fileCont[0]=='\'')
			flags|=SINGLE_QUOTE;
		else if(fileCont[0]=='"')
			flags|=DOUBLE_QUOTE;
		for(i=1;i<fileLen;i++)
		{
			if(fileCont[i]==newLine)
				row++;
			if(INCOMMENT(flags))
			{
				switch(INCOMMENT(flags))
				{
					case SINGLE_LINE:
						if(fileCont[i]==newLine)
							flags^=SINGLE_LINE;
						break;
					case MULTI_LINE:
						if(fileCont[i]=='/' && fileCont[i-1]=='*')
							flags^=MULTI_LINE;
						break;
					default:
						cout<<"Unexpected value for variable \"unsigned int flags\" with value "<<flags<<". Exiting."<<endl;
						return -1;
						break;
				}
				continue;
			}
			if(INQUOTE(flags))
			{
				switch(INQUOTE(flags))
				{
					case SINGLE_QUOTE:
						if(fileCont[i]=='\'')
						{
							for(j=1;j<=i && fileCont[i-j]=='\\';j++);
							if(j%2)
								flags^=SINGLE_QUOTE;
						}
						break;
					case DOUBLE_QUOTE:
						if(fileCont[i]=='"')
						{
							for(j=1;j<=i && fileCont[i-j]=='\\';j++);
							if(j%2)
								flags^=DOUBLE_QUOTE;
						}
						break;
					default:
						cout<<"Unexpected value for variable \"unsigned int flags\" with value "<<flags<<". Exiting."<<endl;
						return -1;
						break;
				}
				continue;
			}
			errno=0;
			switch(fileCont[i])
			{
				case '/':
					if(fileCont[i-1]=='/')
						flags|=SINGLE_LINE;
					break;
				case '*':
					if(fileCont[i-1]=='/')
						flags|=MULTI_LINE;
					break;
				case '\'':
					flags|=SINGLE_QUOTE;
					break;
				case '"':
					flags|=DOUBLE_QUOTE;
					break;
				case '}':
					if(!openCbr)
					{
						cout<<"Unexpected closing bracket in file \""<<currentFile<<"\" at line "<<row<<". Skipping."<<endl;
						errno=1;
					}
					else if((--openCbr)==startCbr)
						pInf.parentFunc=NULL;
					break;
				case '{':
					openCbr++;
					break;
				case '(':
					currentFunc=getFuncNom(fileCont, i);
					if(!openPar)
					{
						initFuncNom=currentFunc;
						tmpRow=row;
					}
					else if(!currentFunc.empty())
					{
						if(pInf.parentFunc!=NULL)
						{
							pInf.row=row;
							for(j=0;j<funcsCollection.size();j++)
								if(funcsCollection[j]->funcNom==currentFunc)
									break;
							if(j==funcsCollection.size())
							{
								tmpFunc=new(nothrow) FuncStruct(currentFunc);
								if(tmpFunc==NULL)
								{
									cout<<"Could not allocate enough memory to hold function data. Exiting."<<endl;
									return -1;
								}
								funcsCollection.push_back(tmpFunc);
							}
							funcsCollection[j]->callingFuncs.push_back(pInf);
						}
					}
					openPar++;
					break;
				case ')':
					if(!openPar)
					{
						cout<<"Unexpected closing parenthesis in file \""<<currentFile<<"\" at line "<<row<<". Exiting."<<endl;
						return -1;
					}
					if(!(--openPar))
					{
						if(!initFuncNom.empty() && initFuncNom!="if" && initFuncNom!="for" && initFuncNom!="while" && initFuncNom!="switch")
						{
							for(i++;i<fileLen && isWhitespace(fileCont[i]);i++)
								if(fileCont[i]==newLine)
									row++;
							if(i<fileLen)
							{
								for(j=0;j<funcsCollection.size();j++)
								{
									if(funcsCollection[j]->funcNom==initFuncNom)
									{
										tmpFunc=funcsCollection[j];
										break;
									}
								}
								if(pInf.parentFunc==NULL && fileCont[i]=='{')
								{
									startCbr=openCbr;
									if(j==funcsCollection.size() || (tmpFunc->fileNom!=currentFile && !tmpFunc->fileNom.empty()))
									{
										pInf.parentFunc=new(nothrow) FuncStruct(initFuncNom);
										if(pInf.parentFunc==NULL)
										{
											cout<<"Could not allocate enought memory to hold functions data. Exiting."<<endl;
											return -1;
										}
										pInf.parentFunc->fileNom=currentFile;
										funcsCollection.push_back(pInf.parentFunc);
									}
									else if(tmpFunc->fileNom.empty())
									{
										pInf.parentFunc=tmpFunc;
										pInf.parentFunc->fileNom=currentFile;
									}
									else
									{
										cout<<"Detected multiple declarations of function \""<<initFuncNom<<"\" in file \""<<currentFile<<"\". Exiting."<<endl;
										return -1;
									}
								}
								else if(pInf.parentFunc!=NULL)
								{
									if(j==funcsCollection.size())
									{
										tmpFunc=new(nothrow) FuncStruct(initFuncNom);
										if(tmpFunc==NULL)
										{
											cout<<"Could not allocate enought memory to hold functions data. Exiting."<<endl;
											return -1;
										}
										funcsCollection.push_back(tmpFunc);
									}
									pInf.row=tmpRow;
									funcsCollection[j]->callingFuncs.push_back(pInf);
								}
							}
							i--;
						}
					}
					break;
			}
			if(errno)
				break;
		}
		free(fileCont);
		cout<<"Parsing of \""<<currentFile<<"\" done."<<endl;
	}
	fileList.close();
	cout<<"All files parsed, enter command for execution or help for a list of commands."<<endl;
	while(true)
	{
		cout<<"> ";
		getline(cin, currentFunc);
		if(currentFunc=="exit")
			return 0;
		else if(currentFunc=="help")
			cout<<"Commands:"<<endl<<"show <function name>"<<endl<<"list"<<endl<<"help"<<endl<<"exit"<<endl;
		else if(currentFunc=="list")
			for(j=0;j<funcsCollection.size();j++)
				cout<<funcsCollection[j]->funcNom<<endl;
		else if(strncmp(currentFunc.c_str(), "show", 4)==0)
		{
			if(currentFunc.length()>4)
			{
				j=currentFunc.find_first_not_of(" \t", 4);
				if(j!=string::npos)
				{
					for(i=0;i<funcsCollection.size() && funcsCollection[i]->funcNom!=currentFunc.substr(j, string::npos);i++);
					if(i==funcsCollection.size())
						cout<<"Function with name \""<<currentFunc.substr(j, string::npos)<<"\" not found."<<endl;
					else
						funcsCollection[i]->printShortPaths();
				}
				else
					cout<<"show <function name>"<<endl;
			}
			else
				cout<<"show <function name>"<<endl;
		}
	}
}

bool isWhitespace(const char &inp)
{
	switch(inp)
	{
		case ' ':
		case '\t':
		case '\r':
		case '\n':
			return true;
			break;
		default:
			return false;
			break;
	}
}

bool isVarChar(const char &inp)
{
	if((inp>47 && inp<58) || inp==95 || (inp>64 && inp<91) || (inp>96 && inp<123))
		return true;
	else
		return false;
}

string getFuncNom(const char *fileCont, int pos)
{
	string funcNom;
	int i;
	for(pos--;pos>=0 && isWhitespace(fileCont[pos]);pos--);
	for(i=pos;i>=0 && isVarChar(fileCont[i]);i--);
	funcNom.assign(fileCont+i+1, pos-i);
	return funcNom;
}

void printUse(char *binom)
{
	cout<<binom<<" [filelist]"<<endl<<endl<<"filelist - A file containing the absolute or relative paths of the files to parse."<<endl;
}