/*
    Copyright (C) 2012 Bartosz Broda, Jacek Bochenek, Łukasz Biłeńkij, 
    Damian Jaworski, Wojciech Mazur, Dominik Piasecki, Maciej Piasecki, 
    Radosław Ramocki
    
    This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser 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 LICENSE and COPYING files for more details.
*/

#include "../lex_headers.h"
#include "MEParser.h"

namespace LexicalUnits
{
	namespace MEParser
	{
		boost::regex  MEParser::m_AnyTagOpenening ( "([^<]*)<(.*)", boost::regex::icase );
		boost::regex  MEParser::m_AnyTagClosing ( "([^>]*)>(.*)", boost::regex::icase );

		boost::regex MEParser::m_MakroTagOpen ( ".*< *macros( .*)?>(.*)", boost::regex::icase );
		boost::regex MEParser::m_MakroTagClose ( ".*</macros( .*)?>(.*)", boost::regex::icase );
		boost::regex MEParser::m_MTagOpen ( ".*< *m( .*)?>(.*)", boost::regex::icase );
		boost::regex MEParser::m_MTagClose ( ".*</m( .*)?>(.*)", boost::regex::icase );
		boost::regex MEParser::m_NameTag ( ".*< *name( .*)*>([^<>]*)</name( .*)*>(.*)", boost::regex::icase );
		boost::regex MEParser::m_VarTag ( ".*< *var( .*)*>([^<>]*)</var( .*)*>(.*)", boost::regex::icase );
		boost::regex MEParser::m_ConTagOpen ( ".*< *con( .*)?>(.*)", boost::regex::icase );
		boost::regex MEParser::m_ConTagClose ( "(.*)</con( .*)?>(.*)", boost::regex::icase );

		boost::regex MEParser::m_LUListTagOpen ( ".*< *LUList( .*)?>(.*)", boost::regex::icase );
		boost::regex MEParser::m_LUListTagClose ( ".*</LUList( .*)?>(.*)", boost::regex::icase );
		boost::regex MEParser::m_LUTagOpen ( ".*< *LU( .*)?>(.*)", boost::regex::icase );
		boost::regex MEParser::m_LUTagClose ( ".*</LU( .*)?>(.*)", boost::regex::icase );
		boost::regex MEParser::m_LuBaseTag( ".*< *LUbase( .*)*>([^<>]*)</LUbase( .*)*>(.*)", boost::regex::icase );
		boost::regex MEParser::m_TTag( ".*< *t( .*)*>([^<>]*)</t( .*)*>(.*)", boost::regex::icase );
		boost::regex MEParser::m_PatTag( ".*< *pat( .*)*>([^<>]*)</pat( .*)*>(.*)", boost::regex::icase );
		boost::regex MEParser::m_HTag( ".*< *h( .*)*>([^<>]*)</h( .*)*>(.*)", boost::regex::icase );
		boost::regex MEParser::m_FTag( ".*< *f( .*)*>([^<>]*)</f( .*)*>(.*)", boost::regex::icase );


		std::vector<LexicalUnitPtr> MEParser::GetUnits()
		{
			return m_vUnits;
		}

		void MEParser::InstantiateUnits()
		{
			for( size_t i = 0 ; i < m_vStubs.size() ;i++ )
			{
				UnitStubPtr stub = m_vStubs[i];
				MakroPtr makro = m_MakroNameToMakro[stub->GetMakro()];


				LexicalUnitPtr unit = makro->InstantiateUnit( stub ) ;


				if( unit == NULL )
					std::cerr << "Failed to instatiate unit - " << Corpus::CWordDictionary::GetInstance()->GetAt(stub->GetBaseIndex()) << std::endl;
				else
					m_vUnits.push_back( unit );
			}

			if( m_vUnits.size() != m_vStubs.size() )
				std::cerr << "Failed to instatiate " << m_vStubs.size() - m_vUnits.size()<< " unit stubs\n";
			else
				std::cout << "Instantiated " << m_vUnits.size() << " multiword units." << std::endl;

		}

		void MEParser::Parse ( std::istream & in )
		{
			std::string chunk;
			bool load = true;

			const size_t BUF_MAX = 4096;

			char line[BUF_MAX];

			std::string eaten;

			while ( in && !in.eof() )
			{
				if ( load )
				{
					in.getline ( line, BUF_MAX );
					chunk = line;
					load = true;
				}

				boost::smatch match;
				
				if ( boost::regex_match ( chunk, match, m_MakroTagOpen ) )
				{
					eaten = match[2];
					chunk = ParseMakros ( in, eaten );

					if ( !chunk.empty() && boost::regex_match ( chunk, match, m_AnyTagOpenening ) )
						load = false;

				}
				else
					if ( boost::regex_match ( chunk, match, m_LUListTagOpen ) )
					{
						eaten = match[2];
						chunk = ParseLUList ( in, eaten );

						if ( !chunk.empty() && boost::regex_match ( chunk, match, m_AnyTagOpenening ) )
							load = false;
					}

			}

			InstantiateUnits();

		}

		std::string  MEParser::ParseMakros ( std::istream &in, std::string &eaten )
		{
			std::string chunk = eaten;
			bool firstLine = true;
			bool load = true;

			const size_t BUF_MAX = 4096;

			char line[BUF_MAX];

			while ( in && !in.eof() )
			{
				if ( !firstLine && load )
				{
					in.getline ( line, BUF_MAX );
					chunk = line;
					load = true;
				}
				else
					firstLine = false;

				boost::smatch match;

				if ( boost::regex_match ( chunk, match, m_MTagOpen ) )
				{
					eaten = match[2];
					chunk = ParseSingleMakro ( in, eaten );

					if ( !chunk.empty() && boost::regex_match ( chunk, match, m_AnyTagOpenening ) )
						load = false;
				}
				else
					if ( boost::regex_match ( chunk, match, m_MakroTagClose ) )
					{
						eaten = match[2];
						return eaten;
					}

			}//while ( in && !in.eof() )

			return "";
		}

		std::string  MEParser::ParseSingleMakro ( std::istream &in, std::string &eaten )
		{
			std::string chunk = eaten;
			bool firstLine = true;
			bool load = true;
			const size_t BUF_MAX = 4096;
			char line[BUF_MAX];
			std::string name;
			bool nameset = false;
			std::string var;
			bool varset = false;
			std::vector<std::string> conditionLines;
			bool conset = false;

			while ( in && !in.eof() )
			{
				if ( !firstLine && load )
				{
					in.getline ( line, BUF_MAX );
					chunk = line;
					load = true;
				}
				else
					firstLine = false;

				boost::smatch match;

				if ( boost::regex_match ( chunk, match, m_NameTag ) )
				{
					name = match[2];

					if ( ! boost::trim_copy ( name ).empty() )
						nameset = true;

					chunk = match[4];

					if ( !chunk.empty() && boost::regex_match ( chunk, match, m_AnyTagOpenening ) )
						load = false;

					continue;
				}

				if ( boost::regex_match ( chunk, match, m_VarTag ) )
				{
					var = match[2];

					if ( !boost::trim_copy ( var ).empty() )
						varset = true;

					chunk = match[4];

					if ( !chunk.empty() && boost::regex_match ( chunk, match, m_AnyTagOpenening ) )
						load = false;
					continue;
				}

				if ( boost::regex_match ( chunk, match, m_ConTagOpen ) )
				{
					eaten = match[2];
					chunk = ParseConLinesMakro ( in, eaten, conditionLines );

					if( conditionLines.size() )
						conset =  true;

					if ( !chunk.empty() && boost::regex_match ( chunk, match, m_AnyTagOpenening ) )
						load = false;

					continue;
				}

				if ( boost::regex_match ( chunk, match, m_MTagClose ) )
				{
					if ( nameset && conset && varset )
					{
						if ( m_MakroNameToMakro.count ( name ) )
							throw std::runtime_error ( "Duplicated makro: " + name );

						m_MakroNameToMakro[name] = MakroPtr ( new Makro ( name, var, conditionLines ) );
					}
					else
					{
						if ( nameset && !varset )
							std::cerr << "No varaibles for makro: " << name << std::endl;

						if ( nameset && !conset )
							std::cerr << "No varaibles for makro: " << name << std::endl;eaten = match[2];

						if( !nameset)
							std::cerr << "No name for some makro set.\n";
					}

					return eaten;
				}

			}//while ( in && !in.eof() )

			return "";
		}

		std::string MEParser::ParseConLinesMakro ( std::istream &in,
				std::string &eaten, std::vector<std::string> &opLines )
		{
			opLines.clear();
			std::string chunk = eaten;
			bool firstLine = true;

			const size_t BUF_MAX = 4096;

			char line[BUF_MAX];

			while ( in && !in.eof() )
			{
				if ( !firstLine )
				{
					in.getline ( line, BUF_MAX );
					chunk = line;
				}
				else
					firstLine = false;



				boost::smatch match;

				if ( boost::regex_match ( chunk, match, m_ConTagClose ) )
				{
					if ( !std::string ( match[1] ).empty() )
						opLines.push_back ( match[1] );

					return match[3];
				}

				if ( !chunk.empty() )
					opLines.push_back ( chunk );

			}//while ( in && !in.eof() )

			return "";
		}

		std::string  MEParser::ParseLUList ( std::istream &in, std::string &eaten )
		{
			std::string chunk = eaten;
			bool firstLine = true;
			bool load = true;

			const size_t BUF_MAX = 4096;

			char line[BUF_MAX];

			while ( in && !in.eof() )
			{
				if ( !firstLine && load )
				{
					in.getline ( line, BUF_MAX );
					chunk = line;
					load = true;
				}
				else
					firstLine = false;

				boost::smatch match;

				if ( boost::regex_match ( chunk, match, m_LUTagOpen ) )
				{
					eaten = match[2];
					chunk = ParseSingleLU ( in, eaten );

					if ( !chunk.empty() && boost::regex_match ( chunk, match, m_AnyTagOpenening ) )
						load = false;
				}
				else
					if ( boost::regex_match ( chunk, match, m_LUListTagClose) )
				{
					eaten = match[2];
					return eaten;
				}

			}//while ( in && !in.eof() )

			return "";
		}

		std::string  MEParser::ParseSingleLU ( std::istream &in, std::string &eaten )
		{
			std::string chunk = eaten;
			bool firstLine = true;
			bool load = true;
			const size_t BUF_MAX = 4096;
			char line[BUF_MAX];
			std::string lubase, t, pat, h, f;
			bool lubaseSet = false, tSet = false, patSet = false, hSet = false, fSet = false;
			while ( in && !in.eof() )
			{
				if ( !firstLine && load )
				{
					in.getline ( line, BUF_MAX );
					chunk = line;
					load = true;
				}
				else
					firstLine = false;

				boost::smatch match;

				if ( boost::regex_match ( chunk, match, m_LuBaseTag ) )
				{
					lubase = match[2];
                                        //std::cout << lubase << std::endl;
					if ( ! boost::trim_copy ( lubase ).empty() )
						lubaseSet = true;

					chunk = match[4];

					if ( !chunk.empty() && boost::regex_match ( chunk, match, m_AnyTagOpenening ) )
						load = false;

					continue;
				}

				if ( boost::regex_match ( chunk, match, m_TTag) )
				{
					t = match[2];

					if ( ! boost::trim_copy ( t ).empty() )
						tSet = true;

					chunk = match[4];

					if ( !chunk.empty() && boost::regex_match ( chunk, match, m_AnyTagOpenening ) )
						load = false;

					continue;
				}

				if ( boost::regex_match ( chunk, match, m_PatTag) )
				{
					pat= match[2];

					if ( ! boost::trim_copy ( pat ).empty() )
						patSet = true;

					chunk = match[4];

					if ( !chunk.empty() && boost::regex_match ( chunk, match, m_AnyTagOpenening ) )
						load = false;

					continue;
				}

				if ( boost::regex_match ( chunk, match, m_HTag) )
				{
					h= match[2];

					if ( ! boost::trim_copy ( h ).empty() )
						hSet = true;

					chunk = match[4];

					if ( !chunk.empty() && boost::regex_match ( chunk, match, m_AnyTagOpenening ) )
						load = false;

					continue;
				}

				if ( boost::regex_match ( chunk, match, m_FTag) )
				{
					f= match[2];

					if ( ! boost::trim_copy ( f ).empty() )
						fSet = true;

					chunk = match[4];

					if ( !chunk.empty() && boost::regex_match ( chunk, match, m_AnyTagOpenening ) )
						load = false;

					continue;
				}


				if ( boost::regex_match ( chunk, match, m_LUTagClose ) )
				{
					if ( lubaseSet && tSet && patSet && hSet && fSet)
					{
						UnitStubPtr ptr(new UnitStub(lubase, pat, t, h, f) );
						m_vStubs.push_back(ptr);
					}
					else
					{
						std::cerr << "Some unit stub don't have compleate information. Information set: "
								<< lubase << ", " << t << ", " << pat << ", " << h  << ", " << f << std::endl;
					}

					return eaten;
				}

			}//while ( in && !in.eof() )

			return "";
		}

		void MEParser::Parse (const char * filename )
		{
			std::ifstream file ( filename );

			if ( file )
				Parse ( file );
			else
				throw std::runtime_error( std::string("Cannot open file with ME: ")+filename );

		}
	}//namespace MEParser
}//namespace LexicalUnits
