//
// Copyright (c) 2017-2026, Manticore Software LTD (https://manticoresearch.com)
// Copyright (c) 2001-2016, Andrew Aksyonoff
// Copyright (c) 2008-2016, Sphinx Technologies Inc
// All rights reserved
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License. You should have
// received a copy of the GPL license along with this program; if you
// did not, you can find it at http://www.gnu.org/
//

#ifndef _sphinxrt_
#define _sphinxrt_

#include "sphinxutils.h"
#include "dict/stem/sphinxstem.h"
#include "sphinxint.h"
#include "killlist.h"
#include "attribute.h"
#include "docstore.h"
#include "columnarrt.h"
#include "coroutine.h"
#include "uuid_docid.h"
#include "tokenizer/tokenizer.h"
#include "indexing_sources/source_document.h"

class RtAccum_t;

using VisitChunk_fn = std::function<void ( const CSphIndex* pIndex )>;
using VisitChunkEx_fn = std::function<void ( const CSphIndex* pIndex, bool bOptimizing )>;

enum class InsertDocidMode_e
{
	NUMERIC,
	UUID_AUTO,
	UUID_EXPLICIT
};

class InsertDocData_c
{
public:
	CSphMatch							m_tDoc;
	CSphVector<VecTraits_T<const char>>	m_dFields;
	CSphVector<const char*>				m_dStrings;

	CSphAttrLocator						m_tDocIDLocator;

	CSphVector<SphAttr_t>				m_dColumnarAttrs;
	int									m_iColumnarID = -1;
	int64_t								m_iTotalBytes = 0;
	InsertDocidMode_e					m_eDocidMode = InsertDocidMode_e::NUMERIC;
	int									m_iUuidString = -1;

										explicit InsertDocData_c ( const ISphSchema & tSchema );

	void								SetID ( SphAttr_t tDocID );
	SphAttr_t							GetID() const;
	void								ResetPrimaryIdState();
	void								SetUuidDocidString ( CSphString && sUuid );
	const char *						GetUuidDocidString() const;

	void								AddMVALength ( int iLength, bool bDefault=false );
	void								AddMVAValue ( int64_t iValue )						{ m_dMvas.Add(iValue); }
	void								ResetMVAs()											{ m_dMvas.Resize(0); }
	const int64_t *						GetMVA ( int iMVA ) const							{ return m_dMvas.Begin()+iMVA; }
	const VecTraits_T<int64_t> &		GetMVAs() const										{ return m_dMvas; }
	void								FixParsedMVAs ( const CSphVector<int64_t> & dParsed, int iCount );
	static std::pair<int, bool>			ReadMVALength ( const int64_t * & pMVA );
	void								SwapMVAs ( InsertDocData_c & tSrc )					{ Swap ( m_dMvas, tSrc.m_dMvas ); }

private:
	static const uint64_t DEFAULT_FLAG = 1ULL << 63;

	CSphVector<int64_t>					m_dMvas;
	CSphString							m_sOwnedUuidDocid;
};

struct OptimizeTask_t
{
	enum OptimizeVerb_e {
		eManualOptimize,
		eDrop,
		eCompress,
		eSplit,
		eMerge,
		eAutoOptimize,
		eDedup,
	};

	OptimizeVerb_e m_eVerb;
	int m_iCutoff	=	0;
	int m_iFrom		=	-1;
	int m_iTo		=	-1;
	bool m_bByOrder = false;
	CSphString m_sUvarFilter;
};

struct CSphReconfigureSettings
{
	CSphTokenizerSettings m_tTokenizer;
	CSphDictSettings m_tDict;
	CSphIndexSettings m_tIndex;
	CSphFieldFilterSettings m_tFieldFilter;
	CSphSchema m_tSchema;
	MutableIndexSettings_c m_tMutableSettings;

	bool m_bChangeSchema = false;
};

struct CSphReconfigureSetup
{
	TokenizerRefPtr_c m_pTokenizer;
	DictRefPtr_c m_pDict;
	CSphIndexSettings m_tIndex;
	std::unique_ptr<ISphFieldFilter> m_pFieldFilter;
	CSphSchema m_tSchema;
	MutableIndexSettings_c m_tMutableSettings;

	bool m_bChangeSchema = false;
};

/// RAM based updateable backend interface
class RtIndex_i;

struct AttachArgs_t
{
	RtIndex_i *	m_pSrcIndex = nullptr;
	bool		m_bTruncate = false;
	bool		m_bConfigless = false;
	bool		m_bFatal = false;

	AttachArgs_t ( RtIndex_i * pSrcIndex ) : m_pSrcIndex ( pSrcIndex ) {}
};

class RtIndex_i : public CSphIndexStub
{
public:
	RtIndex_i ( CSphString sIndexName, CSphString sPath ) : CSphIndexStub { std::move ( sIndexName ), std::move ( sPath ) } {}
	~RtIndex_i()
	{
		if ( m_fnOnDestroyed )
			m_fnOnDestroyed();
	}

	/// get internal schema (to use for Add calls)
	virtual const CSphSchema & GetInternalSchema () const { return m_tSchema; }
	virtual uint64_t GetSchemaHash () const = 0;

	/// insert/update document in current txn
	/// fails in case of two open txns to different indexes
	virtual bool AddDocument ( InsertDocData_c & tDoc, bool bReplace, const CSphString & sTokenFilterOptions, CSphString & sError, CSphString & sWarning, RtAccum_t * pAccExt ) = 0;

	/// delete document in current txn
	/// fails in case of two open txns to different indexes
	virtual bool DeleteDocument ( const VecTraits_T<DocID_t> & dDocs, CSphString & sError, RtAccum_t * pAccExt ) = 0;

	/// commit pending changes
	virtual bool Commit ( int * pDeleted, RtAccum_t * pAccExt, CSphString* pError = nullptr ) = 0;
	virtual bool PreCommit ( RtAccum_t * pAccExt, CSphString & sError ) { return true; }

	/// undo pending changes
	virtual void RollBack ( RtAccum_t * pAccExt ) = 0;

	/// forcibly flush RAM chunk to disk
	virtual void ForceRamFlush ( const char* szReason ) = 0;

	virtual bool IsFlushNeed() const = 0;

	/// get time of last flush happened
	virtual int64_t GetLastFlushTimestamp() const = 0;

	/// forcibly save RAM chunk as a new disk chunk
	virtual bool ForceDiskChunk () = 0;

	/// attach a disk chunk to current index
	virtual bool AttachDiskIndex ( CSphIndex * pIndex, bool bTruncate, bool & bFatal, CSphString & sError ) { return true; }

	/// attach all the content of the RT index (flush ramchunk then disk chunks) to the current index
	virtual bool AttachRtIndex ( AttachArgs_t & tArgs, CSphString & sError ) { return true; }

	/// truncate index (that is, kill all data)
	enum Truncate_e : bool { TRUNCATE, DROP };
	virtual bool Truncate ( CSphString & sError, Truncate_e eAction ) = 0;

	virtual void Optimize ( OptimizeTask_t tTask ) {}
	virtual bool StartOptimize ( OptimizeTask_t tTask ) { return true; }
	virtual int OptimizesRunning () const noexcept { return 0; }
	virtual void ManualOptimizeCutoff ( int iCutoff ) {}

	virtual int GetNumOfLocks () const noexcept { return 0; }

	/// check settings vs current and return back tokenizer and dictionary in case of difference
	virtual bool IsSameSettings ( CSphReconfigureSettings & tSettings, CSphReconfigureSetup & tSetup, StrVec_t & dWarnings, CSphString & sError ) const = 0;

	/// reconfigure index by using new tokenizer, dictionary and index settings
	/// current data got saved with current settings
	virtual bool Reconfigure ( CSphReconfigureSetup & tSetup ) = 0;

	// generation typically changes on Reconfigure
	virtual int GetAlterGeneration() const { return 0; }

	/// do something const with disk chunk (query settings, status, etc.)
	/// hides internal disk chunks storage
	virtual void ProcessDiskChunk ( int iChunk, VisitChunk_fn&& fnVisitor ) const {};
	virtual void ProcessDiskChunkEx ( int iChunk, VisitChunkEx_fn&& fnVisitor ) const {};

	/// bind indexing accumulator
	/// returns false if another index already uses it in an open txn
	virtual bool BindAccum ( RtAccum_t * pAccExt, CSphString * pError ) = 0;

	// instead of cloning for each AddDocument() call we could just call this method and improve batch inserts speed
	virtual TokenizerRefPtr_c CloneIndexingTokenizer() const = 0;

	// hint an index that it was deleted and should cleanup its files when destructed
	void IndexDeleted ( Threads::Handler&& fnOnDestroyed )
	{
		m_bIndexDeleted = true;
		m_fnOnDestroyed = std::move ( fnOnDestroyed );
	}

	virtual void ProhibitSave() = 0;
	virtual void EnableSave() = 0;
	virtual void LockFileState ( CSphVector<CSphString> & dFiles ) = 0;

	virtual void WaitLockEnabledState() noexcept {};
	virtual void UnlockEnabledState () noexcept {};
	
	virtual bool	NeedStoreWordID () const = 0;
	virtual	int64_t	GetMemLimit() const = 0;
	virtual int		GetChunkId () const { return 0; };

protected:
	bool PrepareAccum ( RtAccum_t* pAccExt, DictFormat_e eDictFormat, CSphString* pError );
	bool				m_bIndexDeleted = false;

private:
	Threads::Handler	m_fnOnDestroyed = nullptr;
};

/// initialize subsystem
class CSphConfigSection;
void sphRTInit ( CSphString sBinlogPath, bool bCommonBinlog = false, const CSphConfigSection * pCommon = nullptr );
bool sphRTSchemaConfigure ( const CSphConfigSection & hIndex, CSphSchema & tSchema, const CSphIndexSettings & tSettings, StrVec_t * pWarnings, CSphString & sError, bool bSkipValidation, bool bPQ );
void sphRTSetTestMode ();

/// RT index factory
std::unique_ptr<RtIndex_i> sphCreateIndexRT ( CSphString sIndexName, CSphString sPath, CSphSchema tSchema, int64_t iRamSize );

typedef void ProgressCallbackSimple_t ();

//////////////////////////////////////////////////////////////////////////

/// Exposed internal stuff (for pq and for testing)

#define SPH_MAX_KEYWORD_LEN (3*SPH_MAX_WORD_LEN+4)
static_assert ( SPH_MAX_KEYWORD_LEN<255, "SPH_MAX_KEYWORD_LEN should fit in byte" );

struct RtDoc_t
{
	RowID_t m_tRowID { INVALID_ROWID };	///< row id
	DWORD m_uDocFields = 0;				///< fields mask
	DWORD m_uHits = 0;					///< hit count
	DWORD m_uHit = 0;					///< either index into segment hits, or the only hit itself (if hit count is 1)
};


struct RtWord_t
{
	union
	{
		SphWordID_t m_uWordID;    ///< my keyword id
		const BYTE * m_sWord;
		INIT_WITH_0 ( SphWordID_t, const BYTE* );
	};
	int m_iWordLen = 0;
	DWORD m_uDocs = 0;		///< document count (for stats and/or BM25)
	DWORD m_uHits = 0;		///< hit count (for stats and/or BM25)
	DWORD m_uDoc = 0;		///< index into segment docs
	bool m_bHasHitlist = true;
};


struct RtWordCheckpoint_t
{
	union
	{
		SphWordID_t m_uWordID;
		const char * m_szWord;
	};
	int m_iOffset;
};

// this is what actually stores index data
// RAM chunk consists of such segments
struct RtSegment_t final : IndexSegment_c, ISphRefcountedMT
{
public:
	mutable int						m_iLocked = 0;	// if segment currently used in an op
	mutable Threads::Coro::RWLock_c	m_tLock;		// fine-grain lock

	CSphTightVector<BYTE>			m_dWords;
	CSphVector<RtWordCheckpoint_t>	m_dWordCheckpoints;
	CSphTightVector<uint64_t>		m_dInfixFilterCP;
	CSphTightVector<BYTE>			m_dDocs;
	CSphTightVector<BYTE>			m_dHits;

	DWORD							m_uRows = 0;			///< number of actually allocated rows
	std::atomic<int64_t>			m_tAliveRows { 0 };		///< number of alive (non-killed) rows
	CSphTightVector<CSphRowitem>	m_dRows GUARDED_BY ( m_tLock );				///< row data storage
	CSphTightVector<BYTE>			m_dBlobs GUARDED_BY ( m_tLock );            ///< storage for blob attrs
	CSphVector<BYTE>				m_dKeywordCheckpoints;
	std::atomic<int64_t> *			m_pRAMCounter = nullptr;///< external RAM counter
	OpenHashTable_T<DocID_t, RowID_t>	m_tDocIDtoRowID;		///< speeds up docid-rowid lookups
	OpenHashTable_T<UuidDocidKey_t, DocID_t, UuidDocidKeyHash_fn> m_tUuidDocID { 0 }; ///< speeds up UUID public-id lookups
	DeadRowMap_Ram_c				m_tDeadRowMap;
	std::unique_ptr<DocstoreRT_i>	m_pDocstore;
	std::unique_ptr<ColumnarRT_i>	m_pColumnar;
	const ISphSchema&				m_tSchema;

	mutable bool					m_bConsistent{false};

							RtSegment_t ( DWORD uDocs, const ISphSchema& tSchema );

	int64_t					GetUsedRam() const;				// get cached ram usage counter
	void					UpdateUsedRam() const;			// recalculate ram usage, update index ram counter
	DWORD					GetMergeFactor() const;
	int						GetStride() const;

	bool					IsAlive ( DocID_t tDocid ) const;
	const CSphRowitem *		GetDocinfoByRowID ( RowID_t tRowID ) const;
	RowID_t					GetAliveRowidByDocid ( DocID_t tDocid ) const;
	RowID_t					GetRowidByDocid ( DocID_t tDocID ) const;

	int						Kill ( DocID_t tDocID ) override;
	int						KillMulti ( const VecTraits_T<DocID_t> & dKlist ) override;

	void					SetupDocstore ( const CSphSchema * pSchema );
	void					BuildDocID2RowIDMap ( const CSphSchema & tSchema );

	void					MaybeAddPostponedUpdate ( const RowsToUpdate_t& dRows, const UpdateContext_t& tCtx );
	void					UpdateAttributesOffline ( VecTraits_T<PostponedUpdate_t>& dPostUpdates ) final;

private:
	mutable int64_t			m_iUsedRam = 0;			///< ram usage counter

							~RtSegment_t () final;

	void					FixupRAMCounter ( int64_t iDelta ) const;
	bool					Update_WriteBlobRow ( UpdateContext_t& tCtx, RowID_t tRowID, ByteBlob_t tBlob, int nBlobAttrs, const CSphAttrLocator& tBlobRowLoc, bool& bCritical, CSphString& sError ) final;
};

using RtSegmentRefPtf_t = CSphRefcountedPtr<RtSegment_t>;
using ConstRtSegmentRefPtf_t = CSphRefcountedPtr<const RtSegment_t>;

class RtWordReader_c
{
	BYTE m_tPackedWord[SPH_MAX_KEYWORD_LEN + 1];
	CSphFixedVector<BYTE> m_dKeyword { 0 };
	RtWord_t m_tWord;
	int m_iWords = 0;

	DictFormat_e m_eDictFormat;
	int m_iWordsCheckpoint;
	int m_iCheckpoint = 0;
	const ESphHitless m_eHitlessMode = SPH_HITLESS_NONE;

public:
	const BYTE* m_pCur = nullptr;
	const BYTE* m_pMax = nullptr;

	RtWordReader_c ( const RtSegment_t * pSeg, DictFormat_e eDictFormat, int iWordsCheckpoint, ESphHitless eHitlessMode );
	void Reset ( const RtSegment_t * pSeg );
	inline int Checkpoint() const { return m_iCheckpoint; }
	const RtWord_t* UnzipWord();
	inline explicit operator RtWord_t*() { return &m_tWord; }
	inline RtWord_t* operator->() { return &m_tWord; }
	inline const RtWord_t& operator*() const { return m_tWord; }
};

class RtDocReader_c
{
	const BYTE* m_pDocs = nullptr;
	int m_iLeft = 0;
	RtDoc_t m_tDoc;

public:
	RtDocReader_c() = default;
	RtDocReader_c ( const RtSegment_t* pSeg, const RtWord_t& tWord );
	void Init ( const RtSegment_t* pSeg, const RtWord_t& tWord );
	void Reset () { m_pDocs = nullptr; m_iLeft = 0; }
	bool UnzipDoc();
	inline explicit operator RtDoc_t*() { return &m_tDoc; }
	inline RtDoc_t* operator->() { return &m_tDoc; }
	inline const RtDoc_t& operator*() const { return m_tDoc; }
};

class RtHitReader_c
{
	const BYTE* m_pCur = nullptr;
	DWORD m_uLeft = 0;
	DWORD m_uValue = 0;

public:
	RtHitReader_c() = default;
	RtHitReader_c ( const RtSegment_t& dSeg, const RtDoc_t& dDoc );
	void Seek ( const RtSegment_t& dSeg, const RtDoc_t& dDoc );
	void Seek ( const BYTE* pHits, DWORD uHits );
	inline DWORD operator*() const { return m_uValue; }
	DWORD UnzipHit ();
};

ByteBlob_t GetHitsBlob ( const RtSegment_t* pSeg, const RtDoc_t* pDoc );

class CSphSource_StringVector : public CSphSource
{
public:
	explicit			CSphSource_StringVector ( const VecTraits_T<VecTraits_T<const char >> &dFields, const CSphSchema &tSchema );
						~CSphSource_StringVector () override = default;

	bool		Connect ( CSphString & ) override;
	void		Disconnect () override;

	bool		IterateStart ( CSphString & ) override { m_iPlainFieldsLength = m_tSchema.GetFieldsCount(); return true; }

	bool		IterateMultivaluedStart ( int, CSphString & ) override { return false; }
	bool		IterateMultivaluedNext ( int64_t & iDocID, int64_t & iMvaValue ) override { return false; }

	CSphVector<int64_t> * GetFieldMVA ( int iAttr ) override { return nullptr; }

	bool		IterateKillListStart ( CSphString & ) override { return false; }
	bool		IterateKillListNext ( DocID_t & ) override { return false; }

	BYTE **		NextDocument ( bool &, CSphString & ) override { return m_dFields.Begin(); }
	const int *	GetFieldLengths () const override { return m_dFieldLengths.Begin(); }
	void		SetMorphFields ( const CSphBitvec & tMorphFields ) { m_tMorphFields = tMorphFields; }

protected:
	CSphVector<BYTE *>			m_dFields;
	CSphVector<int>				m_dFieldLengths;
};

static constexpr DWORD PERCOLATE_BLOOM_WILD_COUNT = 32;
static constexpr DWORD BLOOM_PER_ENTRY_VALS_COUNT = 8;
static constexpr DWORD BLOOM_HASHES_COUNT = 2;
static constexpr DWORD BLOOM_NGRAM_0 = 2;
static constexpr DWORD BLOOM_NGRAM_1 = 4;

struct BloomGenTraits_t
{
	uint64_t * m_pBuf = nullptr;

	explicit BloomGenTraits_t ( uint64_t * pBuf )
		: m_pBuf ( pBuf )
	{}

	void Set ( int iPos, uint64_t uVal )
	{
		m_pBuf[iPos] |= uVal;
	}

	bool IterateNext () const noexcept
	{ return true; }
};

struct BloomCheckTraits_t
{
	const uint64_t * m_pBuf = nullptr;
	bool m_bSame = true;

	explicit BloomCheckTraits_t ( const uint64_t * pBuf )
		: m_pBuf ( pBuf )
	{}

	void Set ( int iPos, uint64_t uVal )
	{
		m_bSame = ( ( m_pBuf[iPos] & uVal )==uVal );
	}

	bool IterateNext () const noexcept
	{ return m_bSame; }
};

bool BuildBloom ( const BYTE * sWord, int iLen, int iInfixCodepointCount, bool bUtf8,
	int iKeyValCount, BloomGenTraits_t &tBloom );

bool BuildBloom ( const BYTE * sWord, int iLen, int iInfixCodepointCount, bool bUtf8,
	int iKeyValCount, BloomCheckTraits_t &tBloom );

void BuildSegmentInfixes ( RtSegment_t * pSeg, bool bHasMorphology, DictFormat_e eDictFormat, int iMinInfixLen,
	int iWordsCheckpoint, bool bUtf8, ESphHitless eHitlessMode );

bool ExtractInfixCheckpoints ( const char * sInfix, int iBytes, int iMaxCodepointLength, int iDictCpCount,
	const CSphTightVector<uint64_t> &dFilter, CSphVector<DWORD> &dCheckpoints );

void SetupExactTokenizer ( const TokenizerRefPtr_c & pTokenizer );
void SetupStarTokenizer ( const TokenizerRefPtr_c & pTokenizer );

bool CreateReconfigure ( const CSphString & sIndexName, bool bIsStarDict, DictFormat_e eCurrentDictFormat, const ISphFieldFilter * pFieldFilter,
	const CSphIndexSettings & tIndexSettings, uint64_t uTokHash, uint64_t uDictHash, int iMaxCodepointLength, int64_t iMemLimit,
	bool bSame, CSphReconfigureSettings & tSettings, CSphReconfigureSetup & tSetup, StrVec_t & dWarnings, CSphString & sError );

// Get global flag of w-available RT
volatile bool &RTChangesAllowed () noexcept;

// Get global flag of autooptimize
volatile int & AutoOptimizeCutoffMultiplier() noexcept;
volatile bool & OptimizeCutoffExplicit() noexcept;
volatile int & ParallelChunkMergesLimit() noexcept;
volatile int & MergeChunksPerJob() noexcept;
volatile int AutoOptimizeCutoff() noexcept;
volatile int AutoOptimizeCutoffKNN() noexcept;
volatile int & KNNParallelBuild() noexcept;
volatile int & EmbeddingsThreads() noexcept;
int GetEmbeddingsThreadsToUse ( int iMaxOverride = -1 );

void SetRtFlushDiskPeriod ( int iFlushWrite, int iFlushSearch );

inline ByteBlob_t GetPackedKeywordLegacy ( const BYTE * pPacked )
{
	assert ( pPacked );
	int iLen = pPacked[0];
	assert ( iLen>0 && iLen<SPH_MAX_KEYWORD_LEN );
	return { pPacked+1, iLen };
}


inline ByteBlob_t GetPackedKeywordV2 ( const BYTE * pPacked )
{
	assert ( pPacked );
	const BYTE * pCur = pPacked;
	int iLen = (int)UnzipIntLE ( pCur );
	assert ( iLen>0 && iLen<=GetKeywordMaxStoredBytes ( DictFormat_e::KEYWORDS_V2 ) );
	return { pCur, iLen };
}

#endif // _sphinxrt_
