// SetOps.h
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2022

#pragma once
#include "Ceda/cxUtils/xostream.h"
#include "Ceda/cxUtils/VariableLengthSerialise.h"
#include "Ceda/cxUtils/Archive.h"
#include "Ceda/cxUtils/CedaAssert.h"
#include <map>

// See /ceda-implementation/ceda-core-libs/cxOT/sets/composite-set-operations.php

namespace ceda
{
    /*
    Information about presence of a key for a given site si.

        (si,ti) - identifies the last operation op(si,ti) that performed an insertion 
                  of the key by site si

        (sd,td) - if defined then operation op(sd,td) deleted the key and masked 
                  op(si,ti).
                  It doesn't matter which delete we record if this is ambiguous.
    */
    template<typename S, typename T>
    struct TKeyPresentForSite
    {
        using SiteId = S;
        using TimeIndex = T;

        inline bool empty() const { return ti < 0; }
        inline bool IsInsert() const { return td < 0; }

        inline void SetLocalInsert(TimeIndex t)
        {
            ti = t;
            td = -1;
        }
        inline void SetLocalDelete(SiteId s, TimeIndex t)
        {
            sd = s;
            td = t;
        }
        
        TimeIndex ti = -1;      // ti<0 means no value has been inserted
        SiteId sd;
        TimeIndex td = -1;      // td<0 means (sd,td) not defined
    };

    template<typename S, typename T>
    inline bool operator==(const TKeyPresentForSite<S,T>& k1, const TKeyPresentForSite<S,T>& k2)
    {
        return k1.ti == k2.ti &&
               k1.sd == k2.sd &&
               k1.td == k2.td;
    }

    template<typename S, typename T>
    inline xostream& operator<<(xostream& os, const TKeyPresentForSite<S,T>& k)
    {
        if (k.empty())
        {
            os << "e";
        }
        else if (k.IsInsert())
        {
            os << "i(ti=" << k.ti << ')';
        }
        else
        {
            os << "d(ti=" << k.ti << " by S" << k.sd << ',' << k.td << ')';
        }
        return os;
    }

    template<typename Archive, typename S, typename T>
    inline void Serialise(Archive& ar, const TKeyPresentForSite<S,T>& k)
    {
        SerialiseVariableLengthUint(ar, k.ti);
        ar << k.sd;
        SerialiseVariableLengthUint(ar, k.td);
    }

    template<typename Archive, typename S, typename T>
    inline void Deserialise(Archive& ar, TKeyPresentForSite<S,T>& k)
    {
        DeserialiseVariableLengthUint(ar,k.ti);
        ar >> k.sd;
        DeserialiseVariableLengthUint(ar,k.td);
    }

    // k1 += k2
    // Merge k2 into k1 where it is assumed these are for the same si.
    // Returns true if any changes were made to k1
    template<typename S, typename T>
    bool Merge(TKeyPresentForSite<S,T>& k1, const TKeyPresentForSite<S,T>& k2)
    {
        cxAssert(!k2.empty());

        // Note that k2.ti > k1.ti is true if k1 is empty (i.e. k1.ti < 0)
        if (k2.ti > k1.ti || 
            k2.ti == k1.ti && k1.IsInsert() && !k2.IsInsert())
        {
            k1 = k2;
            return true;
        }
        return false;
    }

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

    template<typename S, typename T>
    using TKeyPresentInfo = std::map<S, TKeyPresentForSite<S,T>>;

    template<typename S, typename T>
    inline xostream& operator<<(xostream& os, const TKeyPresentInfo<S,T>& m)
    {
        bool first = true;
        os << '{';
        for (const auto& [si,k] : m)
        {
            if (first) first=false; else os << ' ';
            os << 'S' << si << "->" << k;
        }
        os << '}';
        return os;
    }

    template<typename S, typename T>
    void LocalInsert(TKeyPresentInfo<S,T>& m, const S& s, T t)
    {
        auto& k = m[s];
        k.SetLocalInsert(t);
    }

    template<typename S, typename T>
    void LocalDelete(TKeyPresentInfo<S,T>& m, const S& s, T t)
    {
        for (auto& [si,k] : m)
        {
            if (k.IsInsert())
            {
                k.SetLocalDelete(s,t);
            }
        }
    }

    template<typename S, typename T>
    bool KeyIsPresent(const TKeyPresentInfo<S,T>& m)
    {
        for (const auto& [si,k] : m)
        {
            if (k.IsInsert()) return true;
        }
        return false;
    }

    // m1 += m2
    // Returns true if any changes were made to m1
    template<typename S, typename T>
    bool MergeKeyPresentInfo(TKeyPresentInfo<S,T>& m1, const TKeyPresentInfo<S,T>& m2)
    {
        bool changed = false;
        for (const auto& [si,k2] : m2)
        {
            auto& k1 = m1[si];                  // May create entry if doesn't already exist
            if (Merge(k1,k2)) changed = true;   // k1 += k2
        }
        return changed;
    }

    // Set rf = RFactor(m,v)
    template<typename VectorTime, typename S, typename T>
    void GetRFactor(TKeyPresentInfo<S,T>& rf, const TKeyPresentInfo<S,T>& m, const VectorTime& v)
    {
        rf.clear();
        for (const auto& [si,k2] : m)
        {
            cxAssert(!k2.empty());
            if (k2.IsInsert())
            {
                if (k2.ti >= v(si))
                {
                    rf[si] = k2;
                }
            }
            else
            {
                if (k2.td >= v(k2.sd))
                {
                    rf[si] = k2;
                }
            }
        }
    }
} // namespace ceda
