// -*- mode: C++; c-indent-level: 4; c-basic-offset: 4; tab-width: 8 -*-
//
// setdiff.h: Rcpp R/C++ interface class library -- setdiff
//
// Copyright (C) 2012 - 2024  Dirk Eddelbuettel and Romain Francois
// Copyright (C) 2025         Dirk Eddelbuettel, Romain Francois and Iñaki Ucar
//
// This file is part of Rcpp.
//
// Rcpp 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 2 of the License, or
// (at your option) any later version.
//
// Rcpp 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 Rcpp.  If not, see <http://www.gnu.org/licenses/>.

#ifndef Rcpp__sugar__setdiff_h
#define Rcpp__sugar__setdiff_h

namespace Rcpp{
namespace sugar{

    template <typename SET>
    class RemoveFromSet {
    public:
        RemoveFromSet( SET& set_) : set(set_){}

        template <typename T>
        void operator()(T value){
            set.erase( value );
        }

    private:
        SET& set ;
    } ;

    template <int RTYPE, bool LHS_NA, typename LHS_T, bool RHS_NA, typename RHS_T>
    class SetDiff {
    public:
        typedef typename Rcpp::traits::storage_type<RTYPE>::type STORAGE ;

        SetDiff( const LHS_T& lhs, const RHS_T& rhs) :
            lhs_set( get_const_begin(lhs), get_const_end(lhs) ),
            rhs_set( get_const_begin(rhs), get_const_end(rhs) )
        {

            std::for_each( rhs_set.begin(), rhs_set.end(), RemoveFromSet<SET>(lhs_set) ) ;
        }

        Vector<RTYPE> get() const {
            R_xlen_t n = lhs_set.size() ;
            Vector<RTYPE> out = no_init(n) ;
            std::copy( lhs_set.begin(), lhs_set.end(), out.begin() ) ;
            return out ;
        }

    private:
        typedef std::unordered_set<STORAGE> SET ;
        typedef typename SET::const_iterator ITERATOR ;
        SET lhs_set ;
        SET rhs_set ;

    } ;

    template <int RTYPE, bool LHS_NA, typename LHS_T, bool RHS_NA, typename RHS_T>
    class SetEqual {
    public:
        typedef typename Rcpp::traits::storage_type<RTYPE>::type STORAGE ;

        SetEqual( const LHS_T& lhs, const RHS_T& rhs) :
            lhs_set( get_const_begin(lhs), get_const_end(lhs) ),
            rhs_set( get_const_begin(rhs), get_const_end(rhs) )
        {
        }

        bool get() const {
            if( lhs_set.size() != rhs_set.size() ) return false ;

            ITERATOR it = lhs_set.begin(), end = lhs_set.end(), rhs_end = rhs_set.end() ;
            for( ; it != end; ){
                if( rhs_set.find(*it++) == rhs_end ) return false ;
            }
            return true ;
        }

    private:
        typedef std::unordered_set<STORAGE> SET ;
        typedef typename SET::const_iterator ITERATOR ;
        SET lhs_set ;
        SET rhs_set ;

    } ;

    template <int RTYPE, bool LHS_NA, typename LHS_T, bool RHS_NA, typename RHS_T>
    class Intersect {
    public:
        typedef typename Rcpp::traits::storage_type<RTYPE>::type STORAGE ;

        Intersect( const LHS_T& lhs, const RHS_T& rhs) :
            intersect()
        {

            SET lhs_set( get_const_begin(lhs), get_const_end(lhs) ) ;
            SET rhs_set( get_const_begin(rhs), get_const_end(rhs) ) ;

            ITERATOR end = lhs_set.end() ;
            ITERATOR rhs_end = rhs_set.end() ;
            for( ITERATOR it=lhs_set.begin(); it != end; it++){
                if( rhs_set.find(*it) != rhs_end ) intersect.insert(*it) ;
            }
        }

        Vector<RTYPE> get() const {
            R_xlen_t n = intersect.size() ;
            Vector<RTYPE> out = no_init(n) ;
            std::copy( intersect.begin(), intersect.end(), out.begin() ) ;
            return out ;
        }

    private:
        typedef std::unordered_set<STORAGE> SET ;
        typedef typename SET::const_iterator ITERATOR ;
        SET intersect ;

    } ;

    template <int RTYPE, bool LHS_NA, typename LHS_T, bool RHS_NA, typename RHS_T>
    class Union {
    public:
        typedef typename Rcpp::traits::storage_type<RTYPE>::type STORAGE ;

        Union( const LHS_T& lhs, const RHS_T& rhs) :
            result( get_const_begin(lhs), get_const_end(lhs) )
        {
            result.insert( get_const_begin(rhs), get_const_end(rhs) ) ;
        }

        Vector<RTYPE> get() const {
            R_xlen_t n = result.size() ;
            Vector<RTYPE> out = no_init(n) ;
            std::copy( result.begin(), result.end(), out.begin() ) ;
            return out ;
        }

    private:
        typedef std::unordered_set<STORAGE> SET ;
        typedef typename SET::const_iterator ITERATOR ;
        SET result ;

    } ;


} // sugar

template <int RTYPE, bool LHS_NA, typename LHS_T, bool RHS_NA, typename RHS_T>
inline Vector<RTYPE> setdiff( const VectorBase<RTYPE,LHS_NA,LHS_T>& lhs, const VectorBase<RTYPE,RHS_NA,RHS_T>& rhs ){
    return sugar::SetDiff<RTYPE,LHS_NA,LHS_T,RHS_NA,RHS_T>( lhs.get_ref(), rhs.get_ref() ).get() ;
}

template <int RTYPE, bool LHS_NA, typename LHS_T, bool RHS_NA, typename RHS_T>
inline bool setequal( const VectorBase<RTYPE,LHS_NA,LHS_T>& lhs, const VectorBase<RTYPE,RHS_NA,RHS_T>& rhs ){
    return sugar::SetEqual<RTYPE,LHS_NA,LHS_T,RHS_NA,RHS_T>( lhs.get_ref(), rhs.get_ref() ).get() ;
}

template <int RTYPE, bool LHS_NA, typename LHS_T, bool RHS_NA, typename RHS_T>
inline Vector<RTYPE> intersect( const VectorBase<RTYPE,LHS_NA,LHS_T>& lhs, const VectorBase<RTYPE,RHS_NA,RHS_T>& rhs ){
    return sugar::Intersect<RTYPE,LHS_NA,LHS_T,RHS_NA,RHS_T>( lhs.get_ref(), rhs.get_ref() ).get() ;
}

// we cannot use "union" because it is a keyword
template <int RTYPE, bool LHS_NA, typename LHS_T, bool RHS_NA, typename RHS_T>
inline Vector<RTYPE> union_( const VectorBase<RTYPE,LHS_NA,LHS_T>& lhs, const VectorBase<RTYPE,RHS_NA,RHS_T>& rhs ){
    return sugar::Union<RTYPE,LHS_NA,LHS_T,RHS_NA,RHS_T>( lhs.get_ref(), rhs.get_ref() ).get() ;
}



} // Rcpp
#endif

