#pragma once

#include <functional>

#include "Iterator.h"
#include "ReferenceCounter.h"

namespace Framework
{
    namespace Exceptions
    {
        class EndOfSupplier
        {
            const char* msg;

        public:
            EndOfSupplier()
                : msg("End of Supplier")
            {}
        };
    } // namespace Exceptions

    template<typename T> class Supplier : public ReferenceCounter
    {
    public:
        Supplier()
            : ReferenceCounter()
        {}

        virtual ~Supplier() {}

        virtual T next() noexcept(false) = 0;
    };

    template<typename T> class IteratorSupplier : public Supplier<T>
    {
    private:
        BasicIterator<T>* iterator;

    public:
        IteratorSupplier(BasicIterator<T>* iterator)
            : Supplier<T>(),
              iterator(iterator)
        {}

        ~IteratorSupplier()
        {
            delete iterator;
        }

        T next() noexcept(false) override
        {
            if (!*iterator)
            {
                throw Exceptions::EndOfSupplier();
            }
            T val = iterator->val();
            iterator->plusPlus();
            return val;
        }
    };

    template<typename T> class FilteredSupplier : public Supplier<T>

    {
    private:
        Supplier<T>* supplier;
        std::function<bool(T)> filter;

    public:
        FilteredSupplier(Supplier<T>* supplier, std::function<bool(T)> filter)
            : Supplier<T>(),
              supplier(supplier),
              filter(filter)
        {}

        ~FilteredSupplier()
        {
            supplier->release();
        }

        T next() noexcept(false) override
        {
            T val = supplier->next();
            while (!filter(val))
            {
                val = supplier->next();
            }
            return val;
        }
    };

    template<typename T, typename R> class MappedSupplier : public Supplier<R>
    {
    private:
        Supplier<T>* supplier;
        std::function<R(T)> mapper;

    public:
        MappedSupplier(Supplier<T>* supplier, std::function<R(T)> mapper)
            : Supplier<T>(),
              supplier(supplier),
              mapper(mapper)
        {}

        ~MappedSupplier()
        {
            supplier->release();
        }

        R next() noexcept(false) override
        {
            return mapper(supplier->next());
        }
    };

    template<typename T> class Stream;

    template<typename T, typename R> class FlatMappedSupplier
        : public Supplier<R>
    {
    private:
        Supplier<T>* supplier;
        std::function<Supplier<R>*(T)> mapper;
        Supplier<R>* currentSupplier;

    public:
        FlatMappedSupplier(
            Supplier<T>* supplier, std::function<Supplier<R>*(T)> mapper)
            : Supplier<R>(),
              supplier(supplier),
              mapper(mapper),
              currentSupplier(nullptr)
        {}

        ~FlatMappedSupplier()
        {
            if (currentSupplier != nullptr)
            {
                currentSupplier->release();
            }
            supplier->release();
        }

        R next() noexcept(false) override
        {
            while (true)
            {
                if (currentSupplier == nullptr)
                {
                    currentSupplier = mapper(supplier->next());
                }
                try
                {
                    return currentSupplier->next();
                } catch (Exceptions::EndOfSupplier)
                {
                    currentSupplier->release();
                    currentSupplier = nullptr;
                }
            }
        }
    };
} // namespace Framework