/*
 * Copyright (c) 2010-2019 Belledonne Communications SARL.
 *
 * This file is part of Liblinphone.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include <set>

#include <belr/abnf.h>
#include <belr/grammarbuilder.h>
#include "bctoolbox/utils.hh"

#include "linphone/utils/utils.h"

#include "content/content-type.h"
#include "logger/logger.h"
#include "object/object-p.h"

#include "cpim-parser.h"

// =============================================================================

using namespace std;

LINPHONE_BEGIN_NAMESPACE

namespace {
	string CpimGrammar("cpim_grammar");
}

namespace Cpim {
	class Node {
	public:
		virtual ~Node () = default;
	};

	class HeaderNode : public Node {
	public:
		HeaderNode () = default;

		explicit HeaderNode (const Header &header) : mName(header.getName()), mValue(header.getValue()) {
			const GenericHeader *genericHeader = dynamic_cast<const GenericHeader *>(&header);
			if (genericHeader) {
				for (const auto &parameter : *genericHeader->getParameters())
					mParameters += ";" + parameter.first + "=" + parameter.second;
			}
		}

		string getName () const {
			return mName;
		}

		void setName (const string &name) {
			static const set<string> reserved = {
				"From", "To", "cc", "DateTime", "Subject", "NS", "Require"
			};

			if (reserved.find(name) == reserved.end())
				mName = name;
		}

		string getParameters () const {
			return mParameters;
		}

		void setParameters (const string &parameters) {
			mParameters = parameters;
		}

		string getValue () const {
			return mValue;
		}

		void setValue (const string &value) {
			mValue = value;
		}

		virtual shared_ptr<Header> createHeader () const;

		virtual bool isValid () const;

	private:
		string mName;
		string mValue;
		string mParameters;
	};

	bool HeaderNode::isValid () const {
		return !mName.empty() && !mValue.empty();
	}

	shared_ptr<Header> HeaderNode::createHeader () const {
		if (!isValid())
			return nullptr;

		shared_ptr<GenericHeader> genericHeader = make_shared<GenericHeader>();
		genericHeader->setName(mName);

		for (const auto &parameter : bctoolbox::Utils::split(mParameters, ';')) {
			size_t equalIndex = parameter.find('=');
			if (equalIndex != string::npos)
				genericHeader->addParameter(parameter.substr(0, equalIndex), parameter.substr(equalIndex + 1));
		}

		genericHeader->setValue(mValue);
		return genericHeader;
	}

	// -------------------------------------------------------------------------

	class ContactHeaderNode : public HeaderNode {
	public:
		ContactHeaderNode () = default;

		string getFormalName () const {
			return mFormalName;
		}

		void setFormalName (const string &formalName) {
			mFormalName = formalName;
		}

		string getUri () const {
			return mUri;
		}

		void setUri (const string &uri) {
			mUri = uri;
		}

		bool isValid () const override;

	private:
		string mFormalName;
		string mUri;
	};

	bool ContactHeaderNode::isValid () const {
		return !mUri.empty();
	}

	// -------------------------------------------------------------------------

	class FromHeaderNode : public ContactHeaderNode {
	public:
		FromHeaderNode () = default;

		explicit FromHeaderNode (const Header &header) {
			const FromHeader *fromHeader = dynamic_cast<const FromHeader *>(&header);
			if (fromHeader) {
				setFormalName(fromHeader->getFormalName());
				setUri(fromHeader->getUri());
			}
		}

		shared_ptr<Header> createHeader () const override;
	};

	shared_ptr<Header> FromHeaderNode::createHeader () const {
		if (!isValid())
			return nullptr;

		return make_shared<FromHeader>(getUri(), getFormalName());
	}

	// -------------------------------------------------------------------------

	class ToHeaderNode : public ContactHeaderNode {
	public:
		ToHeaderNode () = default;

		explicit ToHeaderNode (const Header &header) {
			const ToHeader *toHeader = dynamic_cast<const ToHeader *>(&header);
			if (toHeader) {
				setFormalName(toHeader->getFormalName());
				setUri(toHeader->getUri());
			}
		}

		shared_ptr<Header> createHeader () const override;
	};

	shared_ptr<Header> ToHeaderNode::createHeader () const {
		if (!isValid())
			return nullptr;

		return make_shared<ToHeader>(getUri(), getFormalName());
	}

	// -------------------------------------------------------------------------

	class CcHeaderNode : public ContactHeaderNode {
	public:
		CcHeaderNode () = default;

		explicit CcHeaderNode (const Header &header) {
			const CcHeader *ccHeader = dynamic_cast<const CcHeader *>(&header);
			if (ccHeader) {
				setFormalName(ccHeader->getFormalName());
				setUri(ccHeader->getUri());
			}
		}

		shared_ptr<Header> createHeader () const override;
	};

	shared_ptr<Header> CcHeaderNode::createHeader () const {
		if (!isValid())
			return nullptr;

		return make_shared<CcHeader>(getUri(), getFormalName());
	}

	// -------------------------------------------------------------------------

	class DateTimeOffsetNode : public Node {
		friend class DateTimeHeaderNode;

	public:
		DateTimeOffsetNode () {
			mSign = "Z";
		}

		void setHour (const string &value) {
			mHour = Utils::stoi(value);
		}

		void setMinute (const string &value) {
			mMinute = Utils::stoi(value);
		}

		void setSign (const string &value) {
			mSign = value;
		}

	private:
		string mSign;
		int mHour;
		int mMinute;
	};

	class DateTimeHeaderNode : public HeaderNode {
	public:
		DateTimeHeaderNode () = default;

		explicit DateTimeHeaderNode (const Header &header) {
			const DateTimeHeader *dateTimeHeader = dynamic_cast<const DateTimeHeader *>(&header);
			if (dateTimeHeader) {
				setTime(dateTimeHeader->getTimeStruct());
				setTimeOffset(dateTimeHeader->getTimeOffset());
				setSignOffset(dateTimeHeader->getSignOffset());
			}
		}

		struct tm getTime () const {
			return mTime;
		}

		void setTime (const struct tm &time) {
			mTime = time;
		}

		struct tm getTimeOffset () const {
			return mTimeOffset;
		}

		void setTimeOffset (const struct tm &timeOffset) {
			mTimeOffset = timeOffset;
		}

		string getSignOffset () const {
			return mSignOffset;
		}

		void setSignOffset (const string &signOffset) {
			mSignOffset = signOffset;
		}

		void setYear (const string &value) {
			mTime.tm_year = Utils::stoi(value);
		}

		void setMonth (const string &value) {
			mTime.tm_mon = Utils::stoi(value) - 1;
		}

		void setMonthDay (const string &value) {
			mTime.tm_mday = Utils::stoi(value);
		}

		void setHour (const string &value) {
			mTime.tm_hour = Utils::stoi(value);
		}

		void setMinute (const string &value) {
			mTime.tm_min = Utils::stoi(value);
		}

		void setSecond (const string &value) {
			mTime.tm_sec = Utils::stoi(value);
		}

		void setOffset (const shared_ptr<DateTimeOffsetNode> &offset) {
			mTimeOffset.tm_hour = offset->mHour;
			mTimeOffset.tm_min = offset->mMinute;
			mSignOffset = offset->mSign;
		}

		bool isValid () const override;

		shared_ptr<Header> createHeader() const override;

	private:
		tm mTime;
		tm mTimeOffset;
		string mSignOffset;
	};

	bool DateTimeHeaderNode::isValid () const {
		static const int daysInMonth[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

		// Check date.
		const bool isLeapYear = (mTime.tm_year % 4 == 0 && mTime.tm_year % 100 != 0) || mTime.tm_year % 400 == 0;

		if (mTime.tm_mon < 1 || mTime.tm_mon > 12)
			return false;

		if (mTime.tm_mday < 1 || (mTime.tm_mon == 2 && isLeapYear ? mTime.tm_mday > 29 : mTime.tm_mday > daysInMonth[mTime.tm_mon]))
			return false;

		// Check time.
		if (mTime.tm_hour > 24 || mTime.tm_min > 59 || mTime.tm_sec > 60)
			return false;

		// Check num offset.
		if (mSignOffset != "Z") {
			if (mTimeOffset.tm_hour > 24 || mTime.tm_min > 59)
				return false;
		}

		return true;
	}

	shared_ptr<Header> DateTimeHeaderNode::createHeader () const {
		if (!isValid())
			return nullptr;

		return make_shared<DateTimeHeader>(getTime(), getTimeOffset(), getSignOffset());
	}

	// -------------------------------------------------------------------------

	class SubjectHeaderNode : public HeaderNode {
	public:
		SubjectHeaderNode () = default;

		explicit SubjectHeaderNode (const Header &header) {
			const SubjectHeader *subjectHeader = dynamic_cast<const SubjectHeader *>(&header);
			if (subjectHeader) {
				setLanguage(subjectHeader->getLanguage());
				setSubject(subjectHeader->getSubject());
			}
		}

		string getLanguage () const {
			return mLanguage;
		}

		void setLanguage (const string &language) {
			mLanguage = language;
		}

		string getSubject () const {
			return mSubject;
		}

		void setSubject (const string &subject) {
			mSubject = subject;
		}

		bool isValid () const override;

		shared_ptr<Header> createHeader () const override;

	private:
		string mLanguage;
		string mSubject;
	};

	bool SubjectHeaderNode::isValid () const {
		return !mSubject.empty();
	}

	shared_ptr<Header> SubjectHeaderNode::createHeader () const {
		if (!isValid())
			return nullptr;

		return make_shared<SubjectHeader>(getSubject(), getLanguage());
	}

	// -------------------------------------------------------------------------

	class NsHeaderNode : public HeaderNode {
	public:
		NsHeaderNode () = default;

		explicit NsHeaderNode (const Header &header) {
			const NsHeader *nsHeader = dynamic_cast<const NsHeader *>(&header);
			if (nsHeader) {
				setPrefixName(nsHeader->getPrefixName());
				setUri(nsHeader->getUri());
			}
		}

		string getPrefixName () const {
			return mPrefixName;
		}

		void setPrefixName (const string &prefixName) {
			mPrefixName = prefixName;
		}

		string getUri () const {
			return mUri;
		}

		void setUri (const string &uri) {
			mUri = uri;
		}

		bool isValid () const override;

		shared_ptr<Header> createHeader () const override;

	private:
		string mPrefixName;
		string mUri;
	};

	bool NsHeaderNode::isValid () const {
		return !mUri.empty();
	}

	shared_ptr<Header> NsHeaderNode::createHeader () const {
		if (!isValid())
			return nullptr;

		return make_shared<NsHeader>(getUri(), getPrefixName());
	}

	// -------------------------------------------------------------------------

	class RequireHeaderNode : public HeaderNode {
	public:
		RequireHeaderNode () = default;

		explicit RequireHeaderNode (const Header &header) {
			const RequireHeader *requireHeader = dynamic_cast<const RequireHeader *>(&header);
			if (requireHeader) {
				for (const auto &header : requireHeader->getHeaderNames()) {
					if (header != requireHeader->getHeaderNames().front())
						mHeaderNames += ",";
					mHeaderNames += header;
				}
			}
		}

		string getHeaderNames () const {
			return mHeaderNames;
		}

		void setHeaderNames (const string &headerNames) {
			mHeaderNames = headerNames;
		}

		bool isValid () const override;

		shared_ptr<Header> createHeader () const override;

	private:
		string mHeaderNames;
	};

	bool RequireHeaderNode::isValid () const {
		return !mHeaderNames.empty();
	}

	shared_ptr<Header> RequireHeaderNode::createHeader () const  {
		if (!isValid())
			return nullptr;

		return make_shared<RequireHeader>(mHeaderNames);
	}

	// -------------------------------------------------------------------------

	class ListHeaderNode :
		public Node,
		public list<shared_ptr<HeaderNode> > {
		public:
		void push_back(const shared_ptr<HeaderNode> &node){
			list<shared_ptr<HeaderNode>>::push_back(node);
		}
	};

	// -------------------------------------------------------------------------

	class MessageNode : public Node {
	public:
		void addMessageHeaders (const shared_ptr<ListHeaderNode> &headers) {
			for (const auto &headerNode : *headers) {
				mMessageHeaders.push_back(headerNode);
			}
		}

		void addContentHeaders (const shared_ptr<ListHeaderNode> &headers) {
			for (const auto &headerNode : *headers) {
				mContentHeaders.push_back(headerNode);
			}
		}

		// Warning: Call this function one time!
		shared_ptr<Message> createMessage () const {
			if (mContentHeaders.empty() || mMessageHeaders.empty()) {
				lWarning() << "Bad headers lists size.";
				return nullptr;
			}

			//TODO: Verify all headers from other namespaces

			const shared_ptr<Message> message = make_shared<Message>();

			// Add message headers.
			for (const auto &headerNode : mMessageHeaders) {
				string ns = "";

				string::size_type n = headerNode->getName().find(".");
				if (n != string::npos) {
					ns = headerNode->getName().substr(0, n);
					headerNode->setName(headerNode->getName().substr(n + 1));
				}

				const shared_ptr<const Header> header = headerNode->createHeader();
				if (!header)
					return nullptr;

				message->addMessageHeader(*header, ns);
			}

			// Add content headers.
			for (const auto &headerNode : mContentHeaders) {
				const shared_ptr<const Header> header = headerNode->createHeader();
				if (!header)
					return nullptr;

				message->addContentHeader(*header);
			}

			return message;
		}

	private:
		list<shared_ptr<HeaderNode>> mContentHeaders;
		list<shared_ptr<HeaderNode>> mMessageHeaders;
	};
}

// -----------------------------------------------------------------------------

class Cpim::ParserPrivate : public ObjectPrivate {
public:
	
	shared_ptr<belr::Parser<shared_ptr<Node> >> parser;
};

Cpim::Parser::Parser () : Singleton(*new ParserPrivate) {
	L_D();
	
	shared_ptr<belr::Grammar> grammar = belr::GrammarLoader::get().load(CpimGrammar);
	if (!grammar)
		lFatal() << "Unable to load CPIM grammar.";
	d->parser = make_shared<belr::Parser<shared_ptr<Node>>>(grammar);
	
	d->parser->setHandler("Message", belr::make_fn(make_shared<MessageNode>))
		->setCollector("Message-headers", belr::make_sfn(&MessageNode::addMessageHeaders))
		->setCollector("Content-headers", belr::make_sfn(&MessageNode::addContentHeaders));

	d->parser->setHandler("Message-headers", belr::make_fn(make_shared<ListHeaderNode>))
		->setCollector("Header", belr::make_sfn(&ListHeaderNode::push_back))
		->setCollector("From-header", belr::make_sfn(&ListHeaderNode::push_back))
		->setCollector("To-header", belr::make_sfn(&ListHeaderNode::push_back))
		->setCollector("DateTime-header", belr::make_sfn(&ListHeaderNode::push_back))
		->setCollector("cc-header", belr::make_sfn(&ListHeaderNode::push_back))
		->setCollector("Subject-header", belr::make_sfn(&ListHeaderNode::push_back))
		->setCollector("NS-header", belr::make_sfn(&ListHeaderNode::push_back))
		->setCollector("Require-header", belr::make_sfn(&ListHeaderNode::push_back));

	d->parser->setHandler("Content-headers", belr::make_fn(make_shared<ListHeaderNode>))
		->setCollector("Header", belr::make_sfn(&ListHeaderNode::push_back));

	d->parser->setHandler("Header", belr::make_fn(make_shared<HeaderNode>))
		->setCollector("Header-name", belr::make_sfn(&HeaderNode::setName))
		->setCollector("Header-value", belr::make_sfn(&HeaderNode::setValue))
		->setCollector("Header-parameters", belr::make_sfn(&HeaderNode::setParameters));

	d->parser->setHandler("From-header", belr::make_fn(make_shared<FromHeaderNode>))
		->setCollector("Formal-name", belr::make_sfn(&FromHeaderNode::setFormalName))
		->setCollector("URI", belr::make_sfn(&FromHeaderNode::setUri));

	d->parser->setHandler("To-header", belr::make_fn(make_shared<ToHeaderNode>))
		->setCollector("Formal-name", belr::make_sfn(&ToHeaderNode::setFormalName))
		->setCollector("URI", belr::make_sfn(&ToHeaderNode::setUri));

	d->parser->setHandler("cc-header", belr::make_fn(make_shared<CcHeaderNode>))
		->setCollector("Formal-name", belr::make_sfn(&CcHeaderNode::setFormalName))
		->setCollector("URI", belr::make_sfn(&CcHeaderNode::setUri));

	d->parser->setHandler("DateTime-header", belr::make_fn(make_shared<DateTimeHeaderNode>))
		->setCollector("date-fullyear", belr::make_sfn(&DateTimeHeaderNode::setYear))
		->setCollector("date-month", belr::make_sfn(&DateTimeHeaderNode::setMonth))
		->setCollector("date-mday", belr::make_sfn(&DateTimeHeaderNode::setMonthDay))
		->setCollector("time-hour", belr::make_sfn(&DateTimeHeaderNode::setHour))
		->setCollector("time-minute", belr::make_sfn(&DateTimeHeaderNode::setMinute))
		->setCollector("time-second", belr::make_sfn(&DateTimeHeaderNode::setSecond))
		->setCollector("time-offset", belr::make_sfn(&DateTimeHeaderNode::setOffset));

	d->parser->setHandler("time-offset", belr::make_fn(make_shared<DateTimeOffsetNode>))
		->setCollector("time-sign", belr::make_sfn(&DateTimeOffsetNode::setSign))
		->setCollector("time-hour", belr::make_sfn(&DateTimeOffsetNode::setHour))
		->setCollector("time-minute", belr::make_sfn(&DateTimeOffsetNode::setMinute));

	d->parser->setHandler("Subject-header", belr::make_fn(make_shared<SubjectHeaderNode>))
		->setCollector("Language-tag", belr::make_sfn(&SubjectHeaderNode::setLanguage))
		->setCollector("Header-value", belr::make_sfn(&SubjectHeaderNode::setSubject));

	d->parser->setHandler("Ns-header", belr::make_fn(make_shared<NsHeaderNode>))
		->setCollector("Name-prefix", belr::make_sfn(&NsHeaderNode::setPrefixName))
		->setCollector("URI", belr::make_sfn(&NsHeaderNode::setUri));

	d->parser->setHandler("Require-header", belr::make_fn(make_shared<RequireHeaderNode>))
		->setCollector("Require-header-value", belr::make_sfn(&RequireHeaderNode::setHeaderNames));
}

// -----------------------------------------------------------------------------

shared_ptr<Cpim::Message> Cpim::Parser::parseMessage (const string &input) {
	L_D();

	size_t parsedSize;
	shared_ptr<Node> node = d->parser->parseInput("Message", input, &parsedSize);
	if (!node) {
		lWarning() << "Unable to parse message.";
		return nullptr;
	}

	shared_ptr<MessageNode> messageNode = dynamic_pointer_cast<MessageNode>(node);
	if (!messageNode) {
		lWarning() << "Unable to cast belr result to message node.";
		return nullptr;
	}

	shared_ptr<Message> message = messageNode->createMessage();
	if (message) {
		message->setContent(input.substr(parsedSize));
	}
	return message;
}

// -----------------------------------------------------------------------------

shared_ptr<Cpim::Header> Cpim::Parser::cloneHeader (const Header &header) {
	if (header.getName() == "From")
		return FromHeaderNode(header).createHeader();

	if (header.getName() == "To")
		return ToHeaderNode(header).createHeader();

	if (header.getName() == "cc")
		return CcHeaderNode(header).createHeader();

	if (header.getName() == "DateTime")
		return DateTimeHeaderNode(header).createHeader();

	if (header.getName() == "Subject")
		return SubjectHeaderNode(header).createHeader();

	if (header.getName() == "NS")
		return NsHeaderNode(header).createHeader();

	if (header.getName() == "Require")
		return RequireHeaderNode(header).createHeader();

	return HeaderNode(header).createHeader();
}

LINPHONE_END_NAMESPACE
