/*******************************************************************************
 * libretroshare/src/gxstrans: p3gxstrans.h                                    *
 *                                                                             *
 * libretroshare: retroshare core library                                      *
 *                                                                             *
 * Copyright (C) 2016-2019  Gioacchino Mazzurco <gio@eigenlab.org>             *
 *                                                                             *
 * 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                *
 * GNU Lesser General Public License for more details.                         *
 *                                                                             *
 * You should have received a copy of the GNU Lesser General Public License    *
 * along with this program. If not, see <https://www.gnu.org/licenses/>.       *
 *                                                                             *
 *******************************************************************************/
#pragma once

#include <cstdint>
#include <unordered_map>
#include <map>

#include "retroshare/rsgxsifacetypes.h" // For RsGxsId, RsGxsCircleId
#include "gxs/gxstokenqueue.h" // For GxsTokenQueue
#include "gxstrans/p3gxstransitems.h"
#include "services/p3idservice.h" // For p3IdService
#include "util/rsthreads.h"
#include "retroshare/rsgxstrans.h"

class p3GxsTrans;

/// Services who want to make use of p3GxsTrans should inherit this struct
struct GxsTransClient
{
	/**
	 * This will be called by p3GxsTrans to dispatch mails to the subservice
	 * @param authorId message sender
	 * @param decryptId recipient id
	 * @param data buffer containing the decrypted data
	 * @param dataSize size of the buffer
	 * @return true if dispatching goes fine, false otherwise
	 */
	virtual bool receiveGxsTransMail( const RsGxsId& authorId,
	                                  const RsGxsId& recipientId,
	                                  const uint8_t* data, uint32_t dataSize
	                                  ) = 0;

	/**
	 * This will be called by p3GxsTrans to notify the subservice about the
	 * status of a sent email.
	 * @param originalMessage message for with the notification is made
	 * @param status the new status of the message
	 * @return true if notification goes fine, false otherwise (ignored ATM)
	 */
	virtual bool notifyGxsTransSendStatus( RsGxsTransId mailId,
	                                       GxsTransSendStatus status ) = 0;
};

struct MsgSizeCount
{
	MsgSizeCount() : size(0),count(0) {}

	uint32_t size ;
	uint32_t count ;
};

/**
 * @brief p3GxsTrans asyncronous redundant small mail trasport on top of GXS.
 * p3GxsTrans is capable of asynchronous mail delivery and acknowledgement.
 * p3GxsTrans is meant to be capable of multiple encryption options,
 * @see RsGxsTransEncryptionMode at moment messages are encrypted using RSA
 * unless the user ask for them being sent in clear text ( this is not supposed
 * to happen in non testing environment so warnings and stack traces are printed
 * in the log if an attempt to send something in clear text is made ).
 * p3GxsTrans try to hide metadata so the travelling message signed by the author
 * but the recipient is not disclosed, instead to avoid everyone trying to
 * decrypt every message a hint has been introduced, the hint is calculated in a
 * way that one can easily prove that a message is not destined to someone, but
 * cannot prove the message is destined to someone
 * @see RsGxsTransMailItem::recipientHint for more details.
 * p3GxsTrans expose a simple API to send and receive mails, the API also
 * provide notification for the sending mail status @see sendMail(...),
 * @see querySendStatus(...), @see registerGxsTransClient(...),
 * @see GxsTransClient::receiveGxsTransMail(...),
 * @see GxsTransClient::notifyGxsTransSendStatus(...).
 */
class p3GxsTrans : public RsGenExchange, public GxsTokenQueue, public p3Config, public RsGxsTrans
{
public:
	p3GxsTrans( RsGeneralDataService* gds, RsNetworkExchangeService* nes,
	            p3IdService& identities ) :
	    RsGenExchange( gds, nes, new RsGxsTransSerializer(),
	                   RS_SERVICE_TYPE_GXS_TRANS, &identities,
	                   AuthenPolicy()),
	    GxsTokenQueue(this),
	    RsGxsTrans(static_cast<RsGxsIface&>(*this)),
	    // always check 30 secs after start)
	    mLastMsgCleanup(time(NULL) - MAX_DELAY_BETWEEN_CLEANUPS + 30),
	    mIdService(identities),
	    mServClientsMutex("p3GxsTrans client services map mutex"),
	    mOutgoingMutex("p3GxsTrans outgoing queue map mutex"),
	    mIngoingMutex("p3GxsTrans ingoing queue map mutex"),
	    mCleanupThread(nullptr),
	    mPerUserStatsMutex("p3GxsTrans user stats mutex"),
	    mDataMutex("p3GxsTrans data mutex") {}

	virtual ~p3GxsTrans();

    /*!
     * \brief getStatistics
     * 				Gathers all sorts of statistics about the data transported by p3GxsTrans, in order to display info about the running status,
     * 			message transport, etc. This is a blocking call. Use it in a thread.
     * \param stats This structure contains all statistics information.
     * \return 		true is the call succeeds.
     */

	virtual bool getDataStatistics(GxsTransStatistics& stats) override;

    /*!
     * \brief getGroupStatistics
     * 				Gathers statistics about GXS groups and messages used by GxsTrans to transport data. This is a blocking call. Use it in a thread.
     * \param stats
     * \return true if the data collection succeeds.
     */
	virtual bool getGroupStatistics(std::map<RsGxsGroupId,RsGxsTransGroupStatistics>& stats) override;

	/**
	 * Send an email to recipient, in the process author of the email is
	 * disclosed to the network (because the sent GXS item is signed), while
	 * recipient is not @see RsGxsTransMailItem::recipientHint for details on
	 * recipient protection.
	 * This method is part of the public interface of this service.
	 * @return true if the mail will be sent, false if not
	 */
	bool sendData( RsGxsTransId& mailId,
	               GxsTransSubServices service,
	               const RsGxsId& own_gxsid, const RsGxsId& recipient,
	               const uint8_t* data, uint32_t size,
	               RsGxsTransEncryptionMode cm = RsGxsTransEncryptionMode::RSA
	        );

	/**
	 * This method is part of the public interface of this service.
	 * @return false if mail is not found in outgoing queue, true otherwise
	 */
	bool querySendStatus( RsGxsTransId mailId, GxsTransSendStatus& st );

	/**
	 * Register a client service to p3GxsTrans to receive mails via
	 * GxsTransClient::receiveGxsTransMail(...) callback
	 * This method is part of the public interface of this service.
	 */
	void registerGxsTransClient( GxsTransSubServices serviceType,
	                             GxsTransClient* service );

	/// @see RsGenExchange::getServiceInfo()
	virtual RsServiceInfo getServiceInfo() override { return RsServiceInfo( RS_SERVICE_TYPE_GXS_TRANS, "GXS Mails", 0, 1, 0, 1 ); }

	static const uint32_t GXS_STORAGE_PERIOD = 15*86400;	// 15 days.
	static const uint32_t GXS_SYNC_PERIOD    = 15*86400;
private:
	/** Time interval of inactivity before a distribution group is unsubscribed.
	 * Approximatively 3 months seems ok ATM. */
	const static int32_t UNUSED_GROUP_UNSUBSCRIBE_INTERVAL = 16*86400; // 16 days

	/**
	 * This should be as little as possible as the size of the database can grow
	 * very fast taking in account we are handling mails for the whole network.
	 * We do prefer to resend a not acknowledged yet mail after
	 * GXS_STORAGE_PERIOD has passed and keep it little.
	 * Tought it can't be too little as this may cause signed receipts to
	 * get lost thus causing resend and fastly grow perceived async latency, in
	 * case two sporadically connected users sends mails each other.
	 * While it is ok for signed acknowledged to stays in the DB for a
	 * full GXS_STORAGE_PERIOD, mails should be removed as soon as a valid
	 * signed acknowledged is received for each of them.
	 * Two weeks seems fair ATM.
	 */
	static const uint32_t MAX_DELAY_BETWEEN_CLEANUPS ; // every 20 mins. Could be less.

    rstime_t mLastMsgCleanup ;

	/// Define how the backend should handle authentication based on signatures
	static uint32_t AuthenPolicy();

	/// Types to mark queries in tokens queue
	enum GxsReqResTypes
	{
		GROUPS_LIST        = 1,
		GROUP_CREATE       = 2,
		MAILS_UPDATE       = 3
	};

	/// Store the id of the preferred GXS group to send emails
	RsGxsGroupId mPreferredGroupId;

	/// Used for items {de,en}cryption
	p3IdService& mIdService;

	/// Stores pointers to client services to notify them about new mails
	std::map<GxsTransSubServices, GxsTransClient*> mServClients;
	RsMutex mServClientsMutex;

	/**
	 * @brief Keep track of outgoing mails.
	 * Records enter the queue when a mail is sent, and are removed when a
	 * receipt has been received or sending is considered definetly failed.
	 * Items are saved in config for consistence accross RetroShare shutdowns.
	 */
	typedef std::map<RsGxsTransId, OutgoingRecord> prMap;
	prMap mOutgoingQueue;
	RsMutex mOutgoingMutex;
	void locked_processOutgoingRecord(OutgoingRecord& r);

	/**
	 * @brief Ingoing mail and receipt processing queue.
	 * At shutdown remaining items are saved in config and then deleted in
	 * destructor for consistence accross RetroShare instances.
	 * In order to avoid malicious messages ( non malicious collision has 1/2^64
	 * probablity ) to smash items in the queue thus causing previous incoming
	 * item to not being processed and memleaked multimap is used instead of map
	 * for incoming queue.
	 */
	typedef std::unordered_multimap<RsGxsTransId, RsGxsTransBaseMsgItem*> inMap;
	inMap mIncomingQueue;
	RsMutex mIngoingMutex;

	/// @see GxsTokenQueue::handleResponse(uint32_t token, uint32_t req_type
	///                                    , RsTokenService::GxsRequestStatus status)
	virtual void handleResponse(uint32_t token, uint32_t req_type
	                            , RsTokenService::GxsRequestStatus status) override;

	/// @see RsGenExchange::service_tick()
	virtual void service_tick() override;

	/// @see RsGenExchange::service_CreateGroup(...)
	RsGenExchange::ServiceCreate_Return service_CreateGroup(
	        RsGxsGrpItem* grpItem, RsTlvSecurityKeySet& ) override;

	/// @see RsGenExchange::notifyChanges(std::vector<RsGxsNotify *> &changes)
	void notifyChanges(std::vector<RsGxsNotify *> &changes) override;

	/// @see p3Config::setupSerialiser()
	virtual RsSerialiser* setupSerialiser() override;

	/// @see p3Config::saveList(bool &cleanup, std::list<RsItem *>&)
	virtual bool saveList(bool &cleanup, std::list<RsItem *>&saveList) override;

	/// @see p3Config::saveDone()
	void saveDone() override;

	/// @see p3Config::loadList(std::list<RsItem *>&)
	virtual bool loadList(std::list<RsItem *>& loadList) override;

	/// Request groups list to GXS backend. Async method.
	bool requestGroupsData(const std::list<RsGxsGroupId>* groupIds = NULL);

	/**
	 * Check if current preferredGroupId is the best against potentialGrId, if
	 * the passed one is better update it.
	 * Useful when GXS backend notifies groups changes, or when a reponse to an
	 * async grop request (@see GXS_REQUEST_TYPE_GROUP_*) is received.
	 * @return true if preferredGroupId has been supeseded by potentialGrId
	 *   false otherwise.
	 */
	bool inline locked_supersedePreferredGroup(const RsGxsGroupId& potentialGrId)
	{
		if(mPreferredGroupId < potentialGrId)
		{
            // std::cerr << "supersedePreferredGroup(...) " << potentialGrId << " supersed " << mPreferredGroupId << std::endl;
			mPreferredGroupId = potentialGrId;
			return true;
		}
		return false;
	}

	/** @return true if has passed more then interval seconds between timeStamp
	 * and ref. @param ref by default now is taked as reference. */
	bool static inline olderThen(rstime_t timeStamp, int32_t interval,
	                             rstime_t ref = time(NULL))
	{ return (timeStamp + interval) < ref; }


	/// Decrypt email content and pass it to dispatchDecryptedMail(...)
	bool handleEncryptedMail(const RsGxsTransMailItem* mail);

	/// Dispatch the message to the recipient service
	bool dispatchDecryptedMail( const RsGxsId& authorId,
	                            const RsGxsId& decryptId,
	                            const uint8_t* decrypted_data,
	                            uint32_t decrypted_data_size );

	void notifyClientService(const OutgoingRecord& pr);

	/// Checks the integrity message and groups
	class GxsTransIntegrityCleanupThread : public RsThread
	{
		enum CheckState { CheckStart, CheckChecking };

	public:
		explicit GxsTransIntegrityCleanupThread(
		        RsGeneralDataService* const dataService ):
		    mDs(dataService), mMtx("GxsTransIntegrityCheck"), mDone(false) {}

		bool isDone();
		void run();

		void getDeletedIds(std::list<RsGxsGroupId>& grpIds, std::map<RsGxsGroupId, std::vector<RsGxsMessageId> >& msgIds);

		void getMessagesToDelete(GxsMsgReq& req) ;
		void getPerUserStatistics(std::map<RsGxsId,MsgSizeCount>& m) ;

	private:
		RsGeneralDataService* const mDs;
		RsMutex mMtx ;

		GxsMsgReq mMsgToDel ;
		std::map<RsGxsId,MsgSizeCount> total_message_size_and_count;
		bool mDone;
	};

	// Overloaded from RsGenExchange.

	bool acceptNewMessage(const RsGxsMsgMetaData *msgMeta, uint32_t size) override;

	GxsTransIntegrityCleanupThread *mCleanupThread ;

	// statistics of the load across all groups, per user.

	RsMutex mPerUserStatsMutex;
	std::map<RsGxsId,MsgSizeCount> per_user_statistics ;

	// Mutex to protect local data

	RsMutex mDataMutex;
};

