/*
 * Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#pragma once

#include <AK/NonnullOwnPtr.h>
#include <LibJS/Runtime/Shape.h>
#include <LibJS/Runtime/Value.h>

namespace JS {

struct ValueAndAttributes {
    Value value;
    PropertyAttributes attributes { default_attributes };
};

class IndexedProperties;
class IndexedPropertyIterator;
class GenericIndexedPropertyStorage;

class IndexedPropertyStorage {
public:
    virtual ~IndexedPropertyStorage() = default;

    virtual bool has_index(u32 index) const = 0;
    virtual Optional<ValueAndAttributes> get(u32 index) const = 0;
    virtual void put(u32 index, Value value, PropertyAttributes attributes = default_attributes) = 0;
    virtual void remove(u32 index) = 0;

    virtual ValueAndAttributes take_first() = 0;
    virtual ValueAndAttributes take_last() = 0;

    virtual size_t size() const = 0;
    virtual size_t array_like_size() const = 0;
    virtual bool set_array_like_size(size_t new_size) = 0;

    virtual bool is_simple_storage() const { return false; }
};

class SimpleIndexedPropertyStorage final : public IndexedPropertyStorage {
public:
    SimpleIndexedPropertyStorage() = default;
    explicit SimpleIndexedPropertyStorage(Vector<Value>&& initial_values);

    virtual bool has_index(u32 index) const override;
    virtual Optional<ValueAndAttributes> get(u32 index) const override;
    virtual void put(u32 index, Value value, PropertyAttributes attributes = default_attributes) override;
    virtual void remove(u32 index) override;

    virtual ValueAndAttributes take_first() override;
    virtual ValueAndAttributes take_last() override;

    virtual size_t size() const override { return m_packed_elements.size(); }
    virtual size_t array_like_size() const override { return m_array_size; }
    virtual bool set_array_like_size(size_t new_size) override;

    virtual bool is_simple_storage() const override { return true; }
    const Vector<Value>& elements() const { return m_packed_elements; }

private:
    friend GenericIndexedPropertyStorage;

    void grow_storage_if_needed();

    size_t m_array_size { 0 };
    Vector<Value> m_packed_elements;
};

class GenericIndexedPropertyStorage final : public IndexedPropertyStorage {
public:
    explicit GenericIndexedPropertyStorage(SimpleIndexedPropertyStorage&&);
    explicit GenericIndexedPropertyStorage() = default;

    virtual bool has_index(u32 index) const override;
    virtual Optional<ValueAndAttributes> get(u32 index) const override;
    virtual void put(u32 index, Value value, PropertyAttributes attributes = default_attributes) override;
    virtual void remove(u32 index) override;

    virtual ValueAndAttributes take_first() override;
    virtual ValueAndAttributes take_last() override;

    virtual size_t size() const override { return m_sparse_elements.size(); }
    virtual size_t array_like_size() const override { return m_array_size; }
    virtual bool set_array_like_size(size_t new_size) override;

    const HashMap<u32, ValueAndAttributes>& sparse_elements() const { return m_sparse_elements; }

private:
    size_t m_array_size { 0 };
    HashMap<u32, ValueAndAttributes> m_sparse_elements;
};

class IndexedPropertyIterator {
public:
    IndexedPropertyIterator(const IndexedProperties&, u32 starting_index, bool skip_empty);

    IndexedPropertyIterator& operator++();
    IndexedPropertyIterator& operator*();
    bool operator!=(const IndexedPropertyIterator&) const;

    u32 index() const { return m_index; };

private:
    void skip_empty_indices();

    const IndexedProperties& m_indexed_properties;
    Vector<u32> m_cached_indices;
    u32 m_index { 0 };
    bool m_skip_empty { false };
};

class IndexedProperties {
public:
    IndexedProperties() = default;

    explicit IndexedProperties(Vector<Value> values)
    {
        if (!values.is_empty())
            m_storage = make<SimpleIndexedPropertyStorage>(move(values));
    }

    bool has_index(u32 index) const { return m_storage ? m_storage->has_index(index) : false; }
    Optional<ValueAndAttributes> get(u32 index) const;
    void put(u32 index, Value value, PropertyAttributes attributes = default_attributes);
    void remove(u32 index);

    void append(Value value, PropertyAttributes attributes = default_attributes) { put(array_like_size(), value, attributes); }

    IndexedPropertyIterator begin(bool skip_empty = true) const { return IndexedPropertyIterator(*this, 0, skip_empty); };
    IndexedPropertyIterator end() const { return IndexedPropertyIterator(*this, array_like_size(), false); };

    bool is_empty() const { return array_like_size() == 0; }
    size_t array_like_size() const { return m_storage ? m_storage->array_like_size() : 0; }
    bool set_array_like_size(size_t);

    size_t real_size() const;

    Vector<u32> indices() const;

    template<typename Callback>
    void for_each_value(Callback callback)
    {
        if (!m_storage)
            return;
        if (m_storage->is_simple_storage()) {
            for (auto& value : static_cast<SimpleIndexedPropertyStorage&>(*m_storage).elements())
                callback(value);
        } else {
            for (auto& element : static_cast<const GenericIndexedPropertyStorage&>(*m_storage).sparse_elements())
                callback(element.value.value);
        }
    }

private:
    void switch_to_generic_storage();
    void ensure_storage();

    OwnPtr<IndexedPropertyStorage> m_storage;
};

}