/*  *********************************************************************
 *  ASTRIS - Remote JTAG probe
 *
 *  Astris.framework - C++ API
 *
 *  File: NewAPI_Ranges.hpp
 *
 ***********************************************************************
 *
 *  Copyright (C) 2021- Apple Inc. All rights reserved.
 *
 *  This document is the property of Apple Inc.
 *  It is considered confidential and proprietary.
 *
 *  This document may not be reproduced or transmitted in any form,
 *  in whole or in part, without the express written permission of
 *  Apple Inc.
 ********************************************************************* */

#ifndef ranges_h
#define ranges_h

//#include <exception>
#include <stdint.h>
#include <utility>
#include <stdexcept>

namespace AstrisAPI {

template <typename I>
class RangeIterObj
{
public:
    virtual size_t retain() = 0;
    virtual size_t release() = 0;

    virtual void advance() = 0;
    virtual const I* ptr() const = 0;
    virtual const I& ref() const = 0;

protected:
    virtual ~RangeIterObj() {}      // can only be deleted by the derived class
};

template <typename I>
class RangeIter
{

public:
    using self_t = RangeIter<I>;
    using impl_t = RangeIterObj<I>;

    RangeIter(impl_t *impl):
        m_impl(impl)
    {
    }

    RangeIter(const self_t &other):
        m_impl(other.m_impl)
    {
        if (m_impl) {
            m_impl->retain();
        }
    }

    ~RangeIter()
    {
        if (m_impl) {
            m_impl->release();
        }
    }

    self_t& operator=(const self_t &other)
    {
        if (m_impl) {
            m_impl->release();
        }
        m_impl = other.m_impl;
        if (m_impl) {
            m_impl->retain();
        }

        return *this;
    }

    self_t& operator++()
    {
        if (m_impl) {
            m_impl->advance();
        }
        return *this;
    }

    const I* operator->() const
    {
        return m_impl ? m_impl->ptr() : nullptr;
    }

    const I& operator*() const
    {
        return m_impl ? m_impl->ref() : none();
    }

    bool operator==(const self_t &other) const
    {
        if (m_impl != nullptr && other.m_impl != nullptr) {
            return  m_impl->ptr() == other.m_impl->ptr();
        }
        return m_impl == nullptr && other.m_impl == nullptr;
    }

    operator const I*() const
    {
        return m_impl ? m_impl->ptr() : nullptr;
    }

private:
    static I& none()
    {
        static I s_none;

        return s_none;
    }

    impl_t *m_impl;
};

template <typename I>
class ListRangeObj
{
public:
    using iter_t = RangeIter<I>;

    virtual size_t retain() = 0;
    virtual size_t release() = 0;

    virtual iter_t begin() = 0;
    virtual iter_t end() = 0;
    virtual size_t size() = 0;
    virtual const I& at(size_t) = 0;

protected:
    virtual ~ListRangeObj() {}
};

template <typename I>
class ListRange
{
public:
    using self_t = ListRange<I>;
    using impl_t = ListRangeObj<I>;
    using iter_t = RangeIter<I>;

    ListRange():
        m_impl(nullptr)
    {
    }

    ListRange(impl_t *impl):
        m_impl(impl)
    {
    }

    ListRange(const self_t &other):
        m_impl(other.m_impl)
    {
        if (m_impl) {
            m_impl->retain();
        }
    }

    ~ListRange()
    {
        if (m_impl) {
            m_impl->release();
        }
    }

    self_t& operator=(const self_t &other)
    {
        if (m_impl) {
            m_impl->release();
        }
        m_impl = other->m_impl;
        if (m_impl) {
            m_impl->retain();
        }
    }

    iter_t begin()
    {
        return m_impl ? m_impl->begin() : none();
    }

    iter_t end()
    {
        return m_impl ? m_impl->end() : none();
    }

    size_t size()
    {
        return m_impl ? m_impl->size() : 0;
    }

    const I& operator[](size_t index)
    {
        if (index >= size()) {
            throw ::std::out_of_range("Index is out of list range");
        }

        return m_impl->at(index);
    }

private:
    static iter_t none()
    {
        static iter_t s_none(nullptr);

        return s_none;
    }

    impl_t *m_impl;
};

template <typename K, typename V>
class DictRangeObj
{
public:
    using item_t = ::std::pair<const K,V>;
    using iter_t = RangeIter<item_t>;

    virtual size_t retain() = 0;
    virtual size_t release() = 0;

    virtual iter_t begin() = 0;
    virtual iter_t end() = 0;
    virtual size_t size() = 0;
    virtual bool hasKey(const K&) = 0;
    virtual const V& at(const K&) = 0;

protected:
    virtual ~DictRangeObj() {}
};

template <typename K, typename V>
class DictRange
{
public:
    using self_t = DictRange<K, V>;
    using impl_t = DictRangeObj<K, V>;
    using item_t = ::std::pair<const K,V>;
    using iter_t = RangeIter<item_t>;

    DictRange():
        m_impl(nullptr)
    {
    }

    DictRange(impl_t *impl):
        m_impl(impl)
    {
    }

    DictRange(const self_t &other):
        m_impl(other.m_impl)
    {
        if (m_impl) {
            m_impl->retain();
        }
    }

    ~DictRange()
    {
        if (m_impl) {
            m_impl->release();
        }
    }

    self_t& operator=(const self_t &other)
    {
        if (m_impl) {
            m_impl->release();
        }
        m_impl = other->m_impl;
        if (m_impl) {
            m_impl->retain();
        }
    }

    iter_t begin()
    {
        return m_impl ? m_impl->begin() : none();
    }

    iter_t end()
    {
        return m_impl ? m_impl->end() : none();
    }

    size_t size()
    {
        return m_impl ? m_impl->size() : 0;
    }

    const V& operator[](const K &key)
    {
        if (!m_impl->hasKey(key)) {
            throw ::std::out_of_range("Key does not exist in the dictionary");
        }
        return m_impl->at(key);
    }

private:
    static iter_t none()
    {
        static iter_t s_none(nullptr);

        return s_none;
    }

    impl_t *m_impl;
};

};   // namespace AstrisAPI

#endif /* ranges_h */
