/*
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.

Author: Leonardo de Moura
*/
#pragma once
#include "runtime/buffer.h"
#include "runtime/object_ref.h"

namespace lean {
template<typename C> object * to_array(C const & elems) {
    object * a = alloc_array(elems.size(), elems.size());
    size_t i   = 0;
    for (typename C::value_type const & elem : elems) {
        inc(elem.raw());
        array_set(a, i, elem.raw());
        i++;
    }
    return a;
}

/* Wrapper for manipulating Lean Arrays in C++ */
template<typename T>
class array_ref : public object_ref {
public:
    explicit array_ref(obj_arg o):object_ref(o) {}
    array_ref(b_obj_arg o, bool b):object_ref(o, b) {}
    array_ref():object_ref(array_mk_empty()) {}
    array_ref(array_ref const & other):object_ref(other) {}
    array_ref(array_ref && other):object_ref(other) {}
    array_ref(std::initializer_list<T> const & elems):object_ref(to_array(elems)) {}
    array_ref(buffer<T> const & elems):object_ref(to_array(elems)) {}

    array_ref & operator=(array_ref const & other) { object_ref::operator=(other); return *this; }
    array_ref & operator=(array_ref && other) { object_ref::operator=(other); return *this; }

    size_t size() const { return array_size(raw()); }

    T const & operator[](size_t i) const {
        static_assert(sizeof(T const &) == sizeof(object *), "unexpected array_ref element size"); // NOLINT
        return reinterpret_cast<T const *>(array_cptr(raw()))[i];
    }

    class iterator {
        friend class array_ref;
        object ** m_it;
        iterator(object ** o):m_it(o) {}
    public:
        typedef std::forward_iterator_tag iterator_category;
        typedef T         value_type;
        typedef unsigned  difference_type;
        typedef T const * pointer;
        typedef T const & reference;
        iterator(iterator const & s):m_it(s.m_it) {}
        iterator & operator++() { m_it++; return *this; }
        iterator operator++(int) { iterator tmp(*this); operator++(); return tmp; }
        bool operator==(iterator const & s) const { return m_it == s.m_it; }
        bool operator!=(iterator const & s) const { return !operator==(s); }
        T const & operator*() { return reinterpret_cast<T const &>(*m_it); }
    };

    iterator begin() const { return iterator(array_cptr(raw())); }
    iterator end() const { return iterator(array_cptr(raw()) + size()); }
};
}
