/**********************************************************************
 * LeechCraft - modular cross-platform feature rich internet client.
 * Copyright (C) 2006-2014  Georg Rudoy
 *
 * Boost Software License - Version 1.0 - August 17th, 2003
 *
 * Permission is hereby granted, free of charge, to any person or organization
 * obtaining a copy of the software and accompanying documentation covered by
 * this license (the "Software") to use, reproduce, display, distribute,
 * execute, and transmit the Software, and to prepare derivative works of the
 * Software, and to permit third-parties to whom the Software is furnished to
 * do so, all subject to the following:
 *
 * The copyright notices in the Software and this entire statement, including
 * the above license grant, this restriction and the following disclaimer,
 * must be included in all copies of the Software, in whole or in part, and
 * all derivative works of the Software, unless such copies or derivative
 * works are solely in the form of machine-executable object code generated by
 * a source language processor.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
 * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **********************************************************************/

#include "lineparser.h"
#include <QtDebug>
#include "filter.h"

namespace LeechCraft
{
namespace Poshuku
{
namespace CleanWeb
{
	LineParser::LineParser (Filter *f)
	: Filter_ (f)
	, Total_ (0)
	, Success_ (0)
	{
	}

	int LineParser::GetTotal () const
	{
		return Total_;
	}

	int LineParser::GetSuccess () const
	{
		return Success_;
	}

	namespace
	{
		QStringList ParseOptions (QStringList options, FilterOption& f)
		{
			if (options.removeAll ("match-case"))
				f.Case_ = Qt::CaseSensitive;

			if (options.removeAll ("third-party"))
				f.AbortForeign_ = true;

			if (options.removeAll ("~third-party"))
				f.AbortForeign_ = false;

			Q_FOREACH (const QString& option, options)
				if (option.startsWith ("domain="))
				{
					const auto& domain = option.mid (7);
					if (domain.startsWith ('~'))
						f.NotDomains_ << domain.mid (1);
					else
						f.Domains_ << domain;
					options.removeAll (option);
				}

			auto handleSubobj = [&options, &f] (const QString& name, FilterOption::MatchObject obj)
			{
				if (options.removeAll (name))
					f.MatchObjects_ |= obj;
				if (options.removeAll ("~" + name))
					f.MatchObjects_ |= ~FilterOption::MatchObjects (obj);
			};
			handleSubobj ("image", FilterOption::MatchObject::Image);
			handleSubobj ("script", FilterOption::MatchObject::Script);
			handleSubobj ("object", FilterOption::MatchObject::Object);
			handleSubobj ("stylesheet", FilterOption::MatchObject::CSS);
			handleSubobj ("object-subrequest", FilterOption::MatchObject::ObjSubrequest);
			handleSubobj ("subdocument", FilterOption::MatchObject::Subdocument);
			handleSubobj ("xmlhttprequest", FilterOption::MatchObject::AJAX);
			handleSubobj ("popup", FilterOption::MatchObject::Popup);

			return options;
		}

		void ParseWithOption (QString actualLine, FilterOption f, QList<FilterItem_ptr>& items)
		{
			if (actualLine.startsWith ('/') &&
					actualLine.endsWith ('/'))
			{
				actualLine = actualLine.mid (1, actualLine.size () - 2);
				f.MatchType_ = FilterOption::MTRegexp;
				const FilterItem_ptr item (new FilterItem
						{
							Util::RegExp (actualLine, f.Case_),
							{},
							f
						});
				items << item;
				return;
			}

			while (!actualLine.isEmpty () && actualLine.at (0) == '*')
				actualLine = actualLine.mid (1);
			while (!actualLine.isEmpty () && actualLine.at (actualLine.size () - 1) == '*')
				actualLine.chop (1);

			if (actualLine.startsWith ("||"))
			{
				auto spawned = "." + actualLine.mid (2);
				if (f.Case_ == Qt::CaseInsensitive)
					spawned = spawned.toLower ();
				f.MatchType_ = FilterOption::MTPlain;
				ParseWithOption (spawned, f, items);

				actualLine = actualLine.mid (2);
				actualLine.prepend ('/');
			}

			if (actualLine.endsWith ('|') && actualLine.startsWith ('|'))
				actualLine = actualLine.mid (1, actualLine.size () - 2);
			else if (actualLine.endsWith ('|'))
			{
				actualLine.chop (1);
				if (actualLine.contains ('*'))
				{
					actualLine.prepend ('*');
					f.MatchType_ = FilterOption::MTWildcard;
				}
				else
					f.MatchType_ = FilterOption::MTEnd;
			}
			else if (actualLine.startsWith ('|'))
			{
				actualLine.remove (0, 1);
				if (actualLine.contains ('*'))
				{
					actualLine.append ('*');
					f.MatchType_ = FilterOption::MTWildcard;
				}
				else
					f.MatchType_ = FilterOption::MTBegin;
			}
			else
			{
				if (actualLine.contains ('*'))
				{
					actualLine.prepend ('*');
					actualLine.append ('*');
					f.MatchType_ = FilterOption::MTWildcard;
				}
				else
					f.MatchType_ = FilterOption::MTPlain;
			}

			if (f.MatchType_ == FilterOption::MTWildcard)
				actualLine.replace ('?', "\\?");

			if (f.MatchType_ != FilterOption::MTRegexp && actualLine.contains ('^'))
			{
				if (!Util::RegExp::IsFast ())
					return;

				actualLine.replace ('*', ".*");
				if (f.MatchType_ != FilterOption::MTWildcard)
					actualLine.replace ('?', "\\?");
				switch (f.MatchType_)
				{
				case FilterOption::MTEnd:
					actualLine.prepend (".*");
					break;
				case FilterOption::MTBegin:
					actualLine.append (".*");
					break;
				case FilterOption::MTPlain:
					actualLine.prepend (".*");
					actualLine.append (".*");
					break;
				case FilterOption::MTWildcard:
				case FilterOption::MTRegexp:
					break;
				}
				actualLine.replace ('^', "[^a-zA-Z0-9_\\.%-]");
				f.MatchType_ = FilterOption::MTRegexp;
			}

			const auto& casedOrigStr = (f.Case_ == Qt::CaseSensitive ?
					actualLine :
					actualLine.toLower ()).toUtf8 ();
			const auto& itemRx = f.MatchType_ == FilterOption::MTRegexp ?
					Util::RegExp (actualLine, f.Case_) :
					Util::RegExp ();
			const FilterItem_ptr item (new FilterItem
					{
						itemRx,
						casedOrigStr,
						f
					});
			items << item;
		}
	}

	void LineParser::operator() (const QString& line)
	{
		if (line.startsWith ('!'))
			return;

		++Total_;

		QString actualLine = line;
		QStringList additionalLines;
		FilterOption f = FilterOption ();

		if (actualLine.contains ("##"))
		{
			const auto& split = actualLine.split ("##");
			if (split.size () != 2)
			{
				qWarning () << Q_FUNC_INFO
					<< "incorrect usage of ##-pattern:"
					<< split.size ()
					<< line;
				return;
			}

			actualLine = split.at (0);
			f.HideSelector_ = split.at (1);
		}

		if (actualLine.contains ('$'))
		{
			const auto& splitted = actualLine.split ('$', QString::SkipEmptyParts);

			if (splitted.size () != 2)
			{
				qWarning () << Q_FUNC_INFO
					<< "incorrect usage of $-pattern:"
					<< splitted.size ()
					<< actualLine;
				return;
			}

			actualLine = splitted.at (0);

			const auto& remaining = ParseOptions (splitted.at (1).split (',', QString::SkipEmptyParts), f);
			if (remaining.size ())
			{
				/*
				qWarning () << Q_FUNC_INFO
						<< "unsupported options for filter"
						<< actualLine
						<< remaining;
						*/
				return;
			}
		}

		bool white = false;
		if (actualLine.startsWith ("@@"))
		{
			actualLine.remove (0, 2);
			white = true;
		}

		ParseWithOption (actualLine, f, white ? Filter_->Exceptions_ : Filter_->Filters_);

		++Success_;
	}
}
}
}
