/*******************************************************************************
 * This file is part of the "Enduro2D"
 * For conditions of distribution and use, see copyright notice in LICENSE.md
 * Copyright (C) 2018-2020, by Matvey Cherevko (blackmatov@gmail.com)
 ******************************************************************************/

#pragma once

#include "../core/_all.hpp"

#include <ecs.hpp/ecs.hpp>

namespace e2d
{
    namespace ecs
    {
        using namespace ecs_hpp;
    }
}

namespace e2d
{
    class atlas_asset;
    class binary_asset;
    class flipbook_asset;
    class font_asset;
    class image_asset;
    class json_asset;
    class material_asset;
    class mesh_asset;
    class model_asset;
    class prefab_asset;
    class shader_asset;
    class shape_asset;
    class sound_asset;
    class sprite_asset;
    class text_asset;
    class texture_asset;
    class xml_asset;

    class actor;
    class camera;
    class rect_collider;
    class circle_collider;
    class polygon_collider;
    template < typename C >
    class commands;
    template < typename T >
    class disabled;
    template < typename E >
    class events;
    class flipbook_player;
    class label;
    class layout;
    class model_renderer;
    class named;
    class renderer;
    class scene;
    class sprite_renderer;
    class touchable;
    class widget;

    class atlas;
    class flipbook;
    class model;
    class prefab;
    class sprite;

    class camera_system;
    class flipbook_system;
    class frame_system;
    class gizmos_system;
    class label_system;
    class layout_system;
    class render_system;
    class touch_system;
    class world_system;

    template < typename Asset, typename Content >
    class content_asset;
    class asset;

    class factory;
    class library;
    class asset_store;
    class asset_group;
    class asset_dependencies;

    class editor;
    class inspector;
    class starter;
    class world;

    class node;
    class gobject;
    template < typename T >
    class gcomponent;
    template < typename T >
    class const_gcomponent;
}

namespace e2d::ecsex
{
    template < typename T, typename Disposer, typename... Opts >
    void remove_all_components_with_disposer(
        ecs::registry& owner,
        Disposer&& disposer,
        Opts&&... opts)
    {
        static thread_local vector<ecs::entity> to_remove_components;
        DEFER_HPP([](){ to_remove_components.clear(); });

        owner.for_each_component<T>([](const ecs::entity& e, const T&){
            to_remove_components.push_back(e);
        }, std::forward<Opts>(opts)...);

        for ( ecs::entity& e : to_remove_components ) {
            std::invoke(disposer, e, e.get_component<T>());
            e.remove_component<T>();
        }
    }

    template < typename T, typename... Opts >
    void remove_all_components(ecs::registry& owner, Opts&&... opts) {
        remove_all_components_with_disposer<T>(
            owner,
            null_disposer(),
            std::forward<Opts>(opts)...);
    }
}

namespace e2d::ecsex
{
    template < typename... Ts, typename Iter, typename... Opts >
    std::size_t extract_components(ecs::registry& owner, Iter iter, Opts&&... opts) {
        std::size_t count{0u};
        owner.for_joined_components<Ts...>(
        [&iter, &count](const ecs::entity& e, Ts&... cs){
            iter++ = std::make_tuple(e, cs...);
            ++count;
        }, std::forward<Opts>(opts)...);
        return count;
    }

    template < typename... Ts, typename Iter, typename... Opts >
    std::size_t extract_components(const ecs::registry& owner, Iter iter, Opts&&... opts) {
        std::size_t count{0u};
        owner.for_joined_components<Ts...>(
        [&iter, &count](const ecs::const_entity& e, const Ts&... cs){
            iter++ = std::make_tuple(e, cs...);
            ++count;
        }, std::forward<Opts>(opts)...);
        return count;
    }
}

namespace e2d::ecsex
{
    template < typename... Ts, typename F, typename... Opts >
    void for_extracted_components(ecs::registry& owner, F&& f, Opts&&... opts) {
        //TODO(BlackMat): replace it to frame allocator
        static thread_local vector<
            std::tuple<ecs::entity, Ts...>> components;

        const std::size_t begin_index = components.size();
        DEFER_HPP([begin_index](){
            components.erase(
                components.begin() + begin_index,
                components.end());
        });

        extract_components<Ts...>(
            owner,
            std::back_inserter(components),
            std::forward<Opts>(opts)...);

        const std::size_t end_index = components.size();
        for ( std::size_t i = begin_index; i < end_index; ++i ) {
            std::apply(f, components[i]);
        }
    }

    template < typename... Ts, typename F, typename... Opts >
    void for_extracted_components(const ecs::registry& owner, F&& f, Opts&&... opts) {
        //TODO(BlackMat): replace it to frame allocator
        static thread_local vector<
            std::tuple<ecs::const_entity, Ts...>> components;

        const std::size_t begin_index = components.size();
        DEFER_HPP([begin_index](){
            components.erase(
                components.begin() + begin_index,
                components.end());
        });

        extract_components<Ts...>(
            owner,
            std::back_inserter(components),
            std::forward<Opts>(opts)...);

        const std::size_t end_index = components.size();
        for ( std::size_t i = begin_index; i < end_index; ++i ) {
            std::apply(f, components[i]);
        }
    }
}

namespace e2d::ecsex
{
    template < typename... Ts, typename Comp, typename F, typename... Opts >
    void for_extracted_sorted_components(ecs::registry& owner, Comp&& comp, F&& f, Opts&&... opts) {
        //TODO(BlackMat): replace it to frame allocator
        static thread_local vector<
            std::tuple<ecs::entity, Ts...>> components;

        const std::size_t begin_index = components.size();
        DEFER_HPP([begin_index](){
            components.erase(
                components.begin() + begin_index,
                components.end());
        });

        extract_components<Ts...>(
            owner,
            std::back_inserter(components),
            std::forward<Opts>(opts)...);

        std::sort(
            components.begin() + begin_index,
            components.end(),
            comp);

        const std::size_t end_index = components.size();
        for ( std::size_t i = begin_index; i < end_index; ++i ) {
            std::apply(f, components[i]);
        }
    }

    template < typename... Ts, typename Comp, typename F, typename... Opts >
    void for_extracted_sorted_components(const ecs::registry& owner, Comp&& comp, F&& f, Opts&&... opts) {
        //TODO(BlackMat): replace it to frame allocator
        static thread_local vector<
            std::tuple<ecs::const_entity, Ts...>> components;

        const std::size_t begin_index = components.size();
        DEFER_HPP([begin_index](){
            components.erase(
                components.begin() + begin_index,
                components.end());
        });

        extract_components<Ts...>(
            owner,
            std::back_inserter(components),
            std::forward<Opts>(opts)...);

        std::sort(
            components.begin() + begin_index,
            components.end(),
            comp);

        const std::size_t end_index = components.size();
        for ( std::size_t i = begin_index; i < end_index; ++i ) {
            std::apply(f, components[i]);
        }
    }
}

namespace e2d::strings
{
    template <>
    class format_arg<ecs::entity> {
        ecs::entity value_;
        u8 width_;
    public:
        template < typename U >
        explicit format_arg(U&& value, u8 width = 0) noexcept
        : value_(std::forward<U>(value)), width_(width) {}

        std::ptrdiff_t write(char* dst, size_t size) const {
            return math::numeric_cast<std::ptrdiff_t>(
                format(dst, size, "(%0,%1)",
                    make_format_arg(ecs::detail::entity_id_index(value_.id()), width_),
                    make_format_arg(ecs::detail::entity_id_version(value_.id()), width_)));
        }
    };

    template <>
    class format_arg<ecs::const_entity> {
        ecs::const_entity value_;
        u8 width_;
    public:
        template < typename U >
        explicit format_arg(U&& value, u8 width = 0) noexcept
        : value_(std::forward<U>(value)), width_(width) {}

        std::ptrdiff_t write(char* dst, size_t size) const {
            return math::numeric_cast<std::ptrdiff_t>(
                format(dst, size, "(%0,%1)",
                    make_format_arg(ecs::detail::entity_id_index(value_.id()), width_),
                    make_format_arg(ecs::detail::entity_id_version(value_.id()), width_)));
        }
    };
}
