/*************************************************************************
*    UrBackup - Client/Server backup system
*    Copyright (C) 2011-2016 Martin Raiber
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU Affero General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU Affero General Public License for more details.
*
*    You should have received a copy of the GNU Affero General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/

#include "TreeReader.h"
#include <fstream>
#include <iostream>
#include <memory.h>
#include <stack>
#include "../../stringtools.h"
#include "../../urbackupcommon/os_functions.h"
#include "../../Interface/Server.h"
#include "../../Interface/File.h"
#include <assert.h>

bool TreeReader::readTree(IFile* f)
{
	f->Seek(0);
	std::vector<char> buffer(512 * 1024);
	size_t read;
	int state=0;
	size_t lines=0;
	size_t stringbuffer_size=0;
	std::string name;
	char ltype=0;
	do
	{
		bool has_read_error = false;
		read = f->Read(buffer.data(), buffer.size(), &has_read_error);

		if (has_read_error)
		{
			Log("Error reading from tree file -1");
			return false;
		}
		
		for(size_t i=0;i<read;++i)
		{
			const char ch=buffer[i];
			switch(state)
			{
			case 0:
				if(ch=='f' || ch=='d')
				{
					ltype = ch;
					state = 1;
				}
				else if(ch=='u')
				{
					state = 10;
					ltype = 'd';
					name = "..";
				}
				else
				{
					Log("Error parsing file readTree - 0. Expected 'f', 'd', or 'u'. Got '"+std::string(1, ch)+"' at line "+convert(lines)+" while reading "+ f->getFilename());
					return false;
				}
				break;
			case 1:
				//"
				state=2;
				break;
			case 2:
				if(ch=='"')
				{
					state=3;
				}
				else if(ch=='\\')
				{
					state=5;
				}
				else
				{
					name+=ch;
				}
				break;
			case 5:
				if(ch!='\"' && ch!='\\')
				{
					name+='\\';
				}
				name+=ch;
				state=2;
				break;
			case 3:
				if(ch==' ')
				{
					state=4;
					break;
				}
				else
				{
					state=10;
				}
			case 4:
				if(state==4)
				{
					if(ch!='\n')
					{
						break;
					}
				}
			case 10:
				if(ch=='\n')
				{
					state=0;
					if(ltype=='f')
					{
						stringbuffer_size+=2*sizeof(int64);
					}
					if(ltype!='d' || name!="..")
					{
						if(ltype=='d')
						{
							stringbuffer_size+=sizeof(int64);
						}

						++lines;
						stringbuffer_size+=name.size()+1;
					}
					name.clear();
					ltype=0;
				}
			}		
		}
	}
	while(read>0);

	name.clear();

	f->Seek(0);


	size_t stringbuffer_pos=0;
	stringbuffer.resize(stringbuffer_size+5);
	
	
	state=0;
	bool isdir=false;
	std::string data;
	std::stack<TreeNode*> parents;
	std::stack<TreeNode*> lastNodes;
	bool firstChild=true;

	size_t idx=1;
	nodes.resize(lines+1);

	std::string root_str = "root";
	memcpy(&stringbuffer[0], root_str.c_str(), root_str.size()+1);
	stringbuffer_pos+=root_str.size()+1;

	nodes[0].setName(&stringbuffer[0]);

	parents.push(&nodes[0]);
	lastNodes.push(&nodes[0]);

	lines=0;


	do
	{
		bool has_read_error = false;
		read = f->Read(buffer.data(), buffer.size(), &has_read_error);

		if (has_read_error)
		{
			Log("Error reading from tree file -2");
			return false;
		}
		
		for(size_t i=0;i<read;++i)
		{
			const char ch=buffer[i];
			switch(state)
			{
				case 0:
					if(ch=='f')
					{
						data+='f';
						state=1;
					}
					else if(ch=='d')
					{
						data+='d';
						state=1;
					}
					else if(ch=='u')
					{
						name="..";
						state=10;
					}
					else
					{
						Log("Error parsing file readTree - 1. Expected 'f', 'd', or 'u'. Got '" + std::string(1, ch) + "' at line " + convert(lines)+" while reading "+f->getFilename());
						return false;
					}					
					break;
				case 1:
					//"
					state=2;
					break;
				case 2:
					if(ch=='"')
					{
						state=3;
					}
					else if(ch=='\\')
					{
						state=5;
					}
					else
					{
						name+=ch;
					}
					break;
				case 5:
					if(ch!='\"' && ch!='\\')
					{
						name+='\\';
					}
					name+=ch;
					state=2;
					break;
				case 3:
					if(ch==' ')
					{
						state=4;
						break;
					}
					else
					{
						state=10;
					}
				case 4:
					if(state==4)
					{
						if(ch!='\n')
						{
							data+=ch;
							break;
						}
					}
				case 10:
					if(ch=='\n')
					{
						if(name!="..")
						{
							memcpy(&stringbuffer[stringbuffer_pos], name.c_str(), name.size()+1);
							nodes[idx].setName(&stringbuffer[stringbuffer_pos]);
							stringbuffer_pos+=name.size()+1;
							nodes[idx].setId(lines);

							char ch=data[0];
							nodes[idx].setType(ch);
							if(ch=='f')
							{
								std::string sdata=data.substr(1);
								std::string filesize=getuntil(" ", sdata);
								std::string last_mod=getafter(" ", sdata);

								_i64 ifilesize=os_atoi64(filesize);
								_i64 ilast_mod=os_atoi64(last_mod);

								char* ndata=&stringbuffer[stringbuffer_pos];
								memcpy(&stringbuffer[stringbuffer_pos], &ifilesize, sizeof(_i64));
								stringbuffer_pos+=sizeof(_i64);
								memcpy(&stringbuffer[stringbuffer_pos], &ilast_mod, sizeof(_i64));
								stringbuffer_pos+=sizeof(_i64);

								nodes[idx].setData(ndata);
							}
							else
							{
								std::string sdata=data.substr(1);
								std::string last_mod=getafter(" ", sdata);

								_i64 ilast_mod=os_atoi64(last_mod);
								char* ndata=&stringbuffer[stringbuffer_pos];

								memcpy(&stringbuffer[stringbuffer_pos], &ilast_mod, sizeof(_i64));
								stringbuffer_pos+=sizeof(_i64);

								nodes[idx].setData(ndata);
							}

							if(firstChild)
							{
								lastNodes.push(&nodes[idx]);
								firstChild=false;
							}
							else
							{
								lastNodes.top()->setNextSibling(&nodes[idx]);
								lastNodes.pop();
								lastNodes.push(&nodes[idx]);
							}

							if(!parents.empty())
							{
								parents.top()->incrementNumChildren();
								nodes[idx].setParent(parents.top());
							}

							if(ch=='d')
							{
								parents.push(&nodes[idx]);
								firstChild=true;
							}

							++idx;
						}
						else
						{
							if(!parents.empty())
							{
								parents.pop();
							}
							else
							{
								Log("TreeReader: parents empty");
								return false;
							}
							if(!firstChild)
							{
								if(lastNodes.empty())
								{
									Log("TreeReader: lastNodes empty");
									return false;
								}
								lastNodes.top()->setNextSibling(NULL);
								lastNodes.pop();
							}
							firstChild=false;
						}

						name.clear();
						data.clear();
						state=0;
						++lines;
					}
			}		
		}
	}
	while(read==buffer.size());

	assert(idx == nodes.size());
	assert(stringbuffer_pos == stringbuffer.size());

	nodes[0].setNextSibling(NULL);

	return true;
}

void TreeReader::Log(const std::string &str)
{
	Server->Log(str, LL_ERROR);
}

std::vector<TreeNode> * TreeReader::getNodes(void)
{
	return &nodes;
}